[muscript] Even better errors

This commit is contained in:
Johannes Frohnmeyer 2022-06-13 13:31:54 +02:00
parent a9cd45c39d
commit 8bba3a33d5
Signed by: Johannes
GPG Key ID: E76429612C2929F4
47 changed files with 477 additions and 189 deletions

View File

@ -1,9 +1,9 @@
# μScript
MuScript was created to allow respackopts pack authors to specify conditions for loading resources
μScript was created to allow respackopts pack authors to specify conditions for loading resources
in a more human-friendly manner than the previous json tree-based system.
It is intended to be vaguely like java in its syntax, though it deviates in some aspects.
The language is parsed into an AST representation which is executed in-place, no compilation is performed.
MuScript supports outputting data using various types, not just strings or booleans
μScript supports outputting data using various types, not just strings or booleans
## Value types
This DSL supports numbers (double), booleans, strings, objects, lists and functions.
@ -52,4 +52,48 @@ Objects support the following operators (x is an object with an entry called `en
Parentheses (`()`) may be used to indicate order of operation
Namespacing is done using double colons like in c++ (`namespace::value`), though it should not be used
Namespacing is done using double colons like in c++ (`namespace::value`), though it should not be used
## Embedding μScript
μScript is available as a [maven package](https://gitlab.com/JFronny/java-commons/-/packages) which you can add to your project.
To use it, first parse a script via `Parser.parse(String script)` and convert the returned generic expression to a typed one
by calling `as(Bool|String|Number|Dynamic)Expr`.
This process may throw a ParseException.
You can call `get(Dynamic<?> dataRoot)` on the result to execute the script on the provided data, which should be an
`ExpressionParameter` on which you called `StandardLib.addTo()` to add standard methods.
This is also where you can add custom data to be accessed by your script.
The execution of a script can throw a LocationalException which may be converted to a LocationalError for printing
using the source of the expression if available.
You may also call `StarScriptIngester.starScriptToMu()` to generate μScript code from StarScript code.
A full example could look as follows:
```java
String source = args[0];
Expr<?> parsed;
try {
parsed = Parser.parse(source); // or Parser.parse(StarScriptIngester.starScriptToMu(source))
} catch (Parser.ParseException e) { // Could not parse
System.err.println(e.error);
return;
}
BoolExpr typed;
try {
typed = parsed.asBoolExpr();
} catch (LocationalException e) {
System.err.println(e.asPrintable(source));
return;
}
ExpressionParameter parameter = StandardLib.addTo(new ExpressionParameter())
.set("someValue", DFinal.of(15))
.set("someOther", DFinal.of(Map.of(
"subValue", DFinal.of(true)
)));
boolean result;
try {
result = typed.get(parameter);
} catch (LocationalException e) {
System.err.println(e.asPrintable(source));
return;
}
System.out.println("Result: " + result);
```

View File

@ -8,7 +8,9 @@ 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 io.gitlab.jfronny.muscript.error.*;
import java.lang.*;
import java.util.*;
public class Parser {
@ -37,6 +39,9 @@ public class Parser {
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());
}
}
@ -45,10 +50,11 @@ public class Parser {
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(expr.asBoolExpr(), trueExpr, falseExpr);
expr = new UnresolvedConditional(character, asBool(expr), trueExpr, falseExpr);
}
return expr;
@ -58,8 +64,9 @@ public class Parser {
Expr<?> expr = or();
while (match(Token.And)) {
int character = previous.start;
Expr<?> right = or();
expr = new And(expr.asBoolExpr(), right.asBoolExpr());
expr = new And(character, asBool(expr), asBool(right));
}
return expr;
@ -69,8 +76,9 @@ public class Parser {
Expr<?> expr = equality();
while (match(Token.Or)) {
int character = previous.start;
Expr<?> right = equality();
expr = new Or(expr.asBoolExpr(), right.asBoolExpr());
expr = new Or(character, asBool(expr), asBool(right));
}
return expr;
@ -81,9 +89,10 @@ public class Parser {
while (match(Token.EqualEqual, Token.BangEqual)) {
Token op = previous.token;
int character = previous.start;
Expr<?> right = concat();
BoolExpr e = new Equal(expr, right);
if (op == Token.BangEqual) e = new Not(e);
BoolExpr e = new Equal(character, expr, right);
if (op == Token.BangEqual) e = new Not(character, e);
expr = e;
}
@ -94,8 +103,9 @@ public class Parser {
Expr<?> expr = comparison();
while (match(Token.Concat)) {
int character = previous.start;
Expr<?> right = comparison();
expr = new Concatenate(expr.asStringExpr(), right.asStringExpr());
expr = new Concatenate(character, asString(expr), asString(right));
}
return expr;
@ -106,12 +116,13 @@ public class Parser {
while (match(Token.Greater, Token.GreaterEqual, Token.Less, Token.LessEqual)) {
Token op = previous.token;
NumberExpr right = term().asNumberExpr();
int character = previous.start;
NumberExpr right = asNumber(term());
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));
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();
};
}
@ -124,10 +135,11 @@ public class Parser {
while (match(Token.Plus, Token.Minus)) {
Token op = previous.token;
NumberExpr right = factor().asNumberExpr();
int character = previous.start;
NumberExpr right = asNumber(factor());
expr = switch (op) {
case Plus -> new Plus(expr.asNumberExpr(), right);
case Minus -> new Minus(expr.asNumberExpr(), right);
case Plus -> new Plus(character, asNumber(expr), right);
case Minus -> new Minus(character, asNumber(expr), right);
default -> throw new IllegalStateException();
};
}
@ -140,11 +152,12 @@ public class Parser {
while (match(Token.Star, Token.Slash, Token.Percentage)) {
Token op = previous.token;
NumberExpr right = exp().asNumberExpr();
int character = previous.start;
NumberExpr right = asNumber(exp());
expr = switch (op) {
case Star -> new Multiply(expr.asNumberExpr(), right);
case Slash -> new Divide(expr.asNumberExpr(), right);
case Percentage -> new Modulo(expr.asNumberExpr(), right);
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();
};
}
@ -156,8 +169,9 @@ public class Parser {
Expr<?> expr = unary();
while (match(Token.UpArrow)) {
NumberExpr right = unary().asNumberExpr();
expr = new Power(expr.asNumberExpr(), right);
int character = previous.start;
NumberExpr right = asNumber(unary());
expr = new Power(character, asNumber(expr), right);
}
return expr;
@ -166,10 +180,11 @@ public class Parser {
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(right.asBoolExpr());
case Minus -> new Invert(right.asNumberExpr());
case Bang -> new Not(character, asBool(right));
case Minus -> new Invert(character, asNumber(right));
default -> throw new IllegalStateException();
};
}
@ -181,14 +196,15 @@ public class Parser {
Expr<?> expr = primary();
while (match(Token.LeftParen, Token.Dot, Token.LeftBracket)) {
int character = previous.start;
expr = switch (previous.token) {
case LeftParen -> finishCall(expr);
case LeftParen -> finishCall(character, expr);
case Dot -> {
TokenData name = consume(Token.Identifier, "Expected field name after '.'.");
yield new Get(expr.asDynamicExpr(), Expr.literal(name.lexeme));
yield new Get(character, asDynamic(expr), Expr.literal(name.start, name.lexeme));
}
case LeftBracket -> {
expr = new Get(expr.asDynamicExpr(), expression());
expr = new Get(character, asDynamic(expr), expression());
consume(Token.RightBracket, "Expected closing bracket");
yield expr;
}
@ -199,25 +215,25 @@ public class Parser {
return expr;
}
private Expr<?> finishCall(Expr<?> callee) {
private Expr<?> finishCall(int character, Expr<?> callee) {
List<DynamicExpr> args = new ArrayList<>(2);
if (!check(Token.RightParen)) {
do {
args.add(expression().asDynamicExpr());
args.add(asDynamic(expression()));
} while (match(Token.Comma));
}
consume(Token.RightParen, "Expected ')' after function arguments.");
return new Call(callee.asDynamicExpr(), args);
return new Call(character, asDynamic(callee), 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.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();
@ -228,9 +244,42 @@ public class Parser {
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(Error.create(lexer.source, current.current - 1, message));
return new ParseException(LocationalError.create(lexer.source, current.current - 1, message));
}
private TokenData consume(Token token, String message) {
@ -298,9 +347,9 @@ public class Parser {
// Parse Exception
public static class ParseException extends RuntimeException {
public final Error error;
public final LocationalError error;
public ParseException(Error error) {
public ParseException(LocationalError error) {
super(error.toString());
this.error = error;
}

View File

@ -4,6 +4,10 @@ import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.compiler.expr.dynamic.*;
public abstract non-sealed class BoolExpr extends Expr<Boolean> {
public BoolExpr(int character) {
super(character);
}
@Override
public Type getResultType() {
return Type.Boolean;
@ -11,6 +15,6 @@ public abstract non-sealed class BoolExpr extends Expr<Boolean> {
@Override
public DynamicExpr asDynamicExpr() {
return new DynamicCoerce(this);
return new DynamicCoerce(character, this);
}
}

View File

@ -5,6 +5,10 @@ import io.gitlab.jfronny.muscript.compiler.expr.dynamic.unpack.*;
import io.gitlab.jfronny.muscript.dynamic.*;
public abstract non-sealed class DynamicExpr extends Expr<Dynamic<?>> {
public DynamicExpr(int character) {
super(character);
}
@Override
public Type getResultType() {
return Type.Dynamic;
@ -12,17 +16,17 @@ public abstract non-sealed class DynamicExpr extends Expr<Dynamic<?>> {
@Override
public BoolExpr asBoolExpr() {
return new BoolUnpack(this);
return new BoolUnpack(character, this);
}
@Override
public StringExpr asStringExpr() {
return new StringUnpack(this);
return new StringUnpack(character, this);
}
@Override
public NumberExpr asNumberExpr() {
return new NumberUnpack(this);
return new NumberUnpack(character, this);
}
@Override

View File

@ -1,31 +1,35 @@
package io.gitlab.jfronny.muscript.compiler.expr;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.compiler.expr.annotations.*;
import io.gitlab.jfronny.muscript.compiler.expr.common.literal.*;
import io.gitlab.jfronny.muscript.compiler.expr.string.*;
import io.gitlab.jfronny.muscript.dynamic.*;
import io.gitlab.jfronny.muscript.error.*;
@CanThrow
public sealed abstract class Expr<T> permits BoolExpr, DynamicExpr, NullLiteral, NumberExpr, StringExpr {
public abstract Type getResultType();
public abstract T get(Dynamic<?> branch, Dynamic<?> dataRoot);
public T get(Dynamic<?> branch) {
return get(branch, branch);
protected Expr(int character) {
this.character = character;
}
public abstract Type getResultType();
public abstract T get(Dynamic<?> dataRoot);
public final int character;
public BoolExpr asBoolExpr() {
if (this instanceof BoolExpr e) return e;
throw new IllegalArgumentException("Expected boolean but is " + getResultType());
throw new TypeMismatchException(character, Type.Boolean, getResultType());
}
public StringExpr asStringExpr() {
if (this instanceof StringExpr e) return e;
return new StringCoerce(this);
return new StringCoerce(character, this);
}
public NumberExpr asNumberExpr() {
if (this instanceof NumberExpr e) return e;
throw new IllegalArgumentException("Expected number but is " + getResultType());
throw new TypeMismatchException(character, Type.Number, getResultType());
}
public abstract DynamicExpr asDynamicExpr();
@ -34,19 +38,19 @@ public sealed abstract class Expr<T> permits BoolExpr, DynamicExpr, NullLiteral,
return this instanceof NullLiteral;
}
public static BoolExpr literal(boolean bool) {
return new BoolLiteral(bool);
public static BoolExpr literal(int character, boolean bool) {
return new BoolLiteral(character, bool);
}
public static StringExpr literal(String string) {
return new StringLiteral(string);
public static StringExpr literal(int character, String string) {
return new StringLiteral(character, string);
}
public static NumberExpr literal(double number) {
return new NumberLiteral(number);
public static NumberExpr literal(int character, double number) {
return new NumberLiteral(character, number);
}
public static Expr<?> literalNull() {
return new NullLiteral();
public static NullLiteral literalNull(int character) {
return new NullLiteral(character);
}
}

View File

@ -1,22 +1,44 @@
package io.gitlab.jfronny.muscript.compiler.expr;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.compiler.expr.annotations.*;
import io.gitlab.jfronny.muscript.compiler.expr.common.literal.*;
import io.gitlab.jfronny.muscript.dynamic.*;
import io.gitlab.jfronny.muscript.error.*;
@CanThrow
public final class NullLiteral extends Expr<Object> {
public NullLiteral(int character) {
super(character);
}
@Override
public Type getResultType() {
return Type.Dynamic;
}
@Override
public Object get(Dynamic<?> branch, Dynamic<?> dataRoot) {
public Object get(Dynamic<?> dataRoot) {
return null;
}
@Override
public DynamicExpr asDynamicExpr() {
return new DynamicLiteral<>(new DNull());
return new DynamicLiteral<>(character, new DNull());
}
@Override
public NumberExpr asNumberExpr() {
throw new LocationalException(character, "Attempted to convert null to a number");
}
@Override
public StringExpr asStringExpr() {
return Expr.literal(character, "null");
}
@Override
public BoolExpr asBoolExpr() {
throw new LocationalException(character, "Attempted to convert null to a boolean");
}
}

View File

@ -4,6 +4,10 @@ import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.compiler.expr.dynamic.*;
public abstract non-sealed class NumberExpr extends Expr<Double> {
public NumberExpr(int character) {
super(character);
}
@Override
public Type getResultType() {
return Type.Number;
@ -11,6 +15,6 @@ public abstract non-sealed class NumberExpr extends Expr<Double> {
@Override
public DynamicExpr asDynamicExpr() {
return new DynamicCoerce(this);
return new DynamicCoerce(character, this);
}
}

View File

@ -4,6 +4,10 @@ import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.compiler.expr.dynamic.*;
public abstract non-sealed class StringExpr extends Expr<String> {
public StringExpr(int character) {
super(character);
}
@Override
public Type getResultType() {
return Type.String;
@ -11,6 +15,6 @@ public abstract non-sealed class StringExpr extends Expr<String> {
@Override
public DynamicExpr asDynamicExpr() {
return new DynamicCoerce(this);
return new DynamicCoerce(character, this);
}
}

View File

@ -0,0 +1,11 @@
package io.gitlab.jfronny.muscript.compiler.expr.annotations;
import java.lang.annotation.*;
/**
* Annotation for expressions that may throw exceptions
* Any expression that doesn't have this annotation will only throw if an expression referenced from it throws
*/
@Target(ElementType.TYPE)
public @interface CanThrow {
}

View File

@ -0,0 +1,11 @@
package io.gitlab.jfronny.muscript.compiler.expr.annotations;
import java.lang.annotation.*;
/**
* Annotation for expressions that utilize Dynamic.as*
* These expressions must also be annotated with @CanThrow
*/
@Target(ElementType.TYPE)
public @interface UncheckedDynamic {
}

View File

@ -7,13 +7,14 @@ public class And extends BoolExpr {
private final BoolExpr left;
private final BoolExpr right;
public And(BoolExpr left, BoolExpr right) {
public And(int character, BoolExpr left, BoolExpr right) {
super(character);
this.left = left;
this.right = right;
}
@Override
public Boolean get(Dynamic<?> branch, Dynamic<?> dataRoot) {
return left.get(branch, dataRoot) && right.get(branch, dataRoot);
public Boolean get(Dynamic<?> dataRoot) {
return left.get(dataRoot) && right.get(dataRoot);
}
}

View File

@ -6,12 +6,13 @@ import io.gitlab.jfronny.muscript.dynamic.*;
public class Not extends BoolExpr {
private final BoolExpr inner;
public Not(BoolExpr inner) {
public Not(int character, BoolExpr inner) {
super(character);
this.inner = inner;
}
@Override
public Boolean get(Dynamic<?> branch, Dynamic<?> dataRoot) {
return !inner.get(branch, dataRoot);
public Boolean get(Dynamic<?> dataRoot) {
return !inner.get(dataRoot);
}
}

View File

@ -7,13 +7,14 @@ public class Or extends BoolExpr {
private final BoolExpr left;
private final BoolExpr right;
public Or(BoolExpr left, BoolExpr right) {
public Or(int character, BoolExpr left, BoolExpr right) {
super(character);
this.left = left;
this.right = right;
}
@Override
public Boolean get(Dynamic<?> branch, Dynamic<?> dataRoot) {
return left.get(branch, dataRoot) || right.get(branch, dataRoot);
public Boolean get(Dynamic<?> dataRoot) {
return left.get(dataRoot) || right.get(dataRoot);
}
}

View File

@ -9,14 +9,15 @@ public class Equal extends BoolExpr {
private final Expr<?> left;
private final Expr<?> right;
public Equal(Expr<?> left, Expr<?> right) {
public Equal(int character, Expr<?> left, Expr<?> right) {
super(character);
this.left = left;
this.right = right;
}
@Override
public Boolean get(Dynamic<?> branch, Dynamic<?> dataRoot) {
return Objects.equals(unwrap(left.get(branch, dataRoot)), unwrap(right.get(branch, dataRoot)));
public Boolean get(Dynamic<?> dataRoot) {
return Objects.equals(unwrap(left.get(dataRoot)), unwrap(right.get(dataRoot)));
}
private Object unwrap(Object o) {

View File

@ -8,16 +8,15 @@ public class BoolConditional extends BoolExpr {
public final BoolExpr trueExpr;
public final BoolExpr falseExpr;
public BoolConditional(BoolExpr condition, BoolExpr trueExpr, BoolExpr falseExpr) {
public BoolConditional(int character, BoolExpr condition, BoolExpr trueExpr, BoolExpr falseExpr) {
super(character);
this.condition = condition;
this.trueExpr = trueExpr;
this.falseExpr = falseExpr;
if (trueExpr.getResultType() != falseExpr.getResultType())
throw new IllegalArgumentException("Values used in conditional operator must be of the same type");
}
@Override
public Boolean get(Dynamic<?> branch, Dynamic<?> dataRoot) {
return condition.get(branch, dataRoot) ? trueExpr.get(branch, dataRoot) : falseExpr.get(branch, dataRoot);
public Boolean get(Dynamic<?> dataRoot) {
return condition.get(dataRoot) ? trueExpr.get(dataRoot) : falseExpr.get(dataRoot);
}
}

View File

@ -2,22 +2,22 @@ package io.gitlab.jfronny.muscript.compiler.expr.common.conditional;
import io.gitlab.jfronny.muscript.compiler.expr.*;
import io.gitlab.jfronny.muscript.dynamic.*;
import io.gitlab.jfronny.muscript.error.*;
public class DynamicConditional extends DynamicExpr {
public final BoolExpr condition;
public final DynamicExpr trueExpr;
public final DynamicExpr falseExpr;
public DynamicConditional(BoolExpr condition, DynamicExpr trueExpr, DynamicExpr falseExpr) {
public DynamicConditional(int character, BoolExpr condition, DynamicExpr trueExpr, DynamicExpr falseExpr) throws TypeMismatchException {
super(character);
this.condition = condition;
this.trueExpr = trueExpr;
this.falseExpr = falseExpr;
if (trueExpr.getResultType() != falseExpr.getResultType())
throw new IllegalArgumentException("Values used in conditional operator must be of the same type");
}
@Override
public Dynamic get(Dynamic<?> branch, Dynamic<?> dataRoot) {
return condition.get(branch, dataRoot) ? trueExpr.get(branch, dataRoot) : falseExpr.get(branch, dataRoot);
public Dynamic get(Dynamic<?> dataRoot) {
return condition.get(dataRoot) ? trueExpr.get(dataRoot) : falseExpr.get(dataRoot);
}
}

View File

@ -8,16 +8,15 @@ public class NumberConditional extends NumberExpr {
public final NumberExpr trueExpr;
public final NumberExpr falseExpr;
public NumberConditional(BoolExpr condition, NumberExpr trueExpr, NumberExpr falseExpr) {
public NumberConditional(int character, BoolExpr condition, NumberExpr trueExpr, NumberExpr falseExpr) {
super(character);
this.condition = condition;
this.trueExpr = trueExpr;
this.falseExpr = falseExpr;
if (trueExpr.getResultType() != falseExpr.getResultType())
throw new IllegalArgumentException("Values used in conditional operator must be of the same type");
}
@Override
public Double get(Dynamic<?> branch, Dynamic<?> dataRoot) {
return condition.get(branch, dataRoot) ? trueExpr.get(branch, dataRoot) : falseExpr.get(branch, dataRoot);
public Double get(Dynamic<?> dataRoot) {
return condition.get(dataRoot) ? trueExpr.get(dataRoot) : falseExpr.get(dataRoot);
}
}

View File

@ -8,16 +8,15 @@ public class StringConditional extends StringExpr {
public final StringExpr trueExpr;
public final StringExpr falseExpr;
public StringConditional(BoolExpr condition, StringExpr trueExpr, StringExpr falseExpr) {
public StringConditional(int character, BoolExpr condition, StringExpr trueExpr, StringExpr falseExpr) {
super(character);
this.condition = condition;
this.trueExpr = trueExpr;
this.falseExpr = falseExpr;
if (trueExpr.getResultType() != falseExpr.getResultType())
throw new IllegalArgumentException("Values used in conditional operator must be of the same type");
}
@Override
public String get(Dynamic<?> branch, Dynamic<?> dataRoot) {
return condition.get(branch, dataRoot) ? trueExpr.get(branch, dataRoot) : falseExpr.get(branch, dataRoot);
public String get(Dynamic<?> dataRoot) {
return condition.get(dataRoot) ? trueExpr.get(dataRoot) : falseExpr.get(dataRoot);
}
}

View File

@ -6,12 +6,13 @@ import io.gitlab.jfronny.muscript.dynamic.*;
public final class BoolLiteral extends BoolExpr {
private final boolean value;
public BoolLiteral(boolean value) {
public BoolLiteral(int character, boolean value) {
super(character);
this.value = value;
}
@Override
public Boolean get(Dynamic<?> branch, Dynamic<?> dataRoot) {
public Boolean get(Dynamic<?> dataRoot) {
return value;
}
}

View File

@ -6,12 +6,13 @@ import io.gitlab.jfronny.muscript.dynamic.*;
public final class DynamicLiteral<T> extends DynamicExpr {
private final Dynamic<T> value;
public DynamicLiteral(Dynamic<T> value) {
public DynamicLiteral(int character, Dynamic<T> value) {
super(character);
this.value = value;
}
@Override
public Dynamic<T> get(Dynamic<?> branch, Dynamic<?> dataRoot) {
public Dynamic<T> get(Dynamic<?> dataRoot) {
return value;
}
}

View File

@ -6,12 +6,13 @@ import io.gitlab.jfronny.muscript.dynamic.*;
public final class NumberLiteral extends NumberExpr {
private final double value;
public NumberLiteral(double value) {
public NumberLiteral(int character, double value) {
super(character);
this.value = value;
}
@Override
public Double get(Dynamic<?> branch, Dynamic<?> dataRoot) {
public Double get(Dynamic<?> dataRoot) {
return value;
}
}

View File

@ -6,12 +6,13 @@ import io.gitlab.jfronny.muscript.dynamic.*;
public final class StringLiteral extends StringExpr {
private final String value;
public StringLiteral(String value) {
public StringLiteral(int character, String value) {
super(character);
this.value = value;
}
@Override
public String get(Dynamic<?> branch, Dynamic<?> dataRoot) {
public String get(Dynamic<?> dataRoot) {
return value;
}
}

View File

@ -1,21 +1,28 @@
package io.gitlab.jfronny.muscript.compiler.expr.dynamic;
import io.gitlab.jfronny.muscript.compiler.expr.*;
import io.gitlab.jfronny.muscript.compiler.expr.annotations.*;
import io.gitlab.jfronny.muscript.dynamic.*;
import java.util.*;
@CanThrow @UncheckedDynamic
public class Call extends DynamicExpr {
private final DynamicExpr left;
private final List<DynamicExpr> args;
public Call(DynamicExpr left, List<DynamicExpr> args) {
public Call(int character, DynamicExpr left, List<DynamicExpr> args) {
super(character);
this.left = left;
this.args = args;
}
@Override
public Dynamic<?> get(Dynamic<?> branch, Dynamic<?> dataRoot) {
return left.get(branch, dataRoot).asCallable().getValue().apply(DFinal.of(args.stream().map(e -> e.get(dataRoot, dataRoot)).toArray(Dynamic[]::new)));
public Dynamic<?> get(Dynamic<?> dataRoot) {
try {
return left.get(dataRoot).asCallable().getValue().apply(DFinal.of(args.stream().map(e -> e.get(dataRoot)).toArray(Dynamic[]::new)));
} catch (DynamicTypeConversionException e) {
throw e.locational(character);
}
}
}

View File

@ -6,22 +6,25 @@ import io.gitlab.jfronny.muscript.dynamic.*;
public class DynamicCoerce extends DynamicExpr {
private final Expr<?> inner;
public DynamicCoerce(Expr<?> inner) {
public DynamicCoerce(int character, Expr<?> inner) {
super(character);
this.inner = inner;
if (!(inner instanceof DynamicExpr)
&& !(inner instanceof BoolExpr)
&& !(inner instanceof StringExpr)
&& !(inner instanceof NumberExpr)) {
&& !(inner instanceof NumberExpr)
&& !(inner instanceof NullLiteral)) {
throw new IllegalArgumentException("A DynamicCoerce can only be created with a well-defined expression type");
}
}
@Override
public Dynamic<?> get(Dynamic<?> branch, Dynamic<?> dataRoot) {
if (inner instanceof DynamicExpr e) return e.get(branch, dataRoot);
if (inner instanceof BoolExpr e) return DFinal.of(e.get(branch, dataRoot));
if (inner instanceof StringExpr e) return DFinal.of(e.get(branch, dataRoot));
if (inner instanceof NumberExpr e) return DFinal.of(e.get(branch, dataRoot));
public Dynamic<?> get(Dynamic<?> dataRoot) {
if (inner instanceof DynamicExpr e) return e.get(dataRoot);
if (inner instanceof BoolExpr e) return DFinal.of(e.get(dataRoot));
if (inner instanceof StringExpr e) return DFinal.of(e.get(dataRoot));
if (inner instanceof NumberExpr e) return DFinal.of(e.get(dataRoot));
if (inner instanceof NullLiteral) return new DNull();
throw new IllegalStateException("The inner expression of DynamicCoerce should never be of undefined type");
}
}

View File

@ -2,28 +2,32 @@ package io.gitlab.jfronny.muscript.compiler.expr.dynamic;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.compiler.expr.*;
import io.gitlab.jfronny.muscript.compiler.expr.annotations.*;
import io.gitlab.jfronny.muscript.dynamic.*;
import io.gitlab.jfronny.muscript.error.*;
@CanThrow @UncheckedDynamic
public class Get extends DynamicExpr {
private final DynamicExpr left;
private final Expr<?> name;
public Get(DynamicExpr left, Expr<?> name) {
public Get(int character, DynamicExpr left, Expr<?> name) {
super(character);
this.left = left;
this.name = name;
if (name.getResultType() != Type.String && name.getResultType() != Type.Number && name.getResultType() != Type.Dynamic) {
throw new IllegalArgumentException("Name must be either a string or a number");
throw new TypeMismatchException(character, Type.String, name.getResultType(), "Name must be either a string or a number");
}
}
@Override
public Dynamic<?> get(Dynamic<?> branch, Dynamic<?> dataRoot) {
Dynamic<?> left = this.left.get(branch, dataRoot);
public Dynamic<?> get(Dynamic<?> dataRoot) {
Dynamic<?> left = this.left.get(dataRoot);
if (left instanceof DObject o) {
return o.get(name.asStringExpr().get(dataRoot, dataRoot));
return o.get(name.asStringExpr().get(dataRoot));
} else if (left instanceof DList l) {
return l.get(name.asNumberExpr().get(dataRoot, dataRoot).intValue());
return l.get(name.asNumberExpr().get(dataRoot).intValue());
}
throw new IllegalArgumentException("The element to get value \"" + name.asStringExpr().get(dataRoot, dataRoot) + "\" from is not of a valid type");
throw new DynamicTypeConversionException("object or list").locational(character);
}
}

View File

@ -1,26 +1,30 @@
package io.gitlab.jfronny.muscript.compiler.expr.dynamic;
import io.gitlab.jfronny.muscript.compiler.expr.*;
import io.gitlab.jfronny.muscript.compiler.expr.annotations.*;
import io.gitlab.jfronny.muscript.dynamic.*;
import io.gitlab.jfronny.muscript.error.*;
@CanThrow @UncheckedDynamic
public class Variable extends DynamicExpr {
private final String name;
public Variable(String name) {
public Variable(int character, String name) {
super(character);
this.name = name;
}
@Override
public Dynamic<?> get(Dynamic<?> branch, Dynamic<?> dataRoot) {
if (branch.asObject().has(name)) return branch.asObject().get(name);
public Dynamic<?> get(Dynamic<?> dataRoot) {
if (dataRoot.asObject().has(name)) return dataRoot.asObject().get(name);
else if (name.contains("::")) {
Dynamic<?> res = branch;
Dynamic<?> res = dataRoot;
for (String s : name.split("::")) {
if (!res.asObject().has(s))
throw new IllegalArgumentException("This object doesn't contain that name");
throw new LocationalException(character, "This object doesn't contain that name");
res = res.asObject().get(s);
}
return res;
} else throw new IllegalArgumentException("This object doesn't contain that name");
} else throw new LocationalException(character, "This object doesn't contain that name");
}
}

View File

@ -1,17 +1,24 @@
package io.gitlab.jfronny.muscript.compiler.expr.dynamic.unpack;
import io.gitlab.jfronny.muscript.compiler.expr.*;
import io.gitlab.jfronny.muscript.compiler.expr.annotations.*;
import io.gitlab.jfronny.muscript.dynamic.*;
@CanThrow @UncheckedDynamic
public class BoolUnpack extends BoolExpr {
private final DynamicExpr inner;
public BoolUnpack(DynamicExpr inner) {
public BoolUnpack(int character, DynamicExpr inner) {
super(character);
this.inner = inner;
}
@Override
public Boolean get(Dynamic<?> branch, Dynamic<?> dataRoot) {
return inner.get(branch, dataRoot).asBool().getValue();
public Boolean get(Dynamic<?> dataRoot) {
try {
return inner.get(dataRoot).asBool().getValue();
} catch (DynamicTypeConversionException e) {
throw e.locational(character);
}
}
}

View File

@ -1,17 +1,24 @@
package io.gitlab.jfronny.muscript.compiler.expr.dynamic.unpack;
import io.gitlab.jfronny.muscript.compiler.expr.*;
import io.gitlab.jfronny.muscript.compiler.expr.annotations.*;
import io.gitlab.jfronny.muscript.dynamic.*;
@CanThrow @UncheckedDynamic
public class NumberUnpack extends NumberExpr {
private final DynamicExpr inner;
public NumberUnpack(DynamicExpr inner) {
public NumberUnpack(int character, DynamicExpr inner) {
super(character);
this.inner = inner;
}
@Override
public Double get(Dynamic<?> branch, Dynamic<?> dataRoot) {
return inner.get(branch, dataRoot).asNumber().getValue();
public Double get(Dynamic<?> dataRoot) {
try {
return inner.get(dataRoot).asNumber().getValue();
} catch (DynamicTypeConversionException e) {
throw e.locational(character);
}
}
}

View File

@ -1,17 +1,24 @@
package io.gitlab.jfronny.muscript.compiler.expr.dynamic.unpack;
import io.gitlab.jfronny.muscript.compiler.expr.*;
import io.gitlab.jfronny.muscript.compiler.expr.annotations.*;
import io.gitlab.jfronny.muscript.dynamic.*;
@CanThrow @UncheckedDynamic
public class StringUnpack extends StringExpr {
private final DynamicExpr inner;
public StringUnpack(DynamicExpr inner) {
public StringUnpack(int character, DynamicExpr inner) {
super(character);
this.inner = inner;
}
@Override
public String get(Dynamic<?> branch, Dynamic<?> dataRoot) {
return inner.get(branch, dataRoot).asString().getValue();
public String get(Dynamic<?> dataRoot) {
try {
return inner.get(dataRoot).asString().getValue();
} catch (DynamicTypeConversionException e) {
throw e.locational(character);
}
}
}

View File

@ -7,13 +7,14 @@ public class Greater extends BoolExpr {
private final NumberExpr left;
private final NumberExpr right;
public Greater(NumberExpr left, NumberExpr right) {
public Greater(int character, NumberExpr left, NumberExpr right) {
super(character);
this.left = left;
this.right = right;
}
@Override
public Boolean get(Dynamic<?> branch, Dynamic<?> dataRoot) {
return left.get(branch, dataRoot) > right.get(branch, dataRoot);
public Boolean get(Dynamic<?> dataRoot) {
return left.get(dataRoot) > right.get(dataRoot);
}
}

View File

@ -7,13 +7,14 @@ public class Less extends BoolExpr {
private final NumberExpr left;
private final NumberExpr right;
public Less(NumberExpr left, NumberExpr right) {
public Less(int character, NumberExpr left, NumberExpr right) {
super(character);
this.left = left;
this.right = right;
}
@Override
public Boolean get(Dynamic<?> branch, Dynamic<?> dataRoot) {
return left.get(branch, dataRoot) < right.get(branch, dataRoot);
public Boolean get(Dynamic<?> dataRoot) {
return left.get(dataRoot) < right.get(dataRoot);
}
}

View File

@ -7,13 +7,14 @@ public class Divide extends NumberExpr {
private final NumberExpr dividend;
private final NumberExpr divisor;
public Divide(NumberExpr dividend, NumberExpr divisor) {
public Divide(int character, NumberExpr dividend, NumberExpr divisor) {
super(character);
this.dividend = dividend;
this.divisor = divisor;
}
@Override
public Double get(Dynamic<?> branch, Dynamic<?> dataRoot) {
return dividend.get(branch, dataRoot) / divisor.get(branch, dataRoot);
public Double get(Dynamic<?> dataRoot) {
return dividend.get(dataRoot) / divisor.get(dataRoot);
}
}

View File

@ -6,12 +6,13 @@ import io.gitlab.jfronny.muscript.dynamic.*;
public class Invert extends NumberExpr {
private final NumberExpr inner;
public Invert(NumberExpr inner) {
public Invert(int character, NumberExpr inner) {
super(character);
this.inner = inner;
}
@Override
public Double get(Dynamic<?> branch, Dynamic<?> dataRoot) {
return -inner.get(branch, dataRoot);
public Double get(Dynamic<?> dataRoot) {
return -inner.get(dataRoot);
}
}

View File

@ -7,13 +7,14 @@ public class Minus extends NumberExpr {
private final NumberExpr minuend;
private final NumberExpr subtrahend;
public Minus(NumberExpr minuend, NumberExpr subtrahend) {
public Minus(int character, NumberExpr minuend, NumberExpr subtrahend) {
super(character);
this.minuend = minuend;
this.subtrahend = subtrahend;
}
@Override
public Double get(Dynamic<?> branch, Dynamic<?> dataRoot) {
return minuend.get(branch, dataRoot) - subtrahend.get(branch, dataRoot);
public Double get(Dynamic<?> dataRoot) {
return minuend.get(dataRoot) - subtrahend.get(dataRoot);
}
}

View File

@ -7,13 +7,14 @@ public class Modulo extends NumberExpr {
private final NumberExpr dividend;
private final NumberExpr divisor;
public Modulo(NumberExpr dividend, NumberExpr divisor) {
public Modulo(int character, NumberExpr dividend, NumberExpr divisor) {
super(character);
this.dividend = dividend;
this.divisor = divisor;
}
@Override
public Double get(Dynamic<?> branch, Dynamic<?> dataRoot) {
return dividend.get(branch, dataRoot) % divisor.get(branch, dataRoot);
public Double get(Dynamic<?> dataRoot) {
return dividend.get(dataRoot) % divisor.get(dataRoot);
}
}

View File

@ -7,13 +7,14 @@ public class Multiply extends NumberExpr {
private final NumberExpr multiplier;
private final NumberExpr multiplicand;
public Multiply(NumberExpr multiplier, NumberExpr multiplicand) {
public Multiply(int character, NumberExpr multiplier, NumberExpr multiplicand) {
super(character);
this.multiplier = multiplier;
this.multiplicand = multiplicand;
}
@Override
public Double get(Dynamic<?> branch, Dynamic<?> dataRoot) {
return multiplier.get(branch, dataRoot) * multiplicand.get(branch, dataRoot);
public Double get(Dynamic<?> dataRoot) {
return multiplier.get(dataRoot) * multiplicand.get(dataRoot);
}
}

View File

@ -7,13 +7,14 @@ public class Plus extends NumberExpr {
private final NumberExpr augend;
private final NumberExpr addend;
public Plus(NumberExpr augend, NumberExpr addend) {
public Plus(int character, NumberExpr augend, NumberExpr addend) {
super(character);
this.augend = augend;
this.addend = addend;
}
@Override
public Double get(Dynamic<?> branch, Dynamic<?> dataRoot) {
return augend.get(branch, dataRoot) + addend.get(branch, dataRoot);
public Double get(Dynamic<?> dataRoot) {
return augend.get(dataRoot) + addend.get(dataRoot);
}
}

View File

@ -7,13 +7,14 @@ public class Power extends NumberExpr {
private final NumberExpr base;
private final NumberExpr exponent;
public Power(NumberExpr base, NumberExpr exponent) {
public Power(int character, NumberExpr base, NumberExpr exponent) {
super(character);
this.base = base;
this.exponent = exponent;
}
@Override
public Double get(Dynamic<?> branch, Dynamic<?> dataRoot) {
return Math.pow(base.get(branch, dataRoot), exponent.get(branch, dataRoot));
public Double get(Dynamic<?> dataRoot) {
return Math.pow(base.get(dataRoot), exponent.get(dataRoot));
}
}

View File

@ -7,13 +7,14 @@ public class Concatenate extends StringExpr {
private final StringExpr left;
private final StringExpr right;
public Concatenate(StringExpr left, StringExpr right) {
public Concatenate(int character, StringExpr left, StringExpr right) {
super(character);
this.left = left;
this.right = right;
}
@Override
public String get(Dynamic<?> branch, Dynamic<?> dataRoot) {
return left.get(branch, dataRoot) + right.get(branch, dataRoot);
public String get(Dynamic<?> dataRoot) {
return left.get(dataRoot) + right.get(dataRoot);
}
}

View File

@ -7,12 +7,13 @@ import io.gitlab.jfronny.muscript.dynamic.*;
public class StringCoerce extends StringExpr {
private final Expr<?> inner;
public StringCoerce(Expr<?> inner) {
public StringCoerce(int character, Expr<?> inner) {
super(character);
this.inner = inner;
}
@Override
public String get(Dynamic<?> branch, Dynamic<?> dataRoot) {
return StringFormatter.toString(inner.get(branch, dataRoot));
public String get(Dynamic<?> dataRoot) {
return StringFormatter.toString(inner.get(dataRoot));
}
}

View File

@ -2,15 +2,18 @@ package io.gitlab.jfronny.muscript.compiler.expr.unresolved;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.compiler.expr.*;
import io.gitlab.jfronny.muscript.compiler.expr.annotations.*;
import io.gitlab.jfronny.muscript.compiler.expr.common.conditional.*;
import io.gitlab.jfronny.muscript.dynamic.*;
@CanThrow
public class UnresolvedConditional extends DynamicExpr {
private final BoolExpr condition;
private final Expr<?> trueExpr;
private final Expr<?> falseExpr;
public UnresolvedConditional(BoolExpr condition, Expr<?> trueExpr, Expr<?> falseExpr) {
public UnresolvedConditional(int character, BoolExpr condition, Expr<?> trueExpr, Expr<?> falseExpr) {
super(character);
this.condition = condition;
this.trueExpr = trueExpr;
this.falseExpr = falseExpr;
@ -22,27 +25,27 @@ public class UnresolvedConditional extends DynamicExpr {
}
@Override
public Dynamic get(Dynamic branch, Dynamic dataRoot) {
public Dynamic get(Dynamic dataRoot) {
throw new UnsupportedOperationException("Conditional was kept unresolved. This is not supported!");
}
@Override
public DynamicExpr asDynamicExpr() {
return new DynamicConditional(condition, trueExpr.asDynamicExpr(), falseExpr.asDynamicExpr());
return new DynamicConditional(character, condition, trueExpr.asDynamicExpr(), falseExpr.asDynamicExpr());
}
@Override
public BoolExpr asBoolExpr() {
return new BoolConditional(condition, trueExpr.asBoolExpr(), falseExpr.asBoolExpr());
return new BoolConditional(character, condition, trueExpr.asBoolExpr(), falseExpr.asBoolExpr());
}
@Override
public StringExpr asStringExpr() {
return new StringConditional(condition, trueExpr.asStringExpr(), falseExpr.asStringExpr());
return new StringConditional(character, condition, trueExpr.asStringExpr(), falseExpr.asStringExpr());
}
@Override
public NumberExpr asNumberExpr() {
return new NumberConditional(condition, trueExpr.asNumberExpr(), falseExpr.asNumberExpr());
return new NumberConditional(character, condition, trueExpr.asNumberExpr(), falseExpr.asNumberExpr());
}
}

View File

@ -7,12 +7,12 @@ public interface Dynamic<T> {
default DBool asBool() {
if (this instanceof DBool bool) return bool;
else throw new IllegalArgumentException("This value is not a bool");
else throw new DynamicTypeConversionException("bool");
}
default DNumber asNumber() {
if (this instanceof DNumber number) return number;
else throw new IllegalArgumentException("This value is not a number");
else throw new DynamicTypeConversionException("number");
}
default DString asString() {
@ -22,16 +22,16 @@ public interface Dynamic<T> {
default DObject asObject() {
if (this instanceof DObject object) return object;
else throw new IllegalArgumentException("This value is not an object");
else throw new DynamicTypeConversionException("object");
}
default DList asList() {
if (this instanceof DList list) return list;
else throw new IllegalArgumentException("This value is not a list");
else throw new DynamicTypeConversionException("list");
}
default DCallable asCallable() {
if (this instanceof DCallable callable) return callable;
else throw new IllegalArgumentException("This value is not a callable");
else throw new DynamicTypeConversionException("callable");
}
}

View File

@ -0,0 +1,16 @@
package io.gitlab.jfronny.muscript.dynamic;
import io.gitlab.jfronny.muscript.error.*;
public class DynamicTypeConversionException extends RuntimeException {
private final String target;
public DynamicTypeConversionException(String target) {
super("Could not convert dynamic to " + target);
this.target = target;
}
public LocationalException locational(int character) {
return new LocationalException(character, target, this);
}
}

View File

@ -1,9 +1,10 @@
package io.gitlab.jfronny.muscript.compiler;
package io.gitlab.jfronny.muscript.error;
/**
* Class for storing errors produced while parsing.
* Class for storing errors with code context
* Can be generated from a LocationalException with asPrintable
*/
public record Error(String message, String line, int lineNumber, int column) {
public record LocationalError(String message, String line, int lineNumber, int column) {
/**
* Generates an error by getting the relevant data from what is available while lexing/parsing
* @param source The complete source for the code that generated this error
@ -11,7 +12,7 @@ public record Error(String message, String line, int lineNumber, int column) {
* @param message The error message
* @return An error using the provided information
*/
public static Error create(String source, int character, String message) {
public static LocationalError create(String source, int character, String message) {
int lineStart = source.lastIndexOf('\n', character);
int lineEnd = source.indexOf('\n', character);
if (lineEnd == -1) lineEnd = source.length();
@ -19,7 +20,7 @@ public record Error(String message, String line, int lineNumber, int column) {
int lineIndex = lineStart > 0 ? (int) source.substring(0, lineStart).chars().filter(c -> c == '\n').count() : 0;
int column = character - lineStart;
return new Error(message, source.substring(lineStart + 1, lineEnd), lineIndex + 1, column);
return new LocationalError(message, source.substring(lineStart + 1, lineEnd), lineIndex + 1, column);
}
/**

View File

@ -0,0 +1,33 @@
package io.gitlab.jfronny.muscript.error;
/**
* An exception type with a location
* For use in MuScript, can be converted to a pretty LocationalError with asPrintable
*/
public class LocationalException extends RuntimeException {
private final int location;
public LocationalException(int location) {
super();
this.location = location;
}
public LocationalException(int location, String message) {
super(message);
this.location = location;
}
public LocationalException(int location, String message, Throwable cause) {
super(message, cause);
this.location = location;
}
public LocationalException(int location, Throwable cause) {
super(cause);
this.location = location;
}
public LocationalError asPrintable(String source) {
return LocationalError.create(source, location, getLocalizedMessage());
}
}

View File

@ -0,0 +1,23 @@
package io.gitlab.jfronny.muscript.error;
import io.gitlab.jfronny.muscript.compiler.*;
import org.jetbrains.annotations.*;
/**
* An exception that expresses an unexpected type
* To be used in expressions
*/
public class TypeMismatchException extends LocationalException {
private final Type expected;
private final @Nullable Type actual;
public TypeMismatchException(int character, Type expected, @Nullable Type actual) {
this(character, expected, actual, "Expected " + expected + (actual == null ? "" : " but got " + actual));
}
public TypeMismatchException(int character, Type expected, @Nullable Type actual, String message) {
super(character, message);
this.expected = expected;
this.actual = actual;
}
}

View File

@ -5,18 +5,18 @@ import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
public class ErrorTest {
public class LocationalErrorTest {
@Test
void invalidCode() {
assertEquals("""
Error at 'e' (character 9): Expected number but is Boolean
Error at 't' (character 6): Expected Number but got Boolean
1 | 15 + true
^-- Here""",
^-- Here""",
assertThrows(Parser.ParseException.class, () -> Parser.parse("15 + true")).error.toString());
assertEquals("""
Error at ''' (character 10): Expected number but is String
Error at ''' (character 6): Expected Number but got String
1 | 15 + 'yes'
^-- Here""",
^-- Here""",
assertThrows(Parser.ParseException.class, () -> Parser.parse("15 + 'yes'")).error.toString());
assertEquals("""
Error at '=' (character 8): Unexpected character