diff --git a/src/com/craftinginterpreters/lox/CMakeLists.txt b/src/com/craftinginterpreters/lox/CMakeLists.txt index d2807e6..c326c88 100644 --- a/src/com/craftinginterpreters/lox/CMakeLists.txt +++ b/src/com/craftinginterpreters/lox/CMakeLists.txt @@ -13,4 +13,5 @@ add_jar(Lox Scanner.java ${EXPR_JAVA_FILENAME} AstPrinter.java + Parser.java ENTRY_POINT com/craftinginterpreters/lox/Lox) diff --git a/src/com/craftinginterpreters/lox/Lox.java b/src/com/craftinginterpreters/lox/Lox.java index 51d72c7..586442d 100644 --- a/src/com/craftinginterpreters/lox/Lox.java +++ b/src/com/craftinginterpreters/lox/Lox.java @@ -27,7 +27,8 @@ public class Lox { run(new String(bytes, Charset.defaultCharset())); // Indicate an error in the exit code. - if (hadError) System.exit(65); + if (hadError) + System.exit(65); } private static void runPrompt() throws IOException { @@ -37,7 +38,8 @@ public class Lox { for (;;) { System.out.print("> "); String line = reader.readLine(); - if (line == null) break; + if (line == null) + break; run(line); hadError = false; } @@ -46,11 +48,13 @@ public class Lox { private static void run(String source) { Scanner scanner = new Scanner(source); List tokens = scanner.scanTokens(); + Parser parser = new Parser(tokens); + Expr expression = parser.parse(); - // For now, just print the tokens. - for (Token token : tokens) { - System.out.println(token); - } + // Stop if there was a syntax error + if (hadError) return; + + System.out.println(new AstPrinter().print(expression)); } public static void error(int line, String message) { @@ -61,4 +65,12 @@ public class Lox { System.err.println("[line " + line + "] Error" + where + ": " + message); hadError = true; } + + public 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); + } + } } diff --git a/src/com/craftinginterpreters/lox/Parser.java b/src/com/craftinginterpreters/lox/Parser.java new file mode 100644 index 0000000..c5e50e0 --- /dev/null +++ b/src/com/craftinginterpreters/lox/Parser.java @@ -0,0 +1,178 @@ +package com.craftinginterpreters.lox; + +import static com.craftinginterpreters.lox.TokenType.*; + +import java.util.List; + +class Parser { + private static class ParseError extends RuntimeException { + } + + private final List tokens; + private int current = 0; + + Parser(List tokens) { + this.tokens = tokens; + } + + public Expr parse() { + try { + return expression(); + } catch (ParseError error) { + return null; + } + } + + private Expr expression() { + return equality(); + } + + private Expr equality() { + Expr expr = comparison(); + + while (match(BANG_EQUAL, EQUAL_EQUAL)) { + Token operator = previous(); + Expr right = comparison(); + expr = new Expr.Binary(expr, operator, right); + } + + return expr; + } + + private Expr comparison() { + Expr expr = term(); + + while (match(GREATER, GREATER_EQUAL, LESS, LESS_EQUAL)) { + Token operator = previous(); + Expr right = term(); + expr = new Expr.Binary(expr, operator, right); + } + + return expr; + } + + private Expr term() { + Expr expr = factor(); + + while (match(MINUS, PLUS)) { + Token operator = previous(); + Expr right = factor(); + expr = new Expr.Binary(expr, operator, right); + } + + return expr; + } + + private Expr factor() { + Expr expr = unary(); + + while (match(SLASH, STAR)) { + Token operator = previous(); + Expr right = unary(); + expr = new Expr.Binary(expr, operator, right); + } + + return expr; + } + + private Expr unary() { + if (match(BANG, MINUS)) { + Token operator = previous(); + Expr right = unary(); + return new Expr.Unary(operator, right); + } + + return primary(); + } + + private Expr primary() { + if (match(FALSE)) + return new Expr.Literal(false); + if (match(TRUE)) + return new Expr.Literal(true); + if (match(NIL)) + return new Expr.Literal(null); + + if (match(NUMBER, STRING)) { + return new Expr.Literal(previous().literal); + } + + if (match(LEFT_PAREN)) { + Expr expr = expression(); + consume(RIGHT_PAREN, "Expect ')' after expression."); + return new Expr.Grouping(expr); + } + + throw error(peek(), "Expect expression."); + } + + private boolean match(TokenType... types) { + for (TokenType type : types) { + if (check(type)) { + advance(); + return true; + } + } + + return false; + } + + private Token consume(TokenType type, String message) { + if (check(type)) + return advance(); + + throw error(peek(), message); + } + + private boolean check(TokenType type) { + if (isAtEnd()) + return false; + return peek().type == type; + } + + private Token advance() { + if (!isAtEnd()) + current++; + return previous(); + } + + private boolean isAtEnd() { + return peek().type == EOF; + } + + private Token peek() { + return tokens.get(current); + } + + private Token previous() { + return tokens.get(current - 1); + } + + private ParseError error(Token token, String message) { + Lox.error(token, message); + return new ParseError(); + } + + private void synchronize() { + advance(); + + while (!isAtEnd()) { + if (previous().type == SEMICOLON) + return; + + switch (peek().type) { + case CLASS: + case FUN: + case VAR: + case FOR: + case IF: + case WHILE: + case PRINT: + case RETURN: + return; + } + + advance(); + } + } +}