BasicInterpreter
This commit is contained in:
parent
ea62c0d3b0
commit
143979c5b4
|
@ -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}
|
|
@ -0,0 +1,6 @@
|
|||
<a : INTEGER>; <b : INTEGER>; <c : INTEGER>; REAL; <var1 : INTEGER>; INTEGER;
|
||||
|
||||
a = 2
|
||||
b = 9.0
|
||||
c = 15.0
|
||||
var1 = 11.0
|
|
@ -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<String, Symbol> _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<String, String> 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<String, String> 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();
|
||||
}
|
||||
}
|
|
@ -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<String, Token> RESERVED_KEYWORDS = initMap();
|
||||
|
||||
// map of constant keywords
|
||||
private static Map<String, Token> initMap() {
|
||||
Map<String, Token> 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, "");
|
||||
}
|
||||
}
|
|
@ -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<AST> children;
|
||||
|
||||
Compound() {
|
||||
this.children = new ArrayList<AST>();
|
||||
}
|
||||
}
|
||||
|
||||
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<AST> statements;
|
||||
Var variable;
|
||||
|
||||
ForStatement(ArrayList<AST> 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<VarDecl> declaration;
|
||||
Compound compound_statement;
|
||||
|
||||
Block(ArrayList<VarDecl> 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<VarDecl> 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<VarDecl> declarations() throws Exception {
|
||||
ArrayList<VarDecl> declarations = new ArrayList<VarDecl>();
|
||||
if (this.current_token.type == TokenType.VAR) {
|
||||
this.eat(TokenType.VAR);
|
||||
while (this.current_token.type == TokenType.ID) {
|
||||
ArrayList<VarDecl> var_decl = this.variable_declaration();
|
||||
declarations.addAll(var_decl);
|
||||
this.eat(TokenType.SEMI);
|
||||
}
|
||||
}
|
||||
return declarations;
|
||||
}
|
||||
|
||||
// variable_declaration: ID (COMMA ID)* COLON type_spec
|
||||
ArrayList<VarDecl> variable_declaration() throws Exception {
|
||||
ArrayList<Var> var_nodes = new ArrayList<Var>();
|
||||
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<VarDecl> var_declarations = new ArrayList<VarDecl>();
|
||||
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<AST> 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<AST> statement_list() throws Exception {
|
||||
AST node = this.statement();
|
||||
ArrayList<AST> results = new ArrayList<AST>();
|
||||
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<AST> 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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("<a : INTEGER>; <b : INTEGER>; <c : INTEGER>; <d : REAL>; REAL; <var1 : INTEGER>; INTEGER; \n\n", result);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue