From 143979c5b42b26843442efa2e240721c325ca806 Mon Sep 17 00:00:00 2001 From: FollieHiyuki Date: Fri, 21 May 2021 16:30:19 +0300 Subject: [PATCH] BasicInterpreter --- BasicInterpreter/Program.txt | 19 + BasicInterpreter/result.txt | 6 + .../InterpreterAlpha/InterpreterAlpha.java | 393 ++++++++++++++++++ .../src/InterpreterAlpha/Lexer.java | 216 ++++++++++ .../src/InterpreterAlpha/Parser.java | 359 ++++++++++++++++ .../InterpreterAlphaTest.java | 47 +++ .../test/InterpreterAlpha/LexerTest.java | 36 ++ .../SymbolTableBuilderTest.java | 30 ++ 8 files changed, 1106 insertions(+) create mode 100644 BasicInterpreter/Program.txt create mode 100644 BasicInterpreter/result.txt create mode 100644 BasicInterpreter/src/InterpreterAlpha/InterpreterAlpha.java create mode 100644 BasicInterpreter/src/InterpreterAlpha/Lexer.java create mode 100644 BasicInterpreter/src/InterpreterAlpha/Parser.java create mode 100644 BasicInterpreter/test/InterpreterAlpha/InterpreterAlphaTest.java create mode 100644 BasicInterpreter/test/InterpreterAlpha/LexerTest.java create mode 100644 BasicInterpreter/test/InterpreterAlpha/SymbolTableBuilderTest.java diff --git a/BasicInterpreter/Program.txt b/BasicInterpreter/Program.txt new file mode 100644 index 0000000..87f9673 --- /dev/null +++ b/BasicInterpreter/Program.txt @@ -0,0 +1,19 @@ +PROGRAM thisProgram; +VAR + var1, a, b, c : INTEGER; + +BEGIN {example} + a := 2; + var1 := 1; + b := 5; + c := 20; + + LOOP b DO + var1 := var1 + a; + c := c - 1; + END; + + LOOP a DO + b := b + 2 + END +END. {example} \ No newline at end of file diff --git a/BasicInterpreter/result.txt b/BasicInterpreter/result.txt new file mode 100644 index 0000000..296d32e --- /dev/null +++ b/BasicInterpreter/result.txt @@ -0,0 +1,6 @@ +; ; ; REAL; ; INTEGER; + +a = 2 +b = 9.0 +c = 15.0 +var1 = 11.0 diff --git a/BasicInterpreter/src/InterpreterAlpha/InterpreterAlpha.java b/BasicInterpreter/src/InterpreterAlpha/InterpreterAlpha.java new file mode 100644 index 0000000..0437f49 --- /dev/null +++ b/BasicInterpreter/src/InterpreterAlpha/InterpreterAlpha.java @@ -0,0 +1,393 @@ +package InterpreterAlpha; + +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.*; +import java.util.HashMap; +import java.util.Map; + +////////////////////////////////////////////////////////////////// +// // +// AST visitors // +// // +////////////////////////////////////////////////////////////////// + +class NodeVisitor { + String visit(AST node) throws Exception { + if (node instanceof Block) + return visit_Block((Block) node); + else if (node instanceof Program) + return visit_Program((Program) node); + else if (node instanceof BinOp) + return visit_BinOp((BinOp) node); + else if (node instanceof Num) + return visit_Num((Num) node); + else if (node instanceof UnaryOp) + return visit_UnaryOp((UnaryOp) node); + else if (node instanceof Compound) + return visit_Compound((Compound) node); + else if (node instanceof NoOp) + return visit_NoOp((NoOp) node); + else if (node instanceof VarDecl) + return visit_VarDecl((VarDecl) node); + else if (node instanceof Assign) + return visit_Assign((Assign) node); + else if (node instanceof Var) + return visit_Var((Var) node); + else if (node instanceof Type) + return visit_Type((Type) node); + else if (node instanceof ForStatement) + return visit_for((ForStatement) node); + else + throw new Exception("No visit function for the type of node!"); + } + + String visit_Type(Type node) throws Exception { + return ""; + } + + String visit_for(ForStatement node) throws Exception { + return ""; + } + + String visit_Block(Block node) throws Exception { + return ""; + } + + String visit_Program(Program node) throws Exception { + return ""; + } + + String visit_BinOp(BinOp node) throws Exception { + return ""; + } + + String visit_Num(Num node) throws Exception { + return ""; + } + + String visit_UnaryOp(UnaryOp node) throws Exception { + return ""; + } + + String visit_Compound(Compound node) throws Exception { + return ""; + } + + String visit_NoOp(NoOp node) throws Exception { + return ""; + } + + String visit_VarDecl(VarDecl node) throws Exception { + return ""; + } + + String visit_Assign(Assign node) throws Exception { + return ""; + } + + String visit_Var(Var node) throws Exception { + return ""; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// SYMBOL and SYMBOL TABLE // +// // +/////////////////////////////////////////////////////////////////////////////// + +class Symbol { + String name; + TokenType type; // INTEGER | REAL + static public FileWriter resultFile; + + static { + try { + FileWriter deletedFile = new FileWriter("result.txt", false); + deletedFile.write(""); + deletedFile.close(); + resultFile = new FileWriter("result.txt", true); + } catch (IOException e) { + System.out.println("File doesn't exist!"); + } + } + + // built-in type + Symbol(String name) { + this.name = name; + if (name.equals("INTEGER")) + this.type = TokenType.INTEGER; + else + this.type = TokenType.REAL; + } + + // variable + Symbol(String name, TokenType type) { + this.name = name; + this.type = type; + } + + void printSym() throws Exception { + } +} + +class VarSymbol extends Symbol { + VarSymbol(String name, TokenType type) { + super(name, type); + } + + @Override + void printSym() throws Exception { + Symbol.resultFile.append("<" + this.name + " : " + this.type + ">"); + } +} + +class BuiltinTypeSymbol extends Symbol { + BuiltinTypeSymbol(String name) { + super(name); + } + + @Override + void printSym() throws Exception { + Symbol.resultFile.append(this.name); + } +} + +// map of (symbol_name, symbol) +class SymbolTable { + Map _symbols; + + SymbolTable() { + this._symbols = new HashMap<>(); + this.initBuiltins(); + } + + private void initBuiltins() { + this.define(new BuiltinTypeSymbol("INTEGER")); + this.define(new BuiltinTypeSymbol("REAL")); + } + + void define(Symbol symbol) { + this._symbols.put(symbol.name, symbol); + } + + // return an instance of Symbol class or a null objects + Symbol lookup(String name) { + return this._symbols.get(name); + } +} + +class SymbolTableBuilder extends NodeVisitor { + SymbolTable symtab; + + SymbolTableBuilder() { + this.symtab = new SymbolTable(); + } + + void printTable() throws Exception { + for (String name : symtab._symbols.keySet()) { + symtab._symbols.get(name).printSym(); + Symbol.resultFile.append("; "); + } + Symbol.resultFile.append("\n\n"); + } + + @Override + String visit_Block(Block node) throws Exception { + for (VarDecl declaration : node.declaration) + this.visit(declaration); + this.visit(node.compound_statement); + return ""; + } + + @Override + String visit_Program(Program node) throws Exception { + this.visit(node.block); + return ""; + } + + @Override + String visit_BinOp(BinOp node) throws Exception { + this.visit(node.left); + this.visit(node.right); + return ""; + } + + @Override + String visit_UnaryOp(UnaryOp node) throws Exception { + this.visit(node.expr); + return ""; + } + + @Override + String visit_Compound(Compound node) throws Exception { + for (AST child : node.children) + this.visit(child); + return ""; + } + + @Override + String visit_VarDecl(VarDecl node) throws Exception { + String type_name = node.type_node.value; + Symbol type_symbol = this.symtab.lookup(type_name); + String var_name = node.var_node.value; + VarSymbol var_symbol = new VarSymbol(var_name, type_symbol.type); + this.symtab.define(var_symbol); + return ""; + } + + @Override + String visit_Assign(Assign node) throws Exception { + String var_name = node.left.value; + Symbol var_symbol = this.symtab.lookup(var_name); + if (var_symbol == null) + throw new Exception("No var_name declaration for " + var_name); + this.visit(node.right); + return ""; + } + + @Override + String visit_Var(Var node) throws Exception { + String var_name = node.value; + Symbol var_symbol = this.symtab.lookup(var_name); + if (var_symbol == null) + throw new Exception("No var_name declaration for " + var_name); + return ""; + } +} + +///////////////////////////////////////////////////////////////////////////////////// +// // +// INTERPRETER // +// // +///////////////////////////////////////////////////////////////////////////////////// + +public class InterpreterAlpha extends NodeVisitor { + // store table of (var_name; var_value) + Map GLOBAL_SCOPE; + AST tree; + + InterpreterAlpha(AST tree) { + this.tree = tree; + GLOBAL_SCOPE = new HashMap<>(); + } + + void interpret() throws Exception { + AST tree = this.tree; + if (tree == null) { + throw new Exception("Have no tree to trace!"); + } + this.visit(tree); + printTab(GLOBAL_SCOPE); + } + + void printTab(Map scope) throws Exception { + for (String key : scope.keySet()) + Symbol.resultFile.append(key + " = " + scope.get(key) + "\n"); + } + + @Override + String visit_Program(Program node) throws Exception { + return this.visit(node.block); + } + + @Override + String visit_for(ForStatement node) throws Exception { + String val = GLOBAL_SCOPE.getOrDefault(node.variable.value, null); + if (val == null) + throw new Exception("No variable found in for_loop!"); + else { + int end_point = Integer.parseInt(val); + for (int tmp = 1; tmp <= end_point; tmp++) { + for (AST nod : node.statements) + this.visit(nod); + } + } + return ""; + } + + @Override + String visit_Block(Block node) throws Exception { + for (AST declaration : node.declaration) + this.visit(declaration); + this.visit(node.compound_statement); + return ""; + } + + @Override + String visit_BinOp(BinOp node) throws Exception { + switch (node.op.type) { + case PLUS: + return Double.toString(Double.parseDouble(this.visit(node.left)) + Double.parseDouble(this.visit(node.right))); + case MINUS: + return Double.toString(Double.parseDouble(this.visit(node.left)) - Double.parseDouble(this.visit(node.right))); + case MUL: + return Double.toString(Double.parseDouble(this.visit(node.left)) * Double.parseDouble(this.visit(node.right))); + case FLOAT_DIV: + return Double.toString(Double.parseDouble(this.visit(node.left)) / Double.parseDouble(this.visit(node.right))); + case INTEGER_DIV: + return Integer.toString((int) Double.parseDouble(this.visit(node.left)) / (int) Double.parseDouble(this.visit(node.right))); + default: + throw new Exception("Incorrect binary operator!"); + } + } + + @Override + String visit_Num(Num node) throws Exception { + return node.value; + } + + @Override + String visit_UnaryOp(UnaryOp node) throws Exception { + switch (node.op.type) { + case PLUS: + return Double.toString(+Double.parseDouble(this.visit(((UnaryOp) node).expr))); + case MINUS: + return Double.toString(-Double.parseDouble(this.visit(((UnaryOp) node).expr))); + default: + throw new Exception("Incorrect unary operator!"); + } + } + + @Override + String visit_Compound(Compound node) throws Exception { + for (AST child : node.children) + this.visit(child); + return ""; + } + + @Override + String visit_Assign(Assign node) throws Exception { + String var_name = node.left.value; + GLOBAL_SCOPE.put(var_name, this.visit(node.right)); + return ""; + } + + @Override + String visit_Var(Var node) throws Exception { + String var_name = node.value; + String val = this.GLOBAL_SCOPE.get(var_name); + if (val != null) + return val; + else + throw new Exception("Variable has no value!"); + } + + public static void main(String[] args) throws Exception { + String str = Files.readString(Paths.get("Program.txt")); + if (str.equals("")) { + throw new Exception("Please input text into Program.txt"); + } + Lexer lexer = new Lexer(str); + Parser parser = new Parser(lexer); + AST tree = parser.parse(); + SymbolTableBuilder symtab = new SymbolTableBuilder(); + symtab.visit(tree); + symtab.printTable(); + + InterpreterAlpha interpreter = new InterpreterAlpha(tree); + interpreter.interpret(); + Symbol.resultFile.close(); + } +} diff --git a/BasicInterpreter/src/InterpreterAlpha/Lexer.java b/BasicInterpreter/src/InterpreterAlpha/Lexer.java new file mode 100644 index 0000000..7f5783e --- /dev/null +++ b/BasicInterpreter/src/InterpreterAlpha/Lexer.java @@ -0,0 +1,216 @@ +package InterpreterAlpha; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +enum TokenType { + INTEGER, + REAL, + INTEGER_CONST, + REAL_CONST, + PLUS, // + + MINUS, // - + MUL, // * + INTEGER_DIV, // DIV + FLOAT_DIV, // / + LPAREN, // ( + RPAREN, // ) + ID, + ASSIGN, // := + BEGIN, + END, + SEMI, // ; + DOT, // . + PROGRAM, + VAR, + COLON, // : + COMMA, // , + LOOP, + DO, + EOF, + // EOF token is used to indicate that + // there is no more input left for lexical analysis +} + +class Token { + TokenType type; + String value; + + Token(TokenType type, String value) { + this.type = type; + this.value = value; + } +} + +class Lexer { + String text; + int pos; + char current_char; + private static final Map RESERVED_KEYWORDS = initMap(); + + // map of constant keywords + private static Map initMap() { + Map map = new HashMap<>(); + map.put("PROGRAM", new Token(TokenType.PROGRAM, "PROGRAM")); + map.put("VAR", new Token(TokenType.VAR, "VAR")); + map.put("DIV", new Token(TokenType.INTEGER_DIV, "DIV")); + map.put("INTEGER", new Token(TokenType.INTEGER, "INTEGER")); + map.put("REAL", new Token(TokenType.REAL, "REAL")); + map.put("BEGIN", new Token(TokenType.BEGIN, "BEGIN")); + map.put("END", new Token(TokenType.END, "END")); + map.put("LOOP", new Token(TokenType.LOOP, "LOOP")); + map.put("DO", new Token(TokenType.DO, "DO")); + return Collections.unmodifiableMap(map); + } + + Lexer(String text) { + this.text = text; + this.pos = 0; + this.current_char = this.text.charAt(this.pos); + } + + void error() throws Exception { + throw new Exception("Invalid character!"); + } + + // advance the position and set the current_char variable + void advance() { + this.pos++; + if (this.pos > this.text.length() - 1) { + this.current_char = '\0'; // End of input + } else { + this.current_char = this.text.charAt(this.pos); + } + } + + // look at the next character + char peek() { + int peek_pos = this.pos + 1; + if (peek_pos > this.text.length() - 1) { + return '\0'; + } else { + return this.text.charAt(peek_pos); + } + } + + // Return a multi digit integer or real number token (as a String) + Token number() { + StringBuilder result = new StringBuilder(); + while (this.current_char != '\0' && Character.isDigit(this.current_char)) { + result.append(this.current_char); + this.advance(); + } + + if (this.current_char == '.') { + result.append(this.current_char); + this.advance(); + + while (this.current_char != '\0' && Character.isDigit(this.current_char)) { + result.append(this.current_char); + this.advance(); + } + return new Token(TokenType.REAL_CONST, result.toString()); + } else + return new Token(TokenType.INTEGER_CONST, result.toString()); + } + + Token _id() { + StringBuilder result = new StringBuilder(); + while (this.current_char != '\0' && Character.isLetterOrDigit(this.current_char)) { + result.append(this.current_char); + this.advance(); + } + + return RESERVED_KEYWORDS.getOrDefault(result.toString(), new Token(TokenType.ID, result.toString())); + } + + // Lexical analyzer + // that breaks a sentence apart into tokens (1 at a time) + Token get_next_token() throws Exception { + while (this.current_char != '\0') { + // skip whitespaces, End of Line, Tab + if (Character.isSpaceChar(this.current_char) || this.current_char == '\n' || this.current_char == '\t') { + this.advance(); + continue; + } + + // skip comments + if (this.current_char == '{') { + this.advance(); // starting bracket + while (this.current_char != '}') { + this.advance(); + } + this.advance(); // closing bracket + continue; + } + + if (Character.isLetter(this.current_char)) { + return this._id(); + } + + if (Character.isDigit(this.current_char)) { + return this.number(); + } + + if (this.current_char == ':' && this.peek() == '=') { + this.advance(); + this.advance(); + return new Token(TokenType.ASSIGN, ":="); + } + + if (this.current_char == ';') { + this.advance(); + return new Token(TokenType.SEMI, ";"); + } + + if (this.current_char == ':') { + this.advance(); + return new Token(TokenType.COLON, ":"); + } + + if (this.current_char == ',') { + this.advance(); + return new Token(TokenType.COMMA, ","); + } + + if (this.current_char == '+') { + this.advance(); + return new Token(TokenType.PLUS, "+"); + } + + if (this.current_char == '-') { + this.advance(); + return new Token(TokenType.MINUS, "-"); + } + + if (this.current_char == '*') { + this.advance(); + return new Token(TokenType.MUL, "*"); + } + + if (this.current_char == '/') { + this.advance(); + return new Token(TokenType.FLOAT_DIV, "/"); + } + + if (this.current_char == '(') { + this.advance(); + return new Token(TokenType.LPAREN, "("); + } + + if (this.current_char == ')') { + this.advance(); + return new Token(TokenType.RPAREN, ")"); + } + + if (this.current_char == '.') { + this.advance(); + return new Token(TokenType.DOT, "."); + } + + this.error(); + } + return new Token(TokenType.EOF, ""); + } +} diff --git a/BasicInterpreter/src/InterpreterAlpha/Parser.java b/BasicInterpreter/src/InterpreterAlpha/Parser.java new file mode 100644 index 0000000..abd0798 --- /dev/null +++ b/BasicInterpreter/src/InterpreterAlpha/Parser.java @@ -0,0 +1,359 @@ +package InterpreterAlpha; + +import java.util.ArrayList; + +// a node on the Abstract Syntax Tree +class AST { +} + +class BinOp extends AST { + AST left; + AST right; + Token op; + + BinOp(AST left, Token op, AST right) { + this.left = left; + this.op = op; + this.right = right; + } +} + +class UnaryOp extends AST { + Token op; + AST expr; + + UnaryOp(Token op, AST expr) { + this.op = op; + this.expr = expr; + } +} + +class Num extends AST { + Token token; + String value; + + Num(Token token) { + this.token = token; + this.value = token.value; + } +} + +class Compound extends AST { + ArrayList children; + + Compound() { + this.children = new ArrayList(); + } +} + +class Assign extends AST { + Var left; + Token op; + AST right; + + Assign(Var left, Token op, AST right) { + this.left = left; + this.op = op; + this.right = right; + } +} + +class Var extends AST { + Token token; + String value; // variable's name + + Var(Token token) { + this.token = token; + this.value = token.value; + } +} + +class ForStatement extends AST { + ArrayList statements; + Var variable; + + ForStatement(ArrayList statements, Var variable) { + this.statements = statements; + this.variable = variable; + } +} + +// empty operator +class NoOp extends AST { + +} + +class Program extends AST { + String name; + Block block; + + Program(String name, Block block) { + this.name = name; + this.block = block; + } +} + +class Block extends AST { + ArrayList declaration; + Compound compound_statement; + + Block(ArrayList declaration, Compound compound_statement) { + this.declaration = declaration; + this.compound_statement = compound_statement; + } +} + +class VarDecl extends AST { + Var var_node; + Type type_node; + + VarDecl(Var var_node, Type type_node) { + this.var_node = var_node; + this.type_node = type_node; + } +} + +// an integer or a real number +class Type extends AST { + Token token; + String value; + + Type(Token token) { + this.token = token; + this.value = token.value; + } +} + +class Parser { + Lexer lexer; + Token current_token; + + Parser(Lexer lexer) throws Exception { + this.lexer = lexer; + this.current_token = this.lexer.get_next_token(); + } + + void error() throws Exception { + throw new Exception("Invalid syntax!"); + } + + void eat(TokenType token_type) throws Exception { + if (this.current_token.type == token_type) { + this.current_token = this.lexer.get_next_token(); + } else { + this.error(); + } + } + + // program: PROGRAM variable SEMI block DOT + Program program() throws Exception { + this.eat(TokenType.PROGRAM); + Var var_node = this.variable(); + String program_name = var_node.value; + this.eat(TokenType.SEMI); + Block block_node = this.block(); + this.eat(TokenType.DOT); + return new Program(program_name, block_node); + } + + // block: declarations compound_statement + Block block() throws Exception { + ArrayList declaration_nodes = this.declarations(); + Compound compound_statement_node = this.compound_statement(); + return new Block(declaration_nodes, compound_statement_node); + } + + // declarations: VAR (variable_declaration SEMI)* + // | empty + ArrayList declarations() throws Exception { + ArrayList declarations = new ArrayList(); + if (this.current_token.type == TokenType.VAR) { + this.eat(TokenType.VAR); + while (this.current_token.type == TokenType.ID) { + ArrayList var_decl = this.variable_declaration(); + declarations.addAll(var_decl); + this.eat(TokenType.SEMI); + } + } + return declarations; + } + + // variable_declaration: ID (COMMA ID)* COLON type_spec + ArrayList variable_declaration() throws Exception { + ArrayList var_nodes = new ArrayList(); + var_nodes.add(new Var(this.current_token)); + this.eat(TokenType.ID); + + while (this.current_token.type == TokenType.COMMA) { + this.eat(TokenType.COMMA); + var_nodes.add(new Var(this.current_token)); + this.eat(TokenType.ID); + } + + this.eat(TokenType.COLON); + Type type_node = this.type_spec(); + ArrayList var_declarations = new ArrayList(); + for (Var var_node : var_nodes) + var_declarations.add(new VarDecl(var_node, type_node)); + return var_declarations; + } + + // type_spec: INTEGER | REAL + Type type_spec() throws Exception { + Token token = this.current_token; + if (this.current_token.type == TokenType.INTEGER) + this.eat(TokenType.INTEGER); + else + this.eat(TokenType.REAL); + return new Type(token); + } + + // compound_statement: BEGIN statement_list END + Compound compound_statement() throws Exception { + this.eat(TokenType.BEGIN); + ArrayList nodes = this.statement_list(); + this.eat(TokenType.END); + + Compound root = new Compound(); + root.children.addAll(nodes); + return root; + } + + // statement_list: statement + // | statement SEMI statement_list + ArrayList statement_list() throws Exception { + AST node = this.statement(); + ArrayList results = new ArrayList(); + results.add(node); + while (this.current_token.type == TokenType.SEMI) { + this.eat(TokenType.SEMI); + results.add(this.statement()); + } + + if (this.current_token.type == TokenType.ID) + this.error(); + return results; + } + + + // statement: compound_statement + // | for_statement + // | assign_statement + // | empty + AST statement() throws Exception { + AST node; + if (this.current_token.type == TokenType.BEGIN) + node = this.compound_statement(); + else if (this.current_token.type == TokenType.ID) + node = this.assign_statement(); + else if (this.current_token.type == TokenType.LOOP) + node = this.for_statement(); + else + node = this.empty(); + return node; + } + + // for_statement: LOOP ID DO statement_list END + ForStatement for_statement() throws Exception { + this.eat(TokenType.LOOP); + Var variable = this.variable(); + this.eat(TokenType.DO); + ArrayList statements = this.statement_list(); + this.eat(TokenType.END); + return new ForStatement(statements, variable); + } + + // assign_statement: variable ASSIGN expr + Assign assign_statement() throws Exception { + Var left = this.variable(); + Token token = this.current_token; + this.eat(TokenType.ASSIGN); + AST right = this.expr(); + return new Assign(left, token, right); + } + + // variable: ID + Var variable() throws Exception { + Var node = new Var(this.current_token); + this.eat(TokenType.ID); + return node; + } + + NoOp empty() { + return new NoOp(); + } + + // factor: PLUS factor + // | MINUS factor + // | INTEGER_CONST + // | REAL_CONST + // | LPAREN expr RPAREN + // | variable + AST factor() throws Exception { + Token token = this.current_token; + switch (token.type) { + case PLUS: + this.eat(TokenType.PLUS); + return new UnaryOp(token, this.factor()); + case MINUS: + this.eat(TokenType.MINUS); + return new UnaryOp(token, this.factor()); + case INTEGER_CONST: + this.eat(TokenType.INTEGER_CONST); + return new Num(token); + case REAL_CONST: + this.eat(TokenType.REAL_CONST); + return new Num(token); + case LPAREN: + this.eat(TokenType.LPAREN); + AST node = this.expr(); + this.eat(TokenType.RPAREN); + return node; + default: + return this.variable(); + } + } + + // term: factor ((MUL | DIV) factor)* + AST term() throws Exception { + AST node = this.factor(); + + while (this.current_token.type == TokenType.FLOAT_DIV + || this.current_token.type == TokenType.INTEGER_DIV + || this.current_token.type == TokenType.MUL) { + Token token = this.current_token; + if (token.type == TokenType.MUL) { + this.eat(TokenType.MUL); + } else if (token.type == TokenType.INTEGER_DIV) + this.eat(TokenType.INTEGER_DIV); + else + this.eat(TokenType.FLOAT_DIV); + + node = new BinOp(node, token, this.factor()); + } + return node; + } + + // expr: term ((PLUS | MINUS) term)* + AST expr() throws Exception { + AST node = this.term(); + + while (this.current_token.type == TokenType.MINUS || this.current_token.type == TokenType.PLUS) { + Token token = this.current_token; + if (token.type == TokenType.MINUS) { + this.eat(TokenType.MINUS); + } else + this.eat(TokenType.PLUS); + + node = new BinOp(node, token, this.term()); + } + return node; + } + + AST parse() throws Exception { + AST node = this.program(); + if (this.current_token.type != TokenType.EOF) + this.error(); + return node; + } +} diff --git a/BasicInterpreter/test/InterpreterAlpha/InterpreterAlphaTest.java b/BasicInterpreter/test/InterpreterAlpha/InterpreterAlphaTest.java new file mode 100644 index 0000000..d65ff2f --- /dev/null +++ b/BasicInterpreter/test/InterpreterAlpha/InterpreterAlphaTest.java @@ -0,0 +1,47 @@ +package InterpreterAlpha; + +import org.junit.Test; + +import java.nio.file.Files; +import java.nio.file.Paths; + +import static org.junit.Assert.*; + +public class InterpreterAlphaTest { + + @Test + public void interpret() throws Exception{ + String str = "PROGRAM thisProgram;\n" + + "VAR\n" + + " var1, a, b, c : INTEGER;\n" + + "\n" + + "BEGIN {example}\n" + + " a := 2;\n" + + " var1 := 1;\n" + + " b := 5;\n" + + " c := 20;\n" + + "\n" + + " LOOP b DO\n" + + " var1 := var1 + a;\n" + + " c := c - 1;\n" + + " END;\n" + + "\n" + + " LOOP a DO\n" + + " b := b + 2\n" + + " END\n" + + "END. {example}"; + Lexer lexer = new Lexer(str); + Parser parser = new Parser(lexer); + AST tree = parser.parse(); + SymbolTableBuilder symtab = new SymbolTableBuilder(); + symtab.visit(tree); + InterpreterAlpha interpreter = new InterpreterAlpha(tree); + interpreter.interpret(); + Symbol.resultFile.close(); + String result = Files.readString(Paths.get("result.txt")); + assertEquals("a = 2\n" + + "b = 9.0\n" + + "c = 15.0\n" + + "var1 = 11.0\n", result); + } +} diff --git a/BasicInterpreter/test/InterpreterAlpha/LexerTest.java b/BasicInterpreter/test/InterpreterAlpha/LexerTest.java new file mode 100644 index 0000000..1d5a384 --- /dev/null +++ b/BasicInterpreter/test/InterpreterAlpha/LexerTest.java @@ -0,0 +1,36 @@ +package InterpreterAlpha; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class LexerTest { + + @Test + public void get_next_token() throws Exception{ + Lexer x = new Lexer(":="); + Token y = x.get_next_token(); + assertEquals(TokenType.ASSIGN, y.type); + assertEquals(":=", y.value); + + x = new Lexer("{this is a comment}\t\n ;"); + y = x.get_next_token(); + assertEquals(";", y.value); + assertEquals(TokenType.SEMI, y.type); + + x = new Lexer("a12xb"); + y = x.get_next_token(); + assertEquals(TokenType.ID, y.type); + assertEquals("a12xb", y.value); + + x = new Lexer("1483"); + y = x.get_next_token(); + assertEquals(TokenType.INTEGER_CONST, y.type); + assertEquals("1483", y.value); + + x = new Lexer("12.5"); + y = x.get_next_token(); + assertEquals(TokenType.REAL_CONST, y.type); + assertEquals("12.5", y.value); + } +} \ No newline at end of file diff --git a/BasicInterpreter/test/InterpreterAlpha/SymbolTableBuilderTest.java b/BasicInterpreter/test/InterpreterAlpha/SymbolTableBuilderTest.java new file mode 100644 index 0000000..3bc08e0 --- /dev/null +++ b/BasicInterpreter/test/InterpreterAlpha/SymbolTableBuilderTest.java @@ -0,0 +1,30 @@ +package InterpreterAlpha; + +import org.junit.Test; + +import java.nio.file.Files; +import java.nio.file.Paths; + +import static org.junit.Assert.*; + +public class SymbolTableBuilderTest { + + @Test + public void printTable() throws Exception{ + String str = "PROGRAM thisProgram;\n" + + "VAR\n" + + " var1, a, b, c : INTEGER;\n" + + " d : REAL;\n" + + "BEGIN {example}\n" + + "END. {example}"; + Lexer lexer = new Lexer(str); + Parser parser = new Parser(lexer); + AST tree = parser.parse(); + SymbolTableBuilder sym_table = new SymbolTableBuilder(); + sym_table.visit(tree); + sym_table.printTable(); + Symbol.resultFile.close(); + String result = Files.readString(Paths.get("result.txt")); + assertEquals("; ; ; ; REAL; ; INTEGER; \n\n", result); + } +} \ No newline at end of file