feat(muscript): implement additional data

This commit is contained in:
Johannes Frohnmeyer 2024-04-05 14:20:22 +02:00
parent 27b4d535ff
commit 1a210837f3
Signed by: Johannes
GPG Key ID: E76429612C2929F4
32 changed files with 1017 additions and 0 deletions

View File

@ -0,0 +1,29 @@
import io.gitlab.jfronny.scripts.*
plugins {
commons.library
}
dependencies {
implementation(projects.commons)
api(projects.muscriptCore)
testImplementation(libs.junit.jupiter.api)
testRuntimeOnly(libs.junit.jupiter.engine)
}
publishing {
publications {
create<MavenPublication>("maven") {
groupId = "io.gitlab.jfronny"
artifactId = "muscript-data-dynamic"
from(components["java"])
}
}
}
tasks.javadoc {
linksOffline("https://maven.frohnmeyer-wds.de/javadoc/artifacts/io/gitlab/jfronny/commons/$version/raw", projects.commons)
linksOffline("https://maven.frohnmeyer-wds.de/javadoc/artifacts/io/gitlab/jfronny/muscript-core/$version/raw", projects.muscriptCore)
}

View File

@ -0,0 +1,6 @@
package io.gitlab.jfronny.muscript.data.dynamic;
public non-sealed interface DBool extends Dynamic {
@Override
Boolean getValue();
}

View File

@ -0,0 +1,41 @@
package io.gitlab.jfronny.muscript.data.dynamic;
import io.gitlab.jfronny.muscript.data.dynamic.context.ISimpleDynamic;
import io.gitlab.jfronny.muscript.data.dynamic.type.DType;
import io.gitlab.jfronny.muscript.data.dynamic.type.DTypeCallable;
import io.gitlab.jfronny.muscript.data.dynamic.type.TypeMatcher;
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);
}
default Dynamic call(Dynamic... args) {
return call(ISimpleDynamic.INSTANCE.of(args));
}
default String getName() {
return DEFAULT_NAME;
}
default DCallable named(String name) {
return new NamedDCallable(this, name);
}
@Override
Function<DList, Dynamic> getValue();
}

View File

@ -0,0 +1,20 @@
package io.gitlab.jfronny.muscript.data.dynamic;
import java.util.List;
public non-sealed interface DList extends Dynamic {
default Dynamic get(int i) {
return getValue().get(i);
}
default int size() {
return getValue().size();
}
default boolean isEmpty() {
return getValue().isEmpty();
}
@Override
List<? extends Dynamic> getValue();
}

View File

@ -0,0 +1,21 @@
package io.gitlab.jfronny.muscript.data.dynamic;
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
public Object getValue() {
return null;
}
@Override
public DType getSignature() {
return DTypePrimitive.NULL;
}
@Override
public String toString() {
return "null";
}
}

View File

@ -0,0 +1,6 @@
package io.gitlab.jfronny.muscript.data.dynamic;
public non-sealed interface DNumber extends Dynamic {
@Override
Double getValue();
}

View File

@ -0,0 +1,29 @@
package io.gitlab.jfronny.muscript.data.dynamic;
import io.gitlab.jfronny.muscript.data.dynamic.context.ISimpleDynamic;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
import java.util.stream.Collectors;
public non-sealed interface DObject extends Dynamic {
default @Nullable Dynamic get(String key) {
return getValue().get(key);
}
default boolean has(String key) {
return getValue().containsKey(key);
}
@Override
Map<String, ? extends Dynamic> getValue();
@Override
default DString asString() {
return ISimpleDynamic.INSTANCE.of(getValue()
.entrySet()
.stream()
.map(e -> e.getKey() + " = " + e.getValue().asString().getValue())
.collect(Collectors.joining(", ", "{", "}")));
}
}

View File

@ -0,0 +1,6 @@
package io.gitlab.jfronny.muscript.data.dynamic;
public non-sealed interface DString extends Dynamic {
@Override
String getValue();
}

View File

@ -0,0 +1,91 @@
package io.gitlab.jfronny.muscript.data.dynamic;
import io.gitlab.jfronny.commons.StringFormatter;
import io.gitlab.jfronny.muscript.core.IDynamic;
import io.gitlab.jfronny.muscript.data.dynamic.lens.DStringLens;
import io.gitlab.jfronny.muscript.data.dynamic.type.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.Set;
/**
* Represents a value of an unknown type
* Override toString(StringBuilder) to support custom serialization (note: the serialized form is ran with muScript to generate the tree)
*/
public sealed interface Dynamic extends IDynamic permits DBool, DCallable, DList, DNull, DNumber, DObject, DString, DynamicBase {
static boolean isNull(Dynamic dynamic) {
return dynamic == null || dynamic instanceof DNull;
}
static @NotNull Dynamic fromNullable(@Nullable Dynamic dynamic) {
return dynamic == null ? new DNull() : dynamic;
}
Object getValue();
default boolean isBool() {
return this instanceof DBool;
}
default DBool asBool() {
if (this instanceof DBool bool) return bool;
else throw new DynamicTypeConversionException("bool", this);
}
default boolean isNumber() {
return this instanceof DNumber;
}
default DNumber asNumber() {
if (this instanceof DNumber number) return number;
else throw new DynamicTypeConversionException("number", this);
}
default boolean isString() {
return this instanceof DString;
}
default DString asString() {
if (this instanceof DString string) return string;
else return new DStringLens(this, () -> StringFormatter.toString(getValue()));
}
default boolean isObject() {
return this instanceof DObject;
}
default DObject asObject() {
if (this instanceof DObject object) return object;
else throw new DynamicTypeConversionException("object", this);
}
default boolean isList() {
return this instanceof DList;
}
default DList asList() {
if (this instanceof DList list) return list;
else throw new DynamicTypeConversionException("list", this);
}
default boolean isCallable() {
return this instanceof DCallable;
}
default DCallable asCallable() {
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

@ -0,0 +1,8 @@
package io.gitlab.jfronny.muscript.data.dynamic;
/**
* Use this for ABSTRACT (!) implementations of custom dynamic containers.
* Any concrete implementation MUST still implement a valid Dynamic subtype (DBool, DString, ...)
*/
public non-sealed interface DynamicBase extends Dynamic {
}

View File

@ -0,0 +1,14 @@
package io.gitlab.jfronny.muscript.data.dynamic;
public class DynamicTypeConversionException extends RuntimeException {
private static final String MESSAGE1 = "Could not convert dynamic of type ";
private static final String MESSAGE2 = " to ";
private final String target;
private final String actual;
public DynamicTypeConversionException(String target, Dynamic dynamic) {
super(MESSAGE1 + (dynamic == null ? "null" : dynamic.getClass().getSimpleName()) + MESSAGE2 + target);
this.target = target;
this.actual = dynamic == null ? "null" : dynamic.getClass().getSimpleName();
}
}

View File

@ -0,0 +1,27 @@
package io.gitlab.jfronny.muscript.data.dynamic;
import io.gitlab.jfronny.muscript.data.dynamic.type.DType;
import java.util.function.Function;
public record NamedDCallable(DCallable inner, String name) implements DCallable {
@Override
public String getName() {
return name;
}
@Override
public DCallable named(String name) {
return new NamedDCallable(inner, name);
}
@Override
public Function<DList, Dynamic> getValue() {
return inner.getValue();
}
@Override
public DType getSignature() {
return inner.getSignature();
}
}

View File

@ -0,0 +1,19 @@
package io.gitlab.jfronny.muscript.data.dynamic.context;
import io.gitlab.jfronny.muscript.core.ExprWriter;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import java.io.IOException;
import java.util.Objects;
import java.util.ServiceLoader;
public interface DynamicSerializer {
DynamicSerializer INSTANCE = ServiceLoader.load(DynamicSerializer.class)
.findFirst()
.orElseGet(() -> (writer, value) -> writer.append(Objects.toString(value.getValue())));
default String serialize(Dynamic value) {
return ExprWriter.write(writer -> serialize(writer, value), false);
}
void serialize(ExprWriter writer, Dynamic value) throws IOException;
}

View File

@ -0,0 +1,30 @@
package io.gitlab.jfronny.muscript.data.dynamic.context;
import io.gitlab.jfronny.muscript.data.dynamic.DList;
import io.gitlab.jfronny.muscript.data.dynamic.DString;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import java.util.List;
import java.util.ServiceLoader;
public interface ISimpleDynamic {
ISimpleDynamic INSTANCE = ServiceLoader.load(ISimpleDynamic.class)
.findFirst()
.orElseGet(() -> new ISimpleDynamic() {
@Override
public DString of(String value) {
return new SimpleDString(value);
}
@Override
public DList of(Dynamic... values) {
return new SimpleDList(List.of(values));
}
});
DString of(String value);
DList of(Dynamic... values);
record SimpleDString(String getValue) implements DString {}
record SimpleDList(List<Dynamic> getValue) implements DList {}
}

View File

@ -0,0 +1,21 @@
package io.gitlab.jfronny.muscript.data.dynamic.lens;
import io.gitlab.jfronny.muscript.data.dynamic.DBool;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import java.util.Objects;
import java.util.function.BooleanSupplier;
public final class DBoolLens extends DLens implements DBool {
private final BooleanSupplier value;
public DBoolLens(Dynamic source, BooleanSupplier value) {
super(source);
this.value = Objects.requireNonNull(value);
}
@Override
public Boolean getValue() {
return value.getAsBoolean();
}
}

View File

@ -0,0 +1,23 @@
package io.gitlab.jfronny.muscript.data.dynamic.lens;
import io.gitlab.jfronny.muscript.data.dynamic.DCallable;
import io.gitlab.jfronny.muscript.data.dynamic.DList;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;
public final class DCallableLens extends DLens implements DCallable {
private final Supplier<Function<DList, Dynamic>> value;
public DCallableLens(Dynamic source, Supplier<Function<DList, Dynamic>> value) {
super(source);
this.value = Objects.requireNonNull(value);
}
@Override
public Function<DList, Dynamic> getValue() {
return value.get();
}
}

View File

@ -0,0 +1,99 @@
package io.gitlab.jfronny.muscript.data.dynamic.lens;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.data.dynamic.type.DType;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
public abstract sealed class DLens implements DynamicBase permits DBoolLens, DCallableLens, DListLens, DNumberLens, DObjectLens, DStringLens {
protected final Dynamic source;
protected DLens(Dynamic source) {
this.source = Objects.requireNonNull(source);
}
public Dynamic getSource() {
return source;
}
@Override
public boolean isBool() {
return source.isBool();
}
@Override
public DBool asBool() {
return source.asBool();
}
@Override
public boolean isNumber() {
return source.isNumber();
}
@Override
public DNumber asNumber() {
return source.asNumber();
}
@Override
public boolean isString() {
return source.isString();
}
@Override
public DString asString() {
return source.asString();
}
@Override
public boolean isObject() {
return source.isObject();
}
@Override
public DObject asObject() {
return source.asObject();
}
@Override
public boolean isList() {
return source.isList();
}
@Override
public DList asList() {
return source.asList();
}
@Override
public boolean isCallable() {
return source.isCallable();
}
@Override
public DCallable asCallable() {
return source.asCallable();
}
@Override
public @Nullable DType getSignature() {
return source.getSignature();
}
@Override
public int hashCode() {
return source.hashCode();
}
@Override
public boolean equals(Object obj) {
return obj instanceof DLens lens ? source.equals(lens.source) : source.equals(obj);
}
@Override
public String toString() {
return source.toString();
}
}

View File

@ -0,0 +1,22 @@
package io.gitlab.jfronny.muscript.data.dynamic.lens;
import io.gitlab.jfronny.muscript.data.dynamic.DList;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
public final class DListLens extends DLens implements DList {
private final Supplier<List<? extends Dynamic>> value;
public DListLens(Dynamic source, Supplier<List<? extends Dynamic>> value) {
super(source);
this.value = Objects.requireNonNull(value);
}
@Override
public List<? extends Dynamic> getValue() {
return value.get();
}
}

View File

@ -0,0 +1,21 @@
package io.gitlab.jfronny.muscript.data.dynamic.lens;
import io.gitlab.jfronny.muscript.data.dynamic.DNumber;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import java.util.Objects;
import java.util.function.DoubleSupplier;
public final class DNumberLens extends DLens implements DNumber {
private final DoubleSupplier value;
public DNumberLens(Dynamic source, DoubleSupplier value) {
super(source);
this.value = Objects.requireNonNull(value);
}
@Override
public Double getValue() {
return value.getAsDouble();
}
}

View File

@ -0,0 +1,22 @@
package io.gitlab.jfronny.muscript.data.dynamic.lens;
import io.gitlab.jfronny.muscript.data.dynamic.DObject;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
public final class DObjectLens extends DLens implements DObject {
private final Supplier<Map<String, ? extends Dynamic>> value;
public DObjectLens(Dynamic source, Supplier<Map<String, ? extends Dynamic>> value) {
super(source);
this.value = Objects.requireNonNull(value);
}
@Override
public Map<String, ? extends Dynamic> getValue() {
return value.get();
}
}

View File

@ -0,0 +1,21 @@
package io.gitlab.jfronny.muscript.data.dynamic.lens;
import io.gitlab.jfronny.muscript.data.dynamic.DString;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import java.util.Objects;
import java.util.function.Supplier;
public final class DStringLens extends DLens implements DString {
private final Supplier<String> value;
public DStringLens(Dynamic source, Supplier<String> value) {
super(source);
this.value = Objects.requireNonNull(value);
}
@Override
public String getValue() {
return value.get();
}
}

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,48 @@
package io.gitlab.jfronny.muscript.data.dynamic.type;
import io.gitlab.jfronny.commons.data.ImmCollection;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
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,38 @@
package io.gitlab.jfronny.muscript.data.dynamic.type;
import io.gitlab.jfronny.commons.data.ImmCollection;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
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

@ -0,0 +1,11 @@
module io.gitlab.jfronny.commons.muscript.data {
uses io.gitlab.jfronny.muscript.data.dynamic.context.DynamicSerializer;
uses io.gitlab.jfronny.muscript.data.dynamic.context.ISimpleDynamic;
requires io.gitlab.jfronny.commons;
requires io.gitlab.jfronny.commons.muscript.core;
requires static org.jetbrains.annotations;
exports io.gitlab.jfronny.muscript.data.dynamic;
exports io.gitlab.jfronny.muscript.data.dynamic.context;
exports io.gitlab.jfronny.muscript.data.dynamic.lens;
exports io.gitlab.jfronny.muscript.data.dynamic.type;
}