package io.gitlab.jfronny.muscript.compiler; import io.gitlab.jfronny.muscript.compiler.expr.*; import io.gitlab.jfronny.muscript.compiler.expr.bool.*; import io.gitlab.jfronny.muscript.compiler.expr.common.*; import io.gitlab.jfronny.muscript.compiler.expr.dynamic.*; import io.gitlab.jfronny.muscript.compiler.expr.number.compare.*; import io.gitlab.jfronny.muscript.compiler.expr.number.math.*; import io.gitlab.jfronny.muscript.compiler.expr.string.*; import io.gitlab.jfronny.muscript.compiler.expr.unresolved.*; import java.util.*; public class Parser { private final Lexer lexer; private final TokenData previous = new TokenData(); private final TokenData current = new TokenData(); public static Expr parse(String source) { return new Parser(new Lexer(source)).parse(); } public Parser(Lexer lexer) { this.lexer = lexer; } public Expr parse() { advance(); return expression(); } // Expressions private Expr expression() { try { return conditional(); } catch (RuntimeException e) { if (e instanceof ParseException) throw e; else throw error(e.getMessage()); } } private Expr conditional() { Expr expr = and(); if (match(Token.QuestionMark)) { Expr trueExpr = expression(); consume(Token.Colon, "Expected ':' after first part of condition."); Expr falseExpr = expression(); expr = new UnresolvedConditional(expr.asBoolExpr(), trueExpr, falseExpr); } return expr; } private Expr and() { Expr expr = or(); while (match(Token.And)) { Expr right = or(); expr = new And(expr.asBoolExpr(), right.asBoolExpr()); } return expr; } private Expr or() { Expr expr = equality(); while (match(Token.Or)) { Expr right = equality(); expr = new Or(expr.asBoolExpr(), right.asBoolExpr()); } return expr; } private Expr equality() { Expr expr = concat(); while (match(Token.EqualEqual, Token.BangEqual)) { Token op = previous.token; Expr right = concat(); BoolExpr e = new Equal(expr, right); if (op == Token.BangEqual) e = new Not(e); expr = e; } return expr; } private Expr concat() { Expr expr = comparison(); while (match(Token.Concat)) { Expr right = comparison(); expr = new Concatenate(expr.asStringExpr(), right.asStringExpr()); } return expr; } private Expr comparison() { Expr expr = term(); while (match(Token.Greater, Token.GreaterEqual, Token.Less, Token.LessEqual)) { Token op = previous.token; NumberExpr right = term().asNumberExpr(); expr = switch (op) { case Greater -> new Greater(expr.asNumberExpr(), right); case GreaterEqual -> new Not(new Less(expr.asNumberExpr(), right)); case Less -> new Less(expr.asNumberExpr(), right); case LessEqual -> new Not(new Greater(expr.asNumberExpr(), right)); default -> throw new IllegalStateException(); }; } return expr; } private Expr term() { Expr expr = factor(); while (match(Token.Plus, Token.Minus)) { Token op = previous.token; NumberExpr right = factor().asNumberExpr(); expr = switch (op) { case Plus -> new Plus(expr.asNumberExpr(), right); case Minus -> new Minus(expr.asNumberExpr(), right); default -> throw new IllegalStateException(); }; } return expr; } private Expr factor() { Expr expr = exp(); while (match(Token.Star, Token.Slash, Token.Percentage)) { Token op = previous.token; NumberExpr right = exp().asNumberExpr(); expr = switch (op) { case Star -> new Multiply(expr.asNumberExpr(), right); case Slash -> new Divide(expr.asNumberExpr(), right); case Percentage -> new Modulo(expr.asNumberExpr(), right); default -> throw new IllegalStateException(); }; } return expr; } private Expr exp() { Expr expr = unary(); while (match(Token.UpArrow)) { NumberExpr right = unary().asNumberExpr(); expr = new Power(expr.asNumberExpr(), right); } return expr; } private Expr unary() { if (match(Token.Bang, Token.Minus)) { Token op = previous.token; Expr right = unary(); return switch (op) { case Bang -> new Not(right.asBoolExpr()); case Minus -> new Invert(right.asNumberExpr()); default -> throw new IllegalStateException(); }; } return call(); } private Expr call() { Expr expr = primary(); while (match(Token.LeftParen, Token.Dot, Token.LeftBracket)) { expr = switch (previous.token) { case LeftParen -> finishCall(expr); case Dot -> { TokenData name = consume(Token.Identifier, "Expected field name after '.'."); yield new Get(expr.asDynamicExpr(), Expr.literal(name.lexeme)); } case LeftBracket -> { expr = new Get(expr.asDynamicExpr(), expression()); consume(Token.RightBracket, "Expected closing bracket"); yield expr; } default -> throw new IllegalStateException(); }; } return expr; } private Expr finishCall(Expr callee) { List args = new ArrayList<>(2); if (!check(Token.RightParen)) { do { args.add(expression().asDynamicExpr()); } while (match(Token.Comma)); } consume(Token.RightParen, "Expected ')' after function arguments."); return new Call(callee.asDynamicExpr(), args); } private Expr primary() { if (match(Token.Null)) return Expr.literalNull(); if (match(Token.String)) return Expr.literal(previous.lexeme); if (match(Token.True, Token.False)) return Expr.literal(previous.lexeme.equals("true")); if (match(Token.Number)) return Expr.literal(Double.parseDouble(previous.lexeme)); if (match(Token.Identifier)) return new Variable(previous.lexeme); if (match(Token.LeftParen)) { Expr expr = expression(); consume(Token.RightParen, "Expected ')' after expression."); return expr; } throw error("Expected expression."); } // Helpers private ParseException error(String message) { return new ParseException(new Error(current.line, current.character, current.ch, message)); } private TokenData consume(Token token, String message) { if (check(token)) return advance(); throw error(message); } private boolean match(Token... tokens) { for (Token token : tokens) { if (check(token)) { advance(); return true; } } return false; } private boolean check(Token token) { if (isAtEnd()) return false; return current.token == token; } private TokenData advance() { previous.set(current); lexer.next(); current.set(lexer.token, lexer.lexeme, lexer.line, lexer.character, lexer.ch); if (current.token == Token.Error) { throw error(current.lexeme); } return previous; } private boolean isAtEnd() { return current.token == Token.EOF; } // Token data private static class TokenData { public Token token; public String lexeme; public int line, character; public char ch; public void set(Token token, String lexeme, int line, int character, char ch) { this.token = token; this.lexeme = lexeme; this.line = line; this.character = character; this.ch = ch; } public void set(TokenData data) { set(data.token, data.lexeme, data.line, data.character, data.ch); } @Override public String toString() { return String.format("%s '%s'", token, lexeme); } } // Parse Exception public static class ParseException extends RuntimeException { public final Error error; public ParseException(Error error) { super(error.toString()); this.error = error; } } }