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