diff --git a/cobalt/lang/AstPrinter.java b/cobalt/lang/AstPrinter.java new file mode 100644 index 0000000..48d69bd --- /dev/null +++ b/cobalt/lang/AstPrinter.java @@ -0,0 +1,54 @@ +package cobalt.lang; + +// Creates an unambiguous, if ugly, string representation of AST nodes. +class AstPrinter implements Expr.Visitor { + String print(Expr expr) { + return expr.accept(this); + } + + @Override + public String visitBinaryExpr(Expr.Binary expr) { + return parenthesize(expr.operator.lexeme, expr.left, expr.right); + } + + @Override + public String visitGroupingExpr(Expr.Grouping expr) { + return parenthesize("group", expr.expression); + } + + @Override + public String visitLiteralExpr(Expr.Literal expr) { + if (expr.value == null) return "nil"; + return expr.value.toString(); + } + + @Override + public String visitUnaryExpr(Expr.Unary expr) { + return parenthesize(expr.operator.lexeme, expr.right); + } + + private String parenthesize(String name, Expr... exprs) { + StringBuilder builder = new StringBuilder(); + + builder.append("(").append(name); + for (Expr expr : exprs) { + builder.append(" "); + builder.append(expr.accept(this)); + } + builder.append(")"); + + return builder.toString(); + } + + public static void main(String[] args) { + Expr expression = new Expr.Binary( + new Expr.Unary( + new Token(TokenType.MINUS, "-", null, 1), + new Expr.Literal(123)), + new Token(TokenType.STAR, "*", null, 1), + new Expr.Grouping( + new Expr.Literal(45.67))); + + System.out.println(new AstPrinter().print(expression)); + } +} diff --git a/cobalt/lang/Cobalt.java b/cobalt/lang/Cobalt.java index bb0b27d..e2a175f 100644 --- a/cobalt/lang/Cobalt.java +++ b/cobalt/lang/Cobalt.java @@ -1,4 +1,4 @@ -package cobalt; +package cobalt.lang; import java.io.BufferedReader; import java.io.IOException; @@ -23,12 +23,14 @@ public class Cobalt { } } + private static void runFile(String path) throws IOException { byte[] bytes = Files.readAllBytes(Paths.get(path)); run(new String(bytes, Charset.defaultCharset())); if (hadError) System.exit(65); } + private static void runPrompt() throws IOException { InputStreamReader input = new InputStreamReader(System.in); BufferedReader reader = new BufferedReader(input); @@ -42,27 +44,37 @@ public class Cobalt { } } - private static void run(String source) { - // Wont compile since we don't use the standard Java Scanner, - // gets implemented further in the textbook - Scanner scanner = new Scanner(source); - // Same situation for Token type - List tokens = scanner.scanTokens(); - for (Token token : tokens) { - System.out.println(token); - } + private static void run(String source) { + Scanner scanner = new Scanner(source); + List tokens = scanner.scanTokens(); + Parser parser = new Parser(tokens); + Expr expression = parser.parse(); + + if (hadError) return; + + System.out.println(new AstPrinter().print(expression)); } + static void error(int line, String message) { report(line, "", message); } + private static void report(int line, String where, String message) { System.err.println( "[line " + line + "] Error" + where + ": " + message ); hadError = true; + } + + static void error(Token token, String message) { + if (token.type == TokenType.EOF) { + report(token.line, " at end", message); + } else { + report(token.line, " at '" + token.lexeme + "'", message); + } } } \ No newline at end of file diff --git a/cobalt/lang/Expr.java b/cobalt/lang/Expr.java index d22d6dd..c21b6f4 100644 --- a/cobalt/lang/Expr.java +++ b/cobalt/lang/Expr.java @@ -1,6 +1,16 @@ package cobalt.lang; +import java.util.List; + abstract class Expr { + interface Visitor { + R visitBinaryExpr(Binary expr); + R visitGroupingExpr(Grouping expr); + R visitLiteralExpr(Literal expr); + R visitUnaryExpr(Unary expr); + } + + static class Binary extends Expr { Binary(Expr left, Token operator, Expr right) { this.left = left; @@ -8,34 +18,55 @@ abstract class Expr { this.right = right; } + R accept(Visitor visitor) { + return visitor.visitBinaryExpr(this); + } + final Expr left; final Token operator; final Expr right; } - static class Unary extends Expr { - Unary(Token operator, Expr right) { - this.operator = operator; - this.right = right; - } - - final Token operator; - final Expr right; - } static class Grouping extends Expr { Grouping(Expr expression) { this.expression = expression; } + R accept(Visitor visitor) { + return visitor.visitGroupingExpr(this); + } + final Expr expression; } + static class Literal extends Expr { Literal(Object value) { this.value = value; } + R accept(Visitor visitor) { + return visitor.visitLiteralExpr(this); + } + final Object value; } + + + static class Unary extends Expr { + Unary(Token operator, Expr right) { + this.operator = operator; + this.right = right; + } + + R accept(Visitor visitor) { + return visitor.visitUnaryExpr(this); + } + + final Token operator; + final Expr right; + } + + abstract R accept(Visitor visitor); } diff --git a/cobalt/lang/Intepreter.java b/cobalt/lang/Intepreter.java new file mode 100644 index 0000000..c09cf10 --- /dev/null +++ b/cobalt/lang/Intepreter.java @@ -0,0 +1,34 @@ +// +// Continue at Section 7.2.3 +// Page 100 +// + +package cobalt.lang; + +class Interpreter implements Expr.Visitor { + @Override + public Object visitLiteralExpr(Expr.Literal expr) { + return expr.value; + } + + @Override + public Object visitUnaryExpr(Expr.Unary expr) { + Object right = evaluate(expr.right); + + switch (expr.operator.type) { + case MINUS: + return -(double)right; + } + + return null; + } + + @Override + public Object visitGroupingExpr(Expr.Grouping expr) { + return evaluate(expr.expression); + } + + private Object evaluate(Expr expr) { + return expr.accept(this); + } +} diff --git a/cobalt/lang/Parser.java b/cobalt/lang/Parser.java index ceb098f..fce81af 100644 --- a/cobalt/lang/Parser.java +++ b/cobalt/lang/Parser.java @@ -1,16 +1,11 @@ -/////////// -// -// Continue at page 89, Section 6.3 -// -/////////// - - package cobalt.lang; import java.util.List; import static cobalt.lang.TokenType.*; class Parser { + private static class ParseError extends RuntimeException {} + private final List tokens; private int current = 0; @@ -18,6 +13,16 @@ class Parser { this.tokens = tokens; } + + Expr parse() { + try { + return expression(); + } catch (ParseError error) { + return null; + } + } + + private Expr expression() { return equality(); } @@ -38,6 +43,7 @@ class Parser { return expr; } + // Parser definition for handling comparisons // // comparison -> term ( ( ">" | ">=" | "<" | "<=" ) term )* ; @@ -80,7 +86,12 @@ class Parser { return expr; } - + + // Parser definition for handling unary statements + // + // unary -> ( "!" | "-" ) unary + // | primary ; + // private Expr unary() { if (match(BANG, MINUS)) { Token operator = previous(); @@ -92,6 +103,11 @@ class Parser { } + // Parser definition for handling primary statements + // + // primary -> NUMBER | STRING | "true" | "false" | "nil" + // | "(" expression ")" ; + // private Expr primary() { if (match(FALSE)) return new Expr.Literal(false); if (match(TRUE)) return new Expr.Literal(true); @@ -106,6 +122,8 @@ class Parser { consume(RIGHT_PAREN, "Expect ')' after expression."); return new Expr.Grouping(expr); } + + throw error(peek(), "Expected expression."); } @@ -123,6 +141,14 @@ class Parser { } + // Consume function + private Token consume(TokenType type, String message) { + if (check(type)) return advance(); + + throw error(peek(), message); + } + + // Returns true if the current token is of the given type, false if otherwise private boolean check(TokenType type) { if (isAtEnd()) return false; @@ -153,7 +179,37 @@ class Parser { private Token previous() { return tokens.get(current - 1); } - + + + // Throw a new parse error based on the incorrect token + private ParseError error(Token token, String message) { + Cobalt.error(token, message); + return new ParseError(); + } + + + // Once finding a parse error, discard the rest of the tokens in + // a statement in order to prevent cascading errors. + private void synchronize() { + advance(); + while (!isAtEnd()) { + if (previous().type == SEMICOLON) return; + + switch (peek().type) { + case CLASS: + case FOR: + case FUNC: + case IF: + case PRINT: + case RETURN: + case VAR: + case WHILE: + return; + } + + advance(); + } + } } diff --git a/cobalt/lang/Scanner.java b/cobalt/lang/Scanner.java index 3213ae8..3333a9f 100644 --- a/cobalt/lang/Scanner.java +++ b/cobalt/lang/Scanner.java @@ -18,7 +18,8 @@ class Scanner { static { keywords = new HashMap<>(); keywords.put("and", AND); - keywords.put("class", ELSE); + keywords.put("class", CLASS); + keywords.put("else", ELSE); keywords.put("false", FALSE); keywords.put("for", FOR); keywords.put("func", FUNC); @@ -142,6 +143,8 @@ class Scanner { while (isDigit(peek())) advance(); } + + addToken(NUMBER, Double.parseDouble(source.substring(start, current))); }