Support for assignment operators and closures
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

This commit is contained in:
Johannes Frohnmeyer 2023-01-20 21:05:04 +01:00
parent c8f6b0b6c4
commit 2847e05440
Signed by: Johannes
GPG Key ID: E76429612C2929F4
64 changed files with 867 additions and 108 deletions

View File

@ -71,7 +71,7 @@ one
by calling `as(Bool|String|Number|Dynamic)Expr`. by calling `as(Bool|String|Number|Dynamic)Expr`.
This process may throw a ParseException. 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 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. `Scope` 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. 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 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. using the source of the expression if available.
@ -95,7 +95,7 @@ try {
System.err.println(e.asPrintable(source)); System.err.println(e.asPrintable(source));
return; return;
} }
ExpressionParameter parameter = StandardLib.addTo(new ExpressionParameter()) Scope scope = StandardLib.addTo(new Scope())
.set("someValue", DFinal.of(15)) .set("someValue", DFinal.of(15))
.set("someOther", DFinal.of(Map.of( .set("someOther", DFinal.of(Map.of(
"subValue", DFinal.of(true) "subValue", DFinal.of(true)

View File

@ -1,8 +1,8 @@
package io.gitlab.jfronny.muscript; package io.gitlab.jfronny.muscript;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.DList; import io.gitlab.jfronny.muscript.data.dynamic.DList;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.data.ExpressionParameter;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
@ -16,8 +16,8 @@ public class StandardLib {
public static final SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm"); public static final SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm");
public static final SimpleDateFormat dateFormat = new SimpleDateFormat("dd. MM. yyyy"); public static final SimpleDateFormat dateFormat = new SimpleDateFormat("dd. MM. yyyy");
public static ExpressionParameter addTo(ExpressionParameter parameter) { public static Scope addTo(Scope scope) {
return parameter return scope
.set("PI", of(Math.PI)) .set("PI", of(Math.PI))
.set("time", of(timeFormat.format(new Date()))) .set("time", of(timeFormat.format(new Date())))
.set("date", of(dateFormat.format(new Date()))) .set("date", of(dateFormat.format(new Date())))

View File

@ -1,21 +1,30 @@
package io.gitlab.jfronny.muscript.ast; package io.gitlab.jfronny.muscript.ast;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.annotations.CanThrow; import io.gitlab.jfronny.muscript.annotations.CanThrow;
import io.gitlab.jfronny.muscript.ast.literal.*; import io.gitlab.jfronny.muscript.ast.literal.*;
import io.gitlab.jfronny.muscript.ast.string.StringCoerce; import io.gitlab.jfronny.muscript.ast.string.StringCoerce;
import io.gitlab.jfronny.muscript.compiler.Type; import io.gitlab.jfronny.muscript.compiler.Type;
import io.gitlab.jfronny.muscript.data.dynamic.DObject;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.error.TypeMismatchException; import io.gitlab.jfronny.muscript.error.TypeMismatchException;
@CanThrow @CanThrow
public sealed abstract class Expr<T> permits BoolExpr, DynamicExpr, NullLiteral, NumberExpr, StringExpr { public abstract sealed class Expr<T> permits BoolExpr, DynamicExpr, NullLiteral, NumberExpr, StringExpr {
protected Expr(int chStart, int chEnd) { protected Expr(int chStart, int chEnd) {
this.chStart = chStart; this.chStart = chStart;
this.chEnd = chEnd; this.chEnd = chEnd;
} }
public abstract Type getResultType(); public abstract Type getResultType();
public abstract T get(Dynamic<?> dataRoot); public abstract T get(Scope dataRoot);
public T get(DObject dataRoot) {
return get(dataRoot instanceof Scope scope ? scope : new Scope(dataRoot));
}
@Deprecated
public T get(Dynamic<?> dataRoot) {
return get(dataRoot.asObject());
}
public abstract Expr<T> optimize(); public abstract Expr<T> optimize();
public final int chStart; public final int chStart;
public final int chEnd; public final int chEnd;

View File

@ -1,7 +1,7 @@
package io.gitlab.jfronny.muscript.ast; package io.gitlab.jfronny.muscript.ast;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.DNull; import io.gitlab.jfronny.muscript.data.dynamic.DNull;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.annotations.CanThrow; import io.gitlab.jfronny.muscript.annotations.CanThrow;
import io.gitlab.jfronny.muscript.ast.literal.DynamicLiteral; import io.gitlab.jfronny.muscript.ast.literal.DynamicLiteral;
import io.gitlab.jfronny.muscript.compiler.Type; import io.gitlab.jfronny.muscript.compiler.Type;
@ -19,7 +19,7 @@ public final class NullLiteral extends Expr<Object> {
} }
@Override @Override
public Object get(Dynamic<?> dataRoot) { public Object get(Scope dataRoot) {
return null; return null;
} }

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.muscript.ast.bool; package io.gitlab.jfronny.muscript.ast.bool;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.BoolExpr; import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.Expr; import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral; import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral;
@ -16,7 +16,7 @@ public class And extends BoolExpr {
} }
@Override @Override
public Boolean get(Dynamic<?> dataRoot) { public Boolean get(Scope dataRoot) {
return left.get(dataRoot) && right.get(dataRoot); return left.get(dataRoot) && right.get(dataRoot);
} }

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.muscript.ast.bool; package io.gitlab.jfronny.muscript.ast.bool;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.BoolExpr; import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.Expr; import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral; import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral;
@ -14,7 +14,7 @@ public class Not extends BoolExpr {
} }
@Override @Override
public Boolean get(Dynamic<?> dataRoot) { public Boolean get(Scope dataRoot) {
return !inner.get(dataRoot); return !inner.get(dataRoot);
} }

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.muscript.ast.bool; package io.gitlab.jfronny.muscript.ast.bool;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.BoolExpr; import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.Expr; import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral; import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral;
@ -16,7 +16,7 @@ public class Or extends BoolExpr {
} }
@Override @Override
public Boolean get(Dynamic<?> dataRoot) { public Boolean get(Scope dataRoot) {
return left.get(dataRoot) || right.get(dataRoot); return left.get(dataRoot) || right.get(dataRoot);
} }

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.muscript.ast.compare; package io.gitlab.jfronny.muscript.ast.compare;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.ast.BoolExpr; import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.Expr; import io.gitlab.jfronny.muscript.ast.Expr;
@ -17,7 +18,7 @@ public class Equal extends BoolExpr {
} }
@Override @Override
public Boolean get(Dynamic<?> dataRoot) { public Boolean get(Scope dataRoot) {
return Objects.equals(unwrap(left.get(dataRoot)), unwrap(right.get(dataRoot))); return Objects.equals(unwrap(left.get(dataRoot)), unwrap(right.get(dataRoot)));
} }

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.muscript.ast.compare; package io.gitlab.jfronny.muscript.ast.compare;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.*; import io.gitlab.jfronny.muscript.ast.*;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral; import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import io.gitlab.jfronny.muscript.ast.math.*; import io.gitlab.jfronny.muscript.ast.math.*;
@ -16,7 +16,7 @@ public class Greater extends BoolExpr {
} }
@Override @Override
public Boolean get(Dynamic<?> dataRoot) { public Boolean get(Scope dataRoot) {
return left.get(dataRoot) > right.get(dataRoot); return left.get(dataRoot) > right.get(dataRoot);
} }

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.muscript.ast.compare; package io.gitlab.jfronny.muscript.ast.compare;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.*; import io.gitlab.jfronny.muscript.ast.*;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral; import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import io.gitlab.jfronny.muscript.ast.math.*; import io.gitlab.jfronny.muscript.ast.math.*;
@ -16,7 +16,7 @@ public class Less extends BoolExpr {
} }
@Override @Override
public Boolean get(Dynamic<?> dataRoot) { public Boolean get(Scope dataRoot) {
return left.get(dataRoot) < right.get(dataRoot); return left.get(dataRoot) < right.get(dataRoot);
} }

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.muscript.ast.conditional; package io.gitlab.jfronny.muscript.ast.conditional;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.BoolExpr; import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral; import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral;
@ -17,7 +17,7 @@ public class BoolConditional extends BoolExpr {
} }
@Override @Override
public Boolean get(Dynamic<?> dataRoot) { public Boolean get(Scope dataRoot) {
return condition.get(dataRoot) ? trueExpr.get(dataRoot) : falseExpr.get(dataRoot); return condition.get(dataRoot) ? trueExpr.get(dataRoot) : falseExpr.get(dataRoot);
} }

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.muscript.ast.conditional; package io.gitlab.jfronny.muscript.ast.conditional;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.ast.BoolExpr; import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.DynamicExpr; import io.gitlab.jfronny.muscript.ast.DynamicExpr;
@ -18,7 +19,7 @@ public class DynamicConditional extends DynamicExpr {
} }
@Override @Override
public Dynamic<?> get(Dynamic<?> dataRoot) { public Dynamic<?> get(Scope dataRoot) {
return condition.get(dataRoot) ? trueExpr.get(dataRoot) : falseExpr.get(dataRoot); return condition.get(dataRoot) ? trueExpr.get(dataRoot) : falseExpr.get(dataRoot);
} }

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.muscript.ast.conditional; package io.gitlab.jfronny.muscript.ast.conditional;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.BoolExpr; import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.NumberExpr; import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral; import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral;
@ -18,7 +18,7 @@ public class NumberConditional extends NumberExpr {
} }
@Override @Override
public Double get(Dynamic<?> dataRoot) { public Double get(Scope dataRoot) {
return condition.get(dataRoot) ? trueExpr.get(dataRoot) : falseExpr.get(dataRoot); return condition.get(dataRoot) ? trueExpr.get(dataRoot) : falseExpr.get(dataRoot);
} }

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.muscript.ast.conditional; package io.gitlab.jfronny.muscript.ast.conditional;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.BoolExpr; import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.StringExpr; import io.gitlab.jfronny.muscript.ast.StringExpr;
import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral; import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral;
@ -18,7 +18,7 @@ public class StringConditional extends StringExpr {
} }
@Override @Override
public String get(Dynamic<?> dataRoot) { public String get(Scope dataRoot) {
return condition.get(dataRoot) ? trueExpr.get(dataRoot) : falseExpr.get(dataRoot); return condition.get(dataRoot) ? trueExpr.get(dataRoot) : falseExpr.get(dataRoot);
} }

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.muscript.ast.conditional; package io.gitlab.jfronny.muscript.ast.conditional;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.ast.*; import io.gitlab.jfronny.muscript.ast.*;
import io.gitlab.jfronny.muscript.annotations.CanThrow; import io.gitlab.jfronny.muscript.annotations.CanThrow;
@ -35,7 +36,7 @@ public class UnresolvedConditional extends DynamicExpr {
} }
@Override @Override
public Dynamic<?> get(Dynamic<?> dataRoot) { public Dynamic<?> get(Scope dataRoot) {
throw new UnsupportedOperationException("Conditional was kept unresolved. This is not supported!"); throw new UnsupportedOperationException("Conditional was kept unresolved. This is not supported!");
} }

View File

@ -0,0 +1,35 @@
package io.gitlab.jfronny.muscript.ast.dynamic;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
public class Assign extends DynamicExpr {
private final String name;
private final DynamicExpr value;
public Assign(int chStart, int chEnd, String name, DynamicExpr value) {
super(chStart, chEnd);
this.name = name;
this.value = value;
}
@Override
public Dynamic<?> get(Scope dataRoot) {
Dynamic<?> data = value.get(dataRoot);
dataRoot.set(name, data);
return data;
}
@Override
public DynamicExpr optimize() {
return new Assign(chStart, chEnd, name, value.optimize());
}
@Override
public boolean equals(Object obj) {
return obj instanceof Assign assign
&& name.equals(assign.name)
&& value.equals(assign.value);
}
}

View File

@ -3,6 +3,7 @@ package io.gitlab.jfronny.muscript.ast.dynamic;
import io.gitlab.jfronny.muscript.ast.DynamicExpr; import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.annotations.CanThrow; import io.gitlab.jfronny.muscript.annotations.CanThrow;
import io.gitlab.jfronny.muscript.annotations.UncheckedDynamic; import io.gitlab.jfronny.muscript.annotations.UncheckedDynamic;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.*; import io.gitlab.jfronny.muscript.data.dynamic.*;
import java.util.ArrayList; import java.util.ArrayList;
@ -21,7 +22,7 @@ public class Call extends DynamicExpr {
} }
@Override @Override
public Dynamic<?> get(Dynamic<?> dataRoot) { public Dynamic<?> get(Scope dataRoot) {
try { try {
return left.get(dataRoot).asCallable().getValue().apply(DFinal.of(args.stream().map(e -> e.get(dataRoot)).toArray(Dynamic[]::new))); return left.get(dataRoot).asCallable().getValue().apply(DFinal.of(args.stream().map(e -> e.get(dataRoot)).toArray(Dynamic[]::new)));
} catch (DynamicTypeConversionException e) { } catch (DynamicTypeConversionException e) {

View File

@ -0,0 +1,53 @@
package io.gitlab.jfronny.muscript.ast.dynamic;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.error.LocationalException;
import java.util.List;
public class Closure extends DynamicExpr {
private final List<String> boundArgs;
private final List<Expr<?>> steps;
private final DynamicExpr fin;
public Closure(int chStart, int chEnd, List<String> boundArgs, List<Expr<?>> expressions) {
this(chStart, chEnd, boundArgs, expressions.subList(0, expressions.size() - 1), expressions.get(expressions.size() - 1).asDynamicExpr());
}
private Closure(int chStart, int chEnd, List<String> boundArgs, List<Expr<?>> steps, DynamicExpr fin) {
super(chStart, chEnd);
this.boundArgs = List.copyOf(boundArgs);
this.steps = List.copyOf(steps);
this.fin = fin;
}
@Override
public DCallable get(Scope dataRoot) {
return DFinal.of(args -> {
if (args.size() != boundArgs.size()) throw new LocationalException(chStart, chEnd, "Invoked with unexpected number of parameters");
Scope fork = dataRoot.fork();
for (int i = 0; i < boundArgs.size(); i++) fork.set(boundArgs.get(i), args.get(i));
for (Expr<?> step : steps) {
step.get(fork);
}
return fin.get(fork);
});
}
@Override
public DynamicExpr optimize() {
// Eliminating side effect free steps might be possible, but would be too much work imo
return new Closure(chStart, chEnd, boundArgs, steps.stream().<Expr<?>>map(Expr::optimize).toList(), fin.optimize());
}
@Override
public boolean equals(Object obj) {
return obj instanceof Closure closure
&& boundArgs.equals(closure.boundArgs)
&& steps.equals(closure.steps)
&& fin.equals(closure.fin);
}
}

View File

@ -2,6 +2,7 @@ package io.gitlab.jfronny.muscript.ast.dynamic;
import io.gitlab.jfronny.muscript.ast.*; import io.gitlab.jfronny.muscript.ast.*;
import io.gitlab.jfronny.muscript.ast.dynamic.unpack.*; import io.gitlab.jfronny.muscript.ast.dynamic.unpack.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.*; import io.gitlab.jfronny.muscript.data.dynamic.*;
public class DynamicCoerce extends DynamicExpr { public class DynamicCoerce extends DynamicExpr {
@ -20,7 +21,7 @@ public class DynamicCoerce extends DynamicExpr {
} }
@Override @Override
public Dynamic<?> get(Dynamic<?> dataRoot) { public Dynamic<?> get(Scope dataRoot) {
if (inner instanceof DynamicExpr e) return e.get(dataRoot); if (inner instanceof DynamicExpr e) return e.get(dataRoot);
if (inner instanceof BoolExpr e) return DFinal.of(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 StringExpr e) return DFinal.of(e.get(dataRoot));

View File

@ -5,6 +5,7 @@ import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.annotations.CanThrow; import io.gitlab.jfronny.muscript.annotations.CanThrow;
import io.gitlab.jfronny.muscript.annotations.UncheckedDynamic; import io.gitlab.jfronny.muscript.annotations.UncheckedDynamic;
import io.gitlab.jfronny.muscript.compiler.Type; import io.gitlab.jfronny.muscript.compiler.Type;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.*; import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.error.TypeMismatchException; import io.gitlab.jfronny.muscript.error.TypeMismatchException;
@ -24,7 +25,7 @@ public class Get extends DynamicExpr {
} }
@Override @Override
public Dynamic<?> get(Dynamic<?> dataRoot) { public Dynamic<?> get(Scope dataRoot) {
Dynamic<?> left = this.left.get(dataRoot); Dynamic<?> left = this.left.get(dataRoot);
if (left instanceof DObject o) { if (left instanceof DObject o) {
return o.get(name.asStringExpr().get(dataRoot)); return o.get(name.asStringExpr().get(dataRoot));

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.muscript.ast.dynamic; package io.gitlab.jfronny.muscript.ast.dynamic;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.ast.DynamicExpr; import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.annotations.CanThrow; import io.gitlab.jfronny.muscript.annotations.CanThrow;
@ -16,8 +17,12 @@ public class Variable extends DynamicExpr {
this.name = name; this.name = name;
} }
public Assign assign(DynamicExpr value) {
return new Assign(chStart, chEnd, name, value);
}
@Override @Override
public Dynamic<?> get(Dynamic<?> dataRoot) { public Dynamic<?> get(Scope dataRoot) {
if (dataRoot.asObject().has(name)) return dataRoot.asObject().get(name); if (dataRoot.asObject().has(name)) return dataRoot.asObject().get(name);
else if (name.contains("::")) { else if (name.contains("::")) {
Dynamic<?> res = dataRoot; Dynamic<?> res = dataRoot;

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.muscript.ast.dynamic.unpack; package io.gitlab.jfronny.muscript.ast.dynamic.unpack;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.DynamicTypeConversionException; import io.gitlab.jfronny.muscript.data.dynamic.DynamicTypeConversionException;
import io.gitlab.jfronny.muscript.ast.BoolExpr; import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.DynamicExpr; import io.gitlab.jfronny.muscript.ast.DynamicExpr;
@ -19,7 +19,7 @@ public class BoolUnpack extends BoolExpr {
} }
@Override @Override
public Boolean get(Dynamic<?> dataRoot) { public Boolean get(Scope dataRoot) {
try { try {
return inner.get(dataRoot).asBool().getValue(); return inner.get(dataRoot).asBool().getValue();
} catch (DynamicTypeConversionException e) { } catch (DynamicTypeConversionException e) {

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.muscript.ast.dynamic.unpack; package io.gitlab.jfronny.muscript.ast.dynamic.unpack;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.DynamicTypeConversionException; import io.gitlab.jfronny.muscript.data.dynamic.DynamicTypeConversionException;
import io.gitlab.jfronny.muscript.ast.DynamicExpr; import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.NumberExpr; import io.gitlab.jfronny.muscript.ast.NumberExpr;
@ -19,7 +19,7 @@ public class NumberUnpack extends NumberExpr {
} }
@Override @Override
public Double get(Dynamic<?> dataRoot) { public Double get(Scope dataRoot) {
try { try {
return inner.get(dataRoot).asNumber().getValue(); return inner.get(dataRoot).asNumber().getValue();
} catch (DynamicTypeConversionException e) { } catch (DynamicTypeConversionException e) {

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.muscript.ast.dynamic.unpack; package io.gitlab.jfronny.muscript.ast.dynamic.unpack;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.DynamicTypeConversionException; import io.gitlab.jfronny.muscript.data.dynamic.DynamicTypeConversionException;
import io.gitlab.jfronny.muscript.ast.DynamicExpr; import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.StringExpr; import io.gitlab.jfronny.muscript.ast.StringExpr;
@ -19,7 +19,7 @@ public class StringUnpack extends StringExpr {
} }
@Override @Override
public String get(Dynamic<?> dataRoot) { public String get(Scope dataRoot) {
try { try {
return inner.get(dataRoot).asString().getValue(); return inner.get(dataRoot).asString().getValue();
} catch (DynamicTypeConversionException e) { } catch (DynamicTypeConversionException e) {

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.muscript.ast.literal; package io.gitlab.jfronny.muscript.ast.literal;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.BoolExpr; import io.gitlab.jfronny.muscript.ast.BoolExpr;
public final class BoolLiteral extends BoolExpr { public final class BoolLiteral extends BoolExpr {
@ -12,7 +12,7 @@ public final class BoolLiteral extends BoolExpr {
} }
@Override @Override
public Boolean get(Dynamic<?> dataRoot) { public Boolean get(Scope dataRoot) {
return value; return value;
} }

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.muscript.ast.literal; package io.gitlab.jfronny.muscript.ast.literal;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.ast.DynamicExpr; import io.gitlab.jfronny.muscript.ast.DynamicExpr;
@ -12,7 +13,7 @@ public final class DynamicLiteral<T> extends DynamicExpr {
} }
@Override @Override
public Dynamic<T> get(Dynamic<?> dataRoot) { public Dynamic<T> get(Scope dataRoot) {
return value; return value;
} }

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.muscript.ast.literal; package io.gitlab.jfronny.muscript.ast.literal;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.NumberExpr; import io.gitlab.jfronny.muscript.ast.NumberExpr;
public final class NumberLiteral extends NumberExpr { public final class NumberLiteral extends NumberExpr {
@ -12,7 +12,7 @@ public final class NumberLiteral extends NumberExpr {
} }
@Override @Override
public Double get(Dynamic<?> dataRoot) { public Double get(Scope dataRoot) {
return value; return value;
} }

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.muscript.ast.literal; package io.gitlab.jfronny.muscript.ast.literal;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.StringExpr; import io.gitlab.jfronny.muscript.ast.StringExpr;
public final class StringLiteral extends StringExpr { public final class StringLiteral extends StringExpr {
@ -12,7 +12,7 @@ public final class StringLiteral extends StringExpr {
} }
@Override @Override
public String get(Dynamic<?> dataRoot) { public String get(Scope dataRoot) {
return value; return value;
} }

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.muscript.ast.math; package io.gitlab.jfronny.muscript.ast.math;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr; import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.NumberExpr; import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral; import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
@ -16,7 +16,7 @@ public class Divide extends NumberExpr {
} }
@Override @Override
public Double get(Dynamic<?> dataRoot) { public Double get(Scope dataRoot) {
return dividend.get(dataRoot) / divisor.get(dataRoot); return dividend.get(dataRoot) / divisor.get(dataRoot);
} }

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.muscript.ast.math; package io.gitlab.jfronny.muscript.ast.math;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr; import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.NumberExpr; import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral; import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
@ -14,7 +14,7 @@ public class Invert extends NumberExpr {
} }
@Override @Override
public Double get(Dynamic<?> dataRoot) { public Double get(Scope dataRoot) {
return -inner.get(dataRoot); return -inner.get(dataRoot);
} }

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.muscript.ast.math; package io.gitlab.jfronny.muscript.ast.math;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr; import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.NumberExpr; import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral; import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
@ -16,7 +16,7 @@ public class Minus extends NumberExpr {
} }
@Override @Override
public Double get(Dynamic<?> dataRoot) { public Double get(Scope dataRoot) {
return minuend.get(dataRoot) - subtrahend.get(dataRoot); return minuend.get(dataRoot) - subtrahend.get(dataRoot);
} }

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.muscript.ast.math; package io.gitlab.jfronny.muscript.ast.math;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr; import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.NumberExpr; import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral; import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
@ -16,7 +16,7 @@ public class Modulo extends NumberExpr {
} }
@Override @Override
public Double get(Dynamic<?> dataRoot) { public Double get(Scope dataRoot) {
return dividend.get(dataRoot) % divisor.get(dataRoot); return dividend.get(dataRoot) % divisor.get(dataRoot);
} }

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.muscript.ast.math; package io.gitlab.jfronny.muscript.ast.math;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr; import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.NumberExpr; import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral; import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
@ -16,7 +16,7 @@ public class Multiply extends NumberExpr {
} }
@Override @Override
public Double get(Dynamic<?> dataRoot) { public Double get(Scope dataRoot) {
return multiplier.get(dataRoot) * multiplicand.get(dataRoot); return multiplier.get(dataRoot) * multiplicand.get(dataRoot);
} }
@ -26,7 +26,7 @@ public class Multiply extends NumberExpr {
NumberExpr multiplicand = this.multiplicand.optimize(); NumberExpr multiplicand = this.multiplicand.optimize();
if (multiplier instanceof NumberLiteral litEr && multiplicand instanceof NumberLiteral litAnd) if (multiplier instanceof NumberLiteral litEr && multiplicand instanceof NumberLiteral litAnd)
return Expr.literal(chStart, chEnd, litEr.value * litAnd.value); return Expr.literal(chStart, chEnd, litEr.value * litAnd.value);
return new Modulo(chStart, chEnd, multiplier, multiplicand); return new Multiply(chStart, chEnd, multiplier, multiplicand);
} }
@Override @Override

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.muscript.ast.math; package io.gitlab.jfronny.muscript.ast.math;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr; import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.NumberExpr; import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral; import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
@ -16,7 +16,7 @@ public class Plus extends NumberExpr {
} }
@Override @Override
public Double get(Dynamic<?> dataRoot) { public Double get(Scope dataRoot) {
return augend.get(dataRoot) + addend.get(dataRoot); return augend.get(dataRoot) + addend.get(dataRoot);
} }

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.muscript.ast.math; package io.gitlab.jfronny.muscript.ast.math;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr; import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.NumberExpr; import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral; import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
@ -16,7 +16,7 @@ public class Power extends NumberExpr {
} }
@Override @Override
public Double get(Dynamic<?> dataRoot) { public Double get(Scope dataRoot) {
return Math.pow(base.get(dataRoot), exponent.get(dataRoot)); return Math.pow(base.get(dataRoot), exponent.get(dataRoot));
} }

View File

@ -1,6 +1,6 @@
package io.gitlab.jfronny.muscript.ast.string; package io.gitlab.jfronny.muscript.ast.string;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr; import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.StringExpr; import io.gitlab.jfronny.muscript.ast.StringExpr;
import io.gitlab.jfronny.muscript.ast.literal.StringLiteral; import io.gitlab.jfronny.muscript.ast.literal.StringLiteral;
@ -16,7 +16,7 @@ public class Concatenate extends StringExpr {
} }
@Override @Override
public String get(Dynamic<?> dataRoot) { public String get(Scope dataRoot) {
return left.get(dataRoot) + right.get(dataRoot); return left.get(dataRoot) + right.get(dataRoot);
} }

View File

@ -1,7 +1,7 @@
package io.gitlab.jfronny.muscript.ast.string; package io.gitlab.jfronny.muscript.ast.string;
import io.gitlab.jfronny.commons.StringFormatter; import io.gitlab.jfronny.commons.StringFormatter;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.*; import io.gitlab.jfronny.muscript.ast.*;
import io.gitlab.jfronny.muscript.ast.literal.*; import io.gitlab.jfronny.muscript.ast.literal.*;
@ -14,7 +14,7 @@ public class StringCoerce extends StringExpr {
} }
@Override @Override
public String get(Dynamic<?> dataRoot) { public String get(Scope dataRoot) {
return StringFormatter.toString(inner.get(dataRoot)); return StringFormatter.toString(inner.get(dataRoot));
} }

View File

@ -48,12 +48,15 @@ public class Lexer {
case '=' -> { case '=' -> {
if (match('=')) createToken(Token.EqualEqual); if (match('=')) createToken(Token.EqualEqual);
else unexpected(); else createToken(Token.Assign);
} }
case '!' -> createToken(match('=') ? Token.BangEqual : Token.Bang); case '!' -> createToken(match('=') ? Token.BangEqual : Token.Bang);
case '+' -> createToken(Token.Plus); case '+' -> createToken(Token.Plus);
case '-' -> createToken(Token.Minus); case '-' -> {
if (match('>')) createToken(Token.Arrow);
else createToken(Token.Minus);
}
case '*' -> createToken(Token.Star); case '*' -> createToken(Token.Star);
case '/' -> createToken(Token.Slash); case '/' -> createToken(Token.Slash);
case '%' -> createToken(Token.Percentage); case '%' -> createToken(Token.Percentage);
@ -72,6 +75,8 @@ public class Lexer {
case ')' -> createToken(Token.RightParen); case ')' -> createToken(Token.RightParen);
case '[' -> createToken(Token.LeftBracket); case '[' -> createToken(Token.LeftBracket);
case ']' -> createToken(Token.RightBracket); case ']' -> createToken(Token.RightBracket);
case '{' -> createToken(Token.LeftBrace);
case '}' -> createToken(Token.RightBrace);
default -> unexpected(); default -> unexpected();
} }

View File

@ -7,10 +7,11 @@ import io.gitlab.jfronny.muscript.ast.conditional.UnresolvedConditional;
import io.gitlab.jfronny.muscript.ast.dynamic.*; import io.gitlab.jfronny.muscript.ast.dynamic.*;
import io.gitlab.jfronny.muscript.ast.math.*; import io.gitlab.jfronny.muscript.ast.math.*;
import io.gitlab.jfronny.muscript.ast.string.Concatenate; import io.gitlab.jfronny.muscript.ast.string.Concatenate;
import io.gitlab.jfronny.muscript.data.Script;
import io.gitlab.jfronny.muscript.error.*; import io.gitlab.jfronny.muscript.error.*;
import java.util.ArrayList; import java.text.ParseException;
import java.util.List; import java.util.*;
public class Parser { public class Parser {
private final Lexer lexer; private final Lexer lexer;
@ -22,6 +23,10 @@ public class Parser {
return new Parser(new Lexer(source)).parse().optimize(); return new Parser(new Lexer(source)).parse().optimize();
} }
public static Script parseScript(String source) {
return new Parser(new Lexer(source)).parseScript().optimize();
}
public Parser(Lexer lexer) { public Parser(Lexer lexer) {
this.lexer = lexer; this.lexer = lexer;
} }
@ -35,15 +40,31 @@ public class Parser {
public Expr<?> parse() { public Expr<?> parse() {
advance(); advance();
Expr<?> expr = expression(); Expr<?> expr = expression();
if (!lexer.token.equals(Token.EOF)) if (!isAtEnd())
throw new ParseException(LocationalError.create(lexer.source, lexer.start, lexer.current - 1, "Unexpected element after end of expression")); throw new ParseException(LocationalError.create(lexer.source, lexer.start, lexer.current - 1, "Unexpected element after end of expression"));
return expr; return expr;
} }
/**
* 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());
}
if (expressions.isEmpty()) throw new ParseException(LocationalError.create(lexer.source, lexer.start, lexer.current - 1, "Missing any elements in closure"));
return new Script(expressions);
}
// Expressions // Expressions
private Expr<?> expression() { private Expr<?> expression() {
try { try {
return conditional(); return assignment();
} catch (RuntimeException e) { } catch (RuntimeException e) {
if (e instanceof ParseException) throw e; if (e instanceof ParseException) throw e;
else if (e instanceof LocationalException le) { else if (e instanceof LocationalException le) {
@ -52,6 +73,20 @@ public class Parser {
} }
} }
private Expr<?> assignment() {
Expr<?> expr = conditional();
if (match(Token.Assign)) {
if (expr instanceof Variable variable) {
expr = variable.assign(expression().asDynamicExpr());
} else {
throw error("Attempted to assign non-variable. This is not supported!", expr);
}
}
return expr;
}
private Expr<?> conditional() { private Expr<?> conditional() {
Expr<?> expr = and(); Expr<?> expr = and();
@ -258,6 +293,24 @@ public class Parser {
return expr; return expr;
} }
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());
}
int end = previous.start;
return new Closure(start, end, boundArgs, expressions);
}
throw error("Expected expression."); throw error("Expected expression.");
} }

View File

@ -7,6 +7,7 @@ public enum Token {
True, False, True, False,
And, Or, And, Or,
Assign,
EqualEqual, BangEqual, EqualEqual, BangEqual,
Concat, Concat,
@ -23,5 +24,8 @@ public enum Token {
LeftParen, RightParen, LeftParen, RightParen,
LeftBracket, RightBracket, LeftBracket, RightBracket,
LeftBrace, RightBrace,
Arrow,
Error, EOF Error, EOF
} }

View File

@ -1,23 +0,0 @@
package io.gitlab.jfronny.muscript.data;
import io.gitlab.jfronny.muscript.data.dynamic.DObject;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import java.util.HashMap;
import java.util.Map;
public record ExpressionParameter(Map<String, Dynamic<?>> value) implements DObject {
public ExpressionParameter() {
this(new HashMap<>());
}
@Override
public Map<String, Dynamic<?>> getValue() {
return value;
}
public ExpressionParameter set(String key, Dynamic<?> value) {
this.value.put(key, value);
return this;
}
}

View File

@ -0,0 +1,36 @@
package io.gitlab.jfronny.muscript.data;
import io.gitlab.jfronny.commons.data.ImmCollection;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import java.util.HashMap;
import java.util.Map;
public class Scope implements DObject {
private final DObject source;
private final Map<String, Dynamic<?>> override = new HashMap<>();
public Scope() {
this(DFinal.of(Map.of()));
}
public Scope(DObject source) {
this.source = source;
}
@Override
public Map<String, Dynamic<?>> getValue() {
var map = new HashMap<>(source.getValue());
map.putAll(override);
return ImmCollection.of(map);
}
public Scope set(String key, Dynamic<?> value) {
override.put(key, value);
return this;
}
public Scope fork() {
return new Scope(this);
}
}

View File

@ -0,0 +1,43 @@
package io.gitlab.jfronny.muscript.data;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import java.util.List;
public class Script {
private final List<Expr<?>> steps;
private final DynamicExpr fin;
public Script(List<Expr<?>> expressions) {
this(expressions.subList(0, expressions.size() - 1), expressions.get(expressions.size() - 1).asDynamicExpr());
}
private Script(List<Expr<?>> steps, DynamicExpr fin) {
this.steps = steps;
this.fin = fin;
}
public Dynamic<?> run(DObject scope) {
return run(new Scope(scope));
}
public Dynamic<?> run(Scope scope) {
for (Expr<?> expression : steps) {
expression.get(scope);
}
return fin.get(scope);
}
public DCallable bindTo(Scope scope) {
return DFinal.of(args -> {
scope.set("args", args);
return run(scope);
});
}
public Script optimize() {
return new Script(steps.stream().<Expr<?>>map(Expr::optimize).toList(), fin.optimize());
}
}

View File

@ -0,0 +1,22 @@
package io.gitlab.jfronny.muscript.test;
import io.gitlab.jfronny.muscript.ast.StringExpr;
import io.gitlab.jfronny.muscript.ast.dynamic.Assign;
import io.gitlab.jfronny.muscript.ast.literal.StringLiteral;
import io.gitlab.jfronny.muscript.compiler.Parser;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.test.util.UnforkableScope;
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class AssignTest {
@Test
void testAssignSimple() {
StringExpr expr = Parser.parse("someval = 'test'").asStringExpr();
assertEquals(new Assign(0, 6, "someval", new StringLiteral(10, 15, "test").asDynamicExpr()).asStringExpr(), expr);
Scope scope = new UnforkableScope();
assertEquals("test", expr.get(scope));
assertEquals("test", scope.getValue().get("someval").asString().getValue());
}
}

View File

@ -2,7 +2,7 @@ package io.gitlab.jfronny.muscript.test;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import static io.gitlab.jfronny.muscript.test.MuTestUtil.*; import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.*;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
class BooleanTest { class BooleanTest {

View File

@ -2,7 +2,7 @@ package io.gitlab.jfronny.muscript.test;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import static io.gitlab.jfronny.muscript.test.MuTestUtil.*; import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.*;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
class CallableTest { class CallableTest {

View File

@ -0,0 +1,22 @@
package io.gitlab.jfronny.muscript.test;
import io.gitlab.jfronny.muscript.compiler.Parser;
import org.junit.jupiter.api.*;
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.*;
import static org.junit.jupiter.api.Assertions.*;
class ClosureTest {
@Test
void testScript() {
assertEquals(8, Parser.parseScript("function(2, 1) function(2, 2) function(2, 3)").run(makeArgs()).asNumber().getValue());
}
@Test
void testClosure() {
assertEquals(2, number("{->2}()"));
assertEquals(2, number("{n->n}(2)"));
assertEquals(2, number("{n->n()}({->2})"));
assertEquals(2, number("{->num = 2 num = num * 2 num = num - 2}()"));
}
}

View File

@ -2,7 +2,7 @@ package io.gitlab.jfronny.muscript.test;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import static io.gitlab.jfronny.muscript.test.MuTestUtil.*; import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.*;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
class CombinationTest { class CombinationTest {

View File

@ -2,7 +2,7 @@ package io.gitlab.jfronny.muscript.test;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import static io.gitlab.jfronny.muscript.test.MuTestUtil.*; import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.*;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
class ListTest { class ListTest {

View File

@ -18,10 +18,5 @@ class LocationalErrorTest {
1 | 15 + 'yes' 1 | 15 + 'yes'
^---^-- Here""", ^---^-- Here""",
assertThrows(Parser.ParseException.class, () -> Parser.parse("15 + 'yes'")).error.toString()); assertThrows(Parser.ParseException.class, () -> Parser.parse("15 + 'yes'")).error.toString());
assertEquals("""
Error at '=' (character 9): Unexpected character
1 | string = 'Value'
^-- Here""",
assertThrows(Parser.ParseException.class, () -> Parser.parse("string = 'Value'")).error.toString());
} }
} }

View File

@ -2,7 +2,7 @@ package io.gitlab.jfronny.muscript.test;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import static io.gitlab.jfronny.muscript.test.MuTestUtil.*; import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.*;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
class NumberTest { class NumberTest {

View File

@ -2,7 +2,7 @@ package io.gitlab.jfronny.muscript.test;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import static io.gitlab.jfronny.muscript.test.MuTestUtil.*; import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.*;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
class ObjectTest { class ObjectTest {

View File

@ -2,7 +2,7 @@ package io.gitlab.jfronny.muscript.test;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import static io.gitlab.jfronny.muscript.test.MuTestUtil.*; import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.*;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
class StringTest { class StringTest {

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.muscript.test; package io.gitlab.jfronny.muscript.test.util;
import io.gitlab.jfronny.muscript.data.dynamic.DObject;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic; import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.compiler.Parser; import io.gitlab.jfronny.muscript.compiler.Parser;
import io.gitlab.jfronny.muscript.ast.Expr; import io.gitlab.jfronny.muscript.ast.Expr;
@ -32,7 +33,7 @@ public class MuTestUtil {
return Parser.parse(source).asStringExpr().get(makeArgs()); return Parser.parse(source).asStringExpr().get(makeArgs());
} }
public static Dynamic<?> makeArgs() { public static DObject makeArgs() {
return of(Map.of( return of(Map.of(
"boolean", of(true), "boolean", of(true),
"number", of(15), "number", of(15),

View File

@ -0,0 +1,10 @@
package io.gitlab.jfronny.muscript.test.util;
import io.gitlab.jfronny.muscript.data.Scope;
public class UnforkableScope extends Scope {
@Override
public Scope fork() {
return this;
}
}

View File

@ -0,0 +1,35 @@
package io.gitlab.jfronny.commons.data;
import io.gitlab.jfronny.commons.data.immutable.*;
import java.util.*;
public class ImmCollection {
public static <T> Collection<T> of(Collection<T> collection) {
return new ImmutableCollection<>(collection);
}
public static <T> List<T> of(List<T> list) {
return new ImmutableList<>(list);
}
public static <T> Set<T> of(Set<T> set) {
return new ImmutableSet<>(set);
}
public static <K, V> Map<K, V> of(Map<K, V> map) {
return new ImmutableMap<>(map);
}
public static <T> Iterable<T> of(Iterable<T> iterable) {
return new ImmutableIterable<>(iterable);
}
public static <T> Iterator<T> of(Iterator<T> iterator) {
return new ImmutableIterator<>(iterator);
}
public static <T> ListIterator<T> of(ListIterator<T> listIterator) {
return new ImmutableListIterator<>(listIterator);
}
}

View File

@ -0,0 +1,96 @@
package io.gitlab.jfronny.commons.data.immutable;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.stream.Stream;
public class ImmutableCollection<T, S extends Collection<T>> extends ImmutableIterable<T, S> implements Collection<T> {
public ImmutableCollection(S delegate) {
super(delegate);
}
@Override
public int size() {
return delegate.size();
}
@Override
public boolean isEmpty() {
return delegate.isEmpty();
}
@Override
public boolean contains(Object o) {
return delegate.contains(o);
}
@NotNull
@Override
public Object @NotNull [] toArray() {
return delegate.toArray();
}
@NotNull
@Override
public <T1> T1 @NotNull [] toArray(@NotNull T1 @NotNull [] t1s) {
return delegate.toArray(t1s);
}
@Override
public <T1> T1[] toArray(IntFunction<T1[]> generator) {
return delegate.toArray(generator);
}
@Override
public boolean add(T t) {
throw new UnsupportedOperationException();
}
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
@Override
public boolean containsAll(@NotNull Collection<?> collection) {
return delegate.containsAll(collection);
}
@Override
public boolean addAll(@NotNull Collection<? extends T> collection) {
throw new UnsupportedOperationException();
}
@Override
public boolean removeAll(@NotNull Collection<?> collection) {
throw new UnsupportedOperationException();
}
@Override
public boolean removeIf(Predicate<? super T> filter) {
throw new UnsupportedOperationException();
}
@Override
public boolean retainAll(@NotNull Collection<?> collection) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@Override
public Stream<T> stream() {
return delegate.stream();
}
@Override
public Stream<T> parallelStream() {
return delegate.parallelStream();
}
}

View File

@ -0,0 +1,30 @@
package io.gitlab.jfronny.commons.data.immutable;
import io.gitlab.jfronny.commons.data.ImmCollection;
import org.jetbrains.annotations.NotNull;
import java.util.Iterator;
import java.util.Spliterator;
import java.util.function.Consumer;
public class ImmutableIterable<T, S extends Iterable<T>> extends ImmutableObject<S> implements Iterable<T> {
public ImmutableIterable(S delegate) {
super(delegate);
}
@NotNull
@Override
public Iterator<T> iterator() {
return ImmCollection.of(delegate.iterator());
}
@Override
public void forEach(Consumer<? super T> action) {
delegate.forEach(action);
}
@Override
public Spliterator<T> spliterator() {
return delegate.spliterator();
}
}

View File

@ -0,0 +1,30 @@
package io.gitlab.jfronny.commons.data.immutable;
import java.util.Iterator;
import java.util.function.Consumer;
public class ImmutableIterator<T, S extends Iterator<T>> extends ImmutableObject<S> implements Iterator<T> {
public ImmutableIterator(S delegate) {
super(delegate);
}
@Override
public boolean hasNext() {
return delegate.hasNext();
}
@Override
public T next() {
return delegate.next();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public void forEachRemaining(Consumer<? super T> action) {
delegate.forEachRemaining(action);
}
}

View File

@ -0,0 +1,76 @@
package io.gitlab.jfronny.commons.data.immutable;
import io.gitlab.jfronny.commons.data.ImmCollection;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.function.UnaryOperator;
public class ImmutableList<T, S extends List<T>> extends ImmutableCollection<T, S> implements List<T> {
public ImmutableList(S delegate) {
super(delegate);
}
@Override
public boolean addAll(int i, @NotNull Collection<? extends T> collection) {
throw new UnsupportedOperationException();
}
@Override
public void replaceAll(UnaryOperator<T> operator) {
throw new UnsupportedOperationException();
}
@Override
public void sort(Comparator<? super T> c) {
throw new UnsupportedOperationException();
}
@Override
public T get(int i) {
return delegate.get(i);
}
@Override
public T set(int i, T t) {
throw new UnsupportedOperationException();
}
@Override
public void add(int i, T t) {
throw new UnsupportedOperationException();
}
@Override
public T remove(int i) {
throw new UnsupportedOperationException();
}
@Override
public int indexOf(Object o) {
return delegate.indexOf(o);
}
@Override
public int lastIndexOf(Object o) {
return delegate.lastIndexOf(o);
}
@NotNull
@Override
public ListIterator<T> listIterator() {
return ImmCollection.of(delegate.listIterator());
}
@NotNull
@Override
public ListIterator<T> listIterator(int i) {
return ImmCollection.of(delegate.listIterator(i));
}
@NotNull
@Override
public List<T> subList(int i, int i1) {
return ImmCollection.of(delegate.subList(i, i1));
}
}

View File

@ -0,0 +1,39 @@
package io.gitlab.jfronny.commons.data.immutable;
import java.util.ListIterator;
public class ImmutableListIterator<T, S extends ListIterator<T>> extends ImmutableIterator<T, S> implements ListIterator<T> {
public ImmutableListIterator(S delegate) {
super(delegate);
}
@Override
public boolean hasPrevious() {
return delegate.hasPrevious();
}
@Override
public T previous() {
return delegate.previous();
}
@Override
public int nextIndex() {
return delegate.nextIndex();
}
@Override
public int previousIndex() {
return delegate.previousIndex();
}
@Override
public void set(T t) {
throw new UnsupportedOperationException();
}
@Override
public void add(T t) {
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,137 @@
package io.gitlab.jfronny.commons.data.immutable;
import io.gitlab.jfronny.commons.data.ImmCollection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.*;
public class ImmutableMap<K, V> extends ImmutableObject<Map<K, V>> implements Map<K, V> {
public ImmutableMap(Map<K, V> delegate) {
super(delegate);
}
@Override
public int size() {
return delegate.size();
}
@Override
public boolean isEmpty() {
return delegate.isEmpty();
}
@Override
public boolean containsKey(Object o) {
return delegate.containsKey(o);
}
@Override
public boolean containsValue(Object o) {
return delegate.containsValue(o);
}
@Override
public V get(Object o) {
return delegate.get(o);
}
@Nullable
@Override
public V put(K k, V v) {
throw new UnsupportedOperationException();
}
@Override
public V remove(Object o) {
throw new UnsupportedOperationException();
}
@Override
public void putAll(@NotNull Map<? extends K, ? extends V> map) {
throw new UnsupportedOperationException();
}
@Override
public void clear() {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Set<K> keySet() {
return ImmCollection.of(delegate.keySet());
}
@NotNull
@Override
public Collection<V> values() {
return ImmCollection.of(delegate.values());
}
@NotNull
@Override
public Set<Entry<K, V>> entrySet() {
return ImmCollection.of(delegate.entrySet());
}
@Override
public V getOrDefault(Object key, V defaultValue) {
return delegate.getOrDefault(key, defaultValue);
}
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
delegate.forEach(action);
}
@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
throw new UnsupportedOperationException();
}
@Nullable
@Override
public V putIfAbsent(K key, V value) {
if (containsKey(key)) return get(key);
throw new UnsupportedOperationException();
}
@Override
public boolean remove(Object key, Object value) {
throw new UnsupportedOperationException();
}
@Override
public boolean replace(K key, V oldValue, V newValue) {
throw new UnsupportedOperationException();
}
@Nullable
@Override
public V replace(K key, V value) {
throw new UnsupportedOperationException();
}
@Override
public V computeIfAbsent(K key, @NotNull Function<? super K, ? extends V> mappingFunction) {
if (containsKey(key)) return get(key);
throw new UnsupportedOperationException();
}
@Override
public V computeIfPresent(K key, @NotNull BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
throw new UnsupportedOperationException();
}
@Override
public V compute(K key, @NotNull BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
throw new UnsupportedOperationException();
}
@Override
public V merge(K key, @NotNull V value, @NotNull BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,24 @@
package io.gitlab.jfronny.commons.data.immutable;
public class ImmutableObject<S> {
protected final S delegate;
public ImmutableObject(S delegate) {
this.delegate = delegate;
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(Object obj) {
return obj instanceof ImmutableObject<?> ob ? delegate.equals(ob.delegate) : delegate.equals(obj);
}
@Override
public String toString() {
return delegate.toString();
}
}

View File

@ -0,0 +1,15 @@
package io.gitlab.jfronny.commons.data.immutable;
import java.util.Set;
public class ImmutableSet<T, S extends Set<T>> extends ImmutableCollection<T, S> implements Set<T> {
public static class Simple<T> extends ImmutableSet<T, Set<T>> {
public Simple(Set<T> delegate) {
super(delegate);
}
}
public ImmutableSet(S delegate) {
super(delegate);
}
}