From dc66a531c35f3ff9513fedc7515f17a23050c99c Mon Sep 17 00:00:00 2001 From: Gloria Zhu Date: Fri, 19 Aug 2022 00:40:41 -0700 Subject: [PATCH] Add project files --- .classpath | 44 ++ .gitignore | 3 + .project | 23 + README.MD | 13 + all.asm | Bin 0 -> 18887 bytes demo.asm | Bin 0 -> 9230 bytes pom.xml | 75 +++ printSquares.asm | Bin 0 -> 9059 bytes public/printHello.txt | 1 + src/main/java/com/pilers/ast/Assignment.java | 88 +++ src/main/java/com/pilers/ast/BinOp.java | 487 +++++++++++++++ src/main/java/com/pilers/ast/Block.java | 73 +++ src/main/java/com/pilers/ast/Break.java | 35 ++ .../java/com/pilers/ast/BreakException.java | 11 + src/main/java/com/pilers/ast/Continue.java | 36 ++ .../com/pilers/ast/ContinueException.java | 11 + src/main/java/com/pilers/ast/Declaration.java | 80 +++ .../com/pilers/ast/DeclareAssignment.java | 72 +++ src/main/java/com/pilers/ast/Expression.java | 52 ++ src/main/java/com/pilers/ast/For.java | 130 ++++ src/main/java/com/pilers/ast/If.java | 88 +++ .../pilers/ast/ProcedureCallExpression.java | 94 +++ .../pilers/ast/ProcedureCallStatement.java | 84 +++ .../com/pilers/ast/ProcedureDeclaration.java | 248 ++++++++ src/main/java/com/pilers/ast/Program.java | 114 ++++ src/main/java/com/pilers/ast/ReadInteger.java | 91 +++ src/main/java/com/pilers/ast/Statement.java | 50 ++ src/main/java/com/pilers/ast/UnaryOp.java | 195 ++++++ src/main/java/com/pilers/ast/Value.java | 142 +++++ .../com/pilers/ast/VariableReference.java | 79 +++ src/main/java/com/pilers/ast/While.java | 110 ++++ src/main/java/com/pilers/ast/Writeln.java | 74 +++ src/main/java/com/pilers/emitter/Emitter.java | 492 +++++++++++++++ .../com/pilers/environment/Environment.java | 125 ++++ .../environment/InterpreterEnvironment.java | 100 +++ .../SemanticAnalysisEnvironment.java | 91 +++ .../com/pilers/errors/CompileException.java | 30 + .../java/com/pilers/errors/ErrorString.java | 182 ++++++ .../com/pilers/errors/InterpretException.java | 30 + .../pilers/errors/PreprocessorException.java | 31 + .../com/pilers/errors/ScanErrorException.java | 77 +++ src/main/java/com/pilers/parser/Parser.java | 487 +++++++++++++++ .../java/com/pilers/parser/ParserTester.java | 103 ++++ .../parser/tests/official/parserTest10.txt | 12 + .../parser/tests/official/parserTest11.txt | 8 + .../parser/tests/official/parserTest12.txt | 7 + .../parser/tests/official/parserTest13.txt | 9 + .../parser/tests/official/printSquares.txt | 19 + .../parser/tests/old_tests/Combination.txt | 22 + .../parser/tests/old_tests/MathProgram.txt | 27 + .../parser/tests/old_tests/anotherTest.txt | 7 + .../parser/tests/old_tests/breakTest.txt | 12 + .../parser/tests/old_tests/codegenTest1.txt | 10 + .../com/pilers/parser/tests/old_tests/max.txt | 8 + .../parser/tests/old_tests/parserTest0.txt | 2 + .../parser/tests/old_tests/parserTest1.txt | 4 + .../parser/tests/old_tests/parserTest2.txt | 4 + .../parser/tests/old_tests/parserTest3.txt | 8 + .../parser/tests/old_tests/parserTest4.txt | 7 + .../parser/tests/old_tests/parserTest6.txt | 18 + .../parser/tests/old_tests/parserTest7.txt | 28 + .../parser/tests/old_tests/parserTest8.txt | 31 + .../parser/tests/old_tests/typeTest.txt | 6 + .../com/pilers/preprocessor/Preprocessor.java | 257 ++++++++ src/main/java/com/pilers/preprocessor/all.txt | 59 ++ .../java/com/pilers/preprocessor/demo.txt | 29 + .../java/com/pilers/preprocessor/math.txt | 3 + src/main/java/com/pilers/scanner/Scanner.java | 581 ++++++++++++++++++ src/main/java/com/pilers/scanner/Token.java | 406 ++++++++++++ .../com/pilers/scanner/tests/ScannerTest.txt | 61 ++ .../scanner/tests/scannerTestAdvanced.txt | 14 + .../scanner/tests/scannerTestParser.txt | 30 + src/main/java/com/pilers/tester/Tester.java | 84 +++ .../java/com/pilers/scanner/ScannerTest.java | 35 ++ 74 files changed, 6159 insertions(+) create mode 100644 .classpath create mode 100644 .gitignore create mode 100644 .project create mode 100644 README.MD create mode 100644 all.asm create mode 100644 demo.asm create mode 100644 pom.xml create mode 100644 printSquares.asm create mode 100644 public/printHello.txt create mode 100644 src/main/java/com/pilers/ast/Assignment.java create mode 100644 src/main/java/com/pilers/ast/BinOp.java create mode 100644 src/main/java/com/pilers/ast/Block.java create mode 100644 src/main/java/com/pilers/ast/Break.java create mode 100644 src/main/java/com/pilers/ast/BreakException.java create mode 100644 src/main/java/com/pilers/ast/Continue.java create mode 100644 src/main/java/com/pilers/ast/ContinueException.java create mode 100644 src/main/java/com/pilers/ast/Declaration.java create mode 100644 src/main/java/com/pilers/ast/DeclareAssignment.java create mode 100644 src/main/java/com/pilers/ast/Expression.java create mode 100644 src/main/java/com/pilers/ast/For.java create mode 100644 src/main/java/com/pilers/ast/If.java create mode 100644 src/main/java/com/pilers/ast/ProcedureCallExpression.java create mode 100644 src/main/java/com/pilers/ast/ProcedureCallStatement.java create mode 100644 src/main/java/com/pilers/ast/ProcedureDeclaration.java create mode 100644 src/main/java/com/pilers/ast/Program.java create mode 100644 src/main/java/com/pilers/ast/ReadInteger.java create mode 100644 src/main/java/com/pilers/ast/Statement.java create mode 100644 src/main/java/com/pilers/ast/UnaryOp.java create mode 100644 src/main/java/com/pilers/ast/Value.java create mode 100644 src/main/java/com/pilers/ast/VariableReference.java create mode 100644 src/main/java/com/pilers/ast/While.java create mode 100644 src/main/java/com/pilers/ast/Writeln.java create mode 100644 src/main/java/com/pilers/emitter/Emitter.java create mode 100644 src/main/java/com/pilers/environment/Environment.java create mode 100644 src/main/java/com/pilers/environment/InterpreterEnvironment.java create mode 100644 src/main/java/com/pilers/environment/SemanticAnalysisEnvironment.java create mode 100644 src/main/java/com/pilers/errors/CompileException.java create mode 100644 src/main/java/com/pilers/errors/ErrorString.java create mode 100644 src/main/java/com/pilers/errors/InterpretException.java create mode 100644 src/main/java/com/pilers/errors/PreprocessorException.java create mode 100644 src/main/java/com/pilers/errors/ScanErrorException.java create mode 100644 src/main/java/com/pilers/parser/Parser.java create mode 100644 src/main/java/com/pilers/parser/ParserTester.java create mode 100644 src/main/java/com/pilers/parser/tests/official/parserTest10.txt create mode 100644 src/main/java/com/pilers/parser/tests/official/parserTest11.txt create mode 100644 src/main/java/com/pilers/parser/tests/official/parserTest12.txt create mode 100644 src/main/java/com/pilers/parser/tests/official/parserTest13.txt create mode 100644 src/main/java/com/pilers/parser/tests/official/printSquares.txt create mode 100644 src/main/java/com/pilers/parser/tests/old_tests/Combination.txt create mode 100644 src/main/java/com/pilers/parser/tests/old_tests/MathProgram.txt create mode 100644 src/main/java/com/pilers/parser/tests/old_tests/anotherTest.txt create mode 100644 src/main/java/com/pilers/parser/tests/old_tests/breakTest.txt create mode 100644 src/main/java/com/pilers/parser/tests/old_tests/codegenTest1.txt create mode 100644 src/main/java/com/pilers/parser/tests/old_tests/max.txt create mode 100644 src/main/java/com/pilers/parser/tests/old_tests/parserTest0.txt create mode 100644 src/main/java/com/pilers/parser/tests/old_tests/parserTest1.txt create mode 100644 src/main/java/com/pilers/parser/tests/old_tests/parserTest2.txt create mode 100644 src/main/java/com/pilers/parser/tests/old_tests/parserTest3.txt create mode 100644 src/main/java/com/pilers/parser/tests/old_tests/parserTest4.txt create mode 100644 src/main/java/com/pilers/parser/tests/old_tests/parserTest6.txt create mode 100644 src/main/java/com/pilers/parser/tests/old_tests/parserTest7.txt create mode 100644 src/main/java/com/pilers/parser/tests/old_tests/parserTest8.txt create mode 100644 src/main/java/com/pilers/parser/tests/old_tests/typeTest.txt create mode 100644 src/main/java/com/pilers/preprocessor/Preprocessor.java create mode 100644 src/main/java/com/pilers/preprocessor/all.txt create mode 100644 src/main/java/com/pilers/preprocessor/demo.txt create mode 100644 src/main/java/com/pilers/preprocessor/math.txt create mode 100644 src/main/java/com/pilers/scanner/Scanner.java create mode 100644 src/main/java/com/pilers/scanner/Token.java create mode 100644 src/main/java/com/pilers/scanner/tests/ScannerTest.txt create mode 100644 src/main/java/com/pilers/scanner/tests/scannerTestAdvanced.txt create mode 100644 src/main/java/com/pilers/scanner/tests/scannerTestParser.txt create mode 100644 src/main/java/com/pilers/tester/Tester.java create mode 100644 src/test/java/com/pilers/scanner/ScannerTest.java diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..71f5fef --- /dev/null +++ b/.classpath @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..def6926 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.settings +.vscode +target/ \ No newline at end of file diff --git a/.project b/.project new file mode 100644 index 0000000..40cd45a --- /dev/null +++ b/.project @@ -0,0 +1,23 @@ + + + universe + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..cc16c7f --- /dev/null +++ b/README.MD @@ -0,0 +1,13 @@ +# Project Info +A program that compiles (into MIPS Assembly) or interprets code written in a generic programming language. + +Written using Maven. A tester file can be located at `src/main/java/com/pilers/tester/Tester.java`. + +Steps: +1. The preprocessor handles macros (i.e. #include) and pipes the input program as a stream of bytes to the scanner. +2. Lexical analysis: the scanner scans the stream of bytes into a stream of tokens - entities that store their own type (Integer, String, or Boolean), value as a string, and line number. It transfers this stream of tokens to the parser. +3. Syntax analysis: the parser parses the stream of tokens into an abstract syntax tree (AST). It passes this tree to an environment, which acts as a manager for variable and function values. The object nature of Environments allows the creation of sub-Environments, which allows the usage of local variables inside a function that don't affect variables of the same name outside of the function. Note that the parser returns a Program object which acts as the root node ofthe AST. +4. If the program is set to interpret, see step 5. If the program is set to compile, see steps 6 and 7. +5. The program is executed using an interpreter environment, which essentially acts as a map of variable values. +6. The program is analyzed using a semantic analysis environment, which performs actions such as type checking, seeing if a variable exists when it is referenced, etc. +7. The program is compiled into MIPS Assembly code using an Emitter in a specified filepath. \ No newline at end of file diff --git a/all.asm b/all.asm new file mode 100644 index 0000000000000000000000000000000000000000..6d35e2d9473fca6ef8ef7a1ea78cbac1b56597fe GIT binary patch literal 18887 zcmeHPTW{M&7tE^-_&+QX14>^UkyPR&Z%yI)#ca zjzpQZ6g!O{z7H;!dz(EwGkccG&OsVorF*+O2gA{%H!|ZePWE=Y_rH#LX3oy%pW|dGeMs@8>+HFGLk6?*?OBwb&k_?3 z2D4~B&nwPCQ&h}*d}B-GPdgzmkhjVS$kViKUguTqpU>?G8~|BK>#QYZJ@;CtS~!8i zm({{K6uzn!#!z@zEli>Cb+r&{zNr>oKuxb*Ese0$tCl{&Qokt8m%>gxTZ?Drm&te< z+o%|tcX9ID)b#CtPm(A}WdTOn%Gpud*X_C(|R|0CI7=YlB zKHN%WU7Ss(Qx`g^gpQ2`h%EcynRyot<0R1ojz?h{Pi%bde6i!|9@#&7(Z4S0+NLWF zaqLJKQRui0Q4C0j&ffqig$}L-h0Y9WQs`jNQRv_zmZ4Kw!){x4t< zV-`4t1!=aFL#Rnh8A2f~TC7D0Y2V zcp;q(3)|TK*Jw7;z9wHL=`eFu>ndae$_Mx(ZMkOK88@ijir@f)Ls^}w0}N#OMJJb7 zz*_Q0H6|Uu1b!#-H&ED&{Lwp*1w`A)77#_nBMXS$Qi4?bnFI#x1=Ug%I*&9GC+iVT zA6wWoA*Gros1OaW^gOwb3rSnr`Cz>C?i1?Segg$YZ3vE zD&WMig}d>as=yei%OhgLqZF&a71kwU(`-!4hT6q4pxBt04Ox+8K(R3~n_^>PHe}2-=@Cah3Sm|jte*kq#yvdLD&O;``0hrUb0!OITsQOu#4$Ou&dU6R;{JQbEdL0)7j7ld~LZ%!JhwtXuBDO;OHEYZn*k z)cmS3v(#}iJx>=N+@%4~gvPe)X4jB;d#g>WG%~Ou=r?@xEEksDKrRp9 z{#g=F_Xo@(x3$@8mrT{@H3z8AOtoYovctr;e|)Ccj_T^s?fbUOfna78Yjf?nxVkJVaDdJHA;AR3dz{Htc3Y z_-?~cjEUXZ-B-(vNwCzI_%+7l6pm}P%t*wDGC{nE%ty3{$9Q5zRDqHd3-Q719;acj zT~5QGG;tb6C22@N@av1FpPQ(D2`Ef0G)==OGzcq1gY)2+(Bd?Vs!RDwTp~`xV9JW~ z;Hoag6s`haGpbQhR3RFi2S>487?TPW-CAtnY~btjWFEPm$i<2;zK-g)ARJJ&LBd;jX;Eg`<^xl_Q`~TYQlt~E_~7)^t^uY$C1#U#;NLoHZR)imz5Ms#w!i zzl7B_>;k@8!InAMaENsYE2QJy)4h~>_GOJ@Z+Dr|Pb6Z>xh=;`7{>*xL1H?r@{`m3W$-mSQh z91NYl#?PUO7t|e7_ro859)TN?>g6w^fBpI7Jr+vMvhd{Scc~};Kui9KmT`^AJErTZ z+KaA}57|p;-Al)LQ^VS(jjx2AC10*0G5?n8k90wvKhU5ZplN^bh=JG;E- z43Er0u+tPp9*jsF?sE3qIWx0V_C{6wXLYc@H=3vUBsGgD$qx3%i)dQp?ek!L?n%aSJHmm13*tfIV$(kEqH6LXX z|D_saZ>Z#IV6Wb}&&M*gdunYux;(PBiL-@$V-CjCzVQx>&-+FL#ut5K1;&?sV+_Vu zePanm@1$?Vc|EUhO>nH=w`OP!`qnvG2@@{XBTk1nmOv@5h?Me*P)%`WVwHl`Csr%8 z5~~?ni4|^=Y!CEwLQUW~=(E=)r1etDKYoYrTPt zPCYA%PJ4}v!sqttb{U<4YqVzEMOrcjEv*79NpOD5RvW%2aNJjWvpIH3_>tz{zI6^bQ zQtBpH%1W@5TLcRO(kEEB4L-rb=LVE>lywkRK(Iamjer6R3spd@Fen3Jg){Hb1LnO8+d4b$QR2*TEMw^lVm?H%~ZbUS)5h&?td_+QkBFD zfhul@kLPh!*_qN@+DEgFiX@t(vB_48As@G{s41Vm475*?cCvlQyL`EHAgWE>Z8AfgTl+BEe3UgiL3sY9n^p9pS(!vn! zXe({&)91STwy^7!b4;fd(8#F;)U`?ts_`tUqH&T<)731#ic5PWvK|U0GHs&Oo|!OQ z_4wVQ!fsxx>%Cr{XhK>yd8#A)a;{F|j}DW7auH3vsqebb4tUcH`ff4ew$rCR;{6V} zlhQp_PRjOJI4Rj<%SE{!YYe4&ECiJ4@tNG4st%IMqUs>2Dq?lw-V~SP5hh#@AMx-g z%)P1VFNqY^YL8H<=MpPi5%;E8t9@dn#u6*sBJNGGR{O*XYqj6SR8NCDAJfJsF6n$Z zWoEggL0hisKgnpIRV%+Ht-v`luvK%D99ylvuvfoo)l#TmJn=aaqx1ivRi&t|gKa|M z2-7fY6%PY^j>H_nT1AqqRh1(;t+rmNu~Y!miYTv*pz823Qdudlp_Px3m|ub}uRS=w zc+k{`3QXyVV$g6bD$}C=4+CC<-ugskmWy zQ4FZMNP59}`5hG=tvR=3z=3niLtgc&?u0-M_W|lH039-V><30dMa&AGbac)so zdXu266jG>!$Lj;ONWpWzZ}eMY_fyFBD*GNHfZWCgl$3zGL>r7oQUV4$Z7*&S?JgR* zBgS5gm4JJ&QNm|PNB`9{xqAzCITReIGzyNdX|k;*SQGfTbO?m`xTI3upp5vq^buz! zA)&H4O`X3RzKEJgqme*kRv^x(1;UI!AaHJ1Qs_F}HZ<_5o7~=7 zRWCx78=c~}9T?6m-y0yz@_d&n#c`isg%(S-$GIgCWK8RbEk0 zVeR%Qs<3u%PEe0kyFGV?X`80;)vv#u!2!s=&*)zJzWx5 + + + 4.0.0 + + com.pilers.app + universe + 1.0-SNAPSHOT + + universe + + http://www.example.com + + + UTF-8 + 1.7 + 1.7 + + + + + junit + junit + 4.11 + test + + + + + + + + + maven-clean-plugin + 3.1.0 + + + + maven-resources-plugin + 3.0.2 + + + maven-compiler-plugin + 3.8.0 + + + maven-surefire-plugin + 2.22.1 + + + maven-jar-plugin + 3.0.2 + + + maven-install-plugin + 2.5.2 + + + maven-deploy-plugin + 2.8.2 + + + + maven-site-plugin + 3.7.1 + + + maven-project-info-reports-plugin + 3.0.0 + + + + + diff --git a/printSquares.asm b/printSquares.asm new file mode 100644 index 0000000000000000000000000000000000000000..d744e92b8cfddaed5e5cc36d72375942564b2d3d GIT binary patch literal 9059 zcmeHN+iu%N5Y4L%_#d`$0J|@$d2w!UP2o5N3^#4uhXMfvN}^@D6seGu-1O@^yS(TO zkIcf5(-uV@j7S{r?CjY&XJ%K~8&&Zi)xrMWXrAVi)GVSTJJ=sDqG^$rukCa9(JYH3 ztJ3WFTX~d5=2>)NviL4dve-UI%(L~03FTw?xtvC6Y6r&ItoC!TZ@pS)&5N&^kK)9C zsRr2>DtQ{%r+4o2vAA}#)~2J&BWs&DTi7?|U_9*`@4)!HZ!}fP%0`yN<~FbP0=%9m4ekLtX60x ztY&B>tZ<8b!fJ$L2`gnKtdy0odV-!itQPq?b_|ltmRyczvsL|7_+UA-l}?I)HQ#_n zr=BH6$GwI|;dA?RJB`jDG+Hz6A}tw{mR5{NcMER9IVxaI$_>m(N{t9cGL3L3(+C&i zQEFgXd6XIpoR?slpp|e!C_DlRvG52fwIYH_p+Q*TdYMLwC`2Qi*C(ugg!M9wJ|3Z& zz*6icu#}a+QehETn230O}%uy&UITQiovDk;y_vGi&6ztCCPdQz??QcudI`wPvXdA}-el5B2!w5A?N zvvw?R*tY84w~M%16`7IhRK#W33@oB<;Gy{;Un~=80q5pTlKr$aQ~91}aaP&4|Hhn3 zRgx$KR0%_3Jddl&dP;L?AI&-{l4z2~CR;6rV%)l-rhNJ`(9R<5WIM_Ge7ST$RGYfn z;^abp6VH<@(<@5Ss7i7HdvBJk1z3q%e9G6w^^L2;^6_2}hXS-d9yh>Sfo^7pR78uoii`3)y%(j4vYF9YVXljOVah6+{@yG` zS{On*+KRS~=}WzRTiEr=Ii}+ZaOBhi)U`?tRO4AxMdKu!rmIYnCmhaj?3EL&TJ~#aafJ*ZHbM#u zphr-tc?m0A5jT8T0DZzrjU}vbi@4##0_YP~SOEPlr*a5& z0bI58YswEC&H`JCHj2}7^@V-W>Dtcv;z6eX9M(-kgN!ZB z_b?cv>!fkl;I?SX!~1|s*pV)>cB?S~DG#u|Q5s<;K16l%l)b9P6(*sK0w`&5>%=?A*^uoxDP-iSzC~6tS!n)$8%++ltLyvULUZ9 z3ZDDXpx+X^-xs!5*$vY@Hz*#ri-da^JLX{hX;@b{PXQuBB zAk6edh#&g%7E-|@tTLFF>5DNleK9JgFXGJfRr3y}6w_BlMM;IV+o!C;+PyhJJyz}Z z+!>~An#NbZ{CWllAbX$D-KD<${^A1~?a0n}arUzvXYcIo<)gbdwa)B2X6%N_b?n7e z{Zcjb(s|=*KIrPab~PV#<-&FMGxx`IS-+GG&9*nP*Tb?Fx(j}7Mt6BJGZu{IKg7tR QsGKIrpXTYu?CHV&UlGIDga7~l literal 0 HcmV?d00001 diff --git a/public/printHello.txt b/public/printHello.txt new file mode 100644 index 0000000..b929ec7 --- /dev/null +++ b/public/printHello.txt @@ -0,0 +1 @@ +writeln("Hello from the internet!"); \ No newline at end of file diff --git a/src/main/java/com/pilers/ast/Assignment.java b/src/main/java/com/pilers/ast/Assignment.java new file mode 100644 index 0000000..6372cb9 --- /dev/null +++ b/src/main/java/com/pilers/ast/Assignment.java @@ -0,0 +1,88 @@ +package com.pilers.ast; + +import com.pilers.emitter.Emitter; +import com.pilers.environment.*; +import com.pilers.errors.ErrorString; +import com.pilers.errors.InterpretException; +import com.pilers.errors.CompileException; + +/** + * AST Assignment class + * Represents the assignment of a value to a variable + * + * @author Gloria Zhu + */ +public class Assignment extends Statement +{ + private String var; + private Expression exp; + + /** + * Constructs an Assignment object + * + * @param var the name of the variable + * @param exp an expression for the value + * @throws TypeErrorException + */ + public Assignment(String var, Expression exp) + { + this.var = var; + this.exp = exp; + } + + /** + * Executes the assignment + * + * @param env the execution environment (InterpreterEnvironment) + * @throws BreakException if a break statement is executed + * @throws ContinueException if a continue statement is executed + * @throws InterpretException if there is a runtime error in assignment + * Happens if the varaible does not exist, + * or there is a type discrepancy between the + * variable and value + */ + public void exec(InterpreterEnvironment env) + throws BreakException, ContinueException, InterpretException + { + env.setVariable(var, exp.eval(env)); + } + + /** + * Semantic analysis for assignment + * Checks if the variable exists and the types are compatible + * + * @throws CompileException if the conditions above are broken + * @param env the current environment + */ + public void analyze(SemanticAnalysisEnvironment env) throws CompileException + { + exp.analyze(env); + + String expectedType = env.getVariableType(var); + + if (expectedType == null) throw new CompileException(ErrorString.unknownIdentifier(var)); + + if (!expectedType.equals(exp.type)) + throw new CompileException(ErrorString.typeAssignment(expectedType, exp.type)); + } + + /** + * Compiles the assignment + * The specific depends on the type of the varaible + * + * @param e the emitter + */ + public void compile(Emitter e) + { + exp.compile(e); // most recent value is in v0 + + if (e.isGlobalVariable(var)) e.emit("sw $v0 "+"_data_"+var); + else + { + int index = e.getLocalVarIndex(var); + e.emit("sw $v0 " + index * 4 + "($sp)"); + } + + } + +} \ No newline at end of file diff --git a/src/main/java/com/pilers/ast/BinOp.java b/src/main/java/com/pilers/ast/BinOp.java new file mode 100644 index 0000000..940d2b6 --- /dev/null +++ b/src/main/java/com/pilers/ast/BinOp.java @@ -0,0 +1,487 @@ +package com.pilers.ast; + +import com.pilers.environment.*; +import com.pilers.errors.ErrorString; +import com.pilers.errors.InterpretException; +import com.pilers.errors.CompileException; +import com.pilers.emitter.*; + +/** + * AST BinOp class + * Represents a binary operation between two values + * + * BinOp sets type during semantic analysis + * + * @author Gloria Zhu + */ +public class BinOp extends Expression +{ + private String op; + private Expression exp1; + private Expression exp2; + + /** + * Constructs a BinOp object + * + * Deduces the resulting type of the BinOp from the two types + * of the expressions as well as the operand. If the combination + * of types/operand is invalid, ignores it for now - it will either + * be caught in semantic analysis (in the case of compiling) or + * it will throw a runtime exception (in the case of interpreting). + * (Just set the type to an empty string) + * + * @param op operator in string form + * @param exp1 left expression + * @param exp2 right expression + */ + public BinOp(String op, Expression exp1, Expression exp2) + { + this.op = op; + this.exp1 = exp1; + this.exp2 = exp2; + } + + /** + * Evaluates the binary operation according to the types of its values and what + * its operation is. Not all type combinations are valid, and not all operations + * are defined for all type combinations. + * + * For example, Integer << Integer is a valid binary operation, but String << + * Integer is not. + * + * If a set of values and an operation doesn't satisfy any of the valid + * permutations, an error should be thrown (it currently isn't) + * + * @param env the execution environment + * @return the Value of the binary operation + * @throws BreakException if a break statement is executed + * @throws ContinueException if a continue statement is executed + * @throws InterpretException if there is a runtime exception in execution + */ + public Value eval(InterpreterEnvironment env) + throws BreakException, ContinueException, InterpretException + { + Value value1 = exp1.eval(env); // evaluate the two values + Value value2 = exp2.eval(env); + + String type1 = value1.type; + String type2 = value2.type; + + switch(type1+" "+type2) // the type of output depends on the type of input + { + + /** + * Standard operations: + * mathematical (Integer result) + * relational (Boolean result) + * bitwise (Integer result) + */ + case("Integer Integer"): + int case1Val1 = Integer.parseInt(value1.getValue()); + int case1Val2 = Integer.parseInt(value2.getValue()); + + switch(op) + { + // arithmetic operators + case ("+"): + type = "Integer"; + return new Value(case1Val1 + case1Val2); + case ("-"): + type = "Integer"; + return new Value(case1Val1 - case1Val2); + case ("*"): + type = "Integer"; + return new Value(case1Val1 * case1Val2); + case ("/"): + type = "Integer"; + return new Value(case1Val1 / case1Val2); + case ("%"): + type = "Integer"; + return new Value(case1Val1 % case1Val2); + + // relational operators + case("<="): + type = "Boolean"; + return new Value(case1Val1 <= case1Val2); + case(">="): + type = "Boolean"; + return new Value(case1Val1 >= case1Val2); + case("<"): + type = "Boolean"; + return new Value(case1Val1 < case1Val2); + case(">"): return new Value(case1Val1 > case1Val2); + case("!="): + type = "Boolean"; + return new Value(case1Val1 != case1Val2); + case("=="): + type = "Boolean"; + return new Value(case1Val1 == case1Val2); + + // bitwise operators + case("&"): + type = "Integer"; + return new Value(case1Val1 & case1Val2); + case("|"): + type = "Integer"; + return new Value(case1Val1 | case1Val2); + case("<<"): + type = "Integer"; + return new Value(case1Val1 << case1Val2); + case(">>"): + type = "Integer"; + return new Value(case1Val1 >> case1Val2); + case("^"): + type = "Integer"; + return new Value(case1Val1 ^ case1Val2); + } + + /** + * String + Integer concatenates the integer and returns a string + * String * Integer returns the String repeated Integer amount of times + */ + case("String Integer"): + String case2Val1 = value1.getValue(); + int case2Val2 = Integer.parseInt(value2.getValue()); + type = "String"; + switch(op) + { + case("+"): return new Value(case2Val1 + case2Val2, "String"); + case("*"): + String ret = ""; + for(int i=0;i>"): + case ("^"): + type = "Integer"; + break; + + case ("<="): + case (">="): + case ("<"): + case (">"): + case ("!="): + case ("=="): + type = "Boolean"; + break; + + default: + throw new CompileException( + ErrorString.invalidBinOperation(exp1.type, exp2.type, op)); + } + break; + + case ("Integer String"): + case ("String Integer"): + switch (op) + { + case ("+"): + case ("*"): + type = "String"; + break; + default: + throw new CompileException( + ErrorString.invalidBinOperation(exp1.type, exp2.type, op)); + } + break; + + case ("String String"): + switch (op) + { + case ("+"): + type = "String"; + break; + case ("=="): + type = "Boolean"; + break; + + default: + throw new CompileException( + ErrorString.invalidBinOperation(exp1.type, exp2.type, op)); + } + break; + + case ("Boolean Boolean"): + switch (op) + { + case ("&&"): + case ("||"): + case ("^"): + type = "Boolean"; + break; + + default: + throw new CompileException( + ErrorString.invalidBinOperation(exp1.type, exp2.type, op)); + } + break; + + default: + throw new CompileException( + ErrorString.invalidBinOperation(exp1.type, exp2.type, op)); + } + + } + + /** + * Compiles the BinOp + * + * NOTE: This assumes no overflow for integer multiplication, and + * it will also floor integer divide results + * + * @param e the given emitter + */ + public void compile(Emitter e) + { + e.emit("# Compiling BinOp components"); + exp1.compile(e); + + e.emitPush("$v0"); + + e.startCompilingBinOp(); + + exp2.compile(e); + + e.emitPop("$t0"); + + e.stopCompilingBinOp(); + + // first argument in t0, second argument in v0 + + String type1 = exp1.type; + String type2 = exp2.type; + + e.emit("# Beginning BinOp compilation"); + + switch(type1+" "+type2) // the type of output depends on the type of input + { + + /** + * Standard operations: + * mathematical (Integer result) + * relational (Boolean result) + * bitwise (Integer result) + */ + case("Integer Integer"): + switch(op) + { + // arithmetic operators + case ("+"): e.emit("addu $v0 $t0 $v0"); break; + case ("-"): e.emit("subu $v0 $t0 $v0"); break; + case ("*"): + e.emit("multu $v0 $t0"); + e.emit("mflo $v0"); + break; + case ("/"): + e.emit("divu $v0 $t0"); + e.emit("mflo $v0"); + break; + case ("%"): + e.emit("divu $v0 $t0"); + e.emit("mfhi $v0"); + break; + + // relational operators - TODO + case("<="): e.emit("jal _LEQ"); break; + case(">="): e.emit("jal _GEQ"); break; + case("<"): e.emit("jal _LT"); break; + case(">"): e.emit("jal _GT"); break; + case("!="): e.emit("jal _NE"); break; + case("=="): e.emit("jal _E"); break; + + // bitwise operators + case("&"): e.emit("and $v0 $t0 $v0"); break; + case("|"): e.emit("or $v0 $t0 $v0"); break; + case("<<"): e.emit("sllv $v0 $t0 $v0"); break; + case(">>"): e.emit("srlv $v0 $t0 $v0"); break; + case("^"): e.emit("xor $v0 $t0 $v0"); break; + } + break; + + + /** + * Same as below (just swap places before fall-through) + */ + case("Integer String"): + // todo + // e.emit("move $t1 $v0"); + // e.emit("move $v0 $t0"); + // e.emit("move $t0 $t1"); + break; + + /** + * String + Integer concatenates the integer and returns a string + * String * Integer returns the String repeated Integer amount of times + */ + case("String Integer"): + // todo + // switch(op) + // { + // case("+"): // todo + // // find the number of digits in the integer + + // e.emit("li $t1 0"); // t1 is the counter + + // // basically a while loop + // String whileStart = e.nextLabel(); + // String whileStop = e.nextLabel(); + // e.emit(whileStart+": "); + // //e.emit("") + // break; + + // case("*"): // todo + // e.emit("li $t1 ($v0)"); // length of old string in t1 + // e.emit("multu $t0 $t1"); + // e.emit("mflo $t2"); // length of new string in t2 + // e.emit("li $t3 1"); // t3 = 1 (used for decrementing) + // e.emit("move $t5 $v0"); // old string's address in t5 + // // string address in v0 + // // use t0 as the counter (for # of times to "add" the string) + // // use t4 as the internal counter (to iterate over each character) + + // // allocate space for string + // e.emit("li $v0 9"); + // // allocate enough space for strlength + str + null terminator + // e.emit("addiu $a0 $t2 5"); + // e.emit("syscall"); // memory address of newly allocated space is in v0 + + // String keepGoingLabel = e.nextLabel(); + // String stopLabel = e.nextLabel(); + + // String keepGoingLabelNested = e.nextLabel(); + // String stopLabelNested = e.nextLabel(); + + + // e.emit(keepGoingLabel + ": "); + // e.emit("beq $t0 0 " + stopLabel); // stop when t0 hits 0 + + // // concat the string + // e.emit("li $t4 0"); // t4 is the counter + // e.emit(keepGoingLabelNested + ": "); + // e.emit("beq $t4 $t1 " + stopLabelNested); // stop when t4==t1 + // e.emit("multu $t0 $t1"); + // e.emit("mflo $t6"); + // e.emit("addu $t6 $t6 4"); // need to add 4 because of offset + // e.emit("sb $t6($t5) $t6($v0)"); + // e.emit("addiu $t4 1"); + // // concat the current character and decrement t4 + // e.emit("j " + keepGoingLabelNested); + // e.emit(stopLabelNested+":"); + + + // e.emit("subu $t0 $t0 $t3"); // decrement t0 + // e.emit("j " + keepGoingLabel); + // e.emit(stopLabel + ":"); + // } + break; + + + + /** + * String + String returns the String concatenation + * String = String returns a Boolean if they are equal + */ + case ("String String"): + // todo + break; + + /** + * Standard logical operators + */ + case ("Boolean Boolean"): + switch(op) + { + case ("&&"): e.emit("and $v0 $t0 $v0"); break; + case ("||"): e.emit("or $v0 $t0 $v0"); break; + case ("^"): e.emit("xor $v0 $t0 $v0"); break; + } + break; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/pilers/ast/Block.java b/src/main/java/com/pilers/ast/Block.java new file mode 100644 index 0000000..bf37414 --- /dev/null +++ b/src/main/java/com/pilers/ast/Block.java @@ -0,0 +1,73 @@ +package com.pilers.ast; + +import java.util.List; +import com.pilers.environment.*; +import com.pilers.errors.InterpretException; +import com.pilers.errors.CompileException; +import com.pilers.emitter.*; + +/** + * AST Block class + * This represents a chunk of statements + * + * @author Gloria Zhu + */ +public class Block extends Statement +{ + private List stmts; + + /** + * Constructs a Block object + * + * @param stmts a list of statements + */ + public Block(List stmts) + { + this.stmts = stmts; + } + + /** + * Execute the statements in the block + * + * @param env the execution environment + * @throws BreakException if a break statement is encountered, let it keep + * bubbling + * @throws ContinueException if a continue statement is encountered, stop the + * rest of execution of the block + * @throws InterpretException if there is as run time error during execution + */ + public void exec(InterpreterEnvironment env) throws + BreakException, ContinueException, InterpretException + { + try + { + for (Statement s : stmts) s.exec(env); + } + catch (ContinueException e) + { + // do nothing + } + } + + /** + * Performs semantic analysis on this + * Analyzes each statement 1 by 1 + * + * @throws CompileException if any statements throw an error + * @param env the current env + */ + public void analyze(SemanticAnalysisEnvironment env) throws CompileException + { + for (Statement s : stmts) s.analyze(env); + } + + /** + * Compiles the Block + * @param e the given emitter + */ + public void compile(Emitter e) + { + for (Statement s : stmts) s.compile(e); + } + +} \ No newline at end of file diff --git a/src/main/java/com/pilers/ast/Break.java b/src/main/java/com/pilers/ast/Break.java new file mode 100644 index 0000000..cc7f00e --- /dev/null +++ b/src/main/java/com/pilers/ast/Break.java @@ -0,0 +1,35 @@ +package com.pilers.ast; + +import com.pilers.environment.*; +import com.pilers.errors.CompileException; + +/** + * AST Break Class + * + * @author Gloria Zhu + */ +public class Break extends Statement +{ + /** + * A Break statement breaks out of the for and while control blocks + * + * @param env the execution environment + * @throws BreakException when encountered + */ + public void exec(InterpreterEnvironment env) throws BreakException + { + throw new BreakException(); + } + + /** + * Performs semantic analysis on this + * (todo) Checks that the break statement is inside a looping construct + * + * @throws CompileException if the above condition is not met + * @param env the current env + */ + public void analyze(SemanticAnalysisEnvironment env) throws CompileException + { + // TODO check that the break is inside a looping construct + } +} \ No newline at end of file diff --git a/src/main/java/com/pilers/ast/BreakException.java b/src/main/java/com/pilers/ast/BreakException.java new file mode 100644 index 0000000..c4066b1 --- /dev/null +++ b/src/main/java/com/pilers/ast/BreakException.java @@ -0,0 +1,11 @@ +package com.pilers.ast; + +/** + * Thrown when a break statement is encountered + * + * @author Gloria Zhu + */ +public class BreakException extends Exception +{ + private static final long serialVersionUID = 1L; // to satisfy superclass +} \ No newline at end of file diff --git a/src/main/java/com/pilers/ast/Continue.java b/src/main/java/com/pilers/ast/Continue.java new file mode 100644 index 0000000..5d95716 --- /dev/null +++ b/src/main/java/com/pilers/ast/Continue.java @@ -0,0 +1,36 @@ +package com.pilers.ast; + +import com.pilers.environment.*; +import com.pilers.errors.CompileException; + +/** + * AST Break Class + * + * @author Gloria Zhu + */ +public class Continue extends Statement +{ + /** + * Executes the continue statement + * A continue statement breaks out of a block of statements + * + * @param env the execution environment + * @throws ContinueException when encountered + */ + public void exec(InterpreterEnvironment env) throws ContinueException + { + throw new ContinueException(); + } + + /** + * Performs semantic analysis on this (todo) + * Checks that the continue statement is inside a looping construct + * + * @throws CompileException if the above condition is not met + * @param env the current env + */ + public void analyze(SemanticAnalysisEnvironment env) throws CompileException + { + // TODO check that the continue is inside a looping construct + } +} \ No newline at end of file diff --git a/src/main/java/com/pilers/ast/ContinueException.java b/src/main/java/com/pilers/ast/ContinueException.java new file mode 100644 index 0000000..3e484ed --- /dev/null +++ b/src/main/java/com/pilers/ast/ContinueException.java @@ -0,0 +1,11 @@ +package com.pilers.ast; + +/** + * Thrown when a continue statement is executed + * + * @author Gloria Zhu + */ +public class ContinueException extends Exception +{ + private static final long serialVersionUID = 1L; // to satisfy superclass +} \ No newline at end of file diff --git a/src/main/java/com/pilers/ast/Declaration.java b/src/main/java/com/pilers/ast/Declaration.java new file mode 100644 index 0000000..bbb6897 --- /dev/null +++ b/src/main/java/com/pilers/ast/Declaration.java @@ -0,0 +1,80 @@ +package com.pilers.ast; + +import com.pilers.emitter.Emitter; +import com.pilers.environment.*; +import com.pilers.errors.ErrorString; +import com.pilers.errors.InterpretException; +import com.pilers.errors.CompileException; + +/** +* AST Declaration class + * + * @author Gloria Zhu + */ +public class Declaration extends Statement +{ + private String type; + private String var; + + /** + * Constructs an Assignment object + * + * @param type the type of the variable + * @param var a string + * @throws TypeErrorException + */ + public Declaration(String type, String var) + { + this.type = type; + this.var = var; + } + + /** + * Executes the declaration + * + * @param env the execution environment + * @throws BreakException if a break statement is executed + * @throws ContinueException if a continue statement is executed + * @throws InterpretException if there is a run time error (the variable already exists) + */ + public void exec(InterpreterEnvironment env) + throws BreakException, ContinueException, InterpretException + { + env.declareVariable(type, var); + } + + /** + * Performs semantic analysis on this (todo) + * Checks that the variable does not already exist + * + * @throws CompileException if the above condition is not met + * @param env the current env + */ + public void analyze(SemanticAnalysisEnvironment env) throws CompileException + { + String vartype = env.getVariableType(var); + if (vartype != null) throw new CompileException(ErrorString.duplicateIdentifier(var)); + + env.declareVariable(type, var); + } + + /** + * Compiles the declaration + * + * @param e given emitter + */ + public void compile(Emitter e) + { + // if e is not compiling a procedure, then this is a global variable and + // will be declared in .data + + if (e.isCompilingProcedure()) + { + e.addLocalVariable(var); + e.emit("li $v0 0 # Declaring local variable "+var); + // default value 0 for all variables + e.emitPush("$v0"); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/pilers/ast/DeclareAssignment.java b/src/main/java/com/pilers/ast/DeclareAssignment.java new file mode 100644 index 0000000..09a9eda --- /dev/null +++ b/src/main/java/com/pilers/ast/DeclareAssignment.java @@ -0,0 +1,72 @@ +package com.pilers.ast; + +import com.pilers.emitter.Emitter; +import com.pilers.environment.*; +import com.pilers.errors.InterpretException; +import com.pilers.errors.CompileException; + +/** + * AST DeclareAssignment class + * Represents a statement of the from 'TYPE IDENTIFER = VALUE' + * Basically a 2-in-1 package of a declaration and an assignment + * + * @author Gloria Zhu + */ +public class DeclareAssignment extends Statement +{ + private Declaration declaration; + private Assignment assignment; + + /** + * Constructs a DeclareAssignment object + * + * @param type the type of the variable + * @param var a string + * @param exp an expression + * @throws TypeErrorException + */ + public DeclareAssignment(String type, String var, Expression exp) + { + declaration = new Declaration(type, var); + assignment = new Assignment(var, exp); + } + + /** + * Executes the assignment + * + * @param env the execution environment + * @throws BreakException if a break statement is executed + * @throws ContinueException if a continue statement is executed + * @throws InterpretException if there is an error during execution + */ + public void exec(InterpreterEnvironment env) + throws BreakException, ContinueException, InterpretException + { + declaration.exec(env); + assignment.exec(env); + } + + /** + * Performs semantic analysis on this + * Delegates the tasks to the declaration and assignment objs respectively + * + * @throws CompileException if either throws an error + * @param env the current env + */ + public void analyze(SemanticAnalysisEnvironment env) throws CompileException + { + declaration.analyze(env); + assignment.analyze(env); + } + + /** + * Compiles the declaration/assignment + * @param e given emitter + */ + public void compile(Emitter e) + { + declaration.compile(e); + assignment.compile(e); + } + +} \ No newline at end of file diff --git a/src/main/java/com/pilers/ast/Expression.java b/src/main/java/com/pilers/ast/Expression.java new file mode 100644 index 0000000..23d03df --- /dev/null +++ b/src/main/java/com/pilers/ast/Expression.java @@ -0,0 +1,52 @@ +package com.pilers.ast; + +import com.pilers.environment.*; +import com.pilers.emitter.*; +import com.pilers.errors.InterpretException; +import com.pilers.errors.CompileException; + +/** + * AST Expression class + * @author Gloria Zhu + */ +public abstract class Expression +{ + /** + * The type of the expression + * To be used primarily during semantic analysis + */ + protected String type; + + /** + * Evaluates the expression + * To be implemented by subclasses + * + * @param env evaluation environment + * @return the value of the evaluated expression + * @throws BreakException if a break statement is executed + * @throws ContinueException if a continue statement is executed + * @throws InterpretException if there is a run time error during execution + */ + public abstract Value eval(InterpreterEnvironment env) + throws BreakException, ContinueException, InterpretException; + + /** + * Performs semantic analysis (checks things like type, scope) + * To be implemented by subclasses + * analyze() should be used during the semantic analysis stage + * + * @throws CompileException if any errors are found during analysis + * @param env analysis environment (SemanticAnalysisEnvironment) + */ + public abstract void analyze(SemanticAnalysisEnvironment env) throws CompileException; + + /** + * Compiles the statement with the given emitter + * + * @param e the given emitter + */ + public void compile(Emitter e) + { + throw new Error("Implement me!!!!!"); + } +} \ No newline at end of file diff --git a/src/main/java/com/pilers/ast/For.java b/src/main/java/com/pilers/ast/For.java new file mode 100644 index 0000000..6e4235b --- /dev/null +++ b/src/main/java/com/pilers/ast/For.java @@ -0,0 +1,130 @@ +package com.pilers.ast; + +import com.pilers.emitter.Emitter; +import com.pilers.environment.*; +import com.pilers.errors.ErrorString; +import com.pilers.errors.InterpretException; +import com.pilers.errors.CompileException; + +/** + * A For block uses the following syntax: + * + * FOR(ASSIGNMENT STATEMENT;BOOLEAN-VALUED EXPRESSION;ANY STATEMENT;) STATEMENT; + * + * Note the 3 semicolons instead of 2 inside the parentheses + * + * Example: + * + * FOR( i:=0 ; i<3 ; i = i + 1) WRITELN(i); + * + * @author Gloria Zhu + */ +public class For extends Statement +{ + private Statement declaration; + private Expression condition; + private Statement incrementation; + private Statement stmt; + + /** + * Constructs a For object Note: this part doesn't check if the first statement + * is an assignment and that the expression evaluates to a boolean, that's + * checked when the for statement is run + * + * @param decl the initial declaration (assignment) statement + * @param cond some expression that evaluates to a boolean Value + * @param incr some statement (could be anything) but should probably be + * incrementation + * @param stmt the body statement to be executed + */ + public For(Statement decl, Expression cond, Statement incr, Statement stmt) + { + declaration = decl; + condition = cond; + incrementation = incr; + this.stmt = stmt; + } + + /** + * Performs semantic analysis on this + * Checks that the expression is of a boolean type + * + * @throws CompileException if the above condition is broken + * @param env the current env + */ + public void analyze(SemanticAnalysisEnvironment env) throws CompileException + { + declaration.analyze(env); + condition.analyze(env); + incrementation.analyze(env); + stmt.analyze(env); + + if (!condition.type.equals("Boolean")) + throw new CompileException(ErrorString.forLoopConditionInvalid()); + } + + /** + * Executes the for statement Double checks that the first statement is an + * assignment and that the expression evaluates to a boolean; if not, it should + * eventually throw an error (but it currently does not) + * + * @param env the execution environment + * @throws BreakException if a break statement is encountered, terminate + * execution of the rest of the for loop + * @throws ContinueException if a continue statement is encountered, let it + * bubble + * @throws InterpretException if there is a run time exception when running + */ + public void exec(InterpreterEnvironment env) throws ContinueException, InterpretException + { + try + { + declaration.exec(env); + Value condVal = condition.eval(env); + + if (!condVal.getType().equals("Boolean")) + { + // todo throw an error eventually + } + else + { + while(condVal.getValue().equals("TRUE")) + { + stmt.exec(env); // execute statement + incrementation.exec(env); // execute incrementation + condVal = condition.eval(env); // evaluate condition again + + } + } + } + catch (BreakException e) + { + // stop execution + } + } + + /** + * Compiles the for statement + * + * @param e the emitter + */ + public void compile(Emitter e) + { + declaration.compile(e); + condition.compile(e); + + String keepGoingLabel = e.nextLabel(); + String stopLabel = e.nextLabel(); + + e.emit(keepGoingLabel + ":"); + e.emit("beq $v0 0 " + stopLabel); // if false, stop + + stmt.compile(e); + incrementation.compile(e); + + condition.compile(e); // recompile the condition + e.emit("j " + keepGoingLabel); + + e.emit(stopLabel + ":"); + } +} \ No newline at end of file diff --git a/src/main/java/com/pilers/ast/If.java b/src/main/java/com/pilers/ast/If.java new file mode 100644 index 0000000..7b955ac --- /dev/null +++ b/src/main/java/com/pilers/ast/If.java @@ -0,0 +1,88 @@ +package com.pilers.ast; + +import com.pilers.emitter.Emitter; +import com.pilers.environment.*; +import com.pilers.errors.ErrorString; +import com.pilers.errors.InterpretException; +import com.pilers.errors.CompileException; + +/** + * AST If class + * + * @author Gloria Zhu + */ +public class If extends Statement +{ + private Expression condition; + private Statement stmt; + + /** + * Constructs an If object (does not check that the expression evaluates to a + * boolean, that's the job of the execution) + * + * @param condition the condition + * @param stmt the body statement + */ + public If(Expression condition, Statement stmt) + { + this.condition = condition; + this.stmt = stmt; + } + + /** + * Performs semantic analysis on this + * Checks that the expression is of a boolean type + * + * @throws CompileException if the above condition is broken + * @param env the current env + */ + public void analyze(SemanticAnalysisEnvironment env) throws CompileException + { + condition.analyze(env); + stmt.analyze(env); + + if (!condition.type.equals("Boolean")) + throw new CompileException(ErrorString.ifLoopConditionInvalid()); + + // todo give a warning if the if block is empty? + } + + /** + * Evaluates the expression, if it is type boolean and evaluates to true, + * executes the body statement + * + * @param env the execution environment + * @throws BreakException if a break statement is executed, let it bubble + * @throws ContinueException if a continue statement is executed, let it bubble + * @throws InterpretException if an error is thrown during execution + */ + public void exec(InterpreterEnvironment env) + throws BreakException, ContinueException, InterpretException + { + Value val = condition.eval(env); + if (!(val.getType().equals("Boolean"))) + { + // todo throw an error + } + else if (val.getValue().equals("TRUE")) stmt.exec(env); + } + + /** + * Compiles the if statement + * + * @param e the emitter + */ + public void compile(Emitter e) + { + condition.compile(e); + + String label = e.nextLabel(); + + e.emit("beq $v0 0 "+label); // if false, go to label to skip the if statement + + stmt.compile(e); + + e.emit(label+":"); + + } +} \ No newline at end of file diff --git a/src/main/java/com/pilers/ast/ProcedureCallExpression.java b/src/main/java/com/pilers/ast/ProcedureCallExpression.java new file mode 100644 index 0000000..1eeab3e --- /dev/null +++ b/src/main/java/com/pilers/ast/ProcedureCallExpression.java @@ -0,0 +1,94 @@ +package com.pilers.ast; + +import com.pilers.emitter.Emitter; +import com.pilers.environment.*; +import com.pilers.errors.InterpretException; +import com.pilers.errors.CompileException; +import com.pilers.errors.ErrorString; + +/** + * AST ProcedureCallExpression class + * A procedure call used as an expression + * + * Assigns type during semantic analysis + * + * @author Gloria Zhu + */ +public class ProcedureCallExpression extends Expression +{ + private String name; + private Expression[] parameters; + + /** + * Constructs a ProcedureCallExpression objec + * + * @param name the name of the procedure to call + * @param parameters the passed in parameters (as expressions) + */ + public ProcedureCallExpression(String name, Expression[] parameters) + { + this.name = name; + this.parameters = parameters; + } + + /** + * Performs semantic analysis on this + * Uses the helper in the procedure declaration class + * + * @throws CompileException if a CompileException is thrown + * @param env the current env + */ + public void analyze(SemanticAnalysisEnvironment env) throws CompileException + { + for(Expression e : parameters) e.analyze(env); + + ProcedureDeclaration proc = env.getProcedure(name); + + if (proc==null) throw new CompileException(ErrorString.unknownProcedure(name)); + + proc.analyzeProcedureCall(env, parameters); + + this.type = proc.getReturnType(); + } + + /** + * Evaluates a procedure + * + * @param env the execution environment + * @throws BreakException if a break statement is executed, let it bubble + * @throws ContinueException if a continue statement is executed, let it bubble + * @return the returned value of the procedure, if any + * @throws InterpretException if an error is thrown + */ + public Value eval(InterpreterEnvironment env) + throws BreakException, ContinueException, InterpretException + { + ProcedureDeclaration proc = env.getProcedure(name); + + return proc.callProcedure(env, parameters); + } + + /** + * Compiles the procedure call as if it were used as an expression + * + * @param e the emitter + */ + public void compile(Emitter e) + { + + for (int i = 0; i < parameters.length; i++) + { + parameters[i].compile(e); + e.emitPush("$v0"); + e.addPushedParam(); + } + + e.resetPushedParams(); + + e.emit("jal " + name); + + // return value is pushed inside of the procedure for clarity + // now: return value is in v0 + } + +} \ No newline at end of file diff --git a/src/main/java/com/pilers/ast/ProcedureCallStatement.java b/src/main/java/com/pilers/ast/ProcedureCallStatement.java new file mode 100644 index 0000000..2918354 --- /dev/null +++ b/src/main/java/com/pilers/ast/ProcedureCallStatement.java @@ -0,0 +1,84 @@ +package com.pilers.ast; + +import com.pilers.emitter.Emitter; +import com.pilers.environment.*; +import com.pilers.errors.InterpretException; +import com.pilers.errors.CompileException; + +/** + * AST ProcedureCallStatement class + * A procedure call used as a statement + * + * @author Gloria Zhu + */ +public class ProcedureCallStatement extends Statement +{ + private String name; + private Expression[] parameters; + + /** + * Constructs a ProcedureCallStatement objec + * + * @param name the name of the procedure to call + * @param parameters the passed in parameters (as expressions) + */ + public ProcedureCallStatement(String name, Expression[] parameters) + { + this.name = name; + this.parameters = parameters; + } + + /** + * Performs semantic analysis on this + * Uses the helper in the procedure declaration class + * + * @throws CompileException if a CompileException is thrown + * @param env the current env + */ + public void analyze(SemanticAnalysisEnvironment env) throws CompileException + { + for (Expression e : parameters) e.analyze(env); + + ProcedureDeclaration proc = env.getProcedure(name); + proc.analyzeProcedureCall(env, parameters); + } + + /** + * Evaluates a procedure + * + * @param env the execution environment + * @throws BreakException if a break statement is executed, let it bubble + * @throws ContinueException if a continue statement is executed, let it bubble + * @throws InterpretException if there is an error thrown durign execution + */ + public void exec(InterpreterEnvironment env) + throws BreakException, ContinueException, InterpretException + { + ProcedureDeclaration proc = env.getProcedure(name); + proc.callProcedure(env, parameters); + } + + /** + * Compiles the procedure as if it were used as a statement + * + * @param e the emitter + */ + public void compile(Emitter e) + { + + for(int i=0;i hasOccurred = new HashMap(); + for(String p : paramNames) + { + if (hasOccurred.get(p) != null) throw new CompileException( + ErrorString.duplicateParamNames(name, p)); + if (p.equals(name)) throw new CompileException( + ErrorString.paramNameEqualsProcedureName(name)); + } + + env.setProcedure(name, this); + + SemanticAnalysisEnvironment childEnv = new SemanticAnalysisEnvironment(env); + + // setting the parameter values in the child environment + for (int i = 0; i < paramNames.length; i++) + { + childEnv.declareVariable(paramTypes[i], paramNames[i]); + } + + // setting the return value variable (same name as the procedure) + childEnv.declareVariable(returnType, name); + + stmt.analyze(childEnv); + } + + /** + * Performs semantic analysis for a given set of parameters + * + * This is in the declaration class instead of the procedure call classes + * because it's essentially the same for both, so this avoids repetition. + * + * Checks: + * parameter list length is the same + * parameter types match those of the declaration + * + * @param env the current env + * @param parameters the given parameters + * @throws CompileException if one of the above conditions is broken + */ + public void analyzeProcedureCall( + SemanticAnalysisEnvironment env, Expression[] parameters) throws CompileException + { + for(int i=0;i globalVars; // global variables + + private boolean isCompilingProcedure; + + private List procedureParams; + private String procedureName; + + private List procedureVars; + + private boolean isCompilingBinOp; + + // used for calling nested procedures (need to offset stack because of pushed parameters) + // see parsertest13 for an example + private int pushedParams; + + /** + * Creates an emitter for writing to a new file with given name + * + * @param outputFileName the output file name + * @param globalVars hashmap of data (name --> type) + */ + public Emitter(String outputFileName, Map globalVars) + { + this.globalVars = globalVars; + currLabel = 0; + pushedParams = 0; + this.procedureVars = new ArrayList(); + try + { + out = new PrintWriter(new FileWriter(outputFileName), true); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + /** + * Returns the next valid label to use. + * Labels are numbers prefixed with _ so that they + * don't collide with subroutines. They start from 0. + * @return a string of the next valid label + */ + public String nextLabel() + { + currLabel++; + return "_"+(currLabel-1); + } + + /** + * Tells the emitter that it is currently compiling a procedure declaration. + * This is needed so that the emitter knows, when compiling variable references, + * whether to load/store words from the stack, or if they are in global scope + * and should load/store from .data Passes in the names of the parameters as + * well as the name of the procedure name (to be used as the name of the return + * value) + * + * @param params parameter names of the currently-being-compiled procedure + * declaration + * @param procName name of the procedure currently being compiled + */ + public void startCompilingProcedureDeclaration(String[] params, String procName) + { + this.isCompilingProcedure = true; + this.procedureParams = Arrays.asList(params); + this.procedureName = procName; + } + + /** + * Adds a "pushed param" (for compiling nested procedure calls) + */ + public void addPushedParam() + { + pushedParams++; + } + + /** + * Resets the "pushed param" count + */ + public void resetPushedParams() + { + pushedParams = 0; + } + + /** + * Lets the emitter know it's in the middle of compiling a binop + * This is needed because the binop pushes the first value to + * the stack, so all references to variables on the stack + * need to be offset by those 4 bytes + */ + public void startCompilingBinOp() + { + this.isCompilingBinOp = true; + } + + /** + * Stops compiling the bin op + */ + public void stopCompilingBinOp() + { + this.isCompilingBinOp = false; + } + + /** + * Tells the emitter that it has finished compiling a procedure declaration. + * Resets the relevant instance variables. + */ + public void stopCompilingProcedureDeclaration() + { + this.isCompilingProcedure = false; + this.procedureParams = null; + this.procedureName = ""; + this.procedureVars.clear(); + } + + /** + * Adds a local variable to the procedureVars list + * @param name name of the variable + */ + public void addLocalVariable(String name) + { + procedureVars.add(name); + } + + /** + * Returns if the variable with the current name is a global variable or not. + * First sees if the current statements being compiled are in global scope (if so, + * it must be a global variable). Then, if it is in limited scope (inside a + * procedure declaration compilation), tests if any of the parameters or if + * the return value have the same name, and if not, returns true as well. + * + * @param name name of the variable being tested + * @return if the variable is a global reference or not + */ + public boolean isGlobalVariable(String name) + { + return !isCompilingProcedure || + !procedureParams.contains(name) && + !procedureName.equals(name) && + !procedureVars.contains(name); + } + + /** + * @return if the program is currently in the midst of compiling a procedure declaration + */ + public boolean isCompilingProcedure() + { + return isCompilingProcedure; + } + + /** + * Returns the index of the variable as a parameter inside the procedure. Allows + * for retrieval of values from the stack when calling the procedure. + * Parameters are pushed like this: + * left to right + * push a byte for the return value + * push the return address + * + * then, any local variable declarations are pushed whenever they come up + * + * Therefore the index of the return value is 1 + numLocalVars, and the index of the + * parameters goes backwards from there. + * + * @precondition the variable with this name is a local reference, not a global one + * @param name name of the variable in question + * @return the index of the variable in the parameter list + */ + public int getLocalVarIndex(String name) + { + int offset = isCompilingBinOp ? 1 : 0; + offset += pushedParams; + + int numLocalVars = procedureVars.size(); + + // if it's the return parameter + if (name.equals(procedureName)) return 1 + numLocalVars + offset; + + // if it's one of the parameters + if (procedureParams.contains(name)) + return procedureParams.size() - procedureParams.indexOf(name) + + 1 + numLocalVars + offset; + + // if it's a locally declared variable + return procedureVars.size() - procedureVars.indexOf(name) - 1 + offset; + } + + /** + * Loads a given string into v0. Allows for less repitition between + * places with string loading. + * + * Space is allocated for strings as such: + * 4 bytes that store the length of the string + * this will (hopefully) allow us to concatenate strings/get str lengths quickly + * (in the future, not implemented yet) + * n bytes that store the actual string + * 1 byte for a null terminator + * + * @param value the string in question + */ + public void loadString(String value) + { + int len = value.length(); + + emit("li $v0 9"); // instruction to allocate space + // allocate enough space for strlength + str + null terminator + emit("li $a0 " + (len + 5)); + emit("syscall"); // memory address of allocated space is in v0 + + // read in each character of the string byte by byte + char c; + char[] cArray = value.toCharArray(); + + // load string length into first 4 bytes + emit("li $t0 " + len); + emit("sw $t0 ($v0)"); + + // load in actual string + int i = 4; + for (; i < value.length() + 4; i++) + { + c = cArray[i - 4]; + if (c!='\'') emit(String.format("li $t0 '%c'", c)); + else emit("li $t0 '\\''"); + emit(String.format("sb $t0 %d($v0)", i)); + } + + // load in null term + emit(String.format("li $t0 '%c'", '\0')); + emit(String.format("sb $t0 %d($v0)", i)); + } + + /** + * Helper method to push the return address to the stack + */ + public void pushRA() + { + emit("subu $sp $sp 4 # Pushing return address"); + emit("sw $ra ($sp)"); + } + + /** + * Helper method to pop the return address from the stack + */ + public void popRA() + { + int numLocalVars = procedureVars.size(); + emit("lw $ra "+numLocalVars*4+"($sp)"); + } + + /** + * Helper method to pop the return value into v0 + * + * This doesn't need to consider BinOp offset - return + * value is only popped at the very end of a procedure, + * never inside of any other expression (I hope) + */ + public void popReturnValue() + { + + int index = getLocalVarIndex(procedureName); + emit("lw $v0 "+(index)*4 +"($sp)"); + } + + /** + * Clears all procedure variables out to save stack space + * (Just loads everything into a0) + */ + public void clearProcedureVarsFromStack() + { + // Number of local variables + number of parameters + return value + return address + int thingsToClear = procedureVars.size() + procedureParams.size() + 2; + for(;thingsToClear>0;thingsToClear--) + emitPop("$a0"); + } + + /** + * Prints one line of code to file (with non-labels indented) + * Appends a newline + * @param code the line of code + */ + public void emit(String code) + { + if (!code.endsWith(":")) + code = "\t" + code; + out.println(code); + } + + /** + * Prints an array of lines of code + * Calls emit for a singular string + * @param codes the lines of code + */ + public void emit(String[] codes) + { + for(String s : codes) emit(s); + } + + /** + * Emits push commands + * @param reg the register to push from + */ + public void emitPush(String reg) + { + emit("subu $sp $sp 4 # Pushing "+reg); // move stack up 4 bytes + emit("sw "+reg+" ($sp)"); // push value onto stack + } + + /** + * Emits pop commands + * @param reg the register to pop to + */ + public void emitPop(String reg) + { + emit("lw "+reg+" ($sp) # Popping "+reg); // pop $v0 + emit("addu $sp $sp 4"); // move stack down 4 bytes + } + + // These are used for compiling relational BinOps + // For example: 3<4, 5<6, etc. + // They're kind of like mini subroutines without parameters + + // I did this so there wouldn't be a bunch of repetition when + // compiling these binops, since the same series of like 5 lines + // would be repeated many times + + // The first argument is in t0, and the second argument is in v0 always + + /** + * less than or equal to + */ + public void emitLEQ() + { + emit("_LEQ:"); + emit("ble $t0 $v0 _TRUE"); + emit("j _FALSE"); + } + + /** + * less than + */ + public void emitLT() + { + emit("_LT:"); + emit("blt $t0 $v0 _TRUE"); + emit("j _FALSE"); + } + + /** + * greater than or equal to + */ + public void emitGEQ() + { + emit("_GEQ:"); + emit("bge $t0 $v0 _TRUE"); + emit("j _FALSE"); + } + + /** + * greater than + */ + public void emitGT() + { + emit("_GT:"); + emit("bgt $t0 $v0 _TRUE"); + emit("j _FALSE"); + } + + /** + * equal to + */ + public void emitE() + { + emit("_E:"); + emit("beq $t0 $v0 _TRUE"); + emit("j _FALSE"); + } + + /** + * not equal to + */ + public void emitNE() + { + emit("_NE:"); + emit("bne $t0 $v0 _TRUE"); + emit("j _FALSE"); + } + + /** + * Puts true boolean value into v0, + * then jumps to return address + */ + public void emitTrue() + { + emit("_TRUE:"); + emit("li $v0 1"); + emit("jr $ra"); + } + + /** + * Puts false boolean value into v0, + * then jumps to return address + */ + public void emitFalse() + { + emit("_FALSE:"); + emit("li $v0 0"); + emit("jr $ra"); + } + + /** + * TODO print "TRUE" or "FALSE" for booleans and not 1 and 0 + */ + public void emitPrintTRUE() + { + emit("TRUE:"); + emitPush("$a0"); + emitPush("$v0"); + emit("li $a0 1"); + emit("li $v0 1"); + emit("syscall"); + emitPop("$v0"); + emitPop("$a0"); + emit("jr $ra"); + } + + /** + * Private helper method for getting the MIPS representation of types + * @param in our type representation + * @return the MIPS representation + */ + private String formatType(String in) + { + return "word"; // for now + } + + /** + * @param type type of the variable + * @return the default value of the given variable type + */ + private String getDefaultValue(String type) + { + return "0"; // for now + } + + /** + * Emits all (pre-allocated) program data + * (for the .data section) + * Appends "_data_" to the beginning of their names + */ + public void emitProgramData() + { + for (Map.Entry entry : globalVars.entrySet()) + { + String varname = entry.getKey(); + String vartype = entry.getValue(); + + String defaultVal = getDefaultValue(vartype); + vartype = formatType(vartype); + + emit(String.format("%s: .%s %s", "_data_"+varname, vartype, defaultVal)); + } + } + + /** + * Closes the file, should be called after all calls to emit + */ + public void close() + { + out.close(); + } +} \ No newline at end of file diff --git a/src/main/java/com/pilers/environment/Environment.java b/src/main/java/com/pilers/environment/Environment.java new file mode 100644 index 0000000..c2e0f01 --- /dev/null +++ b/src/main/java/com/pilers/environment/Environment.java @@ -0,0 +1,125 @@ +package com.pilers.environment; + +import java.util.HashMap; +import java.util.Map; + +import com.pilers.ast.*; + +/** + * This is the parent class of both environment classes; it has been abstracted + * because there is a lot of shared functionality between SemanticAnalysisEnv + * and InterpreterEnv. + * + * It keeps track of variable names, types, and values; procedure names + * and values; and its parent environment. + * + * Note: the SemanticAnalysisEnvironment does not ever use the value map (varmapVal), + * only the InterpreterEnvironment does. + * + * @author Gloria Zhu + * @version 8/19/22 + */ +public abstract class Environment +{ + + /** + * This maps variable names to their types (represented as strings) + */ + protected Map varmapType; + /** + * This maps variable names to their values + */ + protected Map varmapVal; + + /** + * This maps procedure names to their declarations + */ + protected Map procmap; + + /** + * This is the parent environment of this current env. + * Is null if this is the global env. + */ + protected Environment parentEnv; + + /** + * Creates a new environment with a parent env + * + * @param parentEnv the parent env + */ + public Environment(Environment parentEnv) + { + this.parentEnv = parentEnv; + varmapType = new HashMap(); + varmapVal = new HashMap(); + procmap = new HashMap(); + } + + /** + * Sets a procedure declaration + * + * @param name name of the procedure + * @param procedure value of the procedure + */ + public void setProcedure(String name, ProcedureDeclaration procedure) + { + procmap.put(name, procedure); + } + + /** + * @return if this environment is the global env or not + */ + public boolean isGlobal() + { + return this.parentEnv == null; + } + + /** + * Gets a procedure's declaration value Searches up through the environments + * until it either finds the procedure or reaches the "null" environment (goes + * past the global environment) + * + * @param name name of the procedure + * @return the value of the procedure, or null if not found + */ + public ProcedureDeclaration getProcedure(String name) + { + Environment current = this; + ProcedureDeclaration proc = null; + while (current != null && proc == null) + { + proc = current.procmap.get(name); + current = current.parentEnv; + } + return proc; + } + + /** + * Declares a variable with a given type and name; if the isInterpreter flag + * is set to true, also gives the variable a default value in the value map. + * @param type the type of the variable, represented as a string + * @param name the name of the variable + * @param isInterpreter if the environment invoking this is an interpreter, + * give the variable a default value in the value map + * @return if the variable was successfully declared or not (if the variable + * already existed, this would fail) + */ + public boolean declareVariable(String type, String name, boolean isInterpreter) + { + if (this.varmapType.get(name)!=null) return false; + + varmapType.put(name, type); + if (isInterpreter) + { + if (type.equals("Integer")) + varmapVal.put(name, new Value(0)); + else if (type.equals("String")) + varmapVal.put(name, new Value("")); + else if (type.equals("Boolean")) + varmapVal.put(name, new Value(false)); + } + + return true; + } + +} \ No newline at end of file diff --git a/src/main/java/com/pilers/environment/InterpreterEnvironment.java b/src/main/java/com/pilers/environment/InterpreterEnvironment.java new file mode 100644 index 0000000..0c51a8d --- /dev/null +++ b/src/main/java/com/pilers/environment/InterpreterEnvironment.java @@ -0,0 +1,100 @@ +package com.pilers.environment; + +import java.util.HashMap; + +import com.pilers.ast.*; +import com.pilers.errors.*; + +/** + * The InterpreterEnvironment serves as a runtime environment, + * to be used when interpreting a program. It is passed into + * every execution and evaluation, and essentially serves + * as a variable table. + * + * @author Gloria Zhu + * @version 8/19/22 + */ +public class InterpreterEnvironment extends Environment +{ + + /** + * Creates a new environment with a parent env + * @param parentEnv the parent env + */ + public InterpreterEnvironment(InterpreterEnvironment parentEnv) + { + super(parentEnv); + varmapVal = new HashMap(); + } + + /** + * Factory method to return a new global environment (parent is null) + * @return the new env + */ + public static InterpreterEnvironment newGlobalEnv() + { + return new InterpreterEnvironment(null); + } + + /** + * Declares a variable in the variable map Calls the helper method and passes + * in true; the true flag tells the environemnt to give this variable a default + * value in the value hashmap. + * + * @param type the type of the variable + * @param name the name of the variable + * @throws InterpretException if the variable has already been declared + */ + public void declareVariable(String type, String name) throws InterpretException + { + if (!declareVariable(type, name, true)) + throw new InterpretException(ErrorString.duplicateIdentifier(name)); + } + + /** + * Sets a variable in the variable maps + * + * @param name the name of the variable + * @param value the value of the variable + * @throws CompileException if the variable has not been declared yet + * @throws CompileException if the assigned value does not have the correct type + */ + public void setVariable(String name, Value value) throws InterpretException + { + Environment current = this; + String var = current.varmapType.get(name); + + while (current.parentEnv != null && var == null) + { + current = current.parentEnv; + var = current.varmapType.get(name); + } + + if (var == null) throw new InterpretException(ErrorString.unknownIdentifier(name)); + else if (!var.equals(value.getType())) + throw new InterpretException(ErrorString.typeAssignment(var, value.getType())); + + current.varmapVal.put(name, value); + } + + /** + * Gets a variable's value + * Searches up through the environments until it either + * finds the variable's value or reaches the "null" + * environment (goes past the global environment) + * + * @param name the name of the variable + * @return the value of the variable, or null if not found + */ + public Value getVariable(String name) + { + Environment current = this; + Value varValue = null; + while(current != null && varValue == null) + { + varValue = current.varmapVal.get(name); + current = current.parentEnv; + } + return varValue; + } +} \ No newline at end of file diff --git a/src/main/java/com/pilers/environment/SemanticAnalysisEnvironment.java b/src/main/java/com/pilers/environment/SemanticAnalysisEnvironment.java new file mode 100644 index 0000000..3e8908b --- /dev/null +++ b/src/main/java/com/pilers/environment/SemanticAnalysisEnvironment.java @@ -0,0 +1,91 @@ +package com.pilers.environment; + +import java.util.Map; + +import com.pilers.errors.*; + +/** + * The SemanticAnalysisEnvironment is to be used in semantic + * analysis of the program. This includes things like type checking, + * seeing if a variable exists, etc. + * + * Potential future additions (to act as a linter): + * Checking for empty control statements and sending warnings + * Checking for never-reached code and sending warnings + * + * @author Gloria Zhu + * @version 8/19/22 + */ +public class SemanticAnalysisEnvironment extends Environment +{ + /** + * Creates a new environment with a parent env + * + * @param parentEnv the parent env + */ + public SemanticAnalysisEnvironment(SemanticAnalysisEnvironment parentEnv) + { + super(parentEnv); + } + + /** + * Factory method to return a new global environment (parent is null) + * + * @return the new env + */ + public static SemanticAnalysisEnvironment newGlobalEnv() + { + return new SemanticAnalysisEnvironment(null); + } + + /** + * Declares a space by allocating space for it in the variable map + * + * @param type the type of the variable + * @param name the name of the variable + * @throws CompileException if the variable is already defined + */ + public void declareVariable(String type, String name) throws CompileException + { + if (!declareVariable(type, name, false)) throw new CompileException("TODO: WRITE ME"); + } + + /** + * @param varname the name of the variable + * @return if the variable with the given name exists at the moment + */ + public boolean variableExists(String varname) + { + return getVariableType(varname) != null; + } + + /** + * Gets the type of a variable as a string + * + * @param name the name of the variable + * @return null if the variable does not exist + */ + public String getVariableType(String name) + { + // Environment current = this; + // String var = current.varmapType.get(name); + + // while (current.parentEnv != null && var == null) + // { + // current = current.parentEnv; + // var = current.varmapType.get(name); + // } + + // return var; + + return this.varmapType.get(name); + } + + /** + * @return the map of variable names to types + */ + public Map getVariableInfo() + { + return varmapType; + } +} \ No newline at end of file diff --git a/src/main/java/com/pilers/errors/CompileException.java b/src/main/java/com/pilers/errors/CompileException.java new file mode 100644 index 0000000..5ae910a --- /dev/null +++ b/src/main/java/com/pilers/errors/CompileException.java @@ -0,0 +1,30 @@ +package com.pilers.errors; + +/** + * A CompileException is thrown during the semantic analysis of compilation + * It can occur for a variety of reasons, including type and scope errors + * + * When throwing a CompileException error, a string generated by the ErrorString + * class should be passed in. + * + * @author Gloria Zhu + */ +public class CompileException extends Exception +{ + // satisfy inheritance + private static final long serialVersionUID = 1L; + + /** + * Constructor for CompileException that includes a reason for the error + * + * This should always be called using one of the static reason generators in the + * ErrorString class + * + * @param reason the reason for the CompileException + */ + public CompileException(String reason) + { + super(reason); + } + +} \ No newline at end of file diff --git a/src/main/java/com/pilers/errors/ErrorString.java b/src/main/java/com/pilers/errors/ErrorString.java new file mode 100644 index 0000000..a992fa5 --- /dev/null +++ b/src/main/java/com/pilers/errors/ErrorString.java @@ -0,0 +1,182 @@ +package com.pilers.errors; + +/** + * The purpose of this class is to format error strings for the + * CompileException and InterpretException classes. It has been separated + * into its own class in order to standardize error formatting. + * + * @author Gloria Zhu + */ +public abstract class ErrorString +{ + /** + * @param varname identifier + * @return error message for an unknown identifier + */ + public static String unknownIdentifier(String varname) + { + return "Unknown identifier " + varname; + } + + /** + * @param procname procedure name + * @return error message for an unknown procedure + */ + public static String unknownProcedure(String procname) + { + return "Unknown procedure " + procname; + } + + /** + * @param directiveName directive name + * @return error message for an unknown directive (i.e. #hello) + */ + public static String unknownDirective(String directiveName) + { + return "Unknown directive " + directiveName; + } + + /** + * @param structname struct name + * @return error message for a struct that isn't declared globally + */ + public static String structsMustBeGlobal(String structname) + { + return "struct " + structname + " must be declared globally"; + } + + /** + * @param structname struct name + * @return error message for a duplicate struct declaration + */ + public static String duplicateStructName(String structname) + { + return "Duplicate struct found for name " + structname; + } + + /** + * @param definitionVar variable being defined + * @return error message for a duplicate defind directive + */ + public static String duplicateDefine(String definitionVar) + { + return "Duplicate define directive for " + definitionVar; + } + + /** + * @param expectedType the expected type + * @param actualType the actual type + * @return error message for an invalid type assignemnt + */ + public static String typeAssignment(String expectedType, String actualType) + { + return String.format("Type error: expected %s, got %s", expectedType, actualType); + } + + /** + * @param type1 the first type + * @param type2 the second type + * @param operand operand represented as a string + * @return error message for an invalid binary operation (i.e. BOOLEAN * + * INTEGER) + */ + public static String invalidBinOperation(String type1, String type2, String operand) + { + return String.format("Cannot perform %s operation between %s and %s", + operand, type1, type2); + } + + /** + * @param type the type of the operand + * @param operand operand represented as a string + * @return error message for an invalid unary operation (i.e. BOOLEAN++) + */ + public static String invalidUnaryOperation(String type, String operand) + { + return String.format("Cannot perform %s operation on %s", operand, type); + } + + /** + * @param varname the name of the identifier + * @return error message for a duplicate identifier (i.e. if a variable has + * already been declared) + */ + public static String duplicateIdentifier(String varname) + { + return "Duplicate identifier found for name " + varname; + } + + /** + * @return error message for if a FOR loop's condition is not a boolean + */ + public static String forLoopConditionInvalid() + { + return "Error: FOR loop condition must evaluate to a boolean"; + } + + /** + * @return error message for if a WHILE loop's condition is not a boolean + */ + public static String whileLoopConditionInvalid() + { + return "Error: WHILE loop condition must evaluate to a boolean"; + } + + /** + * @return error message for if a IF loop's condition is not a boolean + */ + public static String ifLoopConditionInvalid() + { + return "Error: IF loop condition must evaluate to a boolean"; + } + + /** + * @param procedureName name of the procedure + * @param expectedNumber the expected number of parameters + * @param actualNumber the actual number of parameters given + * @return error message for having the wrong number of paramters in a + * procedure call + */ + public static String wrongNumberOfParameters( + String procedureName, int expectedNumber, int actualNumber) + { + return String.format("Wrong number of parameters for procedure %s() call: "+ + "expected %d, got %d", procedureName, expectedNumber, actualNumber); + } + + /** + * @param procedureName name of the procedure + * @param expectedType expected type of the procedure + * @param actualType the actual type given + * @param index the index of the parameter (if it's the 1st, 2nd one etc.) + * @return error message for passing in the wrong type of parameter during + * a procedure call + */ + public static String invalidParameterType( + String procedureName, String expectedType, String actualType, int index) + { + return String.format("Invalid type for parameter %d for procedure %s() call: "+ + "expected %s, got %s", index, procedureName, expectedType, actualType); + } + + /** + * @param procedure the procedure name + * @param param the param name + * @return error message for having duplicate parameter names in a procedure declaration + */ + public static String duplicateParamNames(String procedure, String param) + { + return String.format("Duplicate parameter name '%s' in procedure declaration %s()", + param, procedure); + } + + /** + * @param name the procedure/parameter name + * @return error message for having a parameter with the same name as the procedure + */ + public static String paramNameEqualsProcedureName(String name) + { + return String.format("Parameter name cannot equal name of procedure: "+ + "error %s() declaration", name); + } +} \ No newline at end of file diff --git a/src/main/java/com/pilers/errors/InterpretException.java b/src/main/java/com/pilers/errors/InterpretException.java new file mode 100644 index 0000000..1d5e973 --- /dev/null +++ b/src/main/java/com/pilers/errors/InterpretException.java @@ -0,0 +1,30 @@ +package com.pilers.errors; + +/** + * An InterpretException is thrown during the runtime of interpretation It can + * occur for a variety of reasons, including type and scope errors + * + * When throwing an InterpretException error, a string generated by the + * ErrorString class should be passed in. + * + * @author Gloria Zhu + */ +public class InterpretException extends Exception +{ + + // satisfy inheritance + private static final long serialVersionUID = 1L; + + /** + * Constructor for InterpretException that includes a reason for the error + * + * This should always be called using one of the static reason generators in the + * ErrorString class + * + * @param reason the reason for the InterpretException + */ + public InterpretException(String reason) + { + super(reason); + } +} \ No newline at end of file diff --git a/src/main/java/com/pilers/errors/PreprocessorException.java b/src/main/java/com/pilers/errors/PreprocessorException.java new file mode 100644 index 0000000..9304d32 --- /dev/null +++ b/src/main/java/com/pilers/errors/PreprocessorException.java @@ -0,0 +1,31 @@ +package com.pilers.errors; + +/** + * A PreprocessorException is thrown during the preprocessing phase of + * the compiler + * It can be thrown for a variety of reasons, such as unknown directives + * + * When throwing a PreprocessorException error, a string generated by the + * ErrorString class should be passed in. + * + * @author Gloria Zhu + */ +public class PreprocessorException extends Exception +{ + // satisfy inheritance + private static final long serialVersionUID = 1L; + + /** + * Constructor for PreprocessorException that includes a reason for the error + * + * This should always be called using one of the static reason generators in the + * ErrorString class + * + * @param reason the reason for the PreprocessorException + */ + public PreprocessorException(String reason) + { + super(reason); + } + +} \ No newline at end of file diff --git a/src/main/java/com/pilers/errors/ScanErrorException.java b/src/main/java/com/pilers/errors/ScanErrorException.java new file mode 100644 index 0000000..704d0fb --- /dev/null +++ b/src/main/java/com/pilers/errors/ScanErrorException.java @@ -0,0 +1,77 @@ +package com.pilers.errors; + +/** + * ScanErrorException is a sub class of Exception and is thrown during the + * scanning phase in order to indicate a scan error. + * + * Usually, the scanning error is the result of an illegal character in the + * input stream. The error is also thrown when the expected value of the + * character stream does not match the actual value, or if a + * block comment is never closed in the code. + * + * @author Mr. Page + * @author Gloria Zhu + */ +public class ScanErrorException extends Exception +{ + + private static final long serialVersionUID = 1L; + + /** + * Default constructor for ScanErrorObjects, without a reason + */ + public ScanErrorException() + { + super(); + } + + /** + * Constructor for ScanErrorObjects that includes a reason for the error + * + * @param reason the reason for the ScanErrorException + */ + public ScanErrorException(String reason) + { + super(reason); + } + + // Static factory methods for some common errors + + /** + * Returns an illegalCharacter ScanErrorException + + * @param expected the expected character + * @param actual the actual character + * @return the ScanErrorException + */ + public static ScanErrorException illegalCharacter(char expected, char actual) + { + return new ScanErrorException("Illegal character: expected " + expected + + ", got " + actual + " instead."); + } + + /** + * Returns an illegalLexeme ScanErrorException + * + * @param expectedType the type that was expected + * @param illegalLexeme the illegal lexeme that did not match the expected type + * @return the ScanErrorException + */ + public static ScanErrorException illegalLexeme(String expectedType, String illegalLexeme) + { + return new ScanErrorException("Illegal lexeme: expected lexeme of type " + + expectedType + ", got " + illegalLexeme + " instead."); + } + + /** + * Returns a blockCommentNotClosed ScanErrorException (thrown when + * a block comment is not closed before the EOF) + * + * @return the ScanErrorException + */ + public static ScanErrorException blockCommentNotClosed() + { + return new ScanErrorException("Block comment not closed"); + } + +} diff --git a/src/main/java/com/pilers/parser/Parser.java b/src/main/java/com/pilers/parser/Parser.java new file mode 100644 index 0000000..909ca6b --- /dev/null +++ b/src/main/java/com/pilers/parser/Parser.java @@ -0,0 +1,487 @@ +package com.pilers.parser; + +import com.pilers.scanner.*; +import com.pilers.ast.*; +import com.pilers.ast.Value; +import com.pilers.errors.*; + +import java.io.*; +import java.util.ArrayList; + +/** + * A Parser is responsible for reading the token stream from the scanner, one + * token at a time, and processing it into a parse tree according to a grammar. + * + * @author Gloria Zhu + * @version 8/19/22 + */ +public class Parser +{ + private Scanner sc; + private Token currTok; + + /** + * Default constructor for a parser. Reads the first token in from the stream + * and stores it in currTok. + * + * @param scan the input scanner + */ + public Parser(Scanner scan) + { + sc = scan; + + try + { + currTok = sc.nextToken(); + } + catch (IOException e) + { // if IOException, print the error and terminate + System.out.println(e); + System.exit(-1); + } + catch (ScanErrorException e) + { // if ScanErrorException, print the error but don't terminate + System.out.println(e); + } + } + + /** + * @return true if the Parser still has more tokens to parse + */ + public boolean hasNext() + { + return currTok.getType() != Token.END; + } + + /** + * Same as eat, just allows a Token parameter instead of string + * + * @param expected the expected string (wrapped in a token's value) + */ + private void eat(Token expected) + { + this.eat(expected.getValue()); + } + + /** + * Same as eat, just allows a character parameter as well + * + * @param expected the expected string (in char form) + */ + private void eat(char expected) + { + eat("" + expected); + } + + /** + * If the current token matches the passed in expected string, eats it and moves + * to the next token. + * + * @param expected the expected string + * @throws IllegalArgumentException if the string & current token don't match + */ + private void eat(String expected) + { + if (currTok.getValue().equals(expected)) + { // got the expected value, read the next token + + try + { + currTok = sc.nextToken(); + } + catch (IOException e) // print the error and terminate + { + System.out.println(e); + System.exit(-1); + } + catch (ScanErrorException e) // print the error but don't terminate + { + System.out.println(e); + } + + } + else // did not get the expected value, throw an error + { + throw new IllegalArgumentException( + String.format("Expected %s, but got %s %s", + expected, currTok.getTypeAsString(), currTok.getValue())); + } + } + + /** + * Parses a value + * + * @precondition current token is a number, string, or boolean + * @postcondition Value token has been eaten + * @return a Value object + */ + private Value parseValue() + { + String val = currTok.getValue(); + String type = currTok.getTypeAsNiceString(); + eat(currTok); + return new Value(val, type); + + } + + /** + * Parses an expression. An expression consists of a series of factors connected + * by operands. + * + * In order to account for operator precedence, the operators are stored in a 2D + * array (in the Token class), with each subsequent array having higher + * precedence in the previous one. + * + * Then, the parseExpression method takes in a parameter, which is the level of + * precedence. If parseExpression is called from an external method, it should + * always be given 0 as the starting level of precedence. + * + * It searches the operators at the given level of precedence for matches and + * recursively calls itself with levelOfPrecedence+1. The base case is when the + * level of precedence is equal to the length of the 2D array of operators, at + * which point it just returns parseFactor(). + * + * @precondition current token is the beginning of a factor + * @postcondition expression has been parsed and eaten + * @param levelOfPrecedence the level of operator precedence to parse at + * @return an Expression object + */ + private Expression parseExpression(int levelOfPrecedence) + { + // base case + if (levelOfPrecedence == Token.OPERATORS_ORDERED_BY_PRECEDENCE.length) + return parseFactor(); + + Expression val = parseExpression(levelOfPrecedence + 1); + + String oper = currTok.getValue(); + + String[] validOpers = Token.OPERATORS_ORDERED_BY_PRECEDENCE[levelOfPrecedence]; + while (strArrayContains(validOpers, oper)) + { + eat(oper); + val = new BinOp(oper, val, parseExpression(levelOfPrecedence + 1)); + oper = currTok.getValue(); + } + return val; + } + + /** + * Helper method for testing if a string array contains a string + * + * @param arr string array + * @param str string + * @return if the string array contains the string + */ + private boolean strArrayContains(String[] arr, String str) + { + for (int i = 0; i < arr.length; i++) + if (arr[i].equals(str)) + return true; + return false; + } + + /** + * Parses a factor. + * + * A factor consists of either: another factor and a unary operator a variable + * name an expression in parentheses a raw Value (integer, string, boolean) + * + * @precondition current token is a unary operator, identifier, open + * parentheses, or beginning of a Value + * @postcondition factor has been parsed and eaten + * @return an Expression object + */ + private Expression parseFactor() + { + if (strArrayContains(Token.UNARY_OPERATORS, currTok.getValue())) + { + String op = currTok.getValue(); + eat(op); + return new UnaryOp(op, parseFactor()); + } + else if (currTok.getType() == Token.OPEN_PAREN) + { + eat(Token.OPEN_PAREN_CHAR); + Expression val = parseExpression(0); + eat(Token.CLOSE_PAREN_CHAR); + return val; + } + else if (currTok.getType() == Token.IDENTIFIER) + { + String id = currTok.getValue(); + eat(id); + + if (currTok.equals(Token.OPEN_PAREN_CHAR)) + { + eat(Token.OPEN_PAREN_CHAR); + Expression[] params = parseParamsList(); + eat(Token.CLOSE_PAREN_CHAR); + return new ProcedureCallExpression(id, params); + } + + return new VariableReference(id); + } + return parseValue(); + } + + /** + * Parses a list of parameter values separated by commas + * + * @return the array of parameters (Expressions) + */ + private Expression[] parseParamsList() + { + ArrayList params = new ArrayList(); + while (!(currTok.equals(Token.CLOSE_PAREN_CHAR))) + { + params.add(parseExpression(0)); + if (currTok.equals(Token.COMMA_CHAR)) + eat(Token.COMMA_CHAR); + + } + Expression[] temp = new Expression[0]; + + return params.toArray(temp); // cast from Object[] --> Expression[] + } + + /** + * Parses a list of parameter types and names, separated by commas + * + * @return a 2D array that contains 2 String arrays, types and names + */ + private String[][] parseParamsDeclarationList() + { + ArrayList paramTypes = new ArrayList(); + ArrayList paramNames = new ArrayList(); + while (!(currTok.equals(Token.CLOSE_PAREN_CHAR))) + { + paramTypes.add(currTok.getValue()); + eat(currTok); + paramNames.add(currTok.getValue()); + eat(currTok); + + if (currTok.equals(Token.COMMA_CHAR)) + eat(Token.COMMA_CHAR); + + } + String[] temp1 = new String[0]; + String[] temp2 = new String[0]; + + return new String[][] { paramTypes.toArray(temp1), paramNames.toArray(temp2) }; + } + + /** + * Parses a program + * A program consists of 0 or more statements Order of + * procedure declarations / executed statements is arbitrary - this part is + * different from the documentation. + * + * @param args array of command line arguments + * @return the parsed program + */ + public Program parseProgram(String[] args) + { + ArrayList stmts = new ArrayList(); + while (hasNext()) + stmts.add(parseStatement()); + + Statement[] temp = new Statement[0]; + + return new Program(stmts.toArray(temp), args); // cast from Object[] --> Statement[] + } + + /** + * Parses a statement, printing and reading i/o as needed. + * + * @precondition current token is one of the following: "WRITELN" "READLN" + * "BEGIN" of type identifier + * @postcondition statement has been parsed and eaten, including the line + * terminator + * + * @return parsed statement + * @throws CompileException + */ + public Statement parseStatement() + { + switch (currTok.getType()) + { + case (Token.KEYWORD): + + switch (currTok.getValue()) + { + case "writeln": // write line + + eat("writeln"); + eat(Token.OPEN_PAREN_CHAR); + Expression exp = parseExpression(0); + eat(Token.CLOSE_PAREN_CHAR); + eat(Token.LINE_TERMINATOR_CHAR); + + return new Writeln(exp); + + case "readInteger": + // read integer from user input + + eat("readInteger"); + eat(Token.OPEN_PAREN_CHAR); + String userId = currTok.getValue(); + eat(userId); + eat(Token.CLOSE_PAREN_CHAR); + eat(Token.LINE_TERMINATOR_CHAR); + + return new ReadInteger(userId); + + case "if": // begin an if block + eat("if"); + eat(Token.OPEN_PAREN_CHAR); + Expression ifExpr = parseExpression(0); + eat(Token.CLOSE_PAREN_CHAR); + Statement ifStmt = parseStatement(); + + return new If(ifExpr, ifStmt); + + case "while": // begin a while block + eat("while"); + eat(Token.OPEN_PAREN_CHAR); + Expression whileExpr = parseExpression(0); + eat(Token.CLOSE_PAREN_CHAR); + Statement whileStmt = parseStatement(); + + return new While(whileExpr, whileStmt); + + case "for": // begin a for block + eat("for"); + eat(Token.OPEN_PAREN_CHAR); + Statement forDecl = parseStatement(); // should be an assignment + Expression forCond = parseExpression(0); // should be a boolean + eat(";"); + Statement forIncr = parseStatement(); + eat(Token.CLOSE_PAREN_CHAR); + + Statement forStmt = parseStatement(); + + return new For(forDecl, forCond, forIncr, forStmt); + + case "break": // break statement + eat("break"); + eat(Token.LINE_TERMINATOR_CHAR); + + return new Break(); + + case "continue": // break statement + eat("continue"); + eat(Token.LINE_TERMINATOR_CHAR); + + return new Continue(); + + } + break; + + case(Token.OPERATOR): + if (currTok.getValue().equals("{")) + { + eat("{"); + ArrayList stmts = new ArrayList(); + while (!currTok.equals("}")) + { + stmts.add(parseStatement()); + } + eat("}"); + + return new Block(stmts); + } + break; + // todo: throw an error + + default: // not of keyword type + + // if the token does not match any keywords, + // check if it is an identifier + // (this indicates a procedure call or variable assignment) + if (currTok.getType() == Token.IDENTIFIER) + { + String id = currTok.getValue(); + eat(id); + + switch(currTok.getValue()) + { + // variable assignment (no declaration) + case(""+Token.ASSIGNMENT_OPERATOR): + eat(Token.ASSIGNMENT_OPERATOR); + Expression value = parseExpression(0); + eat(Token.LINE_TERMINATOR_CHAR); + + return new Assignment(id, value); + + // procedure call + case(""+Token.OPEN_PAREN_CHAR): + + // for later + eat(Token.OPEN_PAREN_CHAR); + Expression[] params = parseParamsList(); + eat(Token.CLOSE_PAREN_CHAR); + eat(Token.LINE_TERMINATOR_CHAR); + + return new ProcedureCallStatement(id, params); + default: + // todo: throw an error + } + + } + // either a variable declaration, variable declaration+assignment, + // or procedure declaration + else if (currTok.getType() == Token.TYPE) + { + String type = currTok.getValue(); + eat(type); + + String id = currTok.getValue(); + eat(id); + + switch(currTok.getValue()) + { + // has assignment + case(Token.ASSIGNMENT_OPERATOR): + eat(Token.ASSIGNMENT_OPERATOR); + + Expression expr = parseExpression(0); + + eat(Token.LINE_TERMINATOR_CHAR); + + return new DeclareAssignment(type, id, expr); + + // no assignment, just a declaration + case(""+Token.LINE_TERMINATOR_CHAR): + eat(Token.LINE_TERMINATOR_CHAR); + + return new Declaration(type, id); + + // procedure declaration + case(""+Token.OPEN_PAREN_CHAR): + eat(Token.OPEN_PAREN_CHAR); + + String[][] params = parseParamsDeclarationList(); + String[] paramTypes = params[0]; + String[] paramNames = params[1]; + + eat(Token.CLOSE_PAREN_CHAR); + + Statement stmt = parseStatement(); + + return new ProcedureDeclaration(id, stmt, + type, paramTypes, paramNames); + + default: + // todo: throw an error + + } + + } + break; + } + + return null; + + } +} \ No newline at end of file diff --git a/src/main/java/com/pilers/parser/ParserTester.java b/src/main/java/com/pilers/parser/ParserTester.java new file mode 100644 index 0000000..956ede9 --- /dev/null +++ b/src/main/java/com/pilers/parser/ParserTester.java @@ -0,0 +1,103 @@ +package com.pilers.parser; + +import com.pilers.scanner.*; +import com.pilers.ast.Program; +import com.pilers.environment.*; +import com.pilers.emitter.Emitter; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * A class for testing Parser + * + * @author Gloria Zhu + */ +public class ParserTester +{ + + /** + * Main tester method + * @param args arguments from the command line + */ + public static void main(String[] args) + { + + String[] testFiles = new String[] { + // "tests/parserTest10.txt", + // "tests/parserTest11.txt", + // "tests/parserTest12.txt", + // "tests/parserTest13.txt" + "tests/staticVariables.txt" + }; + + String[] compileDests = new String[] { + // "parserTest10.asm", + // "parserTest11.asm", + // "parserTest12.asm", + // "parserTest13.asm" + "printSquares.asm" + }; + + // controls + boolean onMyComputer = true; + + boolean interpretCode = true; + boolean compileCode = false; + + try + { + for (int i = 0; i < testFiles.length; i++) + { + System.out.println("\nNow testing " + testFiles[i]); + + Scanner scanner; + if (onMyComputer) + scanner = new Scanner(Parser.class.getResourceAsStream(testFiles[i])); + else + { + Path currentDir = Paths.get(testFiles[i]); + scanner = new Scanner(new FileInputStream(new File( + currentDir.toAbsolutePath().toString()))); + } + + Parser pa = new Parser(scanner); + + try + { + Program prog = pa.parseProgram(args); + + if (interpretCode) + { + InterpreterEnvironment env = InterpreterEnvironment.newGlobalEnv(); + prog.exec(env); + } + + if (compileCode) + { + SemanticAnalysisEnvironment env = + SemanticAnalysisEnvironment.newGlobalEnv(); + prog.analyze(env); + + Emitter e = new Emitter(compileDests[i], env.getVariableInfo()); + prog.compile(e); + } + + + } + catch (Exception e) + { + e.printStackTrace(); + } + } + } + catch (IOException e) + { + e.printStackTrace(); + System.exit(-1); + } + } +} diff --git a/src/main/java/com/pilers/parser/tests/official/parserTest10.txt b/src/main/java/com/pilers/parser/tests/official/parserTest10.txt new file mode 100644 index 0000000..bbf2486 --- /dev/null +++ b/src/main/java/com/pilers/parser/tests/official/parserTest10.txt @@ -0,0 +1,12 @@ +Integer f = 2; + +Integer bar(Integer f) { + writeln(f); +} +Integer foo(Integer d) { + Integer ignore = bar(d + f); +} + +Integer ignore = foo(3); +writeln(f); +. \ No newline at end of file diff --git a/src/main/java/com/pilers/parser/tests/official/parserTest11.txt b/src/main/java/com/pilers/parser/tests/official/parserTest11.txt new file mode 100644 index 0000000..f728782 --- /dev/null +++ b/src/main/java/com/pilers/parser/tests/official/parserTest11.txt @@ -0,0 +1,8 @@ +Integer print(Integer n) { + writeln(n); +} + +Integer n = 3; +Integer ignore = print(5); +writeln(n); +. \ No newline at end of file diff --git a/src/main/java/com/pilers/parser/tests/official/parserTest12.txt b/src/main/java/com/pilers/parser/tests/official/parserTest12.txt new file mode 100644 index 0000000..1a93fb2 --- /dev/null +++ b/src/main/java/com/pilers/parser/tests/official/parserTest12.txt @@ -0,0 +1,7 @@ +Integer printSquare(Integer n) { + writeln(n * n); +} + +Integer x = 1; +Integer ignore = printSquare(x + 2); +. \ No newline at end of file diff --git a/src/main/java/com/pilers/parser/tests/official/parserTest13.txt b/src/main/java/com/pilers/parser/tests/official/parserTest13.txt new file mode 100644 index 0000000..28c2f67 --- /dev/null +++ b/src/main/java/com/pilers/parser/tests/official/parserTest13.txt @@ -0,0 +1,9 @@ +Integer countUp(Integer count, Integer max) { + if (count<=max) { + writeln(count); + countUp(count + 1, max); + } +} + +Integer x = countUp(2,4); +. \ No newline at end of file diff --git a/src/main/java/com/pilers/parser/tests/official/printSquares.txt b/src/main/java/com/pilers/parser/tests/official/printSquares.txt new file mode 100644 index 0000000..8b53d89 --- /dev/null +++ b/src/main/java/com/pilers/parser/tests/official/printSquares.txt @@ -0,0 +1,19 @@ +Integer count; +Integer ignore; +Integer times; +Integer printSquares(Integer low, Integer high) { + Integer square; + Integer count = low; + Integer times = 0; + while(count<=high) { + square = count * count; + writeln(square); + count = count + 1; + times = times + 1; + } +} +count = 196; +times = 0; +ignore = printSquares(10, 13); +writeln(count); +writeln(times); \ No newline at end of file diff --git a/src/main/java/com/pilers/parser/tests/old_tests/Combination.txt b/src/main/java/com/pilers/parser/tests/old_tests/Combination.txt new file mode 100644 index 0000000..59742ef --- /dev/null +++ b/src/main/java/com/pilers/parser/tests/old_tests/Combination.txt @@ -0,0 +1,22 @@ +BEGIN + WRITELN('Combination Calculator!'); + WRITELN('Calculate A choose B'); + + WRITELN('Enter A'); + READLN(A); + WRITELN('Enter B'); + READLN(B); + WRITELN('Calculating ' + A + ' choose ' + B); + + aFactorial := 1; + FOR( i:= 2; i <= A; i := i+1;) aFactorial := aFactorial * i; + + aMinusBFactorial := 1; + FOR( j:= 2; j <= A-B; j := j+1;) aMinusBFactorial := aMinusBFactorial * j; + + bFactorial := 1; + FOR( k:=2; k <= B; k := k+1;) bFactorial := bFactorial * k; + + WRITELN('Result: ' + aFactorial / aMinusBFactorial / bFactorial); +END; +. \ No newline at end of file diff --git a/src/main/java/com/pilers/parser/tests/old_tests/MathProgram.txt b/src/main/java/com/pilers/parser/tests/old_tests/MathProgram.txt new file mode 100644 index 0000000..bc97157 --- /dev/null +++ b/src/main/java/com/pilers/parser/tests/old_tests/MathProgram.txt @@ -0,0 +1,27 @@ +// Calculates -(a * b mod c) + +WRITELN(''); +WRITELN('Math program'); +WRITELN('Enter a'); +READLN(a); +WRITELN('Enter b'); +READLN(b); +WRITELN('Enter c'); +READLN(c); +WRITELN(-(a * b mod c)); +WRITELN(''); + +(* +Test cases: + +a = 3 +b = 3 +c = 2 +yields -1 + +a = 4 +b = 23 +c = 3 +yields -2 + +*) \ No newline at end of file diff --git a/src/main/java/com/pilers/parser/tests/old_tests/anotherTest.txt b/src/main/java/com/pilers/parser/tests/old_tests/anotherTest.txt new file mode 100644 index 0000000..d54abc1 --- /dev/null +++ b/src/main/java/com/pilers/parser/tests/old_tests/anotherTest.txt @@ -0,0 +1,7 @@ +x := 2; +y := 'Hello'; +WRITELN(x * y); +WRITELN(x << 1); +WRITELN(x+1=5); +WRITELN(!(x!=3) && x*3<5 || y='Hello'); +WRITELN('HELLO' = 'HELLO' && -3 * 2 + 5 = 17 || 3 = 2); \ No newline at end of file diff --git a/src/main/java/com/pilers/parser/tests/old_tests/breakTest.txt b/src/main/java/com/pilers/parser/tests/old_tests/breakTest.txt new file mode 100644 index 0000000..c0dd935 --- /dev/null +++ b/src/main/java/com/pilers/parser/tests/old_tests/breakTest.txt @@ -0,0 +1,12 @@ +i := 0; + +WHILE 1 + 1 = 2 DO +BEGIN +i := i + 1; +IF i = 3 THEN BREAK; + +WRITELN(i); +IF i = 2 THEN CONTINUE; +WRITELN(i); + +END; diff --git a/src/main/java/com/pilers/parser/tests/old_tests/codegenTest1.txt b/src/main/java/com/pilers/parser/tests/old_tests/codegenTest1.txt new file mode 100644 index 0000000..0198d94 --- /dev/null +++ b/src/main/java/com/pilers/parser/tests/old_tests/codegenTest1.txt @@ -0,0 +1,10 @@ +Integer count; +BEGIN +count := 1; +WHILE count <= 15 DO +BEGIN +WRITELN(count); +count := count + 1; +END; +END; +. \ No newline at end of file diff --git a/src/main/java/com/pilers/parser/tests/old_tests/max.txt b/src/main/java/com/pilers/parser/tests/old_tests/max.txt new file mode 100644 index 0000000..60373ce --- /dev/null +++ b/src/main/java/com/pilers/parser/tests/old_tests/max.txt @@ -0,0 +1,8 @@ +PROCEDURE max(x, y); +BEGIN +max := x; +IF y > x THEN max := y; +END; +WRITELN(max(3, 5)); +WRITELN(max(6,4)); +. \ No newline at end of file diff --git a/src/main/java/com/pilers/parser/tests/old_tests/parserTest0.txt b/src/main/java/com/pilers/parser/tests/old_tests/parserTest0.txt new file mode 100644 index 0000000..e4e3f4c --- /dev/null +++ b/src/main/java/com/pilers/parser/tests/old_tests/parserTest0.txt @@ -0,0 +1,2 @@ +WRITELN(3); +. \ No newline at end of file diff --git a/src/main/java/com/pilers/parser/tests/old_tests/parserTest1.txt b/src/main/java/com/pilers/parser/tests/old_tests/parserTest1.txt new file mode 100644 index 0000000..9cd3661 --- /dev/null +++ b/src/main/java/com/pilers/parser/tests/old_tests/parserTest1.txt @@ -0,0 +1,4 @@ +WRITELN(6 * 2 / 3); +WRITELN(6 / 2 * 3); +WRITELN(6 / (2 * 3)); +. \ No newline at end of file diff --git a/src/main/java/com/pilers/parser/tests/old_tests/parserTest2.txt b/src/main/java/com/pilers/parser/tests/old_tests/parserTest2.txt new file mode 100644 index 0000000..9e60286 --- /dev/null +++ b/src/main/java/com/pilers/parser/tests/old_tests/parserTest2.txt @@ -0,0 +1,4 @@ +WRITELN(2 + 3 * 4); +WRITELN(2 * 3 + 4); +WRITELN((2 + 3) * 4); +. \ No newline at end of file diff --git a/src/main/java/com/pilers/parser/tests/old_tests/parserTest3.txt b/src/main/java/com/pilers/parser/tests/old_tests/parserTest3.txt new file mode 100644 index 0000000..c0f2d90 --- /dev/null +++ b/src/main/java/com/pilers/parser/tests/old_tests/parserTest3.txt @@ -0,0 +1,8 @@ +BEGIN +WRITELN(1); +WRITELN(2); +BEGIN +WRITELN(3); +END; +END; +. \ No newline at end of file diff --git a/src/main/java/com/pilers/parser/tests/old_tests/parserTest4.txt b/src/main/java/com/pilers/parser/tests/old_tests/parserTest4.txt new file mode 100644 index 0000000..3c726be --- /dev/null +++ b/src/main/java/com/pilers/parser/tests/old_tests/parserTest4.txt @@ -0,0 +1,7 @@ +BEGIN +x := 2; +y := x + 1; +x := x + y; +WRITELN(x * y); +END; +. \ No newline at end of file diff --git a/src/main/java/com/pilers/parser/tests/old_tests/parserTest6.txt b/src/main/java/com/pilers/parser/tests/old_tests/parserTest6.txt new file mode 100644 index 0000000..fc3be3b --- /dev/null +++ b/src/main/java/com/pilers/parser/tests/old_tests/parserTest6.txt @@ -0,0 +1,18 @@ +BEGIN +x := 2; +y := x + 1; +x := x + y; +WRITELN(x * y); +IF x > y THEN +BEGIN +WRITELN(x); +WRITELN(y); +END; +x := 0; +WHILE x < 10 DO +BEGIN +WRITELN(x); +x := x + 1; +END; +END; +. \ No newline at end of file diff --git a/src/main/java/com/pilers/parser/tests/old_tests/parserTest7.txt b/src/main/java/com/pilers/parser/tests/old_tests/parserTest7.txt new file mode 100644 index 0000000..cee7441 --- /dev/null +++ b/src/main/java/com/pilers/parser/tests/old_tests/parserTest7.txt @@ -0,0 +1,28 @@ +PROCEDURE Add(); +BEGIN +WRITELN(x); +x := x + 2; +END; +BEGIN +x := 2; +y := x + 1; +x := x + y; +WRITELN(x * y); +IF x > y THEN +BEGIN +WRITELN(x); +WRITELN(y); +END; +x := 0; +WHILE x < 10 DO +BEGIN +WRITELN(x); +x := x + 1; +END; +ignore := Add(); +WRITELN(x); +END; +. +. +. +. \ No newline at end of file diff --git a/src/main/java/com/pilers/parser/tests/old_tests/parserTest8.txt b/src/main/java/com/pilers/parser/tests/old_tests/parserTest8.txt new file mode 100644 index 0000000..8741dc3 --- /dev/null +++ b/src/main/java/com/pilers/parser/tests/old_tests/parserTest8.txt @@ -0,0 +1,31 @@ +PROCEDURE Add(y,w,z); +BEGIN +WRITELN(y); +WRITELN(w); +WRITELN(z); +x := x + y; +END; +BEGIN +x := 2; +y := x + 1; +x := x + y; +WRITELN(x * y); +IF x > y THEN +BEGIN +WRITELN(x); +WRITELN(y); +END; +x := 0; +WHILE x < 10 DO +BEGIN +WRITELN(x); +x := x + 1; +END; +WRITELN(y); +ignore := Add(4,x,y); +WRITELN(x); +END; +. +. +. +. \ No newline at end of file diff --git a/src/main/java/com/pilers/parser/tests/old_tests/typeTest.txt b/src/main/java/com/pilers/parser/tests/old_tests/typeTest.txt new file mode 100644 index 0000000..677a1a4 --- /dev/null +++ b/src/main/java/com/pilers/parser/tests/old_tests/typeTest.txt @@ -0,0 +1,6 @@ +Integer x; + + +x := 3; +y := 'Hello'; // throws a type assignment error +. \ No newline at end of file diff --git a/src/main/java/com/pilers/preprocessor/Preprocessor.java b/src/main/java/com/pilers/preprocessor/Preprocessor.java new file mode 100644 index 0000000..7aa9b23 --- /dev/null +++ b/src/main/java/com/pilers/preprocessor/Preprocessor.java @@ -0,0 +1,257 @@ +package com.pilers.preprocessor; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; + +import com.pilers.errors.ErrorString; +import com.pilers.errors.PreprocessorException; + +/** + * Preprocessor class + * + * Takes in a "base" file and pipes all output to a stream of bytes + * Currently supports: + * #include - include other files (inline replacement) + * #define - define macros + * #ifdef - if a macro is defined, include the following code up until endif or eof + * #endif - ends an ifdef + * #undef - undefines a macro + * + * #requestFile - makes a GET request to a specified URL + * allows including online files in the project + * + * @author Gloria Zhu + * @version 8/19/22 + */ +public class Preprocessor +{ + + private String filepath; + + private BufferedReader in; + + private HashMap definitions; + + ArrayList includedFiles; + + ByteArrayOutputStream out; + + private boolean onMyComputer; + + /** + * Preprocessor constructor for constructing a "child" preprocessor + * + * @param filepath file path (a URL if it's online) + * @param onMyComputer (different file structures on mine vs instructor computer) + * @param isOnline if the requested file is online not local + * @param out the existing output stream + * @param includedFiles files that have already been piped to the stream + * @param definitions variables + * + * @throws IOException if there is an issue with file I/O + */ + public Preprocessor( + String filepath, boolean onMyComputer, + boolean isOnline, + ByteArrayOutputStream out, + ArrayList includedFiles, + HashMap definitions) throws IOException + { + if (isOnline) + { + try + { + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = + HttpRequest.newBuilder().uri( + URI.create(filepath)).build(); + + HttpResponse response = + client.send(request, HttpResponse.BodyHandlers.ofString()); + + in = new BufferedReader(new StringReader(response.body())); + } + catch(Exception e) + { + e.printStackTrace(); + } + } + else + { + if (onMyComputer) + in = new BufferedReader( + new InputStreamReader(Preprocessor.class.getResourceAsStream(filepath))); + else + { + Path currentDir = Paths.get(filepath); + in = new BufferedReader( + new InputStreamReader( + new FileInputStream(new File(currentDir.toAbsolutePath().toString())))); + } + } + + this.filepath = filepath; + this.includedFiles = includedFiles; + this.out = out; + this.onMyComputer = onMyComputer; + this.definitions = definitions; + } + + /** + * Preprocessor constructor for constructing the "base" preprocessor + * + * @param filepath file name + * @param onMyComputer (different file structures on mine vs instructor computer) + * @param isOnline if the requested file is online not local + * @throws IOException if there is a problem with file I/O + */ + public Preprocessor(String filepath, boolean onMyComputer, boolean isOnline) throws IOException + { + + if (isOnline) + { + try + { + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder().uri(URI.create(filepath)).build(); + + HttpResponse response = client.send( + request, HttpResponse.BodyHandlers.ofString()); + + in = new BufferedReader(new StringReader(response.body())); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + else + { + if (onMyComputer) + in = new BufferedReader( + new InputStreamReader( + Preprocessor.class.getResourceAsStream(filepath))); + else + { + Path currentDir = Paths.get(filepath); + in = new BufferedReader( + new InputStreamReader( + new FileInputStream( + new File(currentDir.toAbsolutePath().toString())))); + } + } + + this.filepath = filepath; + out = new ByteArrayOutputStream(); + includedFiles = new ArrayList(); + this.onMyComputer = onMyComputer; + definitions = new HashMap(); + } + + /** + * Processes the file and pipes output to a stream of bytes + * + * @throws IOException if there is an issue with File I/O + * @throws PreprocessorException if there is an unknown directive + */ + public void process() throws IOException, PreprocessorException + { + if (includedFiles.contains(filepath)) return; + + includedFiles.add(filepath); + String line = in.readLine(); + + // used for false #endif directives + boolean isWriting = true; + + while (line != null) + { + if (line.length() > 0 && line.charAt(0) == '#') + { + String[] args = line.split(" "); + String directive = args[0]; + + switch (directive) + { + case ("#include"): + new Preprocessor( + args[1], onMyComputer, + false, out, includedFiles, definitions) + .process(); + break; + case ("#define"): + String defVar = args[1]; + String defVal = args[2]; + if(definitions.get(defVar) != null) throw new + PreprocessorException(ErrorString.duplicateDefine(defVar)); + + definitions.put(defVar, defVal); + break; + + case("#ifdef"): + String defVarCond = args[1]; + isWriting = definitions.get(defVarCond) != null; + + break; + + case("#endif"): + isWriting = true; + break; + + case("#undef"): + definitions.put(args[1], null); + break; + + case("#requestFile"): + new Preprocessor(args[1], onMyComputer, + true, out, includedFiles, definitions).process(); + break; + + default: + throw new PreprocessorException(ErrorString.unknownDirective(args[1])); + } + } + else if (isWriting) + { + String[] split = line.split(" "); + for(int i=0;ix) max = y; + + x = -1; + y = -1; // will not affect global variables +} + +writeln("Now testing 'getSign' subroutine"); +writeln(getSign(-1)); // should print "Negative" +writeln(getSign(0)); // should print "Zero" +writeln(getSign(100)); // should print "Positive" + +writeln("Now testing 'max' subroutine"); +Integer x = 3; +Integer y = 4; +writeln(max(x, y)); // should print 4 +writeln(x); // should print 3 not -1 + +writeln("Now testing for/while loops and user input"); +Integer input; + +writeln("Enter a number for the for loop: "); +readInteger(input); +for(Integer i = 0;i='0' && ch<='9'; + } + + /** + * @param ch input char + * @return if the character is a letter + */ + public static boolean isLetter(char ch) + { + return ch>='A' && ch<='z'; + } + /** + * @param ch input char + * @return if the character is whitespace + */ + public static boolean isWhitespace(char ch) + { + for(int i=0;i=", "!=", "&&", "||", ">>", "<<", "!=", "++", "--", "==" }; + /** + * One-character operators + */ + public static final String[] ONECHAR_OPERATORS = new String[] + { "+", "-", "*", "/", "%", ">", "<", "!", "^", "&", "|", "~", "{", "}"}; + + /** + * ALL operators ordered by precedence in a 2D array + * Arrays are ordered in increasing order + * + * Note: this 2D array does not include the assignment operator, which should + * never be referenced in the same context as it. If it is, something + * is seriously wrong. + * + * Precedence example: + * TRUE | 3 + 4 >= 4 + * + * Order of evaluation: +, >=, | + * + * Overall order of precedence: + * Logical Operators + * Relational operators + * Mathematical operators + */ + public static final String[][] OPERATORS_ORDERED_BY_PRECEDENCE = new String[][] + { + new String[]{"||"}, // lowest precedence + new String[]{"&&"}, + new String[]{"^"}, + new String[]{"<=",">=","==", "!=", ">", "<"}, + new String[]{"+", "-"}, + new String[]{"*", "/", "%", ">>", "<<", "&", "|"} + }; + + /** + * All unary operators + */ + public static final String[] UNARY_OPERATORS = new String[] + { + "-", "!", "~", "++", "--" + }; + + /** + * Whitespace characters + */ + public static final char[] WHITESPACE_CHARS = new char[] { ' ', '\n', '\t', '\r' }; + /** + * Line terminator char + */ + public static final char LINE_TERMINATOR_CHAR = ';'; + /** + * Open paren character + */ + public static final char OPEN_PAREN_CHAR = '('; + /** + * Close paren character + */ + public static final char CLOSE_PAREN_CHAR = ')'; + /** + * Quote character + */ + public static final char QUOTE_CHAR = '"'; + /** + * Comma character + */ + public static final char COMMA_CHAR = ','; + + /** + * The character that ends everything + */ + public static final char END_CHARACTER = '?'; + + + /** + * All the types, in string form + */ + public static final String[] TYPE_STRINGS = new String[] + {"END", "IDENTIFIER", "KEYWORD", "NUMBER", "OPERATOR", + "LINE_TERMINATOR", "OPEN_PAREN", "CLOSE_PAREN", "STRING", "BOOLEAN", "COMMA", "TYPE" }; + + /** + * Type names, but nicer; to be used in semantic analysis for type checking + * + * IMPORTANT: the only types in this list that should ever be used are: + * "Integer" "String" and "Boolean". The other ones have just been omitted - + * if they are used, something is SERIOUSLY WRONG + */ + public static final String[] NICE_TYPE_STRINGS = new String[] + { "", "", "", "Integer", "", "", "", "", "String", "Boolean", "" }; + + /** + * the END token "." signifies the end of the input stream + */ + public static final int END = 0; + + /** + * the IDENTIFIER token is a string + */ + public static final int IDENTIFIER = 1; + /** + * the KEYWORD token is a string that is apart of + * a set of keywords predefined in the Scanner. + */ + public static final int KEYWORD = 2; + /** + * the NUMBER token is a string that represents a + * number. todo: support decimal numbers + */ + public static final int NUMBER = 3; + + /** + * the OPERATOR token is a string that represents + * an operator. it can have one or two characters. + */ + public static final int OPERATOR = 4; + + /** + * the LINE_TERMINATOR token is a string that represents + * the end of the line. in this case, it is a semicolon (;). + */ + public static final int LINE_TERMINATOR = 5; + /** + * the OPEN_PAREN token is a string that represents an open paren. + */ + public static final int OPEN_PAREN = 6; + /** + * the CLOSE_PAREN token is a string that represents a close paren + */ + public static final int CLOSE_PAREN = 7; + /** + * A STRING token represents an actual string + * (not an identifier or boolean) + */ + public static final int STRING = 8; + /** + * A BOOLEAN token represents a boolean string + * There is only TRUE_BOOLEAN and FALSE_BOOLEAN (for now) + */ + public static final int BOOLEAN = 9; + /** + * A COMMA token is just a comma :P + */ + public static final int COMMA = 10; + /** + * TYPE token represents a type (see list of types at top of file) + */ + public static final int TYPE = 11; + + /** + * the DUMMY token is used by comment scanning to pass strings + * if comment scanning was unsuccessful. + */ + public static final int DUMMY = -1; + + /** + * Number class + * for checking variable types in the parser + */ + public static final String NUMBER_CLASS = "java.lang.Integer"; + /** + * String class + * for checking variable types in the parser + */ + public static final String STRING_CLASS = "java.lang.String"; + /** + * Boolean class + * for checking variable types in the parser + */ + public static final String BOOLEAN_CLASS = "java.lang.Boolean"; + + private String value; + private int type; + private int line; + + /** + * Constructor for a Token object with a value and a type + * + * @param value the value (as a String) + * @param type the type (as an integer - use the constants in the class) + * @param line the line of the token + */ + public Token(String value, int type, int line) + { + this.value = value; + this.type = type; + this.line = line; + } + + /** + * Same constructor as above but takes a character instead Calls other + * constructor (casts char to string) + * + * @param value the value (as a char) + * @param type the type (as an integer - use the constants in the class) + * @param line the line of the token + */ + public Token(char value, int type, int line) + { + this(""+value, type, line); + } + + /** + * Sets the type of the token + * @param newType the new type + */ + public void setType(int newType) + { + type = newType; + } + + /** + * Sets the value of the token + * @param newValue the new value + */ + public void setValue(String newValue) + { + value = newValue; + } + + /** + * Sets the line of the token + * @param newLine the new line + */ + public void setLine(int newLine) + { + line = newLine; + } + + /** + * @return the type of the token (as an integer) + */ + public int getType() + { + return type; + } + + /** + * @return the type of the token (as a string) + */ + public String getTypeAsString() + { + return TYPE_STRINGS[type]; + } + + /** + * This should only reference "Integer" "Boolean" and "String" + * If it returns something other than that, something is terribly wrong. + * @return the type of the token VALUE (as a string) + */ + public String getTypeAsNiceString() + { + return NICE_TYPE_STRINGS[type]; + } + + /** + * @return the value of the token (in String form) + */ + public String getValue() + { + return value; + } + + /** + * @return the line of the token + */ + public int getLine() + { + return line; + } + + // Static factory methods to make token creation less messy in the Scanner. + + /** + * @param line the line of the token + * @return an end token + */ + public static Token endToken(int line) + { + return new Token(Token.END_CHARACTER, Token.END, line); + } + + /** + * @param line the line of the token + * @return a line terminator token + */ + public static Token lineTerminatorToken(int line) + { + return new Token(LINE_TERMINATOR_CHAR, Token.LINE_TERMINATOR, line); + } + + /** + * @param line the line of the token + * @return an open parentheses token + */ + public static Token openParenToken(int line) + { + return new Token(OPEN_PAREN_CHAR, Token.OPEN_PAREN, line); + } + + /** + * @param line the line of the token + * @return a close parentheses token + */ + public static Token closeParenToken(int line) + { + return new Token(CLOSE_PAREN_CHAR, Token.CLOSE_PAREN, line); + } + + /** + * @param line the line of the token + * @return a comma token + */ + public static Token commaToken(int line) + { + return new Token(COMMA_CHAR, Token.COMMA, line); + } + + /** + * @return a dummy token + * @param value the value to be put into the dummy token + * @param line the line of the token + */ + public static Token dummyToken(String value, int line) + { + return new Token(value, Token.DUMMY, line); + } + + /** + * Overriden equals method + * @param other the other Object + */ + @Override + public boolean equals(Object other) + { + + if (other instanceof String) return value.equals((String) other); + else if (other instanceof Character) return value.equals(""+other); + else if (other instanceof Token) + { + Token casted = (Token)other; + return line == casted.line && type == casted.type && value == casted.value; + } + + return false; + } + + /** + * Overriden toString method + */ + @Override + public String toString() + { + return String.format("%-2s %-20s%s\n", line, this.getTypeAsString(), value); + } + +} \ No newline at end of file diff --git a/src/main/java/com/pilers/scanner/tests/ScannerTest.txt b/src/main/java/com/pilers/scanner/tests/ScannerTest.txt new file mode 100644 index 0000000..a5b0514 --- /dev/null +++ b/src/main/java/com/pilers/scanner/tests/ScannerTest.txt @@ -0,0 +1,61 @@ +(* +Tests the full capabilities (hopefully) of the compiler +*) + +// Declaring subroutines +String getSign(Integer x) { + getSign = "Positive"; + if (x==0) getSign = "Zero"; + if (x<0) getSign = "Negative"; +} +Integer max(Integer x, Integer y) { + max = x; + if (y>x) max = y; + + x = -1; + y = -1; // will not affect global variables +} + +writeln("Now testing 'getSign' subroutine"); +writeln(getSign(-1)); // should print "Negative" +writeln(getSign(0)); // should print "Zero" +writeln(getSign(100)); // should print "Positive" + +writeln("Now testing 'max' subroutine"); +Integer x = 3; +Integer y = 4; +writeln(max(x, y)); // should print 4 +writeln(x); // should print 3 not -1 + + +writeln("Now testing for/while loops and user input"); +Integer input; + +writeln("Enter a number for the for loop: "); +readInteger(input); +for(Integer i = 0;i= 10 DO */ +BEGIN +WRITELN(square(x)); */ +x := x + 1.0; +END; +END; +. \ No newline at end of file diff --git a/src/main/java/com/pilers/scanner/tests/scannerTestParser.txt b/src/main/java/com/pilers/scanner/tests/scannerTestParser.txt new file mode 100644 index 0000000..19f4d5a --- /dev/null +++ b/src/main/java/com/pilers/scanner/tests/scannerTestParser.txt @@ -0,0 +1,30 @@ +// WRITELN('HELLO' == 'HELLO' && -3 * 2 + 5 == 17 || 3 == 2); + +PROCEDURE Add(); +BEGIN +WRITELN(x); +x := x + 2; +END; +BEGIN +x := 2; +y := x + 1; +x := x + y; +WRITELN(x * y); +IF x > y THEN +BEGIN +WRITELN(x); +WRITELN(y); +END; +x := 0; +WHILE x < 10 DO +BEGIN +WRITELN(x); +x := x + 1; +END; +ignore := Add(); +WRITELN(x); +END; +. +. +. +. \ No newline at end of file diff --git a/src/main/java/com/pilers/tester/Tester.java b/src/main/java/com/pilers/tester/Tester.java new file mode 100644 index 0000000..7b1dee6 --- /dev/null +++ b/src/main/java/com/pilers/tester/Tester.java @@ -0,0 +1,84 @@ +package com.pilers.tester; + +import com.pilers.ast.Program; +import com.pilers.emitter.Emitter; +import com.pilers.environment.InterpreterEnvironment; +import com.pilers.environment.SemanticAnalysisEnvironment; +import com.pilers.parser.Parser; +import com.pilers.preprocessor.Preprocessor; +import com.pilers.scanner.Scanner; + +/** + * Overarching Tester class for the entire compiler/interpreter :) + * + * @author Gloria Zhu + * @version 8/19/22 + */ +public class Tester +{ + + /** + * Main method that tests the compiler/interpreter + * + * @param args arguments from the cmd line + */ + public static void main(String[] args) + { + // controls + boolean onMyComputer = true; + boolean interpretCode = false; + boolean compileCode = true; + + String[] testFiles = new String[] { + "demo.txt", + "all.txt" }; + + String[] compileDestinations = new String[] { + "demo.asm", + "all.asm" }; + + for(int i=0;i