package lox; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static lox.TokenType.*; class Parser { private static class ParseError extends RuntimeException {} private final List tokens; private int current = 0; Parser(List tokens) { this.tokens = tokens; } List parse() { List statements = new ArrayList<>(); while (!isAtEnd()) { statements.add(declaration()); } return statements; } private Stmt declaration() { try { if (match(VAR)) { return varDeclaration(); } return statement(); } catch (ParseError error) { synchronize(); return null; } } private Stmt varDeclaration() { Token name = consume(IDENTIFIER, "Expect variable name."); Expr initializer = null; if (match(EQUAL)) { initializer = expression(); } consume(SEMICOLON, "Expect semicolon."); return new Stmt.Var(name, initializer); } private Stmt statement() { if (match(FOR)) { return forStatement(); } if (match(IF)) { return ifStatement(); } if (match(PRINT)) { return printStatement(); } if (match(WHILE)) { return whileStatement(); } if (match(LEFT_BRACE)) { return new Stmt.Block(block()); } return expressionStatement(); } private Stmt printStatement() { Expr value = expression(); consume(SEMICOLON, "Expect ';' after value."); return new Stmt.Print(value); } private Stmt whileStatement() { consume(LEFT_PAREN, "Expect '(' after 'while"); Expr condition = expression(); consume(RIGHT_PAREN, "Expect ')' after while condition"); Stmt body = statement(); return new Stmt.While(condition, body); } private Stmt expressionStatement() { Expr value = expression(); consume(SEMICOLON, "Expect ';' after value."); return new Stmt.Expression(value); } private Stmt ifStatement() { consume(LEFT_PAREN, "Expect '(' after 'if'."); Expr condition = expression(); consume(RIGHT_PAREN, "Expect ')' after if condition"); Stmt thenBranch = statement(); Stmt elseBranch = null; if (match(ELSE)) { elseBranch = statement(); } return new Stmt.If(condition, thenBranch, elseBranch); } private Stmt forStatement() { consume(LEFT_PAREN, "Expect '(' after 'for'."); Stmt initializer; if (match(SEMICOLON)) { initializer = null; } else if (match(VAR)) { initializer = varDeclaration(); } else { initializer = expressionStatement(); } Expr condition = null; if (!check(SEMICOLON)) { condition = expression(); } consume(SEMICOLON, "Expect ';' after loop condition."); Expr increment = null; if (!check(RIGHT_PAREN)) { increment = expression(); } consume(RIGHT_PAREN, "Expect ')' after for clauses"); Stmt body = statement(); if (increment != null) { // move incrementer into the body body = new Stmt.Block(Arrays.asList(body, new Stmt.Expression(increment))); } if (condition == null) { // set an infinite loop up if no condition given condition = new Expr.Literal(true); } body = new Stmt.While(condition, body); if (initializer != null) { // insert the initializer before the while statement body = new Stmt.Block(Arrays.asList(initializer, body)); } return body; } private List block() { List statements = new ArrayList<>(); while (!check(RIGHT_BRACE) && !isAtEnd()) { statements.add(declaration()); } consume(RIGHT_BRACE, "Expect '}' after block."); return statements; } private Expr assignment() { Expr expr = or(); if (match(EQUAL)) { Token equals = previous(); Expr value = assignment(); if (expr instanceof Expr.Variable) { Token name = ((Expr.Variable)expr).name; return new Expr.Assign(name, value); } // does not throw because parsing _can_ continue error(equals, "Invalid assignment target."); } return expr; } private Expr or() { Expr expr = and(); while (match(OR)) { Token operator = previous(); Expr right = and(); expr = new Expr.Logical(expr, operator, right); } return expr; } private Expr and() { Expr expr = equality(); while (match(AND)) { Token operator = previous(); Expr right = equality(); expr = new Expr.Logical(expr, operator, right); } return expr; } private Expr expression() { return assignment(); } 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(PLUS, MINUS)) { Token operator = previous(); Expr right = factor(); expr = new Expr.Binary(expr, operator, right); } return expr; } private Expr factor() { Expr expr = unary(); while (match(STAR, SLASH)) { 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); } if (match(IDENTIFIER)) { return new Expr.Variable(previous()); } throw error(peek(), "Expected 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, FOR, FUN, IF, PRINT, RETURN, VAR, WHILE: return; } } advance(); } }