muScript: support for custom objects, script decompiling and Dynamic serialization

This commit is contained in:
Johannes Frohnmeyer 2023-03-12 15:28:44 +01:00
parent cfbc10387f
commit efb1512a60
Signed by: Johannes
GPG Key ID: E76429612C2929F4
73 changed files with 1077 additions and 267 deletions

View File

@ -2,7 +2,7 @@ plugins {
id("commons.library")
}
version = "1.1-SNAPSHOT"
version = "1.2-SNAPSHOT"
publishing {
publications {

View File

@ -2,14 +2,14 @@ package io.gitlab.jfronny.muscript;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.data.dynamic.additional.*;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.Map;
import java.util.Random;
import java.util.function.Supplier;
import static io.gitlab.jfronny.muscript.data.dynamic.DFinal.of;
import static io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal.of;
public class StandardLib {
private static final Random rnd = new Random();
@ -32,7 +32,7 @@ public class StandardLib {
int a1 = args.get(1).asNumber().getValue().intValue();
int a2 = args.get(2).asNumber().getValue().intValue();
return new DDate(() -> LocalDate.of(a0, a1, a2));
})))
}, () -> "date")))
.set("time", new DCallableObject(Map.of(
"now", new DTime(LocalTime::now)
), DFinal.of(args -> {
@ -43,7 +43,7 @@ public class StandardLib {
int a1 = args.get(1).asNumber().getValue().intValue();
int a2 = args.get(2).asNumber().getValue().intValue();
return new DTime(() -> LocalTime.of(a0, a1, a2));
})))
}, () -> "time")))
.set("round", StandardLib::round)
.set("floor", StandardLib::floor)
@ -64,59 +64,10 @@ public class StandardLib {
.set("map", StandardLib::map)
.set("flatMap", StandardLib::flatMap)
.set("fold", StandardLib::fold)
.set("forEach", StandardLib::forEach);
}
.set("forEach", StandardLib::forEach)
private record DDate(Supplier<LocalDate> date) implements DObject {
@Override
public Map<String, Dynamic<?>> getValue() {
return Map.of(
"year", DFinal.of(date.get().getYear()),
"month", DFinal.of(date.get().getMonthValue()),
"day", DFinal.of(date.get().getDayOfMonth())
);
}
@Override
public DString asString() {
return DFinal.of(toString());
}
@Override
public DNumber asNumber() {
return DFinal.of(date.get().toEpochDay());
}
@Override
public String toString() {
return date.get().toString();
}
}
private record DTime(Supplier<LocalTime> time) implements DObject {
@Override
public Map<String, Dynamic<?>> getValue() {
return Map.of(
"hour", DFinal.of(time.get().getHour()),
"minute", DFinal.of(time.get().getMinute()),
"second", DFinal.of(time.get().getSecond())
);
}
@Override
public DString asString() {
return DFinal.of(toString());
}
@Override
public DNumber asNumber() {
return DFinal.of(time.get().toSecondOfDay());
}
@Override
public String toString() {
return time.get().toString();
}
.set("callableObject", StandardLib::callableObject)
.set("enum", StandardLib::enum_);
}
// Numbers
@ -199,29 +150,29 @@ public class StandardLib {
}
public static DList filter(DList args) {
if (args.size() != 2) throw new IllegalArgumentException("Invalid number of arguments for filter: expected at least 2 but got " + args.size());
if (args.size() != 2) throw new IllegalArgumentException("Invalid number of arguments for filter: expected 2 but got " + args.size());
DCallable dc = args.get(1).asCallable();
return of(args.get(0).asList().getValue().stream().filter(a -> dc.call(a).asBool().getValue()).toList());
}
public static DList map(DList args) {
if (args.size() != 2) throw new IllegalArgumentException("Invalid number of arguments for map: expected at least 2 but got " + args.size());
if (args.size() != 2) throw new IllegalArgumentException("Invalid number of arguments for map: expected 2 but got " + args.size());
return of(args.get(0).asList().getValue().stream().<Dynamic<?>>map(args.get(1).asCallable()::call).toList());
}
public static DList flatMap(DList args) {
if (args.size() != 2) throw new IllegalArgumentException("Invalid number of arguments for flatMap: expected at least 2 but got " + args.size());
if (args.size() != 2) throw new IllegalArgumentException("Invalid number of arguments for flatMap: expected 2 but got " + args.size());
DCallable dc = args.get(1).asCallable();
return of(args.get(0).asList().getValue().stream().flatMap(a -> dc.call(a).asList().getValue().stream()).toList());
}
public static Dynamic<?> fold(DList args) {
if (args.size() != 3) throw new IllegalArgumentException("Invalid number of arguments for fold: expected at least 3 but got " + args.size());
if (args.size() != 3) throw new IllegalArgumentException("Invalid number of arguments for fold: expected 3 but got " + args.size());
return args.get(0).asList().getValue().stream().reduce(args.get(1), args.get(2).asCallable()::call);
}
public static Dynamic<?> forEach(DList args) {
if (args.size() != 2) throw new IllegalArgumentException("Invalid number of arguments for forEach: expected at least 2 but got " + args.size());
if (args.size() != 2) throw new IllegalArgumentException("Invalid number of arguments for forEach: expected 2 but got " + args.size());
Dynamic<?> result = new DNull();
DCallable dc = args.get(1).asCallable();
for (Dynamic<?> dynamic : args.get(0).asList().getValue()) {
@ -229,4 +180,16 @@ public class StandardLib {
}
return result;
}
// Objects
public static DCallableObject callableObject(DList args) {
if (args.size() != 2) throw new IllegalArgumentException("Invalid number of arguments for callableObject: expected 2 but got " + args.size());
return new DCallableObject(args.get(0).asObject().getValue(), args.get(1).asCallable());
}
public static DEnum enum_(DList args) {
if (args.size() == 1) return new DEnum(args.get(0).asObject().getValue());
else if (args.size() == 2) return new DEnum(args.get(0).asObject().getValue(), args.get(1).asString().getValue());
else throw new IllegalArgumentException("Invalid number of arguments for enum: expected 1 or 2 but got " + args.size());
}
}

View File

@ -1,9 +1,7 @@
package io.gitlab.jfronny.muscript;
import io.gitlab.jfronny.commons.log.Logger;
import java.util.Arrays;
import java.util.stream.Collectors;
import io.gitlab.jfronny.muscript.compiler.Decompilable;
public class StarScriptIngester {
private static final Logger LOGGER = Logger.forName("MuScript");
@ -21,7 +19,7 @@ public class StarScriptIngester {
if (c == '{') {
if (!currbuf.isEmpty()) {
if (!result.isEmpty()) result.append(" || ");
result.append(enquote(currbuf.toString()));
result.append(Decompilable.enquote(currbuf.toString()));
}
currbuf = new StringBuilder();
state = State.UnquotedInner;
@ -61,7 +59,7 @@ public class StarScriptIngester {
if (!currbuf.isEmpty() && !result.isEmpty()) result.append(" || ");
switch (state) {
case Surrounding -> {
if (!currbuf.isEmpty()) result.append(enquote(currbuf.toString()));
if (!currbuf.isEmpty()) result.append(Decompilable.enquote(currbuf.toString()));
}
case UnquotedInner -> {
LOGGER.warn("Starscript code segment improperly closed, closing automatically");
@ -79,16 +77,6 @@ public class StarScriptIngester {
return result.toString();
}
/**
* Creates quotes around a string, supports strings containing quotes
*/
private static String enquote(String literalText) {
if (!literalText.contains("'")) return "'" + literalText + "'";
if (!literalText.contains("\"")) return "\"" + literalText + "\"";
return Arrays.stream(literalText.split("'")).map(s -> "'" + s + "'")
.collect(Collectors.joining(" || \"'\" || "));
}
enum State {
Surrounding, UnquotedInner, SingleQuoteInner, DoubleQuoteInner
}

View File

@ -1,11 +1,12 @@
package io.gitlab.jfronny.muscript.ast;
import io.gitlab.jfronny.muscript.ast.dynamic.DynamicCoerce;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.compiler.Type;
public abstract non-sealed class BoolExpr extends Expr<Boolean> {
protected BoolExpr(int chStart, int chEnd) {
super(chStart, chEnd);
protected BoolExpr(Order order, int chStart, int chEnd) {
super(order, chStart, chEnd);
}
@Override

View File

@ -1,12 +1,13 @@
package io.gitlab.jfronny.muscript.ast;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.ast.dynamic.unpack.*;
import io.gitlab.jfronny.muscript.compiler.Type;
public abstract non-sealed class DynamicExpr extends Expr<Dynamic<?>> {
protected DynamicExpr(int chStart, int chEnd) {
super(chStart, chEnd);
protected DynamicExpr(Order order, int chStart, int chEnd) {
super(order, chStart, chEnd);
}
@Override

View File

@ -1,17 +1,21 @@
package io.gitlab.jfronny.muscript.ast;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.annotations.CanThrow;
import io.gitlab.jfronny.muscript.ast.literal.*;
import io.gitlab.jfronny.muscript.ast.string.StringCoerce;
import io.gitlab.jfronny.muscript.compiler.Type;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.DObject;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.error.TypeMismatchException;
@CanThrow
public abstract sealed class Expr<T> permits BoolExpr, DynamicExpr, NullLiteral, NumberExpr, StringExpr {
protected Expr(int chStart, int chEnd) {
public abstract sealed class Expr<T> extends Decompilable
permits BoolExpr, DynamicExpr, NullLiteral, NumberExpr, StringExpr {
public final int chStart;
public final int chEnd;
protected Expr(Order order, int chStart, int chEnd) {
super(order);
this.chStart = chStart;
this.chEnd = chEnd;
}
@ -26,8 +30,6 @@ public abstract sealed class Expr<T> permits BoolExpr, DynamicExpr, NullLiteral,
return get(dataRoot.asObject());
}
public abstract Expr<T> optimize();
public final int chStart;
public final int chEnd;
public BoolExpr asBoolExpr() {
if (this instanceof BoolExpr e) return e;

View File

@ -1,16 +1,18 @@
package io.gitlab.jfronny.muscript.ast;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.DNull;
import io.gitlab.jfronny.muscript.annotations.CanThrow;
import io.gitlab.jfronny.muscript.ast.literal.DynamicLiteral;
import io.gitlab.jfronny.muscript.compiler.Type;
import io.gitlab.jfronny.muscript.error.LocationalException;
import java.io.IOException;
@CanThrow
public final class NullLiteral extends Expr<Object> {
public NullLiteral(int chStart, int chEnd) {
super(chStart, chEnd);
super(Order.Primary, chStart, chEnd);
}
@Override
@ -28,6 +30,11 @@ public final class NullLiteral extends Expr<Object> {
return this;
}
@Override
public void decompile(ExprWriter writer) throws IOException {
writer.append("null");
}
@Override
public DynamicExpr asDynamicExpr() {
return new DynamicLiteral<>(chStart, chEnd, new DNull());

View File

@ -1,11 +1,12 @@
package io.gitlab.jfronny.muscript.ast;
import io.gitlab.jfronny.muscript.ast.dynamic.DynamicCoerce;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.compiler.Type;
public abstract non-sealed class NumberExpr extends Expr<Double> {
protected NumberExpr(int chStart, int chEnd) {
super(chStart, chEnd);
protected NumberExpr(Order order, int chStart, int chEnd) {
super(order, chStart, chEnd);
}
@Override

View File

@ -1,11 +1,12 @@
package io.gitlab.jfronny.muscript.ast;
import io.gitlab.jfronny.muscript.ast.dynamic.DynamicCoerce;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.compiler.Type;
public abstract non-sealed class StringExpr extends Expr<String> {
protected StringExpr(int chStart, int chEnd) {
super(chStart, chEnd);
protected StringExpr(Order order, int chStart, int chEnd) {
super(order, chStart, chEnd);
}
@Override

View File

@ -1,16 +1,20 @@
package io.gitlab.jfronny.muscript.ast.bool;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral;
import java.io.IOException;
public class And extends BoolExpr {
private final BoolExpr left;
private final BoolExpr right;
public And(int chStart, int chEnd, BoolExpr left, BoolExpr right) {
super(chStart, chEnd);
super(Order.And, chStart, chEnd);
this.left = left;
this.right = right;
}
@ -29,6 +33,13 @@ public class And extends BoolExpr {
return new And(chStart, chEnd, left, right);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(left, writer, false);
writer.append(" & ");
parenthesize(right, writer, true);
}
@Override
public boolean equals(Object obj) {
return obj instanceof And and && left.equals(and.left) && right.equals(and.right);

View File

@ -1,15 +1,22 @@
package io.gitlab.jfronny.muscript.ast.bool;
import io.gitlab.jfronny.muscript.ast.compare.Equal;
import io.gitlab.jfronny.muscript.ast.compare.Greater;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral;
import java.io.IOException;
public class Not extends BoolExpr {
private final BoolExpr inner;
public Not(int chStart, int chEnd, BoolExpr inner) {
super(chStart, chEnd);
super(Order.Unary, chStart, chEnd);
this.inner = inner;
}
@ -26,6 +33,28 @@ public class Not extends BoolExpr {
return new Not(chStart, chEnd, inner);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
if (inner instanceof Equal eq) {
parenthesize(eq.left, writer, false);
writer.append(" != ");
parenthesize(eq.right, writer, true);
} else if (inner instanceof Greater gt) {
if (gt.left instanceof NumberLiteral && !(gt.right instanceof NumberLiteral)) {
parenthesize(gt.right, writer, false);
writer.append(" >= ");
parenthesize(gt.left, writer, true);
} else {
parenthesize(gt.left, writer, false);
writer.append(" <= ");
parenthesize(gt.right, writer, true);
}
} else {
writer.append("!");
parenthesize(inner, writer, false);
}
}
@Override
public boolean equals(Object obj) {
return obj instanceof Not not && inner.equals(not.inner);

View File

@ -1,16 +1,20 @@
package io.gitlab.jfronny.muscript.ast.bool;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral;
import java.io.IOException;
public class Or extends BoolExpr {
private final BoolExpr left;
private final BoolExpr right;
public Or(int chStart, int chEnd, BoolExpr left, BoolExpr right) {
super(chStart, chEnd);
super(Order.Or, chStart, chEnd);
this.left = left;
this.right = right;
}
@ -29,6 +33,13 @@ public class Or extends BoolExpr {
return new Or(chStart, chEnd, left, right);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(left, writer, false);
writer.append(" | ");
parenthesize(right, writer, true);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Or or && left.equals(or.left) && right.equals(or.right);

View File

@ -1,18 +1,21 @@
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.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import java.io.IOException;
import java.util.Objects;
public class Equal extends BoolExpr {
private final Expr<?> left;
private final Expr<?> right;
public final Expr<?> left;
public final Expr<?> right;
public Equal(int chStart, int chEnd, Expr<?> left, Expr<?> right) {
super(chStart, chEnd);
super(Order.Equality, chStart, chEnd);
this.left = left;
this.right = right;
}
@ -35,6 +38,13 @@ public class Equal extends BoolExpr {
return new Equal(chStart, chEnd, left, right);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(left, writer, false);
writer.append(" == ");
parenthesize(right, writer, true);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Equal equal && left.equals(equal.left) && right.equals(equal.right);

View File

@ -1,16 +1,20 @@
package io.gitlab.jfronny.muscript.ast.compare;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.*;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import io.gitlab.jfronny.muscript.ast.math.*;
import java.io.IOException;
public class Greater extends BoolExpr {
private final NumberExpr left;
private final NumberExpr right;
public final NumberExpr left;
public final NumberExpr right;
public Greater(int chStart, int chEnd, NumberExpr left, NumberExpr right) {
super(chStart, chEnd);
super(Order.Comparison, chStart, chEnd);
this.left = left;
this.right = right;
}
@ -29,7 +33,7 @@ public class Greater extends BoolExpr {
if (left instanceof Divide divide)
return new Greater(chStart, chEnd, divide.dividend, new Multiply(divide.chStart, divide.chEnd, right, divide.divisor)).optimize();
if (left instanceof Invert invert)
return new Less(chStart, chEnd, invert.inner, new Invert(right.chStart, right.chEnd, right)).optimize();
return new Greater(chStart, chEnd, new Invert(right.chStart, right.chEnd, right), invert.inner).optimize();
if (left instanceof Minus minus)
return new Greater(chStart, chEnd, minus.minuend, new Plus(minus.chStart, minus.chEnd, minus.subtrahend, right)).optimize();
// Modulo is left out because it is too complicated for this naive impl
@ -40,6 +44,19 @@ public class Greater extends BoolExpr {
return new Greater(chStart, chEnd, left, right);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
if (left instanceof NumberLiteral && !(right instanceof NumberLiteral)) {
parenthesize(right, writer, false);
writer.append(" < ");
parenthesize(left, writer, true);
} else {
parenthesize(left, writer, false);
writer.append(" > ");
parenthesize(right, writer, true);
}
}
@Override
public boolean equals(Object obj) {
return obj instanceof Greater greater && left.equals(greater.left) && right.equals(greater.right);

View File

@ -1,47 +0,0 @@
package io.gitlab.jfronny.muscript.ast.compare;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.*;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import io.gitlab.jfronny.muscript.ast.math.*;
public class Less extends BoolExpr {
private final NumberExpr left;
private final NumberExpr right;
public Less(int chStart, int chEnd, NumberExpr left, NumberExpr right) {
super(chStart, chEnd);
this.left = left;
this.right = right;
}
@Override
public Boolean get(Scope dataRoot) {
return left.get(dataRoot) < right.get(dataRoot);
}
@Override
public BoolExpr optimize() {
NumberExpr left = this.left.optimize();
NumberExpr right = this.right.optimize();
if (left instanceof NumberLiteral litL && right instanceof NumberLiteral litR)
return Expr.literal(chStart, chEnd, litL.value < litR.value);
if (left instanceof Divide divide)
return new Less(chStart, chEnd, divide.dividend, new Multiply(divide.chStart, divide.chEnd, divide.divisor, right)).optimize();
if (left instanceof Invert invert)
return new Greater(chStart, chEnd, invert.inner, new Invert(right.chStart, right.chEnd, right)).optimize();
if (left instanceof Minus minus)
return new Less(chStart, chEnd, minus.minuend, new Plus(minus.chStart, minus.chEnd, minus.subtrahend, right)).optimize();
// Modulo is left out because it is too complicated for this naive impl
// Multiply is left out since it would transform into a division and may be 0
if (left instanceof Plus plus)
return new Less(chStart, chEnd, plus.augend, new Minus(plus.chStart, plus.chEnd, plus.addend, right)).optimize();
// Power is left out because it can't be transformed cleanly either
return new Less(chStart, chEnd, left, right);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Less less && left.equals(less.left) && right.equals(less.right);
}
}

View File

@ -1,16 +1,20 @@
package io.gitlab.jfronny.muscript.ast.conditional;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral;
import java.io.IOException;
public class BoolConditional extends BoolExpr {
public final BoolExpr condition;
public final BoolExpr trueExpr;
public final BoolExpr falseExpr;
public BoolConditional(int chStart, int chEnd, BoolExpr condition, BoolExpr trueExpr, BoolExpr falseExpr) {
super(chStart, chEnd);
super(Order.Conditional, chStart, chEnd);
this.condition = condition;
this.trueExpr = trueExpr;
this.falseExpr = falseExpr;
@ -31,6 +35,15 @@ public class BoolConditional extends BoolExpr {
return new BoolConditional(chStart, chEnd, condition, trueExpr, falseExpr);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(condition, writer, true);
writer.append(" ? ");
trueExpr.decompile(writer);
writer.append(" : ");
falseExpr.decompile(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof BoolConditional conditional

View File

@ -1,18 +1,22 @@
package io.gitlab.jfronny.muscript.ast.conditional;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral;
import java.io.IOException;
public class DynamicConditional extends DynamicExpr {
public final BoolExpr condition;
public final DynamicExpr trueExpr;
public final DynamicExpr falseExpr;
public DynamicConditional(int chStart, int chEnd, BoolExpr condition, DynamicExpr trueExpr, DynamicExpr falseExpr) {
super(chStart, chEnd);
super(Order.Conditional, chStart, chEnd);
this.condition = condition;
this.trueExpr = trueExpr;
this.falseExpr = falseExpr;
@ -33,6 +37,15 @@ public class DynamicConditional extends DynamicExpr {
return new DynamicConditional(chStart, chEnd, condition, trueExpr, falseExpr);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(condition, writer, true);
writer.append(" ? ");
trueExpr.decompile(writer);
writer.append(" : ");
falseExpr.decompile(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof DynamicConditional conditional

View File

@ -1,17 +1,21 @@
package io.gitlab.jfronny.muscript.ast.conditional;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral;
import java.io.IOException;
public class NumberConditional extends NumberExpr {
public final BoolExpr condition;
public final NumberExpr trueExpr;
public final NumberExpr falseExpr;
public NumberConditional(int chStart, int chEnd, BoolExpr condition, NumberExpr trueExpr, NumberExpr falseExpr) {
super(chStart, chEnd);
super(Order.Conditional, chStart, chEnd);
this.condition = condition;
this.trueExpr = trueExpr;
this.falseExpr = falseExpr;
@ -32,6 +36,15 @@ public class NumberConditional extends NumberExpr {
return new NumberConditional(chStart, chEnd, condition, trueExpr, falseExpr);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(condition, writer, true);
writer.append(" ? ");
trueExpr.decompile(writer);
writer.append(" : ");
falseExpr.decompile(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof NumberConditional conditional

View File

@ -1,17 +1,21 @@
package io.gitlab.jfronny.muscript.ast.conditional;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.BoolExpr;
import io.gitlab.jfronny.muscript.ast.StringExpr;
import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral;
import java.io.IOException;
public class StringConditional extends StringExpr {
public final BoolExpr condition;
public final StringExpr trueExpr;
public final StringExpr falseExpr;
public StringConditional(int chStart, int chEnd, BoolExpr condition, StringExpr trueExpr, StringExpr falseExpr) {
super(chStart, chEnd);
super(Order.Conditional, chStart, chEnd);
this.condition = condition;
this.trueExpr = trueExpr;
this.falseExpr = falseExpr;
@ -32,6 +36,15 @@ public class StringConditional extends StringExpr {
return new StringConditional(chStart, chEnd, condition, trueExpr, falseExpr);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(condition, writer, true);
writer.append(" ? ");
trueExpr.decompile(writer);
writer.append(" : ");
falseExpr.decompile(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof StringConditional conditional

View File

@ -1,12 +1,14 @@
package io.gitlab.jfronny.muscript.ast.conditional;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.DNull;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.ast.*;
import io.gitlab.jfronny.muscript.annotations.CanThrow;
import io.gitlab.jfronny.muscript.ast.literal.BoolLiteral;
import io.gitlab.jfronny.muscript.compiler.Type;
import java.io.IOException;
@CanThrow
public class UnresolvedConditional extends DynamicExpr {
@ -15,7 +17,7 @@ public class UnresolvedConditional extends DynamicExpr {
private final Expr<?> falseExpr;
public UnresolvedConditional(int chStart, int chEnd, BoolExpr condition, Expr<?> trueExpr, Expr<?> falseExpr) {
super(chStart, chEnd);
super(Order.Conditional, chStart, chEnd);
this.condition = condition;
this.trueExpr = trueExpr;
this.falseExpr = falseExpr;
@ -36,6 +38,15 @@ public class UnresolvedConditional extends DynamicExpr {
return new UnresolvedConditional(chStart, chEnd, condition, trueExpr, falseExpr);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(condition, writer, true);
writer.append(" ? ");
trueExpr.decompile(writer);
writer.append(" : ");
falseExpr.decompile(writer);
}
@Override
public Dynamic<?> get(Scope dataRoot) {
// unresolved conditionals may exist as root elements in scripts/closures

View File

@ -1,16 +1,19 @@
package io.gitlab.jfronny.muscript.ast.dynamic;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.error.LocationalException;
import java.io.IOException;
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);
super(Order.Primary, chStart, chEnd);
if (name.equals("this")) throw new LocationalException(chStart, chEnd, "Cannot reassign 'this'");
this.name = name;
this.value = value;
@ -28,6 +31,13 @@ public class Assign extends DynamicExpr {
return new Assign(chStart, chEnd, name, value.optimize());
}
@Override
public void decompile(ExprWriter writer) throws IOException {
if (!Lexer.isValidId(name)) throw new IllegalArgumentException("Not a valid variable name: " + name);
writer.append(name).append(" = ");
value.decompile(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Assign assign

View File

@ -1,10 +1,12 @@
package io.gitlab.jfronny.muscript.ast.dynamic;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.DFinal;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
@ -13,7 +15,7 @@ public class Bind extends DynamicExpr {
public final DynamicExpr parameter;
public Bind(int chStart, int chEnd, DynamicExpr callable, DynamicExpr parameter) {
super(chStart, chEnd);
super(Order.Call, chStart, chEnd);
this.callable = callable;
this.parameter = parameter;
}
@ -27,7 +29,20 @@ public class Bind extends DynamicExpr {
.asCallable()
.getValue()
.apply(DFinal.of(argsWithParameter));
});
}, this::toString);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(parameter, writer, false);
writer.append("::");
if (callable instanceof Variable) {
callable.decompile(writer);
} else {
writer.append('(');
callable.decompile(writer);
writer.append(')');
}
}
@Override

View File

@ -3,10 +3,14 @@ package io.gitlab.jfronny.muscript.ast.dynamic;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.annotations.CanThrow;
import io.gitlab.jfronny.muscript.annotations.UncheckedDynamic;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal;
import io.gitlab.jfronny.muscript.error.LocationalException;
import java.io.IOException;
import java.util.*;
import java.util.stream.Stream;
@ -17,7 +21,7 @@ public class Call extends DynamicExpr {
private final List<Arg> args;
public Call(int chStart, int chEnd, DynamicExpr left, List<Arg> args) {
super(chStart, chEnd);
super(Order.Call, chStart, chEnd);
this.left = left;
this.args = args;
}
@ -48,6 +52,33 @@ public class Call extends DynamicExpr {
return new Call(chStart, chEnd, left, args);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(left, writer, false);
if (args.size() > 3) {
writer.increaseIndent();
writer.append("(\n");
boolean first = true;
for (Arg arg : args) {
if (!first) writer.append(",\n");
first = false;
arg.expr.decompile(writer);
if (arg.variadic) writer.append("...");
}
writer.decreaseIndent();
writer.append("\n)");
} else {
writer.append("(");
boolean first = true;
for (Arg arg : args) {
if (!first) writer.append(", ");
first = false;
arg.expr.decompile(writer);
if (arg.variadic) writer.append("...");
}
}
}
@Override
public boolean equals(Object obj) {
return obj instanceof Call call && left.equals(call.left) && args.equals(call.args);

View File

@ -2,12 +2,16 @@ 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.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal;
import io.gitlab.jfronny.muscript.error.LocationalException;
import java.io.IOException;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class Closure extends DynamicExpr {
private final List<String> boundArgs;
@ -20,7 +24,7 @@ public class Closure extends DynamicExpr {
}
private Closure(int chStart, int chEnd, List<String> boundArgs, List<Expr<?>> steps, DynamicExpr fin, boolean variadic) {
super(chStart, chEnd);
super(Order.Primary, chStart, chEnd);
this.boundArgs = List.copyOf(boundArgs);
this.steps = List.copyOf(steps);
this.fin = fin;
@ -46,7 +50,7 @@ public class Closure extends DynamicExpr {
step.get(fork);
}
return fin.get(fork);
});
}, this::toString);
}
@Override
@ -55,6 +59,25 @@ public class Closure extends DynamicExpr {
return new Closure(chStart, chEnd, boundArgs, steps.stream().<Expr<?>>map(Expr::optimize).toList(), fin.optimize(), variadic);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
writer.append("{ ");
for (int i = 0; i < boundArgs.size(); i++) {
String arg = boundArgs.get(i);
if (!Lexer.isValidId(arg)) throw new IllegalArgumentException("Not a valid argument name: " + arg);
writer.append(arg);
if (i == boundArgs.size() - 1 && variadic) writer.append("...");
writer.append(' ');
}
writer.append("->").increaseIndent();
for (Expr<?> expr : stream().toList()) {
writer.append("\n");
expr.decompile(writer);
writer.append(";");
}
writer.decreaseIndent().append("\n}");
}
@Override
public boolean equals(Object obj) {
return obj instanceof Closure closure
@ -62,4 +85,8 @@ public class Closure extends DynamicExpr {
&& steps.equals(closure.steps)
&& fin.equals(closure.fin);
}
public Stream<Expr<?>> stream() {
return Stream.concat(steps.stream(), Stream.of(fin));
}
}

View File

@ -2,14 +2,18 @@ package io.gitlab.jfronny.muscript.ast.dynamic;
import io.gitlab.jfronny.muscript.ast.*;
import io.gitlab.jfronny.muscript.ast.dynamic.unpack.*;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal;
import java.io.IOException;
public class DynamicCoerce extends DynamicExpr {
public final Expr<?> inner;
public DynamicCoerce(int chStart, int chEnd, Expr<?> inner) {
super(chStart, chEnd);
super(inner.order, chStart, chEnd);
this.inner = inner;
if (!(inner instanceof DynamicExpr)
&& !(inner instanceof BoolExpr)
@ -39,6 +43,11 @@ public class DynamicCoerce extends DynamicExpr {
return new DynamicCoerce(chStart, chEnd, inner);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
inner.decompile(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof DynamicCoerce coerce && inner.equals(coerce.inner);

View File

@ -4,11 +4,14 @@ import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.annotations.CanThrow;
import io.gitlab.jfronny.muscript.annotations.UncheckedDynamic;
import io.gitlab.jfronny.muscript.compiler.Type;
import io.gitlab.jfronny.muscript.ast.literal.StringLiteral;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.error.TypeMismatchException;
import java.io.IOException;
@CanThrow
@UncheckedDynamic
public class Get extends DynamicExpr {
@ -16,7 +19,7 @@ public class Get extends DynamicExpr {
private final Expr<?> name;
public Get(int chStart, int chEnd, DynamicExpr left, Expr<?> name) {
super(chStart, chEnd);
super(Order.Call, chStart, chEnd);
this.left = left;
this.name = name;
if (name.getResultType() != Type.String && name.getResultType() != Type.Number && name.getResultType() != Type.Dynamic) {
@ -42,6 +45,18 @@ public class Get extends DynamicExpr {
return new Get(chStart, chEnd, left, name);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(left, writer, false);
if (name instanceof StringLiteral lit && Lexer.isValidId(lit.value)) {
writer.append('.').append(lit.value);
} else {
writer.append('[');
name.decompile(writer);
writer.append(']');
}
}
@Override
public boolean equals(Object obj) {
return obj instanceof Get get && left.equals(get.left) && name.equals(get.name);

View File

@ -0,0 +1,57 @@
package io.gitlab.jfronny.muscript.ast.dynamic;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.literal.DynamicLiteral;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
public class ObjectLiteral extends DynamicExpr {
private final Map<String, DynamicExpr> content;
public ObjectLiteral(int chStart, int chEnd, Map<String, DynamicExpr> content) {
super(Order.Primary, chStart, chEnd);
this.content = content;
}
@Override
public Dynamic<?> get(Scope dataRoot) {
Map<String, Dynamic<?>> result = new LinkedHashMap<>();
this.content.forEach((k, v) -> result.put(k, v.get(dataRoot)));
return DFinal.of(result);
}
@Override
public DynamicExpr optimize() {
Map<String, DynamicExpr> content = new LinkedHashMap<>();
Map<String, Dynamic<?>> literalContent = new LinkedHashMap<>();
boolean literal = true;
for (Map.Entry<String, DynamicExpr> entry : this.content.entrySet()) {
DynamicExpr de = entry.getValue().optimize();
if (de instanceof DynamicLiteral<?> dl && literal) literalContent.put(entry.getKey(), dl.value);
else literal = false;
content.put(entry.getKey(), de);
}
if (literal) return new DynamicLiteral<>(chStart, chEnd, DFinal.of(literalContent));
return new ObjectLiteral(chStart, chEnd, content);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
writer.increaseIndent().append("{\n");
boolean first = true;
for (Map.Entry<String, DynamicExpr> entry : content.entrySet()) {
if (!Lexer.isValidId(entry.getKey())) throw new IllegalStateException("Illegal key: " + entry.getKey());
if (!first) writer.append(",\n");
first = false;
writer.append(entry.getKey()).append(" = ");
entry.getValue().decompile(writer);
}
writer.decreaseIndent().append("\n}");
}
}

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.muscript.ast.dynamic;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
@ -7,13 +8,15 @@ import io.gitlab.jfronny.muscript.annotations.CanThrow;
import io.gitlab.jfronny.muscript.annotations.UncheckedDynamic;
import io.gitlab.jfronny.muscript.error.LocationalException;
import java.io.IOException;
@CanThrow
@UncheckedDynamic
public class Variable extends DynamicExpr {
private final String name;
public final String name;
public Variable(int chStart, int chEnd, String name) {
super(chStart, chEnd);
super(Order.Primary, chStart, chEnd);
this.name = name;
}
@ -29,6 +32,12 @@ public class Variable extends DynamicExpr {
return this;
}
@Override
public void decompile(ExprWriter writer) throws IOException {
if (!Lexer.isValidId(name)) throw new IllegalArgumentException("Not a valid variable name: " + name);
writer.append(name);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Variable variable && name.equals(variable.name);

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.muscript.ast.dynamic.unpack;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.DynamicTypeConversionException;
import io.gitlab.jfronny.muscript.ast.BoolExpr;
@ -8,13 +9,15 @@ import io.gitlab.jfronny.muscript.annotations.CanThrow;
import io.gitlab.jfronny.muscript.annotations.UncheckedDynamic;
import io.gitlab.jfronny.muscript.ast.dynamic.DynamicCoerce;
import java.io.IOException;
@CanThrow
@UncheckedDynamic
public class BoolUnpack extends BoolExpr {
public final DynamicExpr inner;
public BoolUnpack(int chStart, int chEnd, DynamicExpr inner) {
super(chStart, chEnd);
super(inner.order, chStart, chEnd);
this.inner = inner;
}
@ -34,6 +37,11 @@ public class BoolUnpack extends BoolExpr {
return new BoolUnpack(chStart, chEnd, inner);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
inner.decompile(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof BoolUnpack unpack && inner.equals(unpack.inner);

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.muscript.ast.dynamic.unpack;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.DynamicTypeConversionException;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
@ -8,13 +9,15 @@ import io.gitlab.jfronny.muscript.annotations.CanThrow;
import io.gitlab.jfronny.muscript.annotations.UncheckedDynamic;
import io.gitlab.jfronny.muscript.ast.dynamic.DynamicCoerce;
import java.io.IOException;
@CanThrow
@UncheckedDynamic
public class NumberUnpack extends NumberExpr {
public final DynamicExpr inner;
public NumberUnpack(int chStart, int chEnd, DynamicExpr inner) {
super(chStart, chEnd);
super(inner.order, chStart, chEnd);
this.inner = inner;
}
@ -34,6 +37,11 @@ public class NumberUnpack extends NumberExpr {
return new NumberUnpack(chStart, chEnd, inner.optimize());
}
@Override
public void decompile(ExprWriter writer) throws IOException {
inner.decompile(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof NumberUnpack unpack && inner.equals(unpack.inner);

View File

@ -1,5 +1,6 @@
package io.gitlab.jfronny.muscript.ast.dynamic.unpack;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.DynamicTypeConversionException;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
@ -8,13 +9,15 @@ import io.gitlab.jfronny.muscript.annotations.CanThrow;
import io.gitlab.jfronny.muscript.annotations.UncheckedDynamic;
import io.gitlab.jfronny.muscript.ast.dynamic.DynamicCoerce;
import java.io.IOException;
@CanThrow
@UncheckedDynamic
public class StringUnpack extends StringExpr {
public final DynamicExpr inner;
public StringUnpack(int chStart, int chEnd, DynamicExpr inner) {
super(chStart, chEnd);
super(inner.order, chStart, chEnd);
this.inner = inner;
}
@ -34,6 +37,11 @@ public class StringUnpack extends StringExpr {
return new StringUnpack(chStart, chEnd, inner);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
inner.decompile(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof StringUnpack unpack && inner.equals(unpack.inner);

View File

@ -1,13 +1,17 @@
package io.gitlab.jfronny.muscript.ast.literal;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.BoolExpr;
import java.io.IOException;
public final class BoolLiteral extends BoolExpr {
public final boolean value;
public BoolLiteral(int chStart, int chEnd, boolean value) {
super(chStart, chEnd);
super(Order.Primary, chStart, chEnd);
this.value = value;
}
@ -21,6 +25,11 @@ public final class BoolLiteral extends BoolExpr {
return this;
}
@Override
public void decompile(ExprWriter writer) throws IOException {
writer.append(value ? "true" : "false");
}
@Override
public boolean equals(Object obj) {
return obj instanceof BoolLiteral literal && value == literal.value;

View File

@ -1,14 +1,18 @@
package io.gitlab.jfronny.muscript.ast.literal;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import java.io.IOException;
public final class DynamicLiteral<T> extends DynamicExpr {
public final Dynamic<T> value;
public DynamicLiteral(int chStart, int chEnd, Dynamic<T> value) {
super(chStart, chEnd);
super(Order.Primary, chStart, chEnd);
this.value = value;
}
@ -22,6 +26,11 @@ public final class DynamicLiteral<T> extends DynamicExpr {
return this;
}
@Override
public void decompile(ExprWriter writer) throws IOException {
value.serialize(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof DynamicLiteral<?> literal && value.equals(literal.value);

View File

@ -1,13 +1,18 @@
package io.gitlab.jfronny.muscript.ast.literal;
import io.gitlab.jfronny.commons.StringFormatter;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.NumberExpr;
import java.io.IOException;
public final class NumberLiteral extends NumberExpr {
public final double value;
public NumberLiteral(int chStart, int chEnd, double value) {
super(chStart, chEnd);
super(Order.Primary, chStart, chEnd);
this.value = value;
}
@ -21,6 +26,11 @@ public final class NumberLiteral extends NumberExpr {
return this;
}
@Override
public void decompile(ExprWriter writer) throws IOException {
writer.append(StringFormatter.toString(value));
}
@Override
public boolean equals(Object obj) {
return obj instanceof NumberLiteral literal && value == literal.value;

View File

@ -1,13 +1,17 @@
package io.gitlab.jfronny.muscript.ast.literal;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.StringExpr;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import java.io.IOException;
public final class StringLiteral extends StringExpr {
public final String value;
public StringLiteral(int chStart, int chEnd, String value) {
super(chStart, chEnd);
super(Order.Primary, chStart, chEnd);
this.value = value;
}
@ -21,6 +25,11 @@ public final class StringLiteral extends StringExpr {
return this;
}
@Override
public void decompile(ExprWriter writer) throws IOException {
writer.append(enquote(value));
}
@Override
public boolean equals(Object obj) {
return obj instanceof StringLiteral literal && value.equals(literal.value);

View File

@ -1,16 +1,20 @@
package io.gitlab.jfronny.muscript.ast.math;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import java.io.IOException;
public class Divide extends NumberExpr {
public final NumberExpr dividend;
public final NumberExpr divisor;
public Divide(int chStart, int chEnd, NumberExpr dividend, NumberExpr divisor) {
super(chStart, chEnd);
super(Order.Factor, chStart, chEnd);
this.dividend = dividend;
this.divisor = divisor;
}
@ -31,6 +35,13 @@ public class Divide extends NumberExpr {
return new Divide(chStart, chEnd, dividend, divisor);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(dividend, writer, false);
writer.append(" / ");
parenthesize(divisor, writer, true);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Divide divide && dividend.equals(divide.dividend) && divisor.equals(divide.divisor);

View File

@ -1,15 +1,19 @@
package io.gitlab.jfronny.muscript.ast.math;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import java.io.IOException;
public class Invert extends NumberExpr {
public final NumberExpr inner;
public Invert(int chStart, int chEnd, NumberExpr inner) {
super(chStart, chEnd);
super(Order.Unary, chStart, chEnd);
this.inner = inner;
}
@ -23,9 +27,16 @@ public class Invert extends NumberExpr {
NumberExpr inner = this.inner.optimize();
if (inner instanceof Invert invert) return invert.inner;
if (inner instanceof NumberLiteral literal) return Expr.literal(chStart, chEnd, -literal.value);
if (inner instanceof Minus minus) return new Minus(chStart, chEnd, minus.subtrahend, minus.minuend).optimize();
return new Invert(chStart, chEnd, inner);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
writer.append('-');
parenthesize(inner, writer, false);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Invert invert && inner.equals(invert.inner);

View File

@ -1,16 +1,20 @@
package io.gitlab.jfronny.muscript.ast.math;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import java.io.IOException;
public class Minus extends NumberExpr {
public final NumberExpr minuend;
public final NumberExpr subtrahend;
public Minus(int chStart, int chEnd, NumberExpr minuend, NumberExpr subtrahend) {
super(chStart, chEnd);
super(Order.Term, chStart, chEnd);
this.minuend = minuend;
this.subtrahend = subtrahend;
}
@ -31,6 +35,13 @@ public class Minus extends NumberExpr {
return new Minus(chStart, chEnd, minuend, subtrahend);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(minuend, writer, false);
writer.append(" - ");
parenthesize(subtrahend, writer, true);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Minus minus && minuend.equals(minus.minuend) && subtrahend.equals(minus.subtrahend);

View File

@ -1,16 +1,20 @@
package io.gitlab.jfronny.muscript.ast.math;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import java.io.IOException;
public class Modulo extends NumberExpr {
private final NumberExpr dividend;
private final NumberExpr divisor;
public Modulo(int chStart, int chEnd, NumberExpr dividend, NumberExpr divisor) {
super(chStart, chEnd);
super(Order.Factor, chStart, chEnd);
this.dividend = dividend;
this.divisor = divisor;
}
@ -29,6 +33,13 @@ public class Modulo extends NumberExpr {
return new Modulo(chStart, chEnd, dividend, divisor);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(dividend, writer, false);
writer.append(" % ");
parenthesize(divisor, writer, true);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Modulo modulo && dividend.equals(modulo.dividend) && divisor.equals(modulo.divisor);

View File

@ -1,16 +1,20 @@
package io.gitlab.jfronny.muscript.ast.math;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import java.io.IOException;
public class Multiply extends NumberExpr {
public final NumberExpr multiplier;
public final NumberExpr multiplicand;
public Multiply(int chStart, int chEnd, NumberExpr multiplier, NumberExpr multiplicand) {
super(chStart, chEnd);
super(Order.Factor, chStart, chEnd);
this.multiplier = multiplier;
this.multiplicand = multiplicand;
}
@ -29,6 +33,13 @@ public class Multiply extends NumberExpr {
return new Multiply(chStart, chEnd, multiplier, multiplicand);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(multiplier, writer, false);
writer.append(" * ");
parenthesize(multiplicand, writer, true);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Multiply multiply && multiplier.equals(multiply.multiplier) && multiplicand.equals(multiply.multiplicand);

View File

@ -1,16 +1,20 @@
package io.gitlab.jfronny.muscript.ast.math;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import java.io.IOException;
public class Plus extends NumberExpr {
public final NumberExpr augend;
public final NumberExpr addend;
public Plus(int chStart, int chEnd, NumberExpr augend, NumberExpr addend) {
super(chStart, chEnd);
super(Order.Term, chStart, chEnd);
this.augend = augend;
this.addend = addend;
}
@ -29,6 +33,13 @@ public class Plus extends NumberExpr {
return new Plus(chStart, chEnd, augend, addend);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(augend, writer, false);
writer.append(" + ");
parenthesize(addend, writer, true);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Plus plus && augend.equals(plus.augend) && addend.equals(plus.addend);

View File

@ -1,16 +1,20 @@
package io.gitlab.jfronny.muscript.ast.math;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.NumberExpr;
import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral;
import java.io.IOException;
public class Power extends NumberExpr {
private final NumberExpr base;
private final NumberExpr exponent;
public Power(int chStart, int chEnd, NumberExpr base, NumberExpr exponent) {
super(chStart, chEnd);
super(Order.Exp, chStart, chEnd);
this.base = base;
this.exponent = exponent;
}
@ -39,6 +43,13 @@ public class Power extends NumberExpr {
return new Power(chStart, chEnd, base, exponent);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(base, writer, false);
writer.append(" ^ ");
parenthesize(exponent, writer, true);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Power power && base.equals(power.base) && exponent.equals(power.exponent);

View File

@ -1,16 +1,20 @@
package io.gitlab.jfronny.muscript.ast.string;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Order;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.StringExpr;
import io.gitlab.jfronny.muscript.ast.literal.StringLiteral;
import java.io.IOException;
public class Concatenate extends StringExpr {
private final StringExpr left;
private final StringExpr right;
public Concatenate(int chStart, int chEnd, StringExpr left, StringExpr right) {
super(chStart, chEnd);
super(Order.Concat, chStart, chEnd);
this.left = left;
this.right = right;
}
@ -35,6 +39,13 @@ public class Concatenate extends StringExpr {
return new Concatenate(chStart, chEnd, left, right);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
parenthesize(left, writer, false);
writer.append(" || ");
parenthesize(right, writer, true);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Concatenate concatenate && left.equals(concatenate.left) && right.equals(concatenate.right);

View File

@ -1,15 +1,18 @@
package io.gitlab.jfronny.muscript.ast.string;
import io.gitlab.jfronny.commons.StringFormatter;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.ast.*;
import io.gitlab.jfronny.muscript.ast.literal.*;
import java.io.IOException;
public class StringCoerce extends StringExpr {
private final Expr<?> inner;
public StringCoerce(int chStart, int chEnd, Expr<?> inner) {
super(chStart, chEnd);
super(inner.order, chStart, chEnd);
this.inner = inner;
}
@ -29,6 +32,11 @@ public class StringCoerce extends StringExpr {
return new StringCoerce(chStart, chEnd, inner);
}
@Override
public void decompile(ExprWriter writer) throws IOException {
inner.decompile(writer);
}
@Override
public boolean equals(Object obj) {
return obj instanceof StringCoerce coerce && inner.equals(coerce.inner);

View File

@ -0,0 +1,43 @@
package io.gitlab.jfronny.muscript.compiler;
import java.io.IOException;
import java.util.Arrays;
import java.util.stream.Collectors;
public abstract class Decompilable {
public final Order order;
protected Decompilable(Order order) {
this.order = order;
}
public abstract void decompile(ExprWriter writer) throws IOException;
protected void parenthesize(Decompilable val, ExprWriter writer, boolean parenEqualOrder) throws IOException {
boolean wrap = !parenEqualOrder ? val.order.ordinal() > this.order.ordinal() : val.order.ordinal() >= this.order.ordinal();
if (wrap) writer.append('(');
val.decompile(writer);
if (wrap) writer.append(')');
}
/**
* Creates quotes around a string, supports strings containing quotes
*/
public static String enquote(String literalText) {
if (!literalText.contains("'")) return "'" + literalText + "'";
if (!literalText.contains("\"")) return "\"" + literalText + "\"";
return Arrays.stream(literalText.split("'")).map(s -> "'" + s + "'")
.collect(Collectors.joining(" || \"'\" || "));
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
try (ExprWriter ew = new ExprWriter(sb)) {
decompile(ew);
} catch (IOException e) {
throw new RuntimeException("Could not decompile", e);
}
return sb.toString();
}
}

View File

@ -0,0 +1,52 @@
package io.gitlab.jfronny.muscript.compiler;
import java.io.Closeable;
import java.io.IOException;
import java.util.stream.Collectors;
public class ExprWriter implements Appendable, Closeable {
private final Appendable target;
private int indent = 0;
public ExprWriter(Appendable target) {
this.target = target;
}
@Override
public ExprWriter append(CharSequence csq) throws IOException {
target.append(csq.toString().lines().collect(Collectors.joining("\n" + indent())));
return this;
}
@Override
public ExprWriter append(CharSequence csq, int start, int end) throws IOException {
return append(csq.subSequence(start, end));
}
@Override
public ExprWriter append(char c) throws IOException {
if (c == '\n' || c == '\r') target.append("\n").append(indent());
else target.append(c);
return this;
}
private String indent() {
return " ".repeat(indent);
}
public ExprWriter increaseIndent() {
indent += 2;
return this;
}
public ExprWriter decreaseIndent() {
if (indent <= 1) throw new IllegalStateException("Attempted to decrease indent lower than 0");
indent -= 2;
return this;
}
@Override
public void close() {
if (indent != 0) throw new IllegalStateException("Attempted to close ExprWriter before end");
}
}

View File

@ -1,5 +1,8 @@
package io.gitlab.jfronny.muscript.compiler;
import java.util.Set;
import java.util.regex.Pattern;
// Heavily inspired by starscript
public class Lexer {
/**
@ -215,6 +218,12 @@ public class Lexer {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '$';
}
public static boolean isValidId(String id) {
return IDENTIFIER.matcher(id).matches() && !RESERVED_IDS.contains(id);
}
public static final Set<String> RESERVED_IDS = Set.of("null", "true", "false");
public static final Pattern IDENTIFIER = Pattern.compile("[a-zA-Z_$][a-zA-Z_$0-9]*");
// Visualization
@Override
public String toString() {

View File

@ -0,0 +1,17 @@
package io.gitlab.jfronny.muscript.compiler;
public enum Order {
Script,
Conditional,
And,
Or,
Equality,
Concat,
Comparison,
Term,
Factor,
Exp,
Unary,
Call,
Primary;
}

View File

@ -2,13 +2,17 @@ package io.gitlab.jfronny.muscript.compiler;
import io.gitlab.jfronny.muscript.ast.*;
import io.gitlab.jfronny.muscript.ast.bool.*;
import io.gitlab.jfronny.muscript.ast.compare.*;
import io.gitlab.jfronny.muscript.ast.compare.Equal;
import io.gitlab.jfronny.muscript.ast.compare.Greater;
import io.gitlab.jfronny.muscript.ast.conditional.UnresolvedConditional;
import io.gitlab.jfronny.muscript.ast.dynamic.*;
import io.gitlab.jfronny.muscript.ast.literal.DynamicLiteral;
import io.gitlab.jfronny.muscript.ast.math.*;
import io.gitlab.jfronny.muscript.ast.string.Concatenate;
import io.gitlab.jfronny.muscript.data.Script;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal;
import io.gitlab.jfronny.muscript.error.*;
import org.jetbrains.annotations.Nullable;
import java.util.*;
@ -153,8 +157,8 @@ public class Parser {
NumberExpr right = asNumber(term());
expr = switch (op) {
case Greater -> new Greater(start, end, asNumber(expr), right);
case GreaterEqual -> new Not(start, end, new Less(start, end, asNumber(expr), right));
case Less -> new Less(start, end, asNumber(expr), right);
case GreaterEqual -> new Not(start, end, new Greater(start, end, right, asNumber(expr)));
case Less -> new Greater(start, end, right, asNumber(expr));
case LessEqual -> new Not(start, end, new Greater(start, end, asNumber(expr), right));
default -> throw new IllegalStateException();
};
@ -297,32 +301,65 @@ public class Parser {
if (match(Token.LeftBrace)) {
int start = previous.start;
List<String> boundArgs = new LinkedList<>();
boolean variadic = false;
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);
if (match(Token.Ellipsis)) {
variadic = true;
consume(Token.Arrow, "Variadic argument MUST be the last argument");
break;
}
if (match(Token.Arrow)) return finishClosure(start, null, false);
if (match(Token.RightBrace)) return new DynamicLiteral<>(start, previous.start, DFinal.of(Map.of()));
consume(Token.Identifier, "Expected arrow or identifier as first element in closure or object");
String first = previous.lexeme;
if (check(Token.Arrow)) return finishClosure(start, first, false);
if (match(Token.Ellipsis)) return finishClosure(start, first, true);
if (check(Token.Comma)) return finishClosure(start, first, false);
if (match(Token.Assign)) {
return finishObject(start, first, expression().asDynamicExpr());
}
List<Expr<?>> expressions = new LinkedList<>();
while (!match(Token.RightBrace)) {
expressions.add(expression());
match(Token.Semicolon); // Consume semicolon if present
}
int end = previous.start;
return new Closure(start, end, boundArgs, expressions, variadic);
throw error("Unexpected");
}
throw error("Expected expression.");
}
private Expr<?> finishClosure(int start, @Nullable String firstArg, boolean firstVariadic) {
List<String> boundArgs = new LinkedList<>();
boolean variadic = false;
if (firstArg != null) {
boundArgs.add(firstArg);
if (firstVariadic) {
consume(Token.Arrow, "Variadic argument MUST be the last argument");
variadic = true;
} else {
while (!match(Token.Arrow)) {
consume(Token.Comma, "Closure parameters MUST be comma-seperated");
consume(Token.Identifier, "Closure arguments MUST be identifiers");
boundArgs.add(previous.lexeme);
if (match(Token.Ellipsis)) {
variadic = true;
consume(Token.Arrow, "Variadic argument MUST be the last argument");
break;
}
}
}
}
List<Expr<?>> expressions = new LinkedList<>();
while (!match(Token.RightBrace)) {
expressions.add(expression());
match(Token.Semicolon); // Consume semicolon if present
}
int end = previous.start;
return new Closure(start, end, boundArgs, expressions, variadic);
}
private Expr<?> finishObject(int start, @Nullable String firstArg, @Nullable DynamicExpr firstValue) {
Map<String, DynamicExpr> content = new LinkedHashMap<>();
content.put(firstArg, firstValue);
while (match(Token.Comma)) {
consume(Token.Identifier, "Object element MUST start with an identifier");
String name = previous.lexeme;
consume(Token.Assign, "Object element name and value MUST be seperated with '='");
content.put(name, expression().asDynamicExpr());
}
consume(Token.RightBrace, "Expected end of object");
return new ObjectLiteral(start, previous.start, content);
}
// Type conversion
private BoolExpr asBool(Expr<?> expression) {
try {

View File

@ -2,6 +2,7 @@ package io.gitlab.jfronny.muscript.data;
import io.gitlab.jfronny.commons.data.ImmCollection;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal;
import org.jetbrains.annotations.Nullable;
import java.util.*;
@ -83,10 +84,15 @@ public class Scope implements DObject {
}
public Scope set(String key, Function<DList, Dynamic<?>> value) {
return set(key, DFinal.of(value));
return set(key, DFinal.of(value, () -> key));
}
public Scope fork() {
return new Scope(this);
}
@Override
public String toString() {
return Dynamic.serialize(this);
}
}

View File

@ -4,12 +4,15 @@ import io.gitlab.jfronny.muscript.ast.DynamicExpr;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.ast.dynamic.Call;
import io.gitlab.jfronny.muscript.ast.dynamic.Closure;
import io.gitlab.jfronny.muscript.compiler.*;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal;
import java.io.IOException;
import java.util.List;
import java.util.stream.Stream;
public class Script {
public class Script extends Decompilable {
private final List<Expr<?>> steps;
private final DynamicExpr fin;
@ -18,6 +21,7 @@ public class Script {
}
private Script(List<Expr<?>> steps, DynamicExpr fin) {
super(Order.Script);
this.steps = steps;
this.fin = fin;
}
@ -37,7 +41,7 @@ public class Script {
return DFinal.of(args -> {
scope.set("args", args);
return run(scope);
});
}, () -> "{->\n" + this + "\n}()");
}
public DynamicExpr asExpr() {
@ -55,4 +59,12 @@ public class Script {
public Stream<Expr<?>> stream() {
return Stream.concat(steps.stream(), Stream.of(fin));
}
@Override
public void decompile(ExprWriter writer) throws IOException {
for (Expr<?> expr : stream().toList()) {
expr.decompile(writer);
writer.append(";\n");
}
}
}

View File

@ -1,4 +1,12 @@
package io.gitlab.jfronny.muscript.data.dynamic;
public interface DBool extends Dynamic<Boolean> {
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import java.io.IOException;
public non-sealed interface DBool extends Dynamic<Boolean> {
@Override
default void serialize(ExprWriter writer) throws IOException {
writer.append(getValue().toString());
}
}

View File

@ -1,8 +1,10 @@
package io.gitlab.jfronny.muscript.data.dynamic;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal;
import java.util.function.Function;
public interface DCallable extends Dynamic<Function<DList, Dynamic<?>>> {
public non-sealed interface DCallable extends Dynamic<Function<DList, Dynamic<?>>> {
default Dynamic<?> call(DList args) {
return getValue().apply(args);
}

View File

@ -1,15 +0,0 @@
package io.gitlab.jfronny.muscript.data.dynamic;
import java.util.Map;
public record DCallableObject(Map<String, Dynamic<?>> value, DCallable callable) implements DObject {
@Override
public Map<String, Dynamic<?>> getValue() {
return value;
}
@Override
public DCallable asCallable() {
return callable;
}
}

View File

@ -1,8 +1,11 @@
package io.gitlab.jfronny.muscript.data.dynamic;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import java.io.IOException;
import java.util.List;
public interface DList extends Dynamic<List<Dynamic<?>>> {
public non-sealed interface DList extends Dynamic<List<Dynamic<?>>> {
default Dynamic<?> get(int i) {
return getValue().get(i);
}
@ -14,4 +17,16 @@ public interface DList extends Dynamic<List<Dynamic<?>>> {
default boolean isEmpty() {
return getValue().isEmpty();
}
@Override
default void serialize(ExprWriter writer) throws IOException {
writer.append("listOf(");
boolean first = true;
for (Dynamic<?> dynamic : getValue()) {
if (!first) writer.append(", ");
first = false;
dynamic.serialize(writer);
}
writer.append(')');
}
}

View File

@ -1,8 +1,22 @@
package io.gitlab.jfronny.muscript.data.dynamic;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import java.io.IOException;
public final class DNull implements Dynamic<Object> {
@Override
public Object getValue() {
return null;
}
@Override
public void serialize(ExprWriter writer) throws IOException {
writer.append(toString());
}
@Override
public String toString() {
return "null";
}
}

View File

@ -1,4 +1,13 @@
package io.gitlab.jfronny.muscript.data.dynamic;
public interface DNumber extends Dynamic<Double> {
import io.gitlab.jfronny.commons.StringFormatter;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import java.io.IOException;
public non-sealed interface DNumber extends Dynamic<Double> {
@Override
default void serialize(ExprWriter writer) throws IOException {
writer.append(StringFormatter.toString(getValue()));
}
}

View File

@ -1,8 +1,12 @@
package io.gitlab.jfronny.muscript.data.dynamic;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Lexer;
import java.io.IOException;
import java.util.Map;
public interface DObject extends Dynamic<Map<String, Dynamic<?>>> {
public non-sealed interface DObject extends Dynamic<Map<String, Dynamic<?>>> {
default Dynamic<?> get(String key) {
return getValue().get(key);
}
@ -10,4 +14,18 @@ public interface DObject extends Dynamic<Map<String, Dynamic<?>>> {
default boolean has(String key) {
return getValue().containsKey(key);
}
@Override
default void serialize(ExprWriter writer) throws IOException {
writer.append('{');
boolean first = true;
for (Map.Entry<String, Dynamic<?>> entry : getValue().entrySet()) {
if (!Lexer.isValidId(entry.getKey())) throw new IllegalStateException("Illegal key: " + entry.getKey());
if (!first) writer.append(", ");
first = false;
writer.append(entry.getKey()).append(" = ");
entry.getValue().serialize(writer);
}
writer.append('}');
}
}

View File

@ -1,4 +1,13 @@
package io.gitlab.jfronny.muscript.data.dynamic;
public interface DString extends Dynamic<String> {
import io.gitlab.jfronny.muscript.compiler.Decompilable;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import java.io.IOException;
public non-sealed interface DString extends Dynamic<String> {
@Override
default void serialize(ExprWriter writer) throws IOException {
writer.append(Decompilable.enquote(getValue()));
}
}

View File

@ -1,8 +1,35 @@
package io.gitlab.jfronny.muscript.data.dynamic;
import io.gitlab.jfronny.commons.StringFormatter;
import io.gitlab.jfronny.muscript.StandardLib;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.compiler.Parser;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DContainer;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal;
public interface Dynamic<T> {
import java.io.IOException;
/**
* Represents a value of an unknown type
* Override toString(StringBuilder) to support custom serialization (note: the serialized form is ran with muScript to generate the tree)
* @param <T> the type represented
*/
public sealed interface Dynamic<T> permits DBool, DNumber, DString, DObject, DList, DCallable, DNull, DContainer {
static Dynamic<?> deserialize(String source) {
return Parser.parse(source).asDynamicExpr().get(StandardLib.createScope());
}
static String serialize(Dynamic<?> dynamic) {
StringBuilder sb = new StringBuilder();
try (ExprWriter ew = new ExprWriter(sb)) {
dynamic.serialize(ew);
} catch (IOException e) {
throw new RuntimeException("Could not stringify", e);
}
return sb.toString();
}
void serialize(ExprWriter writer) throws IOException;
T getValue();
default DBool asBool() {

View File

@ -0,0 +1,32 @@
package io.gitlab.jfronny.muscript.data.dynamic.additional;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import java.io.IOException;
import java.util.Map;
public record DCallableObject(Map<String, Dynamic<?>> value, DCallable callable) implements DObject {
@Override
public Map<String, Dynamic<?>> getValue() {
return value;
}
@Override
public DCallable asCallable() {
return callable;
}
@Override
public void serialize(ExprWriter writer) throws IOException {
DObject.super.serialize(writer);
writer.append("::callableObject(");
callable.serialize(writer);
writer.append(')');
}
@Override
public String toString() {
return Dynamic.serialize(this);
}
}

View File

@ -1,8 +1,8 @@
package io.gitlab.jfronny.muscript.data.dynamic;
package io.gitlab.jfronny.muscript.data.dynamic.additional;
import io.gitlab.jfronny.commons.StringFormatter;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
public abstract class DContainer<T> implements Dynamic<T> {
public abstract non-sealed class DContainer<T> implements Dynamic<T> {
private T value;
@Override
@ -18,6 +18,6 @@ public abstract class DContainer<T> implements Dynamic<T> {
@Override
public String toString() {
return StringFormatter.toString(value);
return Dynamic.serialize(this);
}
}

View File

@ -0,0 +1,44 @@
package io.gitlab.jfronny.muscript.data.dynamic.additional;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import java.io.IOException;
import java.time.LocalDate;
import java.util.Map;
import java.util.function.Supplier;
public record DDate(Supplier<LocalDate> date) implements DObject {
@Override
public Map<String, Dynamic<?>> getValue() {
return Map.of(
"year", DFinal.of(date.get().getYear()),
"month", DFinal.of(date.get().getMonthValue()),
"day", DFinal.of(date.get().getDayOfMonth())
);
}
@Override
public DString asString() {
return DFinal.of(toString());
}
@Override
public DNumber asNumber() {
return DFinal.of(date.get().toEpochDay());
}
@Override
public void serialize(ExprWriter writer) throws IOException {
writer.append("date(")
.append(String.valueOf(date.get().getYear())).append(", ")
.append(String.valueOf(date.get().getMonthValue())).append(", ")
.append(String.valueOf(date.get().getDayOfMonth()))
.append(")");
}
@Override
public String toString() {
return date.get().toString();
}
}

View File

@ -1,8 +1,11 @@
package io.gitlab.jfronny.muscript.data.dynamic;
package io.gitlab.jfronny.muscript.data.dynamic.additional;
import io.gitlab.jfronny.commons.StringFormatter;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.*;
/**
@ -11,7 +14,11 @@ import java.util.*;
*/
public record DEnum(Map<String, Dynamic<?>> values, @Nullable DEnumEntry value) implements DObject {
public DEnum(Map<String, Dynamic<?>> values) {
this(values, null);
this(values, (DEnumEntry) null);
}
public DEnum(Map<String, Dynamic<?>> values, @Nullable String value) {
this(values, value == null ? null : new DEnumEntry(value, values.keySet().stream().toList().indexOf(value), true));
}
public DEnum(List<String> values, String value) {
@ -42,6 +49,16 @@ public record DEnum(Map<String, Dynamic<?>> values, @Nullable DEnumEntry value)
return value != null ? value.asNumber() : DObject.super.asNumber();
}
@Override
public void serialize(ExprWriter writer) throws IOException {
writer.append("enum(");
DObject.super.serialize(writer);
if (value != null) {
writer.append(", ").append(value.value);
}
writer.append(')');
}
private static Map<String, Dynamic<?>> createMap(List<String> values, String value) {
Map<String, Dynamic<?>> result = new LinkedHashMap<>();
DEnumEntry v = new DEnumEntry(value, values.indexOf(value), true);

View File

@ -1,13 +1,18 @@
package io.gitlab.jfronny.muscript.data.dynamic;
package io.gitlab.jfronny.muscript.data.dynamic.additional;
import io.gitlab.jfronny.commons.LazySupplier;
import io.gitlab.jfronny.commons.StringFormatter;
import io.gitlab.jfronny.commons.data.ImmCollection;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
public record DFinal<T>(T value) implements Dynamic<T> {
public class DFinal<T> {
public static DBool of(boolean b) {
return new FBool(b);
}
@ -32,18 +37,8 @@ public record DFinal<T>(T value) implements Dynamic<T> {
return new FList(ImmCollection.of(b));
}
public static DCallable of(Function<DList, Dynamic<?>> b) {
return new FCallable(b);
}
@Override
public T getValue() {
return value;
}
@Override
public String toString() {
return StringFormatter.toString(value);
public static DCallable of(Function<DList, Dynamic<?>> b, Supplier<String> serialized) {
return new FCallable(b, new LazySupplier<>(serialized));
}
private record FBool(boolean value) implements DBool {
@ -78,7 +73,7 @@ public record DFinal<T>(T value) implements Dynamic<T> {
@Override
public String toString() {
return StringFormatter.toString(value);
return Dynamic.serialize(this);
}
}
@ -90,7 +85,7 @@ public record DFinal<T>(T value) implements Dynamic<T> {
@Override
public String toString() {
return StringFormatter.toString(value);
return Dynamic.serialize(this);
}
}
@ -102,11 +97,16 @@ public record DFinal<T>(T value) implements Dynamic<T> {
@Override
public String toString() {
return StringFormatter.toString(value);
return value.toString();
}
}
private record FCallable(Function<DList, Dynamic<?>> value) implements DCallable {
private record FCallable(Function<DList, Dynamic<?>> value, Supplier<String> string) implements DCallable {
@Override
public void serialize(ExprWriter writer) throws IOException {
writer.append(toString());
}
@Override
public Function<DList, Dynamic<?>> getValue() {
return value;
@ -114,7 +114,7 @@ public record DFinal<T>(T value) implements Dynamic<T> {
@Override
public String toString() {
return "<Callable>";
return string.get();
}
}
}

View File

@ -0,0 +1,44 @@
package io.gitlab.jfronny.muscript.data.dynamic.additional;
import io.gitlab.jfronny.muscript.compiler.ExprWriter;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import java.io.IOException;
import java.time.LocalTime;
import java.util.Map;
import java.util.function.Supplier;
public record DTime(Supplier<LocalTime> time) implements DObject {
@Override
public Map<String, Dynamic<?>> getValue() {
return Map.of(
"hour", DFinal.of(time.get().getHour()),
"minute", DFinal.of(time.get().getMinute()),
"second", DFinal.of(time.get().getSecond())
);
}
@Override
public DString asString() {
return DFinal.of(toString());
}
@Override
public DNumber asNumber() {
return DFinal.of(time.get().toSecondOfDay());
}
@Override
public void serialize(ExprWriter writer) throws IOException {
writer.append("time(")
.append(String.valueOf(time.get().getHour())).append(", ")
.append(String.valueOf(time.get().getMinute())).append(", ")
.append(String.valueOf(time.get().getSecond()))
.append(")");
}
@Override
public String toString() {
return time.get().toString();
}
}

View File

@ -8,4 +8,5 @@ module io.gitlab.jfronny.commons.muscript {
exports io.gitlab.jfronny.muscript.error;
exports io.gitlab.jfronny.muscript.data;
exports io.gitlab.jfronny.muscript.data.dynamic;
exports io.gitlab.jfronny.muscript.data.dynamic.additional;
}

View File

@ -16,6 +16,7 @@ class AssignTest {
void testAssignSimple() {
StringExpr expr = Parser.parse("someval = 'test'").asStringExpr();
assertEquals(new Assign(0, 6, "someval", new StringLiteral(10, 15, "test").asDynamicExpr()).asStringExpr(), expr);
assertEquals("someval = 'test'", expr.toString());
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 io.gitlab.jfronny.muscript.compiler.Parser;
import io.gitlab.jfronny.muscript.data.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.DFinal;
import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal;
import org.junit.jupiter.api.Test;
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.makeArgs;

View File

@ -1,5 +1,6 @@
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.*;
@ -21,4 +22,17 @@ class ObjectTest {
assertTrue(bool("object2['sub'].val == 10"));
assertTrue(bool("object2.sub['val'] == 10"));
}
@Test
void objectLiteral() {
assertEquals(12, Parser.parseScript("""
ob = {}
ob = {test = 2, test2 = 3}
t = ob.test
ob = {test2 = 3, test = 2}
t = t * ob.test
ob = {test = 3}
t * ob.test
""").run(makeArgs()).asNumber().getValue());
}
}

View File

@ -14,4 +14,10 @@ class StringTest {
assertTrue(bool("string == 'Value'"));
assertFalse(bool("string == 'Something else'"));
}
@Test
void concatComparison() {
assertEquals("Hellotrue", string("'Hello' || -12 < 5"));
assertEquals("trueHello", string("-12 < 5 || 'Hello'"));
}
}

View File

@ -8,7 +8,7 @@ import io.gitlab.jfronny.muscript.debug.ObjectGraphPrinter;
import java.util.Map;
import static io.gitlab.jfronny.muscript.data.dynamic.DFinal.of;
import static io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal.of;
public class MuTestUtil {
public static double number(String source) {
@ -40,7 +40,7 @@ public class MuTestUtil {
.set("string", "Value")
.set("object", Map.of(
"subvalue", of(1024),
"subfunc", of(v -> of(v.get(1).asNumber().getValue() * v.size())),
"subfunc", of(v -> of(v.get(1).asNumber().getValue() * v.size()), () -> "object.subfunc"),
"1", of("One")
))
.set("object2", Map.of(
@ -48,7 +48,7 @@ public class MuTestUtil {
"sub", of(Map.of(
"val", of(10)
)),
"stringfunc", of(v -> of(v.get(0).asString().getValue()))
"stringfunc", of(v -> of(v.get(0).asString().getValue()), () -> "object2.stringfunc")
))
.set("list", of(of(true), of(2), of("3")))
.set("numbers", of(of(1), of(2), of(3), of(4), of(5), of(6), of(7), of(8), of(9), of(10)))

View File

@ -75,7 +75,7 @@ listOf(
```
Result:
```
[this is correct, false, false, true, true, false]
['this is correct', false, false, true, true, false]
```
</details>
@ -133,7 +133,7 @@ listOf(
```
Result:
```
[subvalue, subvalue, subvalue, subvalue, One, 20, 64, some parameter, 2023-05-13, 23:55:10, false]
['subvalue', 'subvalue', 'subvalue', 'subvalue', 'One', 20, 64, 'some parameter', 2023-05-13, 23:55:10, false]
```
</details>
@ -217,6 +217,6 @@ listOf(
```
Result:
```
[2, some expression(s), 10, 7, 7, 2, 1, 1, 12, 24]
[2, 'some expression(s)', 10, 7, 7, 2, 1, 1, 12, 24]
```
</details>