From 749b475d0d9ed59bde9d95407ec91df1e944c56d Mon Sep 17 00:00:00 2001 From: JFronny Date: Sat, 30 Sep 2023 11:59:44 +0200 Subject: [PATCH] feat(muscript): Weak type signature system for Dynamic, might get extended to muScript expressions --- .../jfronny/muscript/ast/dynamic/Call.java | 2 +- .../jfronny/muscript/ast/dynamic/Closure.java | 13 +- .../gitlab/jfronny/muscript/data/Scope.java | 15 +- .../muscript/data/dynamic/DCallable.java | 11 + .../jfronny/muscript/data/dynamic/DNull.java | 7 + .../muscript/data/dynamic/Dynamic.java | 13 + .../dynamic/additional/DCallableObject.java | 21 +- .../data/dynamic/additional/DDate.java | 14 +- .../data/dynamic/additional/DEmpty.java | 31 ++ .../data/dynamic/additional/DEnum.java | 31 +- .../data/dynamic/additional/DFinal.java | 85 +++++- .../data/dynamic/additional/DTime.java | 15 +- .../dynamic/additional/DelegateDynamic.java | 6 + .../dynamic/additional/NamedDCallable.java | 6 + .../muscript/data/dynamic/lens/DLens.java | 7 + .../muscript/data/dynamic/type/DSL.java | 40 +++ .../muscript/data/dynamic/type/DType.java | 43 +++ .../muscript/data/dynamic/type/DTypeAnd.java | 45 +++ .../data/dynamic/type/DTypeCallable.java | 27 ++ .../data/dynamic/type/DTypeGeneric.java | 8 + .../muscript/data/dynamic/type/DTypeList.java | 10 + .../data/dynamic/type/DTypeObject.java | 11 + .../data/dynamic/type/DTypePrimitive.java | 10 + .../muscript/data/dynamic/type/DTypeSum.java | 35 +++ .../data/dynamic/type/TypeMatcher.java | 195 ++++++++++++ .../gitlab/jfronny/muscript/libs/IOLib.java | 10 +- .../libs/SignatureDesyncException.java | 7 + .../jfronny/muscript/libs/StandardLib.java | 289 ++++++++++++------ muscript/src/main/java/module-info.java | 1 + .../muscript/test/ExceptionHandlingTest.java | 6 +- 30 files changed, 885 insertions(+), 129 deletions(-) create mode 100644 muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DEmpty.java create mode 100644 muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DSL.java create mode 100644 muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DType.java create mode 100644 muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeAnd.java create mode 100644 muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeCallable.java create mode 100644 muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeGeneric.java create mode 100644 muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeList.java create mode 100644 muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeObject.java create mode 100644 muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypePrimitive.java create mode 100644 muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeSum.java create mode 100644 muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/TypeMatcher.java create mode 100644 muscript/src/main/java/io/gitlab/jfronny/muscript/libs/SignatureDesyncException.java 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 eb4a47e..1a82938 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 @@ -42,7 +42,7 @@ public class Call extends DynamicExpr { throw new LocationalException(location, "Could not perform call successfully", e); } try { - return dc.getValue().apply(arg); + return dc.call(arg); } catch (LocationalException le) { throw le.appendStack(new StackFrame.Raw(location.file(), dc.getName(), left.location.chStart())); } catch (DynamicTypeConversionException e) { 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 c1a06a8..0d0e98f 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 @@ -6,9 +6,11 @@ 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.data.dynamic.type.DTypeCallable; import io.gitlab.jfronny.muscript.error.LocationalException; import java.io.IOException; +import java.util.LinkedList; import java.util.List; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -31,9 +33,18 @@ public class Closure extends DynamicExpr { this.variadic = variadic; } + private DTypeCallable getSignature() { + //TODO take into account inner instructions to properly determine type + List args = new LinkedList<>(); + for (int i = 0; i < boundArgs.size(); i++) { + args.add(new DTypeCallable.Arg(boundArgs.get(i), null, variadic && (i == boundArgs.size() - 1))); + } + return new DTypeCallable(args, null); + } + @Override public DCallable get(Scope dataRoot) { - return DFinal.of(args -> { + return DFinal.of(getSignature(), args -> { // Compare with ExprGroup.get() int ac = args.size(); int ae = boundArgs.size(); 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 669a620..78b5930 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 @@ -3,6 +3,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 io.gitlab.jfronny.muscript.data.dynamic.type.*; import org.jetbrains.annotations.Nullable; import java.util.*; @@ -79,12 +80,24 @@ public class Scope implements DObject { return set(key, DFinal.of(value)); } + public Scope set(String key, DTypeObject signature, Map value) { + return set(key, DFinal.of(signature, value)); + } + public Scope set(String key, List value) { return set(key, DFinal.of(value)); } + public Scope set(String key, DTypeList signature, List value) { + return set(key, DFinal.of(signature, value)); + } + public Scope set(String key, Function value) { - return set(key, DFinal.of(value, () -> key)); + return set(key, DFinal.of(value, () -> key, key)); + } + + public Scope set(String key, DType signature, Function value) { + return set(key, DFinal.of(signature, value, () -> key, key)); } public Scope fork() { 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 f92b9a6..d3939fd 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 @@ -2,13 +2,24 @@ package io.gitlab.jfronny.muscript.data.dynamic; import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal; import io.gitlab.jfronny.muscript.data.dynamic.additional.NamedDCallable; +import io.gitlab.jfronny.muscript.data.dynamic.type.*; +import java.util.List; import java.util.function.Function; public non-sealed interface DCallable extends Dynamic { String DEFAULT_NAME = ""; default Dynamic call(DList args) { + DType signature = getSignature(); + if (signature != null) { + List argTypes = args.getValue().stream().map(Dynamic::getSignature).toList(); + if (!TypeMatcher.match(signature, argTypes)) { + List extraArgs = argTypes.stream().map(s -> new DTypeCallable.Arg(null, s, false)).toList(); + DTypeCallable callSignature = new DTypeCallable(extraArgs, null); + throw new IllegalArgumentException("Signature mismatch for " + getName() + ": expected <" + signature + "> but got <" + callSignature + ">"); + } + } return getValue().apply(args); } 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 0b69e8c..c90e3f0 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 @@ -2,6 +2,8 @@ package io.gitlab.jfronny.muscript.data.dynamic; import io.gitlab.jfronny.muscript.ast.NullLiteral; import io.gitlab.jfronny.muscript.compiler.CodeLocation; +import io.gitlab.jfronny.muscript.data.dynamic.type.DType; +import io.gitlab.jfronny.muscript.data.dynamic.type.DTypePrimitive; public final class DNull implements Dynamic { @Override @@ -14,6 +16,11 @@ public final class DNull implements Dynamic { return new NullLiteral(CodeLocation.NONE); } + @Override + public DType getSignature() { + return DTypePrimitive.NULL; + } + @Override public String toString() { return "null"; 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 504f182..c1663de 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 @@ -2,6 +2,7 @@ package io.gitlab.jfronny.muscript.data.dynamic; import io.gitlab.jfronny.commons.StringFormatter; import io.gitlab.jfronny.muscript.compiler.MuScriptVersion; +import io.gitlab.jfronny.muscript.data.dynamic.type.*; import io.gitlab.jfronny.muscript.libs.StandardLib; import io.gitlab.jfronny.muscript.ast.DynamicExpr; import io.gitlab.jfronny.muscript.ast.Expr; @@ -13,6 +14,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; +import java.util.*; /** * Represents a value of an unknown type @@ -107,4 +109,15 @@ public sealed interface Dynamic permits DBool, DCallable, DList, DNull, DNumber, if (this instanceof DCallable callable) return callable; else throw new DynamicTypeConversionException("callable", this); } + + default DType getSignature() { + Set variants = new HashSet<>(); + if (isBool()) variants.add(DTypePrimitive.BOOL); + if (isNumber()) variants.add(DTypePrimitive.NUMBER); + if (isString()) variants.add(DTypePrimitive.STRING); + if (isObject()) variants.add(new DTypeObject(null)); + if (isList()) variants.add(new DTypeList(null)); + if (isCallable()) variants.add(new DTypeCallable(null, null)); + return variants.size() == 1 ? variants.stream().findFirst().orElseThrow() : new DTypeAnd(variants); + } } 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 index 6f21736..c34b02a 100644 --- 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 @@ -5,11 +5,21 @@ import io.gitlab.jfronny.muscript.ast.dynamic.*; import io.gitlab.jfronny.muscript.compiler.CodeLocation; import io.gitlab.jfronny.muscript.data.dynamic.*; import io.gitlab.jfronny.muscript.data.dynamic.lens.DCallableLens; +import io.gitlab.jfronny.muscript.data.dynamic.type.*; -import java.util.List; -import java.util.Map; +import java.util.*; + +public record DCallableObject(Map value, DTypeObject objectSignature, DCallable callable) implements DObject { + public DCallableObject(Map value, DCallable callable) { + this(value, new DTypeObject(null), callable); + } + + public DCallableObject { + Objects.requireNonNull(value); + Objects.requireNonNull(objectSignature); + Objects.requireNonNull(callable); + } -public record DCallableObject(Map value, DCallable callable) implements DObject { @Override public Map getValue() { return value; @@ -30,6 +40,11 @@ public record DCallableObject(Map value, DCallable ca return new Call(CodeLocation.NONE, new Bind(CodeLocation.NONE, new Variable(CodeLocation.NONE, "callableObject"), DObject.super.toExpr()), List.of(new Call.Arg(callable.toExpr().asDynamicExpr(), false))); } + @Override + public DType getSignature() { + return new DTypeAnd(Set.of(objectSignature, callable.getSignature())); + } + @Override public String toString() { 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 index d50932c..db38d3f 100644 --- 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 @@ -8,13 +8,18 @@ import io.gitlab.jfronny.muscript.compiler.CodeLocation; import io.gitlab.jfronny.muscript.data.dynamic.*; import io.gitlab.jfronny.muscript.data.dynamic.lens.DNumberLens; import io.gitlab.jfronny.muscript.data.dynamic.lens.DStringLens; +import io.gitlab.jfronny.muscript.data.dynamic.type.*; import java.time.LocalDate; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.Supplier; +import static io.gitlab.jfronny.muscript.data.dynamic.type.DSL.*; +import static io.gitlab.jfronny.muscript.data.dynamic.type.DSL.NUMBER; + public record DDate(Supplier date) implements DObject { + public static final DType SIGNATURE = object(NUMBER).or(STRING).or(NUMBER); + @Override public Map getValue() { return Map.of( @@ -53,6 +58,11 @@ public record DDate(Supplier date) implements DObject { )); } + @Override + public DType getSignature() { + return SIGNATURE; + } + @Override public String toString() { return date.get().toString(); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DEmpty.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DEmpty.java new file mode 100644 index 0000000..70e2377 --- /dev/null +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DEmpty.java @@ -0,0 +1,31 @@ +package io.gitlab.jfronny.muscript.data.dynamic.additional; + +import io.gitlab.jfronny.muscript.data.dynamic.*; +import io.gitlab.jfronny.muscript.data.dynamic.lens.DListLens; + +import java.util.List; +import java.util.Map; + +public enum DEmpty implements DObject { + INSTANCE; + + @Override + public String toString() { + return "[]"; + } + + @Override + public Map getValue() { + return Map.of(); + } + + @Override + public boolean isList() { + return true; + } + + @Override + public DList asList() { + return new DListLens(this, List::of); + } +} diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DEnum.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DEnum.java index 9e524bd..e5e3fba 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DEnum.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DEnum.java @@ -9,6 +9,7 @@ import io.gitlab.jfronny.muscript.ast.literal.StringLiteral; import io.gitlab.jfronny.muscript.compiler.CodeLocation; import io.gitlab.jfronny.muscript.data.dynamic.*; import io.gitlab.jfronny.muscript.data.dynamic.lens.*; +import io.gitlab.jfronny.muscript.data.dynamic.type.*; import org.jetbrains.annotations.Nullable; import java.util.*; @@ -17,17 +18,25 @@ import java.util.*; * An enum represented as an OObject. * May also have a selected value with automatic conversions to a number (index) and string */ -public record DEnum(Map values, @Nullable DEnumEntry value) implements DObject { +public record DEnum(Map values, @Nullable DEnumEntry value, @Nullable DType valueSignature) implements DObject { public DEnum(Map values) { - this(values, (DEnumEntry) null); + this(null, values); + } + + public DEnum(DType valueSignature, Map values) { + this(values, null, valueSignature); } public DEnum(Map values, @Nullable String value) { - this(values, value == null ? null : new DEnumEntry(value, values.keySet().stream().toList().indexOf(value), true)); + this(null, values, value); + } + + public DEnum(DType valueSignature, Map values, @Nullable String value) { + this(values, value == null ? null : new DEnumEntry(value, values.keySet().stream().toList().indexOf(value), true), valueSignature); } public DEnum(List values, String value) { - this(createMap(values, value), value == null ? null : new DEnumEntry(value, values.indexOf(value), true)); + this(createMap(values, value), value == null ? null : new DEnumEntry(value, values.indexOf(value), true), DTypePrimitive.STRING); } public DEnum(List values) { @@ -61,12 +70,12 @@ public record DEnum(Map values, @Nullable DEnumEntry @Override public boolean isNumber() { - return true; + return value != null; } @Override public DNumber asNumber() { - return value != null ? value.asNumber() : DObject.super.asNumber(); + return value != null ? new DNumberLens(this, value::index) : DObject.super.asNumber(); } @Override @@ -77,6 +86,16 @@ public record DEnum(Map values, @Nullable DEnumEntry return new Call(CodeLocation.NONE, new Variable(CodeLocation.NONE, "enum"), args); } + @Override + public DType getSignature() { + Set types = new HashSet<>(); + types.add(new DTypeObject(valueSignature)); + types.add(new DTypeList(valueSignature)); + types.add(DTypePrimitive.STRING); + if (value != null) types.add(DTypePrimitive.NUMBER); + return new DTypeAnd(types); + } + 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/additional/DFinal.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DFinal.java index 4c2698e..4d21462 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DFinal.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DFinal.java @@ -7,10 +7,9 @@ import io.gitlab.jfronny.muscript.ast.Expr; import io.gitlab.jfronny.muscript.ast.dynamic.Variable; import io.gitlab.jfronny.muscript.compiler.*; import io.gitlab.jfronny.muscript.data.dynamic.*; +import io.gitlab.jfronny.muscript.data.dynamic.type.*; -import java.io.IOException; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.Function; import java.util.function.Supplier; @@ -28,31 +27,59 @@ public class DFinal { } public static DObject of(Map b) { - return new FObject(ImmCollection.copyOf((Map) b)); + return of(new DTypeObject(null), b); + } + + public static DObject of(DTypeObject signature, Map b) { + return new FObject(ImmCollection.copyOf((Map) b), Objects.requireNonNull(signature)); } public static DList of(Dynamic... b) { - return new FList(List.of(b)); + return of(new DTypeList(null), b); + } + + public static DList of(DTypeList signature, Dynamic... b) { + return new FList(List.of(b), Objects.requireNonNull(signature)); } public static DList of(List b) { - return new FList(ImmCollection.copyOf((List) b)); + return of(new DTypeList(null), b); + } + + public static DList of(DTypeList signature, List b) { + return new FList(ImmCollection.copyOf((List) b), Objects.requireNonNull(signature)); } public static DCallable of(Function b, Supplier serialized) { - return of(b, serialized, null); + return of(new DTypeCallable(null, null), b, Objects.requireNonNull(serialized)); + } + + public static DCallable of(DType signature, Function b, Supplier serialized) { + return of(Objects.requireNonNull(signature), b, serialized, null); } public static DCallable of(Function b, String name) { - return of(name, b, () -> new Variable(CodeLocation.NONE, name)); + return of(new DTypeCallable(null, null), b, name); + } + + public static DCallable of(DType signature, Function b, String name) { + return of(Objects.requireNonNull(signature), name, b, () -> new Variable(CodeLocation.NONE, name)); } public static DCallable of(Function b, Supplier serialized, String name) { - return of(name, b, () -> Parser.parse(MuScriptVersion.DEFAULT, serialized.get())); + return of(new DTypeCallable(null, null), b, serialized, name); + } + + public static DCallable of(DType signature, Function b, Supplier serialized, String name) { + return of(Objects.requireNonNull(signature), name, b, () -> Parser.parse(MuScriptVersion.DEFAULT, serialized.get())); } public static DCallable of(String name, Function b, Supplier> serialized) { - return new FCallable((Function) b, new LazySupplier<>(serialized), name); + return of(new DTypeCallable(null, null), name, b, serialized); + } + + public static DCallable of(DType signature, String name, Function b, Supplier> serialized) { + return new FCallable((Function) b, new LazySupplier<>(serialized), name, Objects.requireNonNull(signature)); } /** @@ -71,6 +98,11 @@ public class DFinal { public String toString() { return StringFormatter.toString(value); } + + @Override + public DType getSignature() { + return DTypePrimitive.BOOL; + } } private record FNumber(double value) implements DNumber, FImpl { @@ -83,6 +115,11 @@ public class DFinal { public String toString() { return StringFormatter.toStringPrecise(value); } + + @Override + public DType getSignature() { + return DTypePrimitive.NUMBER; + } } private record FString(String value) implements DString, FImpl { @@ -95,9 +132,14 @@ public class DFinal { public String toString() { return Dynamic.serialize(this); } + + @Override + public DType getSignature() { + return DTypePrimitive.STRING; + } } - private record FObject(Map value) implements DObject, FImpl { + private record FObject(Map value, DTypeObject signature) implements DObject, FImpl { @Override public Map getValue() { return value; @@ -107,9 +149,14 @@ public class DFinal { public String toString() { return Dynamic.serialize(this); } + + @Override + public DType getSignature() { + return signature; + } } - private record FList(List value) implements DList, FImpl { + private record FList(List value, DTypeList signature) implements DList, FImpl { @Override public List getValue() { return value; @@ -119,9 +166,14 @@ public class DFinal { public String toString() { return value.toString(); } + + @Override + public DType getSignature() { + return signature; + } } - private record FCallable(Function value, Supplier> gen, String name) implements DCallable, FImpl { + private record FCallable(Function value, Supplier> gen, String name, DType signature) implements DCallable, FImpl { @Override public Expr toExpr() { return gen.get(); @@ -144,7 +196,12 @@ public class DFinal { @Override public DCallable named(String name) { - return new FCallable(value, gen, name); + return new FCallable(value, gen, name, signature); + } + + @Override + public DType getSignature() { + return signature; } } } 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 index 5ebb42e..86178be 100644 --- 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 @@ -5,18 +5,20 @@ import io.gitlab.jfronny.muscript.ast.dynamic.Call; import io.gitlab.jfronny.muscript.ast.dynamic.Variable; import io.gitlab.jfronny.muscript.ast.literal.NumberLiteral; import io.gitlab.jfronny.muscript.compiler.CodeLocation; -import io.gitlab.jfronny.muscript.compiler.ExprWriter; import io.gitlab.jfronny.muscript.data.dynamic.*; import io.gitlab.jfronny.muscript.data.dynamic.lens.DNumberLens; import io.gitlab.jfronny.muscript.data.dynamic.lens.DStringLens; +import io.gitlab.jfronny.muscript.data.dynamic.type.*; -import java.io.IOException; import java.time.LocalTime; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.Supplier; +import static io.gitlab.jfronny.muscript.data.dynamic.type.DSL.*; + public record DTime(Supplier time) implements DObject { + public static final DType SIGNATURE = object(NUMBER).or(STRING).or(NUMBER); + @Override public Map getValue() { return Map.of( @@ -55,6 +57,11 @@ public record DTime(Supplier time) implements DObject { )); } + @Override + public DType getSignature() { + return SIGNATURE; + } + @Override public String toString() { return time.get().toString(); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DelegateDynamic.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DelegateDynamic.java index 726fdbc..6fe7627 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DelegateDynamic.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/DelegateDynamic.java @@ -3,6 +3,7 @@ package io.gitlab.jfronny.muscript.data.dynamic.additional; import io.gitlab.jfronny.muscript.ast.Expr; import io.gitlab.jfronny.muscript.compiler.ExprWriter; import io.gitlab.jfronny.muscript.data.dynamic.*; +import io.gitlab.jfronny.muscript.data.dynamic.type.DType; import java.io.IOException; @@ -83,4 +84,9 @@ public interface DelegateDynamic extends DynamicBase { default DCallable asCallable() { return getDelegate().asCallable(); } + + @Override + default DType getSignature() { + return getDelegate().getSignature(); + } } diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/NamedDCallable.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/NamedDCallable.java index 90ac4d8..5eccaf0 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/NamedDCallable.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/additional/NamedDCallable.java @@ -2,6 +2,7 @@ package io.gitlab.jfronny.muscript.data.dynamic.additional; import io.gitlab.jfronny.muscript.ast.Expr; import io.gitlab.jfronny.muscript.data.dynamic.*; +import io.gitlab.jfronny.muscript.data.dynamic.type.DType; import java.util.function.Function; @@ -25,4 +26,9 @@ public record NamedDCallable(DCallable inner, String name) implements DCallable public Function getValue() { return inner.getValue(); } + + @Override + public DType getSignature() { + return inner.getSignature(); + } } diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/lens/DLens.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/lens/DLens.java index 0e6bb10..7fd2ae3 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/lens/DLens.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/lens/DLens.java @@ -2,6 +2,8 @@ package io.gitlab.jfronny.muscript.data.dynamic.lens; import io.gitlab.jfronny.muscript.compiler.ExprWriter; import io.gitlab.jfronny.muscript.data.dynamic.*; +import io.gitlab.jfronny.muscript.data.dynamic.type.DType; +import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.util.Objects; @@ -78,6 +80,11 @@ public abstract sealed class DLens implements DynamicBase permits DBoolLens, DCa return source.asCallable(); } + @Override + public @Nullable DType getSignature() { + return source.getSignature(); + } + @Override public int hashCode() { return source.hashCode(); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DSL.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DSL.java new file mode 100644 index 0000000..e0efb44 --- /dev/null +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DSL.java @@ -0,0 +1,40 @@ +package io.gitlab.jfronny.muscript.data.dynamic.type; + +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class DSL { + public static final DTypePrimitive BOOL = DTypePrimitive.BOOL; + public static final DTypePrimitive STRING = DTypePrimitive.STRING; + public static final DTypePrimitive NUMBER = DTypePrimitive.NUMBER; + public static final DTypePrimitive NULL = DTypePrimitive.NULL; + + public static DTypeGeneric generic(int index) { + return new DTypeGeneric(index); + } + + public static DTypeList list(@Nullable DType entryType) { + return new DTypeList(entryType); + } + + public static DTypeObject object(@Nullable DType entryType) { + return new DTypeObject(entryType); + } + + public static DTypeCallable callable(@Nullable List from, @Nullable DType to) { + return new DTypeCallable(from, to); + } + + public static DTypeCallable callable(@Nullable DType to, DTypeCallable.Arg... from) { + return new DTypeCallable(List.of(from), to); + } + + public static DTypeCallable.Arg arg(@Nullable String name, @Nullable DType type) { + return new DTypeCallable.Arg(name, type, false); + } + + public static DTypeCallable.Arg arg(@Nullable String name, @Nullable DType type, boolean variadic) { + return new DTypeCallable.Arg(name, type, variadic); + } +} diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DType.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DType.java new file mode 100644 index 0000000..af3db6b --- /dev/null +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DType.java @@ -0,0 +1,43 @@ +package io.gitlab.jfronny.muscript.data.dynamic.type; + +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +public sealed interface DType permits DTypeCallable, DTypeGeneric, DTypeList, DTypeObject, DTypePrimitive, DTypeSum, DTypeAnd { + static String toString(@Nullable DType type) { + return toString(type, false); + } + + default DType or(DType alternative) { + return new DTypeSum(Set.of(this, alternative)); + } + + default DType and(DType additional) { + return new DTypeAnd(Set.of(this, additional)); + } + + static String toString(@Nullable DType type, boolean wrapComplex) { + //TODO replace with switch pattern + if (type == null) return "any"; + else if (type instanceof DTypePrimitive t) return switch (t) { + case BOOL -> "bool"; + case NUMBER -> "number"; + case STRING -> "string"; + case NULL -> "null"; + }; + else if (type instanceof DTypeGeneric t) return "T" + t.index(); + else if (type instanceof DTypeList t) return "[" + toString(t.entryType(), false) + "]"; + else if (type instanceof DTypeObject t) return "{" + toString(t.entryType(), false) + "}"; + else if (type instanceof DTypeCallable t) { + String args = t.from() == null + ? "any" + : "(" + t.from().stream().map(Objects::toString).collect(Collectors.joining(", ")) + ")"; + return args + " -> " + toString(t.to(), true); + } else if (type instanceof DTypeSum t) return (wrapComplex ? "<" : "") + t.elements().stream().map(s -> toString(s, true)).collect(Collectors.joining(" | ")) + (wrapComplex ? ">" : ""); + else if (type instanceof DTypeAnd t) return (wrapComplex ? "<" : "") + t.elements().stream().map(s -> toString(s, true)).collect(Collectors.joining(" & ")) + (wrapComplex ? ">" : ""); + else throw new IllegalArgumentException("Unexpected DType implementation: " + type.getClass()); + } +} diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeAnd.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeAnd.java new file mode 100644 index 0000000..41fc1c6 --- /dev/null +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeAnd.java @@ -0,0 +1,45 @@ +package io.gitlab.jfronny.muscript.data.dynamic.type; + +import io.gitlab.jfronny.commons.data.ImmCollection; + +import java.util.*; + +public record DTypeAnd(Set elements) implements DType { + public DTypeAnd(Set elements) { + if (elements.isEmpty()) throw new IllegalArgumentException("Cannot create union type without elements"); + + Set simple = new LinkedHashSet<>(); + Set list = new LinkedHashSet<>(); + Set object = new LinkedHashSet<>(); + + boolean foundNullList = false, foundNullObject = false; + + Queue toProcess = new LinkedList<>(elements); + while (!toProcess.isEmpty()) { + DType type = toProcess.remove(); + //TODO replace with pattern match + if (type instanceof DTypePrimitive || type instanceof DTypeSum || type instanceof DTypeCallable || type instanceof DTypeGeneric) simple.add(type); + else if (type instanceof DTypeList u) { + if (u.entryType() == null) { + if (!foundNullList) simple.add(new DTypeList(null)); + foundNullList = true; + } else list.add(u.entryType()); + } else if (type instanceof DTypeObject u) { + if (u.entryType() == null) { + if (!foundNullObject) simple.add(new DTypeObject(null)); + foundNullObject = true; + } else object.add(u.entryType()); + } else if (type instanceof DTypeAnd u) toProcess.addAll(u.elements); + else throw new IllegalArgumentException("Unexpected DType implementation: " + type.getClass()); + } + + if (!list.isEmpty()) simple.add(new DTypeList(new DTypeAnd(list))); + if (!object.isEmpty()) simple.add(new DTypeObject(new DTypeAnd(object))); + this.elements = ImmCollection.copyOf(simple); + } + + @Override + public String toString() { + return DType.toString(this); + } +} diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeCallable.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeCallable.java new file mode 100644 index 0000000..add1f79 --- /dev/null +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeCallable.java @@ -0,0 +1,27 @@ +package io.gitlab.jfronny.muscript.data.dynamic.type; + +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public record DTypeCallable(@Nullable List from, @Nullable DType to) implements DType { + public DTypeCallable { + if (from != null) { + for (Arg arg : from) { + if (arg == null) throw new IllegalArgumentException("Contents of 'from' cannot be null"); + } + } + } + + @Override + public String toString() { + return DType.toString(this); + } + + public record Arg(@Nullable String name, @Nullable DType type, boolean variadic) { + @Override + public String toString() { + return (name == null ? DType.toString(type) : name + ": " + DType.toString(type)) + (variadic ? "..." : ""); + } + } +} diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeGeneric.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeGeneric.java new file mode 100644 index 0000000..47b7543 --- /dev/null +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeGeneric.java @@ -0,0 +1,8 @@ +package io.gitlab.jfronny.muscript.data.dynamic.type; + +public record DTypeGeneric(int index) implements DType { + @Override + public String toString() { + return DType.toString(this); + } +} diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeList.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeList.java new file mode 100644 index 0000000..d72ad73 --- /dev/null +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeList.java @@ -0,0 +1,10 @@ +package io.gitlab.jfronny.muscript.data.dynamic.type; + +import org.jetbrains.annotations.Nullable; + +public record DTypeList(@Nullable DType entryType) implements DType { + @Override + public String toString() { + return DType.toString(this); + } +} diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeObject.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeObject.java new file mode 100644 index 0000000..9285b80 --- /dev/null +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeObject.java @@ -0,0 +1,11 @@ +package io.gitlab.jfronny.muscript.data.dynamic.type; + +import org.jetbrains.annotations.Nullable; + +//TODO redesign for complex objects +public record DTypeObject(@Nullable DType entryType) implements DType { + @Override + public String toString() { + return DType.toString(this); + } +} diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypePrimitive.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypePrimitive.java new file mode 100644 index 0000000..d0eff9e --- /dev/null +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypePrimitive.java @@ -0,0 +1,10 @@ +package io.gitlab.jfronny.muscript.data.dynamic.type; + +public enum DTypePrimitive implements DType { + BOOL, NUMBER, STRING, NULL; + + @Override + public String toString() { + return DType.toString(this); + } +} diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeSum.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeSum.java new file mode 100644 index 0000000..14989cc --- /dev/null +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/DTypeSum.java @@ -0,0 +1,35 @@ +package io.gitlab.jfronny.muscript.data.dynamic.type; + +import io.gitlab.jfronny.commons.data.ImmCollection; + +import java.util.*; + +public record DTypeSum(Set elements) implements DType { + public DTypeSum(Set elements) { + if (elements.isEmpty()) throw new IllegalArgumentException("Cannot create sum type without elements"); + + Set simple = new LinkedHashSet<>(); + Set list = new LinkedHashSet<>(); + Set object = new LinkedHashSet<>(); + + Queue toProcess = new LinkedList<>(elements); + while (!toProcess.isEmpty()) { + DType type = toProcess.remove(); + //TODO replace with pattern match + if (type instanceof DTypePrimitive || type instanceof DTypeAnd || type instanceof DTypeCallable) simple.add(type); + else if (type instanceof DTypeList u) list.add(u.entryType()); + else if (type instanceof DTypeObject u) object.add(u.entryType()); + else if (type instanceof DTypeSum u) toProcess.addAll(u.elements); + else throw new IllegalArgumentException("Unexpected DType implementation: " + type.getClass()); + } + + if (!list.isEmpty()) simple.add(new DTypeList(list.contains(null) ? null : new DTypeSum(list))); + if (!object.isEmpty()) simple.add(new DTypeObject(object.contains(null) ? null : new DTypeSum(object))); + this.elements = ImmCollection.copyOf(simple); + } + + @Override + public String toString() { + return DType.toString(this); + } +} diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/TypeMatcher.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/TypeMatcher.java new file mode 100644 index 0000000..9d109b6 --- /dev/null +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/data/dynamic/type/TypeMatcher.java @@ -0,0 +1,195 @@ +package io.gitlab.jfronny.muscript.data.dynamic.type; + +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class TypeMatcher { + public static boolean match(DType fn, List args) { + if (fn instanceof DTypeAnd ds) { + for (DType element : ds.elements()) { + if (match(element, args)) return true; + } + return false; + } else if (fn instanceof DTypeCallable ds) { + return match(ds, args); + } else return false; // Callable may have multiple other representations + } + + public static boolean match(DTypeCallable fn, List args) { + return fn.from() == null || match(fn.from(), args); + } + + public static boolean match(List declared, List provided) { + return match(declared, provided, new MatchScope()); + } + + private static boolean match(List declared, List provided, MatchScope scope) { + if (declared.isEmpty()) return provided.isEmpty(); + if (provided.isEmpty()) return declared.stream().allMatch(DTypeCallable.Arg::variadic); + if (!declared.get(0).variadic()) { + DType ct = declared.get(0).type(); + DType pt = provided.get(0); + declared = declared.subList(1, declared.size()); + provided = provided.subList(1, provided.size()); + if (ct == null) return match(declared, provided, scope); + Result r = match(ct, pt, scope); + if (r == Result.MATCH) return match(declared, provided, scope); + if (r == Result.FAILED) return false; + for (Map s2 : ((Result.PossibleMatch) r).possibleHydrations()) { + if (match(declared, provided, scope.fork(s2))) return true; + } + return false; + } + DType cArg = declared.get(0).type(); + List nextDeclared = declared.subList(1, declared.size()); + while (!provided.isEmpty()) { + if (match(nextDeclared, provided, scope)) return true; + DType dt = provided.get(0); + provided = provided.subList(1, provided.size()); + if (cArg == null) continue; + Result r = match(cArg, dt, scope); + if (r == Result.FAILED) { + match(cArg, dt, scope); + return false; + } + if (r == Result.MATCH) continue; + for (Map s2 : ((Result.PossibleMatch) r).possibleHydrations()) { + if (match(declared, provided, scope.fork(s2))) return true; + } + return false; + } + return match(nextDeclared, provided, scope); + } + + private static Result match(DType declared, @Nullable DType provided, MatchScope scope) { + if (declared instanceof DTypeGeneric dg) { + if (scope.hydrations.containsKey(dg.index())) { + Result r = match(scope.hydrations.get(dg.index()), provided, scope); + if (r == Result.FAILED) { + if (provided != null) + return new Result.PossibleMatch(Set.of(Map.of(dg.index(), scope.hydrations.get(dg.index()).or(provided)))); + return Result.MATCH; + } + return r; + } + // Ideally, we would actually store our null value, but Map doesn't support that and this works for now + return new Result.PossibleMatch(provided == null ? Set.of(Map.of()) : Set.of(Map.of(dg.index(), provided))); + } else if (provided instanceof DTypeSum dc) { + Result currentResult = Result.MATCH; + for (DType element : dc.elements()) { + currentResult = currentResult.and(match(declared, element, scope), scope); + } + return currentResult; + } else if (provided instanceof DTypeAnd dc && !(declared instanceof DTypeAnd)) { + Result currentResult = Result.FAILED; + for (DType element : dc.elements()) { + currentResult = currentResult.or(match(declared, element, scope), scope); + } + return currentResult; + } else if (declared instanceof DTypeCallable dg) { + if (!(provided instanceof DTypeCallable dc)) return Result.FAILED; + Result currentResult = Result.MATCH; + if (dg.from() != null) { + if (dc.from() == null) return Result.MATCH; + List dgf = dg.from(); + List dcf = dc.from(); + // This would theoretically need more logic to properly test whether the functions match + // but for now this bypass will do +// if (dgf.size() != dcf.size()) return Result.FAILED; +// for (int i = 0; i < dgf.size(); i++) { +// if (dgf.get(i).variadic() != dcf.get(i).variadic()) return Result.FAILED; +// currentResult = currentResult.and(match(dgf.get(i).type(), dcf.get(i).type(), scope), scope); +// if (currentResult == Result.FAILED) return Result.FAILED; +// } + } + if (dg.to() == null) return currentResult; + if (dc.to() == null) return currentResult; // Temporary workaround: muScript closures always return unidentifiable types for now + return currentResult.and(match(dg.to(), dc.to(), scope), scope); + } else if (declared instanceof DTypeList dg) { + if (!(provided instanceof DTypeList dc)) return Result.FAILED; + if (dg.entryType() == null) return Result.MATCH; + return match(dg.entryType(), dc.entryType(), scope); + } else if (declared instanceof DTypeObject dg) { + if (!(provided instanceof DTypeObject dc)) return Result.FAILED; + if (dg.entryType() == null) return Result.MATCH; + return match(dg.entryType(), dc.entryType(), scope); + } else if (declared instanceof DTypePrimitive dg) { + if (!(provided instanceof DTypePrimitive dc)) return Result.FAILED; + return dg == dc ? Result.MATCH : Result.FAILED; + } else if (declared instanceof DTypeSum dg) { + Result currentResult = Result.FAILED; + for (DType element : dg.elements()) { + currentResult = currentResult.or(match(element, provided, scope), scope); + } + return currentResult; + } else if (declared instanceof DTypeAnd dg) { + Result currentResult = Result.MATCH; + for (DType element : dg.elements()) { + currentResult = currentResult.and(match(element, provided, scope), scope); + } + return currentResult; + } else throw new IllegalArgumentException("Unexpected DType implementation: " + declared.getClass()); + } + + private record MatchScope(Map hydrations) { + public MatchScope() { + this(Map.of()); + } + + public MatchScope fork(Map additional) { + Map fork = new LinkedHashMap<>(hydrations); + fork.putAll(additional); + return new MatchScope(Map.copyOf(fork)); + } + } + + private sealed interface Result { + default Result and(Result other, MatchScope scope) { + if (other instanceof PossibleMatch pm) return pm.and(this, scope); + if (other == FAILED) return FAILED; + if (other == MATCH) return this; + throw new IllegalArgumentException("Unexpected Result implementation"); + } + + default Result or(Result other, MatchScope scope) { + if (other instanceof PossibleMatch pm) return pm.and(this, scope); + if (other == FAILED) return this; + if (other == MATCH) return MATCH; + throw new IllegalArgumentException("Unexpected Result implementation"); + } + + Const FAILED = Const.FAILED; + Const MATCH = Const.MATCH; + enum Const implements Result { FAILED, MATCH } + record PossibleMatch(Set> possibleHydrations) implements Result { + @Override + public Result and(Result result, MatchScope scope) { + if (result == FAILED) return FAILED; + if (result == MATCH) return this; + Set> neu = ((PossibleMatch) result).possibleHydrations.stream().flatMap(left -> possibleHydrations.stream().filter(right -> + Stream.concat(left.keySet().stream(), right.keySet().stream()) + .filter(left::containsKey) + .filter(right::containsKey) + .allMatch(s -> { + Result r = match(left.get(s), right.get(s), scope); + if (r == FAILED) return false; + if (r == MATCH) return true; + throw new IllegalArgumentException("Unexpected post-match Result implementation"); + }) + )).collect(Collectors.toCollection(LinkedHashSet::new)); + if (neu.isEmpty()) return FAILED; + return new PossibleMatch(neu); + } + + @Override + public Result or(Result other, MatchScope scope) { + if (other == FAILED) return this; + Stream> oth = other == MATCH ? Stream.of(Map.of()) : ((PossibleMatch) other).possibleHydrations.stream(); + return new PossibleMatch(Stream.concat(possibleHydrations.stream(), oth).collect(Collectors.toCollection(LinkedHashSet::new))); + } + } + } +} diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/libs/IOLib.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/libs/IOLib.java index f60fc75..ac33b69 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/libs/IOLib.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/libs/IOLib.java @@ -9,14 +9,16 @@ import io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal; import java.util.List; +import static io.gitlab.jfronny.muscript.data.dynamic.type.DSL.*; + public class IOLib { public static Scope addTo(MuScriptVersion version, Scope scope, IOWrapper io) { if (version.contains(MuScriptVersion.V3)) { - scope.set("readString", args -> { - if (args.size() != 1) throw new IllegalArgumentException("Invalid number of arguments for readString: expected 1 but got " + args.size()); + scope.set("readString", callable(STRING, arg("fileName", STRING)), args -> { + if (args.size() != 1) throw new SignatureDesyncException("readString"); return DFinal.of(io.readString(args.get(0).asString().getValue())); - }).set("runScript", args -> { - if (args.isEmpty()) throw new IllegalArgumentException("Invalid number of arguments for evaluateScript: expected 1 or more but got " + args.size()); + }).set("runScript", callable(null, arg("fileName", STRING), arg("args", null, true)), args -> { + if (args.isEmpty()) throw new SignatureDesyncException("runScript"); Script script = io.parseScript(args.get(0).asString().getValue()); List l = args.getValue(); DList innerArgs = DFinal.of(l.subList(1, l.size())); diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/libs/SignatureDesyncException.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/libs/SignatureDesyncException.java new file mode 100644 index 0000000..9d8502f --- /dev/null +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/libs/SignatureDesyncException.java @@ -0,0 +1,7 @@ +package io.gitlab.jfronny.muscript.libs; + +public class SignatureDesyncException extends IllegalArgumentException { + public SignatureDesyncException(String method) { + super("Signature desync detected for method: " + method + " (invoked with illegal arguments but passed automatic check)"); + } +} diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/libs/StandardLib.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/libs/StandardLib.java index 91007ec..2f3e778 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/libs/StandardLib.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/libs/StandardLib.java @@ -8,6 +8,7 @@ import io.gitlab.jfronny.muscript.compiler.MuScriptVersion; import io.gitlab.jfronny.muscript.data.Scope; import io.gitlab.jfronny.muscript.data.dynamic.*; import io.gitlab.jfronny.muscript.data.dynamic.additional.*; +import io.gitlab.jfronny.muscript.data.dynamic.type.DType; import io.gitlab.jfronny.muscript.error.LocationalException; import java.time.LocalDate; @@ -18,6 +19,7 @@ import java.util.function.Supplier; import java.util.stream.Collectors; import static io.gitlab.jfronny.muscript.data.dynamic.additional.DFinal.of; +import static io.gitlab.jfronny.muscript.data.dynamic.type.DSL.*; public class StandardLib { private static final Random rnd = new Random(); @@ -33,10 +35,12 @@ public class StandardLib { .set("E", Math.E) .set("date", new DCallableObject(Map.of( "today", new DDate(LocalDate::now) - ), DFinal.of(args -> { + ), object(DDate.SIGNATURE), DFinal.of(callable(DDate.SIGNATURE, arg("epochDay", NUMBER)) + .and(callable(DDate.SIGNATURE, arg("year", NUMBER), arg("month", NUMBER), arg("day", NUMBER))), + args -> { // Constructor if (args.size() == 1) return new DDate(() -> LocalDate.ofEpochDay(args.get(0).asNumber().getValue().longValue())); - if (args.size() != 3) throw new IllegalArgumentException("Expected 3 arguments for full date constructor"); + if (args.size() != 3) throw new SignatureDesyncException("date"); int a0 = args.get(0).asNumber().getValue().intValue(); int a1 = args.get(1).asNumber().getValue().intValue(); int a2 = args.get(2).asNumber().getValue().intValue(); @@ -44,104 +48,138 @@ public class StandardLib { }, "date"))) .set("time", new DCallableObject(Map.of( "now", new DTime(LocalTime::now) - ), DFinal.of(args -> { + ), object(DTime.SIGNATURE), DFinal.of(callable(DTime.SIGNATURE, arg("secondOfDay", NUMBER)) + .and(callable(DTime.SIGNATURE, arg("hour", NUMBER), arg("minute", NUMBER), arg("second", NUMBER))), + args -> { // Constructor if (args.size() == 1) return new DTime(() -> LocalTime.ofSecondOfDay(args.get(0).asNumber().getValue().intValue())); - if (args.size() != 3) throw new IllegalArgumentException("Expected 3 arguments for full time constructor"); + if (args.size() != 3) throw new SignatureDesyncException("time"); int a0 = args.get(0).asNumber().getValue().intValue(); 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) - .set("ceil", StandardLib::ceil) - .set("abs", StandardLib::abs) - .set("random", StandardLib::random) + .set("round", round, StandardLib::round) + .set("floor", floor, StandardLib::floor) + .set("ceil", ceil, StandardLib::ceil) + .set("abs", abs, StandardLib::abs) + .set("random", random, StandardLib::random) - .set("toUpper", StandardLib::toUpper) - .set("toLower", StandardLib::toLower) - .set("contains", StandardLib::contains) - .set("replace", StandardLib::replace); + .set("toUpper", toUpper, StandardLib::toUpper) + .set("toLower", toLower, StandardLib::toLower) + .set("contains", contains, StandardLib::contains) + .set("replace", replace, StandardLib::replace); } if (version.contains(MuScriptVersion.V2)) { scope - .set("listOf", StandardLib::listOf) - .set("len", StandardLib::len) - .set("isEmpty", StandardLib::isEmpty) - .set("concat", StandardLib::concat) - .set("filter", StandardLib::filter) - .set("allMatch", StandardLib::allMatch) - .set("anyMatch", StandardLib::anyMatch) - .set("map", StandardLib::map) - .set("flatMap", StandardLib::flatMap) - .set("fold", StandardLib::fold) - .set("forEach", StandardLib::forEach) - .set("toObject", StandardLib::toObject) + .set("listOf", listOf, StandardLib::listOf) + .set("len", len, StandardLib::len) + .set("isEmpty", isEmpty, StandardLib::isEmpty) + .set("concat", concat, StandardLib::concat) + .set("filter", filter, StandardLib::filter) + .set("allMatch", allMatch, StandardLib::allMatch) + .set("anyMatch", anyMatch, StandardLib::anyMatch) + .set("map", map, StandardLib::map) + .set("flatMap", flatMap, StandardLib::flatMap) + .set("fold", fold, StandardLib::fold) + .set("forEach", forEach, StandardLib::forEach) + .set("toObject", toObject, StandardLib::toObject) - .set("callableObject", StandardLib::callableObject) - .set("enum", StandardLib::enum_) - .set("keys", StandardLib::keys) - .set("values", StandardLib::values) + .set("callableObject", callableObject, StandardLib::callableObject) + .set("enum", enum_, StandardLib::enum_) + .set("keys", keys, StandardLib::keys) + .set("values", values, StandardLib::values) - .set("fail", StandardLib::fail) - .set("try", StandardLib::try_); + .set("fail", fail, StandardLib::fail) + .set("try", try_, StandardLib::try_); } return scope; } - // Numbers - public static DNumber round(DList args) { - if (args.size() == 1) { - return of(Math.round(args.get(0).asNumber().getValue())); - } else if (args.size() == 2) { - double x = Math.pow(10, (int) (double) args.get(1).asNumber().getValue()); - return of(Math.round(args.get(0).asNumber().getValue() * x) / x); - } else { - throw new IllegalArgumentException("Invalid number of arguments for round: expected 1 or 2 but got " + args.size()); - } + public static Map printSignatures(MuScriptVersion version) { + return createScope(version).getValue().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, s -> s.getValue().getSignature().toString())); } + public static void main(String[] args) { + System.out.println(printSignatures(MuScriptVersion.DEFAULT).entrySet().stream().map(s -> s.getKey() + ": " + s.getValue()).collect(Collectors.joining("\n"))); + } + + // Numbers + private static final DType round = callable(NUMBER, arg("number", NUMBER)) + .and(callable(NUMBER, arg("number", NUMBER), arg("decimalPlace", NUMBER))); + public static DNumber round(DList args) { + return switch (args.size()) { + case 1 -> of(Math.round(args.get(0).asNumber().getValue())); + case 2 -> { + double x = Math.pow(10, (int) (double) args.get(1).asNumber().getValue()); + yield of(Math.round(args.get(0).asNumber().getValue() * x) / x); + } + default -> throw new SignatureDesyncException("round"); + }; + } + + private static final DType floor = callable(NUMBER, arg("number", NUMBER)); public static DNumber floor(DList args) { - if (args.size() != 1) throw new IllegalArgumentException("Invalid number of arguments for floor: expected 1 but got " + args.size()); + if (args.size() != 1) throw new SignatureDesyncException("floor"); return of(Math.floor(args.get(0).asNumber().getValue())); } + private static final DType ceil = callable(NUMBER, arg("number", NUMBER)); public static DNumber ceil(DList args) { - if (args.size() != 1) throw new IllegalArgumentException("Invalid number of arguments for ceil: expected 1 but got " + args.size()); + if (args.size() != 1) throw new SignatureDesyncException("ceil"); return of(Math.ceil(args.get(0).asNumber().getValue())); } + private static final DType abs = callable(NUMBER, arg("number", NUMBER)); public static DNumber abs(DList args) { - if (args.size() != 1) throw new IllegalArgumentException("Invalid number of arguments for abs: expected 1 but got " + args.size()); + if (args.size() != 1) throw new SignatureDesyncException("abs"); return of(Math.abs(args.get(0).asNumber().getValue())); } - public static DNumber random(DList args) { - if (args.isEmpty()) return of(rnd.nextDouble()); - else if (args.size() == 2) { - double min = args.get(0).asNumber().getValue(); - double max = args.get(1).asNumber().getValue(); - return of(min + (max - min) * rnd.nextDouble()); - } - - throw new IllegalArgumentException("Invalid number of arguments for random: expected 0 or 2 but got " + args.size()); + private static final DType random = callable(NUMBER) + .and(callable(NUMBER, arg("min", NUMBER), arg("max", NUMBER))) + .and(callable(generic(0), arg("in", list(generic(0))))) + .and(callable(object(generic(0)), arg("in", object(generic(0))))); + public static Dynamic random(DList args) { + return switch (args.size()) { + case 0 -> of(rnd.nextDouble()); + case 2 -> { + double min = args.get(0).asNumber().getValue(); + double max = args.get(1).asNumber().getValue(); + yield of(min + (max - min) * rnd.nextDouble()); + } + case 1 -> { + if (args.get(0).isList()) { + List list = args.get(0).asList().getValue(); + yield list.get(rnd.nextInt(list.size())); + } else if (args.get(0).isObject()) { + var list = List.copyOf(args.get(0).asObject().getValue().entrySet()); + yield objectRepresentation(list.get(rnd.nextInt(list.size()))); + } else throw new SignatureDesyncException("random"); + } + default -> throw new SignatureDesyncException("random"); + }; } // Strings + private static final DType toUpper = callable(STRING, arg("from", STRING)); public static DString toUpper(DList args) { - if (args.size() != 1) throw new IllegalArgumentException("Invalid number of arguments for toUpper: expected 1 but got " + args.size()); + if (args.size() != 1) throw new SignatureDesyncException("toUpper"); return of(args.get(0).asString().getValue().toUpperCase()); } + private static final DType toLower = callable(STRING, arg("from", STRING)); public static DString toLower(DList args) { - if (args.size() != 1) throw new IllegalArgumentException("Invalid number of arguments for toLower: expected 1 but got " + args.size()); + if (args.size() != 1) throw new SignatureDesyncException("toLower"); return of(args.get(0).asString().getValue().toLowerCase()); } + private static final DType contains = callable(BOOL, arg("search", list(generic(0))), arg("entry", generic(0))) + .and(callable(BOOL, arg("search", object(null)), arg("key", STRING))) + .and(callable(BOOL, arg("search", STRING), arg("substring", STRING))); public static DBool contains(DList args) { - if (args.size() != 2) throw new IllegalArgumentException("Invalid number of arguments for contains: expected 2 but got " + args.size()); + if (args.size() != 2) throw new SignatureDesyncException("contains"); Dynamic arg0 = args.get(0); Dynamic arg1 = args.get(1); boolean contained = false; @@ -151,71 +189,106 @@ public class StandardLib { return of(contained); } + private static final DType replace = callable(STRING, arg("in", STRING), arg("target", STRING), arg("replacement", STRING)); public static DString replace(DList args) { - if (args.size() != 3) throw new IllegalArgumentException("Invalid number of arguments for replace: expected 3 but got " + args.size()); + if (args.size() != 3) throw new SignatureDesyncException("replace"); return of(args.get(0).asString().getValue().replace(args.get(1).asString().getValue(), args.get(2).asString().getValue())); } // Lists + private static final DType listOf = callable(list(generic(0)), arg("entries", generic(0), true)); public static DList listOf(DList args) { return args; } + private static final DType len = callable(NUMBER, arg("of", STRING.or(object(null)).or(list(null)))); public static DNumber len(DList args) { - if (args.size() != 1) throw new IllegalArgumentException("Invalid number of arguments for len: expected 1 but got " + args.size()); + if (args.size() != 1) throw new SignatureDesyncException("len"); Dynamic arg0 = args.get(0); if (arg0.isString()) return of(arg0.asString().getValue().length()); if (arg0.isObject()) return of(arg0.asObject().getValue().size()); - return of(args.get(0).asList().size()); + if (arg0.isList()) return of(arg0.asList().size()); + throw new SignatureDesyncException("len"); } + private static final DType isEmpty = callable(BOOL, arg("collection", object(null).or(list(null)).or(STRING))); public static DBool isEmpty(DList args) { - if (args.size() != 1) throw new IllegalArgumentException("Invalid number of arguments for isEmpty: expected 1 but got " + args.size()); + if (args.size() != 1) throw new SignatureDesyncException("isEmpty"); Dynamic arg0 = args.get(0); if (arg0.isObject()) return of(arg0.asObject().getValue().isEmpty()); - return of(arg0.asList().isEmpty()); + if (arg0.isList()) return of(arg0.asList().isEmpty()); + if (arg0.isString()) return of(arg0.asString().getValue().isEmpty()); + throw new SignatureDesyncException("isEmpty"); } - public static DList concat(DList args) { - return of(args.getValue().stream().flatMap(s -> s.asList().getValue().stream()).toList()); + private static final DType concat = callable(list(generic(0)), arg("lists", list(generic(0)), true)) + .and(callable(object(generic(0)), arg("objects", generic(0), true))); + public static Dynamic concat(DList args) { + if (args.isEmpty()) return DEmpty.INSTANCE; + if (args.get(0).isList()) return of(args.getValue().stream().flatMap(s -> s.asList().getValue().stream()).toList()); + if (args.get(0).isObject()) return of(args.getValue().stream().flatMap(s -> s.asObject().getValue().entrySet().stream()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + throw new SignatureDesyncException("concat"); } - public static DList filter(DList args) { - if (args.size() != 2) throw new IllegalArgumentException("Invalid number of arguments for filter: expected 2 but got " + args.size()); + private static final DType filter = callable(list(generic(0)), arg("list", list(generic(0))), arg("predicate", callable(BOOL, arg("current", generic(0))))) + .and(callable(object(generic(0)), arg("object", object(generic(0))), arg("predicate", callable(BOOL, arg("key", STRING), arg("value", generic(0)))))); + public static Dynamic filter(DList args) { + if (args.size() != 2) throw new SignatureDesyncException("filter"); DCallable dc = args.get(1).asCallable(); - return of(args.get(0).asList().getValue().stream().filter(a -> dc.call(a).asBool().getValue()).toList()); + if (args.get(0).isList()) return of(args.get(0).asList().getValue().stream().filter(a -> dc.call(a).asBool().getValue()).toList()); + if (args.get(0).isObject()) return of(args.get(0).asObject().getValue().entrySet().stream().filter(entry -> dc.call(of(entry.getKey()), entry.getValue()).asBool().getValue()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + throw new SignatureDesyncException("filter"); } + private static final DType allMatch = callable(BOOL, arg("list", list(generic(0))), arg("predicate", callable(BOOL, arg("current", generic(0))))) + .and(callable(BOOL, arg("object", object(generic(0))), arg("predicate", callable(BOOL, arg("key", STRING), arg("value", STRING))))); public static DBool allMatch(DList args) { - if (args.size() != 2) throw new IllegalArgumentException("Invalid number of arguments for allMatch: expected 2 but got " + args.size()); + if (args.size() != 2) throw new SignatureDesyncException("allMatch"); DCallable dc = args.get(1).asCallable(); - return of(args.get(0).asList().getValue().stream().allMatch(a -> dc.call(a).asBool().getValue())); + if (args.get(0).isList()) return of(args.get(0).asList().getValue().stream().allMatch(a -> dc.call(a).asBool().getValue())); + if (args.get(0).isObject()) return of(args.get(0).asObject().getValue().entrySet().stream().allMatch(a -> dc.call(of(a.getKey()), a.getValue()).asBool().getValue())); + throw new SignatureDesyncException("allMatch"); } + private static final DType anyMatch = callable(BOOL, arg("list", list(generic(0))), arg("predicate", callable(BOOL, arg("current", generic(0))))) + .and(callable(BOOL, arg("object", object(generic(0))), arg("predicate", callable(BOOL, arg("key", STRING), arg("value", STRING))))); public static DBool anyMatch(DList args) { - if (args.size() != 2) throw new IllegalArgumentException("Invalid number of arguments for anyMatch: expected 2 but got " + args.size()); + if (args.size() != 2) throw new SignatureDesyncException("anyMatch"); DCallable dc = args.get(1).asCallable(); - return of(args.get(0).asList().getValue().stream().anyMatch(a -> dc.call(a).asBool().getValue())); + if (args.get(0).isList()) return of(args.get(0).asList().getValue().stream().anyMatch(a -> dc.call(a).asBool().getValue())); + if (args.get(0).isObject()) return of(args.get(0).asObject().getValue().entrySet().stream().anyMatch(a -> dc.call(of(a.getKey()), a.getValue()).asBool().getValue())); + throw new SignatureDesyncException("anyMatch"); } - public static DList map(DList args) { - 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 2 but got " + args.size()); + private static final DType map = callable(list(generic(1)), arg("list", list(generic(0))), arg("mapper", callable(generic(1), arg("value", generic(0))))) + .and(callable(object(generic(1)), arg("object", object(generic(0))), arg("mapper", callable(generic(1), arg("key", STRING), arg("value", generic(0)))))); + public static Dynamic map(DList args) { + if (args.size() != 2) throw new SignatureDesyncException("map"); DCallable dc = args.get(1).asCallable(); - return of(args.get(0).asList().getValue().stream().flatMap(a -> dc.call(a).asList().getValue().stream()).toList()); + if (args.get(0).isList()) return of(args.get(0).asList().getValue().stream().map(dc::call).toList()); + if (args.get(0).isObject()) return of(args.get(0).asObject().getValue().entrySet().stream().map(e -> new Entry(e.getKey(), dc.call(of(e.getKey()), e.getValue()))).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + throw new SignatureDesyncException("map"); } + private static final DType flatMap = callable(list(generic(1)), arg("list", list(generic(0))), arg("mapper", callable(list(generic(1)), arg("value", generic(0))))) + .and(callable(object(generic(1)), arg("object", object(generic(0))), arg("mapper", callable(object(generic(1)), arg("key", STRING), arg("value", generic(0)))))); + public static Dynamic flatMap(DList args) { + if (args.size() != 2) throw new SignatureDesyncException("flatMap"); + DCallable dc = args.get(1).asCallable(); + if (args.get(0).isList()) return of(args.get(0).asList().getValue().stream().flatMap(a -> dc.call(a).asList().getValue().stream()).toList()); + if (args.get(0).isObject()) return of(args.get(0).asObject().getValue().entrySet().stream().flatMap(e -> dc.call(of(e.getKey()), e.getValue()).asObject().getValue().entrySet().stream()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + throw new SignatureDesyncException("flatMap"); + } + + private static final DType fold = callable(generic(1), arg("list", list(generic(0))), arg("identity", generic(1)), arg("accumulator", callable(generic(1), arg("previous", generic(1)), arg("current", generic(0))))); public static Dynamic fold(DList args) { - if (args.size() != 3) throw new IllegalArgumentException("Invalid number of arguments for fold: expected 3 but got " + args.size()); + if (args.size() != 3) throw new SignatureDesyncException("fold"); return args.get(0).asList().getValue().stream().map(Function.identity()).reduce(args.get(1), args.get(2).asCallable()::call); } + private static final DType forEach = callable(generic(1), arg("list", list(generic(0))), arg("operation", callable(generic(1), arg("item", generic(0))))); public static Dynamic forEach(DList args) { - if (args.size() != 2) throw new IllegalArgumentException("Invalid number of arguments for forEach: expected 2 but got " + args.size()); + if (args.size() != 2) throw new SignatureDesyncException("forEach"); Dynamic result = new DNull(); DCallable dc = args.get(1).asCallable(); for (Dynamic dynamic : args.get(0).asList().getValue()) { @@ -224,8 +297,9 @@ public class StandardLib { return result; } + private static final DType toObject = callable(object(generic(1)), arg("list", list(generic(0))), arg("keyMapper", callable(STRING, arg("item", generic(0)))), arg("valueMapper", callable(generic(1), arg("item", generic(0))))); public static DObject toObject(DList args) { - if (args.size() != 3) throw new IllegalArgumentException("Invalid number of arguments for args: expected 3 but got " + args.size()); + if (args.size() != 3) throw new SignatureDesyncException("toObject"); DCallable keyMapper = args.get(1).asCallable(); DCallable valueMapper = args.get(2).asCallable(); return of(args.get(0) @@ -241,29 +315,36 @@ public class StandardLib { } // Objects + private static final DType callableObject = callable(object(generic(0)).and(callable(null, (DType) null)), arg("object", object(generic(0))), arg("callable", callable(null, (DType) null))); 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()); + if (args.size() != 2) throw new SignatureDesyncException("callableObject"); return new DCallableObject(args.get(0).asObject().getValue(), args.get(1).asCallable()); } + private static final DType enumRepr = object(generic(0)).and(list(generic(0))).and(STRING); + private static final DType enum_ = callable(enumRepr, arg("content", object(generic(0)))) + .and(callable(enumRepr.and(NUMBER), arg("content", object(generic(0))), arg("selectedKey", STRING))); 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()); + else throw new SignatureDesyncException("enum"); } + private static final DType keys = callable(list(STRING), arg("object", object(null))); public static DList keys(DList args) { - if (args.size() != 1) throw new IllegalArgumentException("Invalid number of arguments for keys: expected 1 but got " + args.size()); + if (args.size() != 1) throw new SignatureDesyncException("keys"); return of(args.get(0).asObject().getValue().keySet().stream().map(DFinal::of).toList()); } + private static final DType values = callable(list(generic(0)), arg("object", object(generic(0)))); public static DList values(DList args) { - if (args.size() != 1) throw new IllegalArgumentException("Invalid number of arguments for values: expected 1 but got " + args.size()); + if (args.size() != 1) throw new SignatureDesyncException("values"); return of(args.get(0).asObject().getValue().values().stream().toList()); } + private static final DType try_ = callable(object(null), arg("block", callable(null, arg("args", null, true))), arg("args", null, true)); public static DObject try_(DList args) { - if (args.isEmpty()) throw new IllegalArgumentException("Invalid number of arguments for try: expected 1 or more but got " + args.size()); + if (args.isEmpty()) throw new SignatureDesyncException("try"); var callable = args.get(0).asCallable(); var l = args.getValue(); var innerArgs = of(l.subList(1, l.size())); @@ -277,7 +358,7 @@ public class StandardLib { return of(Map.of( "result", result, "catch", of("catch", param -> { - if (param.size() != 1) throw new IllegalArgumentException("Invalid number of arguments for catch: expected 1 but got " + param.size()); + if (param.size() != 1) throw new SignatureDesyncException("catch"); param.get(0).asCallable(); return of(Map.of("result", result)); }, serializedCatch) @@ -286,7 +367,7 @@ public class StandardLib { return of(Map.of( "result", new DNull(), "catch", of("catch", param -> { - if (param.size() != 1) throw new IllegalArgumentException("Invalid number of arguments for catch: expected 1 but got " + param.size()); + if (param.size() != 1) throw new SignatureDesyncException("catch"); var result = param.get(0).asCallable().call(of(Map.of( "message", of(le.getMessage()) ))); @@ -296,8 +377,36 @@ public class StandardLib { } } + private static final DType fail = callable(null, arg("message", STRING)) + .and(callable(null)); public static DNull fail(DList args) { - if (args.size() > 1) throw new IllegalArgumentException("Invalid number of arguments for fail: expected 0 or 1 but got " + args.size()); - throw new RuntimeException(args.size() == 0 ? "Failed" : args.get(0).asString().getValue()); + if (args.size() > 1) throw new SignatureDesyncException("fail"); + throw new RuntimeException(args.isEmpty() ? "Failed" : args.get(0).asString().getValue()); + } + + // Util + public static DObject objectRepresentation(Map.Entry entry) { + return objectRepresentation(entry.getKey(), entry.getValue()); + } + + public static DObject objectRepresentation(String key, Dynamic value) { + return of(Map.of("key", of(key), "value", value)); + } + + record Entry(String key, Dynamic value) implements Map.Entry { + @Override + public String getKey() { + return key; + } + + @Override + public Dynamic getValue() { + return value; + } + + @Override + public Dynamic setValue(Dynamic value) { + throw new UnsupportedOperationException(); + } } } diff --git a/muscript/src/main/java/module-info.java b/muscript/src/main/java/module-info.java index 73e8fe3..32be06c 100644 --- a/muscript/src/main/java/module-info.java +++ b/muscript/src/main/java/module-info.java @@ -11,4 +11,5 @@ module io.gitlab.jfronny.commons.muscript { exports io.gitlab.jfronny.muscript.data; exports io.gitlab.jfronny.muscript.data.dynamic; exports io.gitlab.jfronny.muscript.data.dynamic.additional; + exports io.gitlab.jfronny.muscript.data.dynamic.type; } \ No newline at end of file diff --git a/muscript/src/test/java/io/gitlab/jfronny/muscript/test/ExceptionHandlingTest.java b/muscript/src/test/java/io/gitlab/jfronny/muscript/test/ExceptionHandlingTest.java index 7017d76..01255fb 100644 --- a/muscript/src/test/java/io/gitlab/jfronny/muscript/test/ExceptionHandlingTest.java +++ b/muscript/src/test/java/io/gitlab/jfronny/muscript/test/ExceptionHandlingTest.java @@ -10,8 +10,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; class ExceptionHandlingTest { @Test void catchStdlib() { - assertEquals("Invalid number of arguments for isEmpty: expected 1 but got 0", assertThrows(LocationalException.class, () -> string("isEmpty()")).getMessage()); - assertEquals("Invalid number of arguments for isEmpty: expected 1 but got 0", string("try({->isEmpty()}).catch({e->e.message}).result")); + assertEquals("Signature mismatch for isEmpty: expected <(collection: string | [any] | {any}) -> bool> but got <() -> any>", assertThrows(LocationalException.class, () -> string("isEmpty()")).getMessage()); + assertEquals("Signature mismatch for isEmpty: expected <(collection: string | [any] | {any}) -> bool> but got <() -> any>", string("try({->isEmpty()}).catch({e->e.message}).result")); } @Test @@ -27,7 +27,7 @@ class ExceptionHandlingTest { @Test void catchInner() { - assertEquals("Got Invalid number of arguments for isEmpty: expected 1 but got 0", assertThrows(LocationalException.class, () -> parseScript(""" + assertEquals("Got Signature mismatch for isEmpty: expected <(collection: string | [any] | {any}) -> bool> but got <() -> any>", assertThrows(LocationalException.class, () -> parseScript(""" inner = {-> isEmpty()} outer = {-> inner()} outer2 = {-> try({a->a()}, outer).catch({e -> fail('Got ' || e.message)}).result}