java-commons/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Parser.java

286 lines
7.6 KiB
Java
Raw Normal View History

package io.gitlab.jfronny.muscript.compiler;
import io.gitlab.jfronny.muscript.compiler.expr.*;
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 Conditional(expr.asBoolExpr(), trueExpr, falseExpr);
}
return expr;
}
private Expr<?> and() {
Expr<?> expr = or();
while (match(Token.And)) {
Expr<?> right = or();
expr = new LogicBiExpr(expr.asBoolExpr(), right.asBoolExpr(), Token.And);
}
return expr;
}
private Expr<?> or() {
Expr<?> expr = equality();
while (match(Token.Or)) {
Expr<?> right = equality();
expr = new LogicBiExpr(expr.asBoolExpr(), right.asBoolExpr(), Token.Or);
}
return expr;
}
private Expr<?> equality() {
Expr<?> expr = concat();
while (match(Token.EqualEqual, Token.BangEqual)) {
Token op = previous.token;
Expr<?> right = concat();
Expr.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)) {
Token op = previous.token;
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;
Expr<?> right = term();
expr = new Compare(expr.asNumberExpr(), right.asNumberExpr(), op);
}
return expr;
}
private Expr<?> term() {
Expr<?> expr = factor();
while (match(Token.Plus, Token.Minus)) {
Token op = previous.token;
Expr<?> right = factor();
expr = new MathBiExpr(expr.asNumberExpr(), right.asNumberExpr(), op);
}
return expr;
}
private Expr<?> factor() {
Expr<?> expr = exp();
while (match(Token.Star, Token.Slash, Token.Percentage, Token.UpArrow)) {
Token op = previous.token;
Expr<?> right = exp();
expr = new MathBiExpr(expr.asNumberExpr(), right.asNumberExpr(), op);
}
return expr;
}
private Expr<?> exp() {
Expr<?> expr = unary();
while (match(Token.UpArrow)) {
Token op = previous.token;
Expr<?> right = unary();
expr = new MathBiExpr(expr.asNumberExpr(), right.asNumberExpr(), op);
}
return expr;
}
private Expr<?> unary() {
if (match(Token.Bang, Token.Minus)) {
Token op = previous.token;
Expr<?> right = unary();
return op == Token.Bang ? new Not(right.asBoolExpr()) : new Invert(right.asNumberExpr());
}
return call();
}
private Expr<?> call() {
Expr<?> expr = primary();
while (true) {
if (match(Token.LeftParen)) {
expr = finishCall(expr);
}
else if (match(Token.Dot)) {
TokenData name = consume(Token.Identifier, "Expected field name after '.'.");
expr = new Get(expr.asDynamicExpr(), Expr.literal(name.lexeme));
}
else if (match(Token.LeftBracket)) {
expr = new Get(expr.asDynamicExpr(), expression());
consume(Token.RightBracket, "Expected closing bracket");
}
else {
break;
}
}
return expr;
}
private Expr<?> finishCall(Expr<?> callee) {
List<Expr.DynamicExpr> 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 new Group(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;
}
}
}