feat(muscript): Weak type signature system for Dynamic, might get extended to muScript expressions
ci/woodpecker/push/woodpecker Pipeline was successful Details

This commit is contained in:
Johannes Frohnmeyer 2023-09-30 11:59:44 +02:00
parent 0c2db6fe08
commit 749b475d0d
Signed by: Johannes
GPG Key ID: E76429612C2929F4
30 changed files with 885 additions and 129 deletions

View File

@ -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) {

View File

@ -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<DTypeCallable.Arg> 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();

View File

@ -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<String, ? extends Dynamic> value) {
return set(key, DFinal.of(signature, value));
}
public Scope set(String key, List<? extends Dynamic> value) {
return set(key, DFinal.of(value));
}
public Scope set(String key, DTypeList signature, List<? extends Dynamic> value) {
return set(key, DFinal.of(signature, value));
}
public Scope set(String key, Function<DList, ? extends Dynamic> value) {
return set(key, DFinal.of(value, () -> key));
return set(key, DFinal.of(value, () -> key, key));
}
public Scope set(String key, DType signature, Function<DList, ? extends Dynamic> value) {
return set(key, DFinal.of(signature, value, () -> key, key));
}
public Scope fork() {

View File

@ -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 = "<unnamed>";
default Dynamic call(DList args) {
DType signature = getSignature();
if (signature != null) {
List<DType> argTypes = args.getValue().stream().map(Dynamic::getSignature).toList();
if (!TypeMatcher.match(signature, argTypes)) {
List<DTypeCallable.Arg> 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);
}

View File

@ -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";

View File

@ -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<DType> 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);
}
}

View File

@ -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<String, ? extends Dynamic> value, DTypeObject objectSignature, DCallable callable) implements DObject {
public DCallableObject(Map<String, ? extends Dynamic> value, DCallable callable) {
this(value, new DTypeObject(null), callable);
}
public DCallableObject {
Objects.requireNonNull(value);
Objects.requireNonNull(objectSignature);
Objects.requireNonNull(callable);
}
public record DCallableObject(Map<String, ? extends Dynamic> value, DCallable callable) implements DObject {
@Override
public Map<String, ? extends Dynamic> getValue() {
return value;
@ -30,6 +40,11 @@ public record DCallableObject(Map<String, ? extends Dynamic> 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);

View File

@ -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<LocalDate> date) implements DObject {
public static final DType SIGNATURE = object(NUMBER).or(STRING).or(NUMBER);
@Override
public Map<String, Dynamic> getValue() {
return Map.of(
@ -53,6 +58,11 @@ public record DDate(Supplier<LocalDate> date) implements DObject {
));
}
@Override
public DType getSignature() {
return SIGNATURE;
}
@Override
public String toString() {
return date.get().toString();

View File

@ -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<String, ? extends Dynamic> getValue() {
return Map.of();
}
@Override
public boolean isList() {
return true;
}
@Override
public DList asList() {
return new DListLens(this, List::of);
}
}

View File

@ -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<String, ? extends Dynamic> values, @Nullable DEnumEntry value) implements DObject {
public record DEnum(Map<String, ? extends Dynamic> values, @Nullable DEnumEntry value, @Nullable DType valueSignature) implements DObject {
public DEnum(Map<String, ? extends Dynamic> values) {
this(values, (DEnumEntry) null);
this(null, values);
}
public DEnum(DType valueSignature, Map<String, ? extends Dynamic> values) {
this(values, null, valueSignature);
}
public DEnum(Map<String, ? extends Dynamic> 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<String, ? extends Dynamic> values, @Nullable String value) {
this(values, value == null ? null : new DEnumEntry(value, values.keySet().stream().toList().indexOf(value), true), valueSignature);
}
public DEnum(List<String> 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<String> values) {
@ -61,12 +70,12 @@ public record DEnum(Map<String, ? extends Dynamic> 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<String, ? extends Dynamic> values, @Nullable DEnumEntry
return new Call(CodeLocation.NONE, new Variable(CodeLocation.NONE, "enum"), args);
}
@Override
public DType getSignature() {
Set<DType> 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<String, Dynamic> createMap(List<String> values, String value) {
Map<String, Dynamic> result = new LinkedHashMap<>();
DEnumEntry v = new DEnumEntry(value, values.indexOf(value), true);

View File

@ -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<String, ? extends Dynamic> b) {
return new FObject(ImmCollection.copyOf((Map<String, Dynamic>) b));
return of(new DTypeObject(null), b);
}
public static DObject of(DTypeObject signature, Map<String, ? extends Dynamic> b) {
return new FObject(ImmCollection.copyOf((Map<String, Dynamic>) 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<? extends Dynamic> b) {
return new FList(ImmCollection.copyOf((List<Dynamic>) b));
return of(new DTypeList(null), b);
}
public static DList of(DTypeList signature, List<? extends Dynamic> b) {
return new FList(ImmCollection.copyOf((List<Dynamic>) b), Objects.requireNonNull(signature));
}
public static DCallable of(Function<DList, ? extends Dynamic> b, Supplier<String> serialized) {
return of(b, serialized, null);
return of(new DTypeCallable(null, null), b, Objects.requireNonNull(serialized));
}
public static DCallable of(DType signature, Function<DList, ? extends Dynamic> b, Supplier<String> serialized) {
return of(Objects.requireNonNull(signature), b, serialized, null);
}
public static DCallable of(Function<DList, ? extends Dynamic> 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<DList, ? extends Dynamic> b, String name) {
return of(Objects.requireNonNull(signature), name, b, () -> new Variable(CodeLocation.NONE, name));
}
public static DCallable of(Function<DList, ? extends Dynamic> b, Supplier<String> 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<DList, ? extends Dynamic> b, Supplier<String> serialized, String name) {
return of(Objects.requireNonNull(signature), name, b, () -> Parser.parse(MuScriptVersion.DEFAULT, serialized.get()));
}
public static DCallable of(String name, Function<DList, ? extends Dynamic> b, Supplier<Expr<?>> serialized) {
return new FCallable((Function<DList, Dynamic>) b, new LazySupplier<>(serialized), name);
return of(new DTypeCallable(null, null), name, b, serialized);
}
public static DCallable of(DType signature, String name, Function<DList, ? extends Dynamic> b, Supplier<Expr<?>> serialized) {
return new FCallable((Function<DList, Dynamic>) 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<String, Dynamic> value) implements DObject, FImpl {
private record FObject(Map<String, Dynamic> value, DTypeObject signature) implements DObject, FImpl {
@Override
public Map<String, Dynamic> 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<Dynamic> value) implements DList, FImpl {
private record FList(List<Dynamic> value, DTypeList signature) implements DList, FImpl {
@Override
public List<Dynamic> 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<DList, Dynamic> value, Supplier<Expr<?>> gen, String name) implements DCallable, FImpl {
private record FCallable(Function<DList, Dynamic> value, Supplier<Expr<?>> 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;
}
}
}

View File

@ -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<LocalTime> time) implements DObject {
public static final DType SIGNATURE = object(NUMBER).or(STRING).or(NUMBER);
@Override
public Map<String, Dynamic> getValue() {
return Map.of(
@ -55,6 +57,11 @@ public record DTime(Supplier<LocalTime> time) implements DObject {
));
}
@Override
public DType getSignature() {
return SIGNATURE;
}
@Override
public String toString() {
return time.get().toString();

View File

@ -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();
}
}

View File

@ -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<DList, Dynamic> getValue() {
return inner.getValue();
}
@Override
public DType getSignature() {
return inner.getSignature();
}
}

View File

@ -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();

View File

@ -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<DTypeCallable.Arg> 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);
}
}

View File

@ -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());
}
}

View File

@ -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<DType> elements) implements DType {
public DTypeAnd(Set<DType> elements) {
if (elements.isEmpty()) throw new IllegalArgumentException("Cannot create union type without elements");
Set<DType> simple = new LinkedHashSet<>();
Set<DType> list = new LinkedHashSet<>();
Set<DType> object = new LinkedHashSet<>();
boolean foundNullList = false, foundNullObject = false;
Queue<DType> 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);
}
}

View File

@ -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<Arg> 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 ? "..." : "");
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<DType> elements) implements DType {
public DTypeSum(Set<DType> elements) {
if (elements.isEmpty()) throw new IllegalArgumentException("Cannot create sum type without elements");
Set<DType> simple = new LinkedHashSet<>();
Set<DType> list = new LinkedHashSet<>();
Set<DType> object = new LinkedHashSet<>();
Queue<DType> 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);
}
}

View File

@ -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<DType> 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<DType> args) {
return fn.from() == null || match(fn.from(), args);
}
public static boolean match(List<DTypeCallable.Arg> declared, List<DType> provided) {
return match(declared, provided, new MatchScope());
}
private static boolean match(List<DTypeCallable.Arg> declared, List<DType> 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<Integer, DType> s2 : ((Result.PossibleMatch) r).possibleHydrations()) {
if (match(declared, provided, scope.fork(s2))) return true;
}
return false;
}
DType cArg = declared.get(0).type();
List<DTypeCallable.Arg> 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<Integer, DType> 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<DTypeCallable.Arg> dgf = dg.from();
List<DTypeCallable.Arg> 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<Integer, DType> hydrations) {
public MatchScope() {
this(Map.of());
}
public MatchScope fork(Map<Integer, DType> additional) {
Map<Integer, DType> 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<Map<Integer, DType>> possibleHydrations) implements Result {
@Override
public Result and(Result result, MatchScope scope) {
if (result == FAILED) return FAILED;
if (result == MATCH) return this;
Set<Map<Integer, DType>> 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<Map<Integer, DType>> oth = other == MATCH ? Stream.of(Map.of()) : ((PossibleMatch) other).possibleHydrations.stream();
return new PossibleMatch(Stream.concat(possibleHydrations.stream(), oth).collect(Collectors.toCollection(LinkedHashSet::new)));
}
}
}
}

View File

@ -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<? extends Dynamic> l = args.getValue();
DList innerArgs = DFinal.of(l.subList(1, l.size()));

View File

@ -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)");
}
}

View File

@ -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<String, String> 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<? extends Dynamic> 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().<Dynamic>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<String, ? extends Dynamic> 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<String, Dynamic> {
@Override
public String getKey() {
return key;
}
@Override
public Dynamic getValue() {
return value;
}
@Override
public Dynamic setValue(Dynamic value) {
throw new UnsupportedOperationException();
}
}
}

View File

@ -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;
}

View File

@ -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}