From e1247c33604421e99ee846c9252660c01a0cd118 Mon Sep 17 00:00:00 2001 From: ctsk <9384305+ctsk@users.noreply.github.com> Date: Thu, 8 Sep 2022 10:04:33 +0200 Subject: [PATCH] Parsing expressions done! --- .../main/java/xyz/ctsk/lox/AstPrinter.java | 52 ++++++ jlox/lox/src/main/java/xyz/ctsk/lox/Lox.java | 34 +++- .../src/main/java/xyz/ctsk/lox/Parser.java | 170 ++++++++++++++++++ .../lox/src/main/java/xyz/ctsk/lox/Token.java | 2 +- 4 files changed, 248 insertions(+), 10 deletions(-) create mode 100644 jlox/lox/src/main/java/xyz/ctsk/lox/AstPrinter.java create mode 100644 jlox/lox/src/main/java/xyz/ctsk/lox/Parser.java diff --git a/jlox/lox/src/main/java/xyz/ctsk/lox/AstPrinter.java b/jlox/lox/src/main/java/xyz/ctsk/lox/AstPrinter.java new file mode 100644 index 0000000..ac91f04 --- /dev/null +++ b/jlox/lox/src/main/java/xyz/ctsk/lox/AstPrinter.java @@ -0,0 +1,52 @@ +package xyz.ctsk.lox; + +public class AstPrinter { + public static String polish(Expr expr, boolean parentheses) { + return expr.accept(new Polish(parentheses, false)); + } + + public static String reverse_polish(Expr expr, boolean parentheses) { + return expr.accept(new Polish(parentheses, true)); + } + + private record Polish(boolean parentheses, boolean reverse) implements Expr.Visitor { + @Override + public String visitBinaryExpr(Expr.Binary binary) { + var left = binary.left().accept(this); + var op = binary.operator().lexeme(); + var right = binary.right().accept(this); + + return reverse ? wrap(left, right, op) : wrap(op, left, right); + } + + @Override + public String visitGroupingExpr(Expr.Grouping grouping) { + var inner = grouping.expression().accept(this); + return wrap(inner); + } + + @Override + public String visitLiteralExpr(Expr.Literal literal) { + if (literal.value() == null) return "nil"; + return literal.value().toString(); + } + + @Override + public String visitUnaryExpr(Expr.Unary unary) { + var op = unary.operator().lexeme(); + var right = unary.right().accept(this); + + return reverse ? wrap(right, op) : wrap(op, right); + } + + public String wrap(String... inner) { + var inners = String.join(" ", inner); + + if (parentheses) { + return "(" + inners + ")"; + } else { + return inners; + } + } + } +} diff --git a/jlox/lox/src/main/java/xyz/ctsk/lox/Lox.java b/jlox/lox/src/main/java/xyz/ctsk/lox/Lox.java index 88a4a69..e130870 100644 --- a/jlox/lox/src/main/java/xyz/ctsk/lox/Lox.java +++ b/jlox/lox/src/main/java/xyz/ctsk/lox/Lox.java @@ -9,20 +9,36 @@ import java.nio.file.Paths; public class Lox { private static boolean hadError = false; - private static void run(String source) { - var scanner = new Scanner(source); - scanner.scanTokens().forEach(System.out::println); - } - - static void error(int line, String message) { - report(line, "", message); - } private static void report(int line, String where, String message) { System.err.printf("[line %d] Error %s: %s%n", line, where, message); hadError = true; } + static void error(int line, String message) { + report(line, "", message); + } + + static void error(Token token, String message) { + if (token.type() == TokenType.EOF) { + report(token.line(), " at end", message); + } else { + report(token.line()," at '%s'".formatted(token.lexeme()), message); + } + } + + private static void run(String source) { + var scanner = new Scanner(source); + var tokens = scanner.scanTokens(); + + Parser parser = new Parser(tokens); + Expr expression = parser.parse(); + + if (hadError) return; + + System.out.println(AstPrinter.polish(expression, true)); + System.out.println(AstPrinter.reverse_polish(expression, false)); + } private static void runPrompt() throws IOException { var input = new InputStreamReader(System.in); @@ -35,7 +51,7 @@ public class Lox { run(line); hadError = false; } - }; + } private static void runFile(String path) throws IOException { byte[] bytes = Files.readAllBytes(Paths.get(path)); diff --git a/jlox/lox/src/main/java/xyz/ctsk/lox/Parser.java b/jlox/lox/src/main/java/xyz/ctsk/lox/Parser.java new file mode 100644 index 0000000..0a7aece --- /dev/null +++ b/jlox/lox/src/main/java/xyz/ctsk/lox/Parser.java @@ -0,0 +1,170 @@ +package xyz.ctsk.lox; + +import java.util.List; + +import static xyz.ctsk.lox.TokenType.*; + +/** + * + * A recursive descent parser for the following grammar: + *

+ * expression → equality ; + * equality → comparison ( ( "!=" | "==" ) comparison )* ; + * comparison → term ( ( ">" | ">=" | "<" | "<=" ) term )* ; + * term → factor ( ( "-" | "+" ) factor )* ; + * factor → unary ( ( "/" | "*" ) unary )* ; + * unary → ( "!" | "-" ) unary + * | primary ; + * primary → NUMBER | STRING | "true" | "false" | "nil" + * | "(" expression ")" ; + */ +public class Parser { + private final List tokens; + + private int current = 0; + + Parser(List tokens) { + this.tokens = tokens; + } + + 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 isAtEnd() { + return peek().type() == EOF; + } + + private Token previous() { + return tokens.get(current - 1); + } + + private Token advance() { + if (!isAtEnd()) current++; + return previous(); + } + + private Token peek() { + return tokens.get(current); + } + private boolean check(TokenType type) { + if (isAtEnd()) return false; + return peek().type() == type; + } + + private boolean match(TokenType... types) { + for (var 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 ParseError error(Token token, String message) { + Lox.error(token, message); + return new ParseError(); + } + + private void synchronize() { + advance(); + + while (!isAtEnd()) { + if (previous().type() == SEMICOLON) return; + advance(); + } + } + + private static class ParseError extends RuntimeException {} +} diff --git a/jlox/lox/src/main/java/xyz/ctsk/lox/Token.java b/jlox/lox/src/main/java/xyz/ctsk/lox/Token.java index 5561fb1..a644dfc 100644 --- a/jlox/lox/src/main/java/xyz/ctsk/lox/Token.java +++ b/jlox/lox/src/main/java/xyz/ctsk/lox/Token.java @@ -13,4 +13,4 @@ public record Token(TokenType type, String lexeme, Object literal, int line) { return "%s %s %s".formatted(type, lexeme, literal); } } -}; +}