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 0000000..6d35e2d Binary files /dev/null and b/all.asm differ diff --git a/demo.asm b/demo.asm new file mode 100644 index 0000000..b3ec6a1 Binary files /dev/null and b/demo.asm differ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..cb7658c --- /dev/null +++ b/pom.xml @@ -0,0 +1,75 @@ + + + + 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 0000000..d744e92 Binary files /dev/null and b/printSquares.asm differ 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