feat(muscript): implement additional data
This commit is contained in:
parent
27b4d535ff
commit
1a210837f3
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package io.gitlab.jfronny.muscript.data.dynamic;
|
||||
|
||||
public non-sealed interface DBool extends Dynamic {
|
||||
@Override
|
||||
Boolean getValue();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package io.gitlab.jfronny.muscript.data.dynamic;
|
||||
|
||||
public non-sealed interface DNumber extends Dynamic {
|
||||
@Override
|
||||
Double getValue();
|
||||
}
|
|
@ -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(", ", "{", "}")));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package io.gitlab.jfronny.muscript.data.dynamic;
|
||||
|
||||
public non-sealed interface DString extends Dynamic {
|
||||
@Override
|
||||
String getValue();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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 {}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 ? "..." : "");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue