From efb1512a605b512bf37be678426f45f473bb2de5 Mon Sep 17 00:00:00 2001 From: JFronny Date: Sun, 12 Mar 2023 15:28:44 +0100 Subject: [PATCH] muScript: support for custom objects, script decompiling and Dynamic serialization --- build.gradle.kts | 2 +- .../gitlab/jfronny/muscript/StandardLib.java | 85 ++++++------------- .../jfronny/muscript/StarScriptIngester.java | 18 +--- .../gitlab/jfronny/muscript/ast/BoolExpr.java | 5 +- .../jfronny/muscript/ast/DynamicExpr.java | 5 +- .../io/gitlab/jfronny/muscript/ast/Expr.java | 14 +-- .../jfronny/muscript/ast/NullLiteral.java | 11 ++- .../jfronny/muscript/ast/NumberExpr.java | 5 +- .../jfronny/muscript/ast/StringExpr.java | 5 +- .../gitlab/jfronny/muscript/ast/bool/And.java | 13 ++- .../gitlab/jfronny/muscript/ast/bool/Not.java | 31 ++++++- .../gitlab/jfronny/muscript/ast/bool/Or.java | 13 ++- .../jfronny/muscript/ast/compare/Equal.java | 20 +++-- .../jfronny/muscript/ast/compare/Greater.java | 25 +++++- .../jfronny/muscript/ast/compare/Less.java | 47 ---------- .../ast/conditional/BoolConditional.java | 15 +++- .../ast/conditional/DynamicConditional.java | 15 +++- .../ast/conditional/NumberConditional.java | 15 +++- .../ast/conditional/StringConditional.java | 15 +++- .../conditional/UnresolvedConditional.java | 15 +++- .../jfronny/muscript/ast/dynamic/Assign.java | 12 ++- .../jfronny/muscript/ast/dynamic/Bind.java | 21 ++++- .../jfronny/muscript/ast/dynamic/Call.java | 33 ++++++- .../jfronny/muscript/ast/dynamic/Closure.java | 31 ++++++- .../muscript/ast/dynamic/DynamicCoerce.java | 11 ++- .../jfronny/muscript/ast/dynamic/Get.java | 19 ++++- .../muscript/ast/dynamic/ObjectLiteral.java | 57 +++++++++++++ .../muscript/ast/dynamic/Variable.java | 13 ++- .../ast/dynamic/unpack/BoolUnpack.java | 10 ++- .../ast/dynamic/unpack/NumberUnpack.java | 10 ++- .../ast/dynamic/unpack/StringUnpack.java | 10 ++- .../muscript/ast/literal/BoolLiteral.java | 11 ++- .../muscript/ast/literal/DynamicLiteral.java | 11 ++- .../muscript/ast/literal/NumberLiteral.java | 12 ++- .../muscript/ast/literal/StringLiteral.java | 13 ++- .../jfronny/muscript/ast/math/Divide.java | 13 ++- .../jfronny/muscript/ast/math/Invert.java | 13 ++- .../jfronny/muscript/ast/math/Minus.java | 13 ++- .../jfronny/muscript/ast/math/Modulo.java | 13 ++- .../jfronny/muscript/ast/math/Multiply.java | 13 ++- .../jfronny/muscript/ast/math/Plus.java | 13 ++- .../jfronny/muscript/ast/math/Power.java | 13 ++- .../muscript/ast/string/Concatenate.java | 13 ++- .../muscript/ast/string/StringCoerce.java | 10 ++- .../muscript/compiler/Decompilable.java | 43 ++++++++++ .../jfronny/muscript/compiler/ExprWriter.java | 52 ++++++++++++ .../jfronny/muscript/compiler/Lexer.java | 9 ++ .../jfronny/muscript/compiler/Order.java | 17 ++++ .../jfronny/muscript/compiler/Parser.java | 83 +++++++++++++----- .../gitlab/jfronny/muscript/data/Scope.java | 8 +- .../gitlab/jfronny/muscript/data/Script.java | 16 +++- .../jfronny/muscript/data/dynamic/DBool.java | 10 ++- .../muscript/data/dynamic/DCallable.java | 4 +- .../data/dynamic/DCallableObject.java | 15 ---- .../jfronny/muscript/data/dynamic/DList.java | 17 +++- .../jfronny/muscript/data/dynamic/DNull.java | 14 +++ .../muscript/data/dynamic/DNumber.java | 11 ++- .../muscript/data/dynamic/DObject.java | 20 ++++- .../muscript/data/dynamic/DString.java | 11 ++- .../muscript/data/dynamic/Dynamic.java | 29 ++++++- .../dynamic/additional/DCallableObject.java | 32 +++++++ .../dynamic/{ => additional}/DContainer.java | 8 +- .../data/dynamic/additional/DDate.java | 44 ++++++++++ .../data/dynamic/{ => additional}/DEnum.java | 21 ++++- .../data/dynamic/{ => additional}/DFinal.java | 38 ++++----- .../data/dynamic/additional/DTime.java | 44 ++++++++++ muscript/src/main/java/module-info.java | 1 + .../jfronny/muscript/test/AssignTest.java | 1 + .../jfronny/muscript/test/ClosureTest.java | 2 +- .../jfronny/muscript/test/ObjectTest.java | 14 +++ .../jfronny/muscript/test/StringTest.java | 6 ++ .../muscript/test/util/MuTestUtil.java | 6 +- muscript/src/test/resources/example.md | 6 +- 73 files changed, 1077 insertions(+), 267 deletions(-) delete mode 100644 muscript/src/main/java/io/gitlab/jfronny/muscript/ast/compare/Less.java create mode 100644 muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/ObjectLiteral.java create mode 100644 muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Decompilable.java create mode 100644 muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/ExprWriter.java create mode 100644 muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Order.java delete mode 100644 muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DCallableObject.java create mode 100644 muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DCallableObject.java rename muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/{ => additional}/DContainer.java (54%) create mode 100644 muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DDate.java rename muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/{ => additional}/DEnum.java (73%) rename muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/{ => additional}/DFinal.java (71%) create mode 100644 muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DTime.java diff --git a/build.gradle.kts b/build.gradle.kts index c956785..f84b711 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,7 @@ plugins { id("commons.library") } -version = "1.1-SNAPSHOT" +version = "1.2-SNAPSHOT" publishing { publications { diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/StandardLib.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/StandardLib.java index 1f45e10..905e5e1 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/StandardLib.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/StandardLib.java @@ -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 date) implements DObject { - @Override - public Map> 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 time) implements DObject { - @Override - public Map> 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().>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()); + } } diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/StarScriptIngester.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/StarScriptIngester.java index 3dd58d6..1cd6570 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/StarScriptIngester.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/StarScriptIngester.java @@ -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 } diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/BoolExpr.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/BoolExpr.java index c96e67e..7ca12ef 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/BoolExpr.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/BoolExpr.java @@ -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 { - protected BoolExpr(int chStart, int chEnd) { - super(chStart, chEnd); + protected BoolExpr(Order order, int chStart, int chEnd) { + super(order, chStart, chEnd); } @Override diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/DynamicExpr.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/DynamicExpr.java index 00ceb79..b5be9c8 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/DynamicExpr.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/DynamicExpr.java @@ -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> { - protected DynamicExpr(int chStart, int chEnd) { - super(chStart, chEnd); + protected DynamicExpr(Order order, int chStart, int chEnd) { + super(order, chStart, chEnd); } @Override diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/Expr.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/Expr.java index 803cf61..5606ed7 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/Expr.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/Expr.java @@ -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 permits BoolExpr, DynamicExpr, NullLiteral, NumberExpr, StringExpr { - protected Expr(int chStart, int chEnd) { +public abstract sealed class Expr 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 permits BoolExpr, DynamicExpr, NullLiteral, return get(dataRoot.asObject()); } public abstract Expr optimize(); - public final int chStart; - public final int chEnd; public BoolExpr asBoolExpr() { if (this instanceof BoolExpr e) return e; diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/NullLiteral.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/NullLiteral.java index e4282ea..dd33ae5 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/NullLiteral.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/NullLiteral.java @@ -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 { 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 { return this; } + @Override + public void decompile(ExprWriter writer) throws IOException { + writer.append("null"); + } + @Override public DynamicExpr asDynamicExpr() { return new DynamicLiteral<>(chStart, chEnd, new DNull()); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/NumberExpr.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/NumberExpr.java index 722abdb..f142de1 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/NumberExpr.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/NumberExpr.java @@ -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 { - protected NumberExpr(int chStart, int chEnd) { - super(chStart, chEnd); + protected NumberExpr(Order order, int chStart, int chEnd) { + super(order, chStart, chEnd); } @Override diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/StringExpr.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/StringExpr.java index 60f95b2..d1a2a4f 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/StringExpr.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/StringExpr.java @@ -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 { - protected StringExpr(int chStart, int chEnd) { - super(chStart, chEnd); + protected StringExpr(Order order, int chStart, int chEnd) { + super(order, chStart, chEnd); } @Override diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/bool/And.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/bool/And.java index 3af311f..b62692f 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/bool/And.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/bool/And.java @@ -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); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/bool/Not.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/bool/Not.java index f3c8db3..b89e166 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/bool/Not.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/bool/Not.java @@ -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); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/bool/Or.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/bool/Or.java index 21cb7d7..7332ef2 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/bool/Or.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/bool/Or.java @@ -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); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/compare/Equal.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/compare/Equal.java index b72751a..9263b16 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/compare/Equal.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/compare/Equal.java @@ -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); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/compare/Greater.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/compare/Greater.java index e0055aa..4083fec 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/compare/Greater.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/compare/Greater.java @@ -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); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/compare/Less.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/compare/Less.java deleted file mode 100644 index b6aec31..0000000 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/compare/Less.java +++ /dev/null @@ -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); - } -} diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/conditional/BoolConditional.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/conditional/BoolConditional.java index d5bf4bd..c9bb2a6 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/conditional/BoolConditional.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/conditional/BoolConditional.java @@ -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 diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/conditional/DynamicConditional.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/conditional/DynamicConditional.java index 1cd2f81..1740dad 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/conditional/DynamicConditional.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/conditional/DynamicConditional.java @@ -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 diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/conditional/NumberConditional.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/conditional/NumberConditional.java index d1c183d..d0b87b0 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/conditional/NumberConditional.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/conditional/NumberConditional.java @@ -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 diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/conditional/StringConditional.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/conditional/StringConditional.java index 8aa7a41..5c8ec35 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/conditional/StringConditional.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/conditional/StringConditional.java @@ -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 diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/conditional/UnresolvedConditional.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/conditional/UnresolvedConditional.java index 4f12ad3..f882abd 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/conditional/UnresolvedConditional.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/conditional/UnresolvedConditional.java @@ -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 diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Assign.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Assign.java index e7be486..fd3f46d 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Assign.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Assign.java @@ -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 diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Bind.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Bind.java index 0ff8426..52380ed 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Bind.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Bind.java @@ -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 diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Call.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Call.java index 4282f59..a48a20a 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Call.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Call.java @@ -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 args; public Call(int chStart, int chEnd, DynamicExpr left, List 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); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Closure.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Closure.java index ff0abb9..8f2af5a 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Closure.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Closure.java @@ -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 boundArgs; @@ -20,7 +24,7 @@ public class Closure extends DynamicExpr { } private Closure(int chStart, int chEnd, List boundArgs, List> 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().>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> stream() { + return Stream.concat(steps.stream(), Stream.of(fin)); + } } diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/DynamicCoerce.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/DynamicCoerce.java index 714e4ef..9abf076 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/DynamicCoerce.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/DynamicCoerce.java @@ -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); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Get.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Get.java index e34965d..221cf98 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Get.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Get.java @@ -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); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/ObjectLiteral.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/ObjectLiteral.java new file mode 100644 index 0000000..db2bbe2 --- /dev/null +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/ObjectLiteral.java @@ -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 content; + + public ObjectLiteral(int chStart, int chEnd, Map content) { + super(Order.Primary, chStart, chEnd); + this.content = content; + } + + @Override + public Dynamic get(Scope dataRoot) { + Map> result = new LinkedHashMap<>(); + this.content.forEach((k, v) -> result.put(k, v.get(dataRoot))); + return DFinal.of(result); + } + + @Override + public DynamicExpr optimize() { + Map content = new LinkedHashMap<>(); + Map> literalContent = new LinkedHashMap<>(); + boolean literal = true; + for (Map.Entry 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 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}"); + } +} diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Variable.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Variable.java index 7878406..e4f538a 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Variable.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/Variable.java @@ -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); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/unpack/BoolUnpack.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/unpack/BoolUnpack.java index 4ce2456..8476b7e 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/unpack/BoolUnpack.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/unpack/BoolUnpack.java @@ -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); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/unpack/NumberUnpack.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/unpack/NumberUnpack.java index ca4e160..214be3f 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/unpack/NumberUnpack.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/unpack/NumberUnpack.java @@ -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); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/unpack/StringUnpack.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/unpack/StringUnpack.java index b438ba2..62664cc 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/unpack/StringUnpack.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/dynamic/unpack/StringUnpack.java @@ -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); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/literal/BoolLiteral.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/literal/BoolLiteral.java index 263100a..56882ae 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/literal/BoolLiteral.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/literal/BoolLiteral.java @@ -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; diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/literal/DynamicLiteral.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/literal/DynamicLiteral.java index 08702ac..6a291d7 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/literal/DynamicLiteral.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/literal/DynamicLiteral.java @@ -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 extends DynamicExpr { public final Dynamic value; public DynamicLiteral(int chStart, int chEnd, Dynamic value) { - super(chStart, chEnd); + super(Order.Primary, chStart, chEnd); this.value = value; } @@ -22,6 +26,11 @@ public final class DynamicLiteral 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); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/literal/NumberLiteral.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/literal/NumberLiteral.java index 331d1af..baa7627 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/literal/NumberLiteral.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/literal/NumberLiteral.java @@ -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; diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/literal/StringLiteral.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/literal/StringLiteral.java index 121e075..43a93e3 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/literal/StringLiteral.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/literal/StringLiteral.java @@ -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); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Divide.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Divide.java index 85eee7d..2a0f8f6 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Divide.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Divide.java @@ -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); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Invert.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Invert.java index af29620..d5553b9 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Invert.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Invert.java @@ -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); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Minus.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Minus.java index bc38205..99d9769 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Minus.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Minus.java @@ -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); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Modulo.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Modulo.java index 1bf8e0a..eb8013a 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Modulo.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Modulo.java @@ -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); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Multiply.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Multiply.java index 663462a..1fb9f83 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Multiply.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Multiply.java @@ -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); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Plus.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Plus.java index 21a3bff..e84dc45 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Plus.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Plus.java @@ -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); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Power.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Power.java index 08387c2..913ca55 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Power.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/math/Power.java @@ -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); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/string/Concatenate.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/string/Concatenate.java index 3647fb4..8775f86 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/string/Concatenate.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/string/Concatenate.java @@ -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); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/string/StringCoerce.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/string/StringCoerce.java index de023ae..9573918 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/string/StringCoerce.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/ast/string/StringCoerce.java @@ -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); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Decompilable.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Decompilable.java new file mode 100644 index 0000000..3f7f6db --- /dev/null +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Decompilable.java @@ -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(); + } +} diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/ExprWriter.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/ExprWriter.java new file mode 100644 index 0000000..c2b00e2 --- /dev/null +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/ExprWriter.java @@ -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"); + } +} diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Lexer.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Lexer.java index a4b430f..3329283 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Lexer.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Lexer.java @@ -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 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() { diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Order.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Order.java new file mode 100644 index 0000000..0f49c25 --- /dev/null +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Order.java @@ -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; +} diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Parser.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Parser.java index 2ef8c6b..adf49b8 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Parser.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Parser.java @@ -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 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> 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 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> 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 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 { diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/Scope.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/Scope.java index 3036b7a..fbba659 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/Scope.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/Scope.java @@ -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> 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); + } } diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/Script.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/Script.java index b177807..fcd9c56 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/Script.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/Script.java @@ -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> steps; private final DynamicExpr fin; @@ -18,6 +21,7 @@ public class Script { } private Script(List> 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> 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"); + } + } } diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DBool.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DBool.java index f423fbf..da148e6 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DBool.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DBool.java @@ -1,4 +1,12 @@ package io.gitlab.jfronny.muscript.data.dynamic; -public interface DBool extends Dynamic { +import io.gitlab.jfronny.muscript.compiler.ExprWriter; + +import java.io.IOException; + +public non-sealed interface DBool extends Dynamic { + @Override + default void serialize(ExprWriter writer) throws IOException { + writer.append(getValue().toString()); + } } diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DCallable.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DCallable.java index 4703b50..31d0b57 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DCallable.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DCallable.java @@ -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>> { +public non-sealed interface DCallable extends Dynamic>> { default Dynamic call(DList args) { return getValue().apply(args); } diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DCallableObject.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DCallableObject.java deleted file mode 100644 index c0892a3..0000000 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DCallableObject.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.gitlab.jfronny.muscript.data.dynamic; - -import java.util.Map; - -public record DCallableObject(Map> value, DCallable callable) implements DObject { - @Override - public Map> getValue() { - return value; - } - - @Override - public DCallable asCallable() { - return callable; - } -} diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DList.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DList.java index 59965a0..ac943d9 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DList.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DList.java @@ -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>> { +public non-sealed interface DList extends Dynamic>> { default Dynamic get(int i) { return getValue().get(i); } @@ -14,4 +17,16 @@ public interface DList extends 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(')'); + } } diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DNull.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DNull.java index c6fbe9a..8ba161d 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DNull.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DNull.java @@ -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 { @Override public Object getValue() { return null; } + + @Override + public void serialize(ExprWriter writer) throws IOException { + writer.append(toString()); + } + + @Override + public String toString() { + return "null"; + } } diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DNumber.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DNumber.java index 9bba78b..dd10ded 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DNumber.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DNumber.java @@ -1,4 +1,13 @@ package io.gitlab.jfronny.muscript.data.dynamic; -public interface DNumber extends Dynamic { +import io.gitlab.jfronny.commons.StringFormatter; +import io.gitlab.jfronny.muscript.compiler.ExprWriter; + +import java.io.IOException; + +public non-sealed interface DNumber extends Dynamic { + @Override + default void serialize(ExprWriter writer) throws IOException { + writer.append(StringFormatter.toString(getValue())); + } } diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DObject.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DObject.java index 85e11a3..e94b6ef 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DObject.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DObject.java @@ -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>> { +public non-sealed interface DObject extends Dynamic>> { default Dynamic get(String key) { return getValue().get(key); } @@ -10,4 +14,18 @@ public interface DObject extends 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> 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('}'); + } } diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DString.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DString.java index d103a77..41b6e72 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DString.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DString.java @@ -1,4 +1,13 @@ package io.gitlab.jfronny.muscript.data.dynamic; -public interface DString extends Dynamic { +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 { + @Override + default void serialize(ExprWriter writer) throws IOException { + writer.append(Decompilable.enquote(getValue())); + } } diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/Dynamic.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/Dynamic.java index 3ddb067..0a11e61 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/Dynamic.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/Dynamic.java @@ -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 { +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 the type represented + */ +public sealed interface Dynamic 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() { diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DCallableObject.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DCallableObject.java new file mode 100644 index 0000000..2f520ed --- /dev/null +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DCallableObject.java @@ -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> value, DCallable callable) implements DObject { + @Override + public Map> 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); + } +} diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DContainer.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DContainer.java similarity index 54% rename from muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DContainer.java rename to muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DContainer.java index 2869466..9bfd552 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DContainer.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DContainer.java @@ -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 implements Dynamic { +public abstract non-sealed class DContainer implements Dynamic { private T value; @Override @@ -18,6 +18,6 @@ public abstract class DContainer implements Dynamic { @Override public String toString() { - return StringFormatter.toString(value); + return Dynamic.serialize(this); } } diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DDate.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DDate.java new file mode 100644 index 0000000..cd9c87d --- /dev/null +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DDate.java @@ -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 date) implements DObject { + @Override + public Map> 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(); + } +} diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DEnum.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DEnum.java similarity index 73% rename from muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DEnum.java rename to muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DEnum.java index 2b6e158..24f89e8 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DEnum.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DEnum.java @@ -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> values, @Nullable DEnumEntry value) implements DObject { public DEnum(Map> values) { - this(values, null); + this(values, (DEnumEntry) null); + } + + public DEnum(Map> values, @Nullable String value) { + this(values, value == null ? null : new DEnumEntry(value, values.keySet().stream().toList().indexOf(value), true)); } public DEnum(List values, String value) { @@ -42,6 +49,16 @@ public record DEnum(Map> 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> createMap(List values, String value) { Map> result = new LinkedHashMap<>(); DEnumEntry v = new DEnumEntry(value, values.indexOf(value), true); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DFinal.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DFinal.java similarity index 71% rename from muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DFinal.java rename to muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DFinal.java index 467e066..f7e08ef 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/DFinal.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DFinal.java @@ -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 value) implements Dynamic { +public class DFinal { public static DBool of(boolean b) { return new FBool(b); } @@ -32,18 +37,8 @@ public record DFinal(T value) implements Dynamic { return new FList(ImmCollection.of(b)); } - public static DCallable of(Function> b) { - return new FCallable(b); - } - - @Override - public T getValue() { - return value; - } - - @Override - public String toString() { - return StringFormatter.toString(value); + public static DCallable of(Function> b, Supplier serialized) { + return new FCallable(b, new LazySupplier<>(serialized)); } private record FBool(boolean value) implements DBool { @@ -78,7 +73,7 @@ public record DFinal(T value) implements Dynamic { @Override public String toString() { - return StringFormatter.toString(value); + return Dynamic.serialize(this); } } @@ -90,7 +85,7 @@ public record DFinal(T value) implements Dynamic { @Override public String toString() { - return StringFormatter.toString(value); + return Dynamic.serialize(this); } } @@ -102,11 +97,16 @@ public record DFinal(T value) implements Dynamic { @Override public String toString() { - return StringFormatter.toString(value); + return value.toString(); } } - private record FCallable(Function> value) implements DCallable { + private record FCallable(Function> value, Supplier string) implements DCallable { + @Override + public void serialize(ExprWriter writer) throws IOException { + writer.append(toString()); + } + @Override public Function> getValue() { return value; @@ -114,7 +114,7 @@ public record DFinal(T value) implements Dynamic { @Override public String toString() { - return ""; + return string.get(); } } } diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DTime.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DTime.java new file mode 100644 index 0000000..4033272 --- /dev/null +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DTime.java @@ -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 time) implements DObject { + @Override + public Map> 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(); + } +} diff --git a/muscript/src/main/java/module-info.java b/muscript/src/main/java/module-info.java index 05c1cab..393ca9e 100644 --- a/muscript/src/main/java/module-info.java +++ b/muscript/src/main/java/module-info.java @@ -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; } \ No newline at end of file diff --git a/muscript/src/test/java/io/gitlab/jfronny/muscript/test/AssignTest.java b/muscript/src/test/java/io/gitlab/jfronny/muscript/test/AssignTest.java index 8647beb..3a83ae7 100644 --- a/muscript/src/test/java/io/gitlab/jfronny/muscript/test/AssignTest.java +++ b/muscript/src/test/java/io/gitlab/jfronny/muscript/test/AssignTest.java @@ -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()); diff --git a/muscript/src/test/java/io/gitlab/jfronny/muscript/test/ClosureTest.java b/muscript/src/test/java/io/gitlab/jfronny/muscript/test/ClosureTest.java index ec42793..52a64b5 100644 --- a/muscript/src/test/java/io/gitlab/jfronny/muscript/test/ClosureTest.java +++ b/muscript/src/test/java/io/gitlab/jfronny/muscript/test/ClosureTest.java @@ -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; diff --git a/muscript/src/test/java/io/gitlab/jfronny/muscript/test/ObjectTest.java b/muscript/src/test/java/io/gitlab/jfronny/muscript/test/ObjectTest.java index f533878..f5ab715 100644 --- a/muscript/src/test/java/io/gitlab/jfronny/muscript/test/ObjectTest.java +++ b/muscript/src/test/java/io/gitlab/jfronny/muscript/test/ObjectTest.java @@ -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()); + } } diff --git a/muscript/src/test/java/io/gitlab/jfronny/muscript/test/StringTest.java b/muscript/src/test/java/io/gitlab/jfronny/muscript/test/StringTest.java index 7ac155f..98a1d5c 100644 --- a/muscript/src/test/java/io/gitlab/jfronny/muscript/test/StringTest.java +++ b/muscript/src/test/java/io/gitlab/jfronny/muscript/test/StringTest.java @@ -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'")); + } } diff --git a/muscript/src/test/java/io/gitlab/jfronny/muscript/test/util/MuTestUtil.java b/muscript/src/test/java/io/gitlab/jfronny/muscript/test/util/MuTestUtil.java index 16ec98c..7fc6c1f 100644 --- a/muscript/src/test/java/io/gitlab/jfronny/muscript/test/util/MuTestUtil.java +++ b/muscript/src/test/java/io/gitlab/jfronny/muscript/test/util/MuTestUtil.java @@ -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))) diff --git a/muscript/src/test/resources/example.md b/muscript/src/test/resources/example.md index cdc4447..c091d7d 100644 --- a/muscript/src/test/resources/example.md +++ b/muscript/src/test/resources/example.md @@ -75,7 +75,7 @@ listOf( ``` Result: ``` -[this is correct, false, false, true, true, false] +['this is correct', false, false, true, true, false] ``` @@ -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] ``` @@ -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] ```