2022-06-03 19:54:31 +02:00
|
|
|
package io.gitlab.jfronny.muscript.compiler;
|
|
|
|
|
2023-01-20 18:52:57 +01:00
|
|
|
import io.gitlab.jfronny.muscript.ast.*;
|
|
|
|
import io.gitlab.jfronny.muscript.ast.bool.*;
|
|
|
|
import io.gitlab.jfronny.muscript.ast.compare.*;
|
|
|
|
import io.gitlab.jfronny.muscript.ast.conditional.UnresolvedConditional;
|
|
|
|
import io.gitlab.jfronny.muscript.ast.dynamic.*;
|
|
|
|
import io.gitlab.jfronny.muscript.ast.math.*;
|
|
|
|
import io.gitlab.jfronny.muscript.ast.string.Concatenate;
|
2023-01-20 21:05:04 +01:00
|
|
|
import io.gitlab.jfronny.muscript.data.Script;
|
2022-06-13 13:31:54 +02:00
|
|
|
import io.gitlab.jfronny.muscript.error.*;
|
2022-06-03 19:54:31 +02:00
|
|
|
|
2023-01-20 21:05:04 +01:00
|
|
|
import java.util.*;
|
2022-06-03 19:54:31 +02:00
|
|
|
|
|
|
|
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) {
|
2022-06-29 17:38:05 +02:00
|
|
|
return new Parser(new Lexer(source)).parse().optimize();
|
2022-06-03 19:54:31 +02:00
|
|
|
}
|
|
|
|
|
2023-01-20 21:05:04 +01:00
|
|
|
public static Script parseScript(String source) {
|
|
|
|
return new Parser(new Lexer(source)).parseScript().optimize();
|
|
|
|
}
|
|
|
|
|
2022-06-03 19:54:31 +02:00
|
|
|
public Parser(Lexer lexer) {
|
|
|
|
this.lexer = lexer;
|
|
|
|
}
|
|
|
|
|
2023-01-20 18:52:57 +01:00
|
|
|
/**
|
|
|
|
* Generate a single expression.
|
|
|
|
* Must exhaust the source!
|
|
|
|
*
|
|
|
|
* @return the resulting expression
|
|
|
|
*/
|
2022-06-03 19:54:31 +02:00
|
|
|
public Expr<?> parse() {
|
|
|
|
advance();
|
2023-01-20 17:47:41 +01:00
|
|
|
Expr<?> expr = expression();
|
2023-01-20 21:05:04 +01:00
|
|
|
if (!isAtEnd())
|
2023-01-20 17:47:41 +01:00
|
|
|
throw new ParseException(LocationalError.create(lexer.source, lexer.start, lexer.current - 1, "Unexpected element after end of expression"));
|
|
|
|
return expr;
|
2022-06-03 19:54:31 +02:00
|
|
|
}
|
|
|
|
|
2023-01-20 21:05:04 +01:00
|
|
|
/**
|
|
|
|
* Generate a script instance.
|
|
|
|
* Multiple instructions will be executed in sequence and the result of the last one will be returned.
|
|
|
|
*
|
|
|
|
* @return the resulting expression
|
|
|
|
*/
|
|
|
|
public Script parseScript() {
|
|
|
|
advance();
|
|
|
|
List<Expr<?>> expressions = new LinkedList<>();
|
|
|
|
while (!isAtEnd()) {
|
|
|
|
expressions.add(expression());
|
2023-01-20 21:09:30 +01:00
|
|
|
match(Token.Semicolon); // Consume semicolon if present
|
2023-01-20 21:05:04 +01:00
|
|
|
}
|
|
|
|
if (expressions.isEmpty()) throw new ParseException(LocationalError.create(lexer.source, lexer.start, lexer.current - 1, "Missing any elements in closure"));
|
|
|
|
return new Script(expressions);
|
|
|
|
}
|
|
|
|
|
2022-06-03 19:54:31 +02:00
|
|
|
// Expressions
|
|
|
|
private Expr<?> expression() {
|
|
|
|
try {
|
2023-01-24 12:29:47 +01:00
|
|
|
return conditional();
|
2022-06-03 19:54:31 +02:00
|
|
|
} catch (RuntimeException e) {
|
|
|
|
if (e instanceof ParseException) throw e;
|
2022-06-13 13:31:54 +02:00
|
|
|
else if (e instanceof LocationalException le) {
|
|
|
|
throw new ParseException(le.asPrintable(lexer.source));
|
2022-11-24 19:05:51 +01:00
|
|
|
} else throw error(e.getMessage());
|
2022-06-03 19:54:31 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private Expr<?> conditional() {
|
|
|
|
Expr<?> expr = and();
|
|
|
|
|
|
|
|
if (match(Token.QuestionMark)) {
|
2023-01-20 17:47:41 +01:00
|
|
|
int start = previous.start;
|
|
|
|
int end = previous.current - 1;
|
2022-06-03 19:54:31 +02:00
|
|
|
Expr<?> trueExpr = expression();
|
|
|
|
consume(Token.Colon, "Expected ':' after first part of condition.");
|
|
|
|
Expr<?> falseExpr = expression();
|
2023-01-20 17:47:41 +01:00
|
|
|
expr = new UnresolvedConditional(start, end, asBool(expr), trueExpr, falseExpr);
|
2022-06-03 19:54:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return expr;
|
|
|
|
}
|
|
|
|
|
|
|
|
private Expr<?> and() {
|
|
|
|
Expr<?> expr = or();
|
|
|
|
|
|
|
|
while (match(Token.And)) {
|
2023-01-20 17:47:41 +01:00
|
|
|
int start = previous.start;
|
|
|
|
int end = previous.current - 1;
|
2022-06-03 19:54:31 +02:00
|
|
|
Expr<?> right = or();
|
2023-01-20 17:47:41 +01:00
|
|
|
expr = new And(start, end, asBool(expr), asBool(right));
|
2022-06-03 19:54:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return expr;
|
|
|
|
}
|
|
|
|
|
|
|
|
private Expr<?> or() {
|
|
|
|
Expr<?> expr = equality();
|
|
|
|
|
|
|
|
while (match(Token.Or)) {
|
2023-01-20 17:47:41 +01:00
|
|
|
int start = previous.start;
|
|
|
|
int end = previous.current - 1;
|
2022-06-03 19:54:31 +02:00
|
|
|
Expr<?> right = equality();
|
2023-01-20 17:47:41 +01:00
|
|
|
expr = new Or(start, end, asBool(expr), asBool(right));
|
2022-06-03 19:54:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return expr;
|
|
|
|
}
|
|
|
|
|
|
|
|
private Expr<?> equality() {
|
|
|
|
Expr<?> expr = concat();
|
|
|
|
|
|
|
|
while (match(Token.EqualEqual, Token.BangEqual)) {
|
|
|
|
Token op = previous.token;
|
2023-01-20 17:47:41 +01:00
|
|
|
int start = previous.start;
|
|
|
|
int end = previous.current - 1;
|
2022-06-03 19:54:31 +02:00
|
|
|
Expr<?> right = concat();
|
2023-01-20 17:47:41 +01:00
|
|
|
BoolExpr e = new Equal(start, end, expr, right);
|
|
|
|
if (op == Token.BangEqual) e = new Not(start, end, e);
|
2022-06-03 19:54:31 +02:00
|
|
|
expr = e;
|
|
|
|
}
|
|
|
|
|
|
|
|
return expr;
|
|
|
|
}
|
|
|
|
|
|
|
|
private Expr<?> concat() {
|
|
|
|
Expr<?> expr = comparison();
|
|
|
|
|
|
|
|
while (match(Token.Concat)) {
|
2023-01-20 17:47:41 +01:00
|
|
|
int start = previous.start;
|
|
|
|
int end = previous.current - 1;
|
2022-06-03 19:54:31 +02:00
|
|
|
Expr<?> right = comparison();
|
2023-01-20 17:47:41 +01:00
|
|
|
expr = new Concatenate(start, end, asString(expr), asString(right));
|
2022-06-03 19:54:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return expr;
|
|
|
|
}
|
|
|
|
|
|
|
|
private Expr<?> comparison() {
|
|
|
|
Expr<?> expr = term();
|
|
|
|
|
|
|
|
while (match(Token.Greater, Token.GreaterEqual, Token.Less, Token.LessEqual)) {
|
|
|
|
Token op = previous.token;
|
2023-01-20 17:47:41 +01:00
|
|
|
int start = previous.start;
|
|
|
|
int end = previous.current - 1;
|
2022-06-13 13:31:54 +02:00
|
|
|
NumberExpr right = asNumber(term());
|
2022-06-07 13:36:48 +02:00
|
|
|
expr = switch (op) {
|
2023-01-20 17:47:41 +01:00
|
|
|
case Greater -> new Greater(start, end, asNumber(expr), right);
|
|
|
|
case GreaterEqual -> new Not(start, end, new Less(start, end, asNumber(expr), right));
|
|
|
|
case Less -> new Less(start, end, asNumber(expr), right);
|
|
|
|
case LessEqual -> new Not(start, end, new Greater(start, end, asNumber(expr), right));
|
2022-06-07 13:36:48 +02:00
|
|
|
default -> throw new IllegalStateException();
|
|
|
|
};
|
2022-06-03 19:54:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return expr;
|
|
|
|
}
|
|
|
|
|
|
|
|
private Expr<?> term() {
|
|
|
|
Expr<?> expr = factor();
|
|
|
|
|
|
|
|
while (match(Token.Plus, Token.Minus)) {
|
|
|
|
Token op = previous.token;
|
2023-01-20 17:47:41 +01:00
|
|
|
int start = previous.start;
|
|
|
|
int end = previous.current - 1;
|
2022-06-13 13:31:54 +02:00
|
|
|
NumberExpr right = asNumber(factor());
|
2022-06-07 13:36:48 +02:00
|
|
|
expr = switch (op) {
|
2023-01-20 17:47:41 +01:00
|
|
|
case Plus -> new Plus(start, end, asNumber(expr), right);
|
|
|
|
case Minus -> new Minus(start, end, asNumber(expr), right);
|
2022-06-07 13:36:48 +02:00
|
|
|
default -> throw new IllegalStateException();
|
|
|
|
};
|
2022-06-03 19:54:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return expr;
|
|
|
|
}
|
|
|
|
|
|
|
|
private Expr<?> factor() {
|
|
|
|
Expr<?> expr = exp();
|
|
|
|
|
2022-06-07 13:36:48 +02:00
|
|
|
while (match(Token.Star, Token.Slash, Token.Percentage)) {
|
2022-06-03 19:54:31 +02:00
|
|
|
Token op = previous.token;
|
2023-01-20 17:47:41 +01:00
|
|
|
int start = previous.start;
|
|
|
|
int end = previous.current - 1;
|
2022-06-13 13:31:54 +02:00
|
|
|
NumberExpr right = asNumber(exp());
|
2022-06-07 13:36:48 +02:00
|
|
|
expr = switch (op) {
|
2023-01-20 17:47:41 +01:00
|
|
|
case Star -> new Multiply(start, end, asNumber(expr), right);
|
|
|
|
case Slash -> new Divide(start, end, asNumber(expr), right);
|
|
|
|
case Percentage -> new Modulo(start, end, asNumber(expr), right);
|
2022-06-07 13:36:48 +02:00
|
|
|
default -> throw new IllegalStateException();
|
|
|
|
};
|
2022-06-03 19:54:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return expr;
|
|
|
|
}
|
|
|
|
|
|
|
|
private Expr<?> exp() {
|
|
|
|
Expr<?> expr = unary();
|
|
|
|
|
|
|
|
while (match(Token.UpArrow)) {
|
2023-01-20 17:47:41 +01:00
|
|
|
int start = previous.start;
|
|
|
|
int end = previous.current - 1;
|
2022-06-13 13:31:54 +02:00
|
|
|
NumberExpr right = asNumber(unary());
|
2023-01-20 17:47:41 +01:00
|
|
|
expr = new Power(start, end, asNumber(expr), right);
|
2022-06-03 19:54:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return expr;
|
|
|
|
}
|
|
|
|
|
|
|
|
private Expr<?> unary() {
|
|
|
|
if (match(Token.Bang, Token.Minus)) {
|
|
|
|
Token op = previous.token;
|
2023-01-20 17:47:41 +01:00
|
|
|
int start = previous.start;
|
|
|
|
int end = previous.current - 1;
|
2022-06-03 19:54:31 +02:00
|
|
|
Expr<?> right = unary();
|
2022-06-07 13:36:48 +02:00
|
|
|
return switch (op) {
|
2023-01-20 17:47:41 +01:00
|
|
|
case Bang -> new Not(start, end, asBool(right));
|
|
|
|
case Minus -> new Invert(start, end, asNumber(right));
|
2022-06-07 13:36:48 +02:00
|
|
|
default -> throw new IllegalStateException();
|
|
|
|
};
|
2022-06-03 19:54:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return call();
|
|
|
|
}
|
|
|
|
|
|
|
|
private Expr<?> call() {
|
|
|
|
Expr<?> expr = primary();
|
|
|
|
|
2022-06-07 13:36:48 +02:00
|
|
|
while (match(Token.LeftParen, Token.Dot, Token.LeftBracket)) {
|
2023-01-20 17:47:41 +01:00
|
|
|
int start = previous.start;
|
|
|
|
int end = previous.current - 1;
|
2022-06-07 13:36:48 +02:00
|
|
|
expr = switch (previous.token) {
|
2023-01-20 17:47:41 +01:00
|
|
|
case LeftParen -> finishCall(start, end, expr);
|
2022-06-07 13:36:48 +02:00
|
|
|
case Dot -> {
|
|
|
|
TokenData name = consume(Token.Identifier, "Expected field name after '.'.");
|
2023-01-20 17:47:41 +01:00
|
|
|
yield new Get(start, end, asDynamic(expr), Expr.literal(name.start, name.current - 1, name.lexeme));
|
2022-06-07 13:36:48 +02:00
|
|
|
}
|
|
|
|
case LeftBracket -> {
|
2023-01-20 17:47:41 +01:00
|
|
|
expr = new Get(start, end, asDynamic(expr), expression());
|
2022-06-07 13:36:48 +02:00
|
|
|
consume(Token.RightBracket, "Expected closing bracket");
|
|
|
|
yield expr;
|
|
|
|
}
|
|
|
|
default -> throw new IllegalStateException();
|
|
|
|
};
|
2022-06-03 19:54:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return expr;
|
|
|
|
}
|
|
|
|
|
2023-01-20 17:47:41 +01:00
|
|
|
private Expr<?> finishCall(int start, int end, Expr<?> callee) {
|
2022-06-07 13:36:48 +02:00
|
|
|
List<DynamicExpr> args = new ArrayList<>(2);
|
2022-06-03 19:54:31 +02:00
|
|
|
|
|
|
|
if (!check(Token.RightParen)) {
|
|
|
|
do {
|
2022-06-13 13:31:54 +02:00
|
|
|
args.add(asDynamic(expression()));
|
2022-06-03 19:54:31 +02:00
|
|
|
} while (match(Token.Comma));
|
|
|
|
}
|
|
|
|
|
|
|
|
consume(Token.RightParen, "Expected ')' after function arguments.");
|
2023-01-20 17:47:41 +01:00
|
|
|
return new Call(start, end, asDynamic(callee), args);
|
2022-06-03 19:54:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private Expr<?> primary() {
|
2023-01-20 17:47:41 +01:00
|
|
|
if (match(Token.Null)) return Expr.literalNull(previous.start, previous.current - 1);
|
|
|
|
if (match(Token.String)) return Expr.literal(previous.start, previous.current - 1, previous.lexeme);
|
|
|
|
if (match(Token.True, Token.False)) return Expr.literal(previous.start, previous.current - 1, previous.lexeme.equals("true"));
|
|
|
|
if (match(Token.Number)) return Expr.literal(previous.start, previous.current - 1, Double.parseDouble(previous.lexeme));
|
2023-01-24 12:29:47 +01:00
|
|
|
if (match(Token.Identifier)) {
|
|
|
|
int start = previous.start;
|
|
|
|
int end = previous.current - 1;
|
|
|
|
String name = previous.lexeme;
|
|
|
|
if (match(Token.Assign)) return new Assign(start, end, name, expression().asDynamicExpr());
|
|
|
|
else return new Variable(start, end, name);
|
|
|
|
}
|
2022-06-03 19:54:31 +02:00
|
|
|
|
|
|
|
if (match(Token.LeftParen)) {
|
|
|
|
Expr<?> expr = expression();
|
|
|
|
consume(Token.RightParen, "Expected ')' after expression.");
|
2022-06-07 13:36:48 +02:00
|
|
|
return expr;
|
2022-06-03 19:54:31 +02:00
|
|
|
}
|
|
|
|
|
2023-01-20 21:05:04 +01:00
|
|
|
if (match(Token.LeftBrace)) {
|
|
|
|
int start = previous.start;
|
|
|
|
List<String> boundArgs = new LinkedList<>();
|
|
|
|
boolean first = true;
|
|
|
|
while (!match(Token.Arrow)) {
|
|
|
|
if (!first) consume(Token.Comma, "Closure parameters MUST be comma-seperated");
|
|
|
|
first = false;
|
|
|
|
consume(Token.Identifier, "Closure arguments MUST be identifiers");
|
|
|
|
boundArgs.add(previous.lexeme);
|
|
|
|
}
|
|
|
|
List<Expr<?>> expressions = new LinkedList<>();
|
|
|
|
while (!match(Token.RightBrace)) {
|
|
|
|
expressions.add(expression());
|
2023-01-20 21:09:30 +01:00
|
|
|
match(Token.Semicolon); // Consume semicolon if present
|
2023-01-20 21:05:04 +01:00
|
|
|
}
|
|
|
|
int end = previous.start;
|
|
|
|
return new Closure(start, end, boundArgs, expressions);
|
|
|
|
}
|
|
|
|
|
2022-06-03 19:54:31 +02:00
|
|
|
throw error("Expected expression.");
|
|
|
|
}
|
|
|
|
|
2022-06-13 13:31:54 +02:00
|
|
|
// Type conversion
|
|
|
|
private BoolExpr asBool(Expr<?> expression) {
|
|
|
|
try {
|
|
|
|
return expression.asBoolExpr();
|
|
|
|
} catch (TypeMismatchException e) {
|
2023-01-20 17:47:41 +01:00
|
|
|
throw error(e.getMessage(), expression);
|
2022-06-13 13:31:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private NumberExpr asNumber(Expr<?> expression) {
|
|
|
|
try {
|
|
|
|
return expression.asNumberExpr();
|
|
|
|
} catch (TypeMismatchException e) {
|
2023-01-20 17:47:41 +01:00
|
|
|
throw error(e.getMessage(), expression);
|
2022-06-13 13:31:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private StringExpr asString(Expr<?> expression) {
|
|
|
|
try {
|
|
|
|
return expression.asStringExpr();
|
|
|
|
} catch (TypeMismatchException e) {
|
2023-01-20 17:47:41 +01:00
|
|
|
throw error(e.getMessage(), expression);
|
2022-06-13 13:31:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private DynamicExpr asDynamic(Expr<?> expression) {
|
|
|
|
try {
|
|
|
|
return expression.asDynamicExpr();
|
|
|
|
} catch (TypeMismatchException e) {
|
2023-01-20 17:47:41 +01:00
|
|
|
throw error(e.getMessage(), expression);
|
2022-06-13 13:31:54 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-03 19:54:31 +02:00
|
|
|
// Helpers
|
|
|
|
private ParseException error(String message) {
|
2022-06-13 13:31:54 +02:00
|
|
|
return new ParseException(LocationalError.create(lexer.source, current.current - 1, message));
|
2022-06-03 19:54:31 +02:00
|
|
|
}
|
|
|
|
|
2023-01-20 17:47:41 +01:00
|
|
|
private ParseException error(String message, Expr<?> expr) {
|
|
|
|
return new ParseException(LocationalError.create(lexer.source, expr.chStart, expr.chEnd, message));
|
|
|
|
}
|
|
|
|
|
2022-06-03 19:54:31 +02:00
|
|
|
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();
|
2022-06-13 10:44:59 +02:00
|
|
|
current.set(lexer.token, lexer.lexeme, lexer.start, lexer.current, lexer.ch);
|
2022-06-03 19:54:31 +02:00
|
|
|
|
|
|
|
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;
|
2022-06-13 10:44:59 +02:00
|
|
|
public int start, current;
|
2022-06-03 19:54:31 +02:00
|
|
|
public char ch;
|
|
|
|
|
2022-06-13 10:44:59 +02:00
|
|
|
public void set(Token token, String lexeme, int start, int current, char ch) {
|
2022-06-03 19:54:31 +02:00
|
|
|
this.token = token;
|
|
|
|
this.lexeme = lexeme;
|
2022-06-13 10:44:59 +02:00
|
|
|
this.start = start;
|
|
|
|
this.current = current;
|
2022-06-03 19:54:31 +02:00
|
|
|
this.ch = ch;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void set(TokenData data) {
|
2022-06-13 10:44:59 +02:00
|
|
|
set(data.token, data.lexeme, data.start, data.current, data.ch);
|
2022-06-03 19:54:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String toString() {
|
|
|
|
return String.format("%s '%s'", token, lexeme);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse Exception
|
|
|
|
public static class ParseException extends RuntimeException {
|
2022-06-13 13:31:54 +02:00
|
|
|
public final LocationalError error;
|
2022-06-03 19:54:31 +02:00
|
|
|
|
2022-06-13 13:31:54 +02:00
|
|
|
public ParseException(LocationalError error) {
|
2022-06-03 19:54:31 +02:00
|
|
|
super(error.toString());
|
|
|
|
this.error = error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|