feat(muscript): implement muscript-runtime
ci/woodpecker/push/woodpecker Pipeline was successful Details

This commit is contained in:
Johannes Frohnmeyer 2024-04-07 15:36:33 +02:00
parent 570264ecbc
commit 10182e91f0
Signed by: Johannes
GPG Key ID: E76429612C2929F4
11 changed files with 470 additions and 0 deletions

View File

@ -0,0 +1,33 @@
import io.gitlab.jfronny.scripts.*
plugins {
commons.library
}
dependencies {
implementation(projects.commons)
api(projects.muscriptAst)
api(projects.muscriptDataDynamic)
api(projects.muscriptDataAdditional)
testImplementation(libs.junit.jupiter.api)
testRuntimeOnly(libs.junit.jupiter.engine)
}
publishing {
publications {
create<MavenPublication>("maven") {
groupId = "io.gitlab.jfronny"
artifactId = "muscript-runtime"
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-ast/$version/raw", projects.muscriptAst)
linksOffline("https://maven.frohnmeyer-wds.de/javadoc/artifacts/io/gitlab/jfronny/muscript-data-dynamic/$version/raw", projects.muscriptDataDynamic)
linksOffline("https://maven.frohnmeyer-wds.de/javadoc/artifacts/io/gitlab/jfronny/muscript-data-additional/$version/raw", projects.muscriptDataAdditional)
}

View File

@ -0,0 +1,41 @@
package io.gitlab.jfronny.muscript.runtime;
import io.gitlab.jfronny.muscript.ast.Expr;
import io.gitlab.jfronny.muscript.core.CodeLocation;
import io.gitlab.jfronny.muscript.core.LocationalException;
import io.gitlab.jfronny.muscript.data.dynamic.DynamicTypeConversionException;
import org.jetbrains.annotations.Nullable;
public class Except {
private static String formatMessage(Expr source, @Nullable String message) {
String msg = "Could not evaluate " + source.getClass().getSimpleName();
if (message != null) {
msg += ": " + message;
}
return msg;
}
public static LocationalException locationalException(Expr source) {
return new LocationalException(source.location(), formatMessage(source, null));
}
public static LocationalException locationalException(Expr source, String message) {
return new LocationalException(source.location(), formatMessage(source, message));
}
public static LocationalException locationalException(Expr source, String message, Throwable cause) {
return new LocationalException(source.location(), formatMessage(source, message), cause);
}
public static LocationalException locationalException(Expr source, Throwable cause) {
return new LocationalException(source.location(), formatMessage(source, null), cause);
}
public static LocationalException locationalException(DynamicTypeConversionException e, Expr source) {
return locationalException(source, formatMessage(source, e.getMessage()), e);
}
public static LocationalException locationalException(DynamicTypeConversionException e, CodeLocation location) {
return new LocationalException(location, e.getMessage(), e);
}
}

View File

@ -0,0 +1,304 @@
package io.gitlab.jfronny.muscript.runtime;
import io.gitlab.jfronny.muscript.ast.*;
import io.gitlab.jfronny.muscript.ast.bool.*;
import io.gitlab.jfronny.muscript.ast.context.IScope;
import io.gitlab.jfronny.muscript.ast.context.Script;
import io.gitlab.jfronny.muscript.ast.dynamic.*;
import io.gitlab.jfronny.muscript.ast.extensible.ExtensibleBoolExpr;
import io.gitlab.jfronny.muscript.ast.extensible.ExtensibleDynamicExpr;
import io.gitlab.jfronny.muscript.ast.extensible.ExtensibleNumberExpr;
import io.gitlab.jfronny.muscript.ast.extensible.ExtensibleStringExpr;
import io.gitlab.jfronny.muscript.ast.number.*;
import io.gitlab.jfronny.muscript.ast.string.*;
import io.gitlab.jfronny.muscript.core.IDynamic;
import io.gitlab.jfronny.muscript.core.LocationalException;
import io.gitlab.jfronny.muscript.core.StackFrame;
import io.gitlab.jfronny.muscript.data.additional.context.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.*;
import io.gitlab.jfronny.muscript.data.dynamic.context.DynamicSerializer;
import io.gitlab.jfronny.muscript.data.dynamic.type.DTypeCallable;
import java.util.*;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static io.gitlab.jfronny.muscript.ast.context.ExprUtils.asNumber;
import static io.gitlab.jfronny.muscript.ast.context.ExprUtils.asString;
import static io.gitlab.jfronny.muscript.data.additional.DFinal.of;
import static io.gitlab.jfronny.muscript.runtime.Except.locationalException;
public class Runtime {
public static Dynamic run(Script script, Scope scope) {
return evaluate(script.content(), scope);
}
public static Dynamic evaluate(Expr expr, Scope scope) {
Objects.requireNonNull(expr);
return switch (expr) {
case StringExpr e -> of(evaluate(e, scope));
case NumberExpr e -> of(evaluate(e, scope));
case BoolExpr e -> of(evaluate(e, scope));
case DynamicExpr e -> evaluate(e, scope);
case NullLiteral e -> new DNull();
};
}
public static String evaluate(StringExpr expr, Scope scope) {
Objects.requireNonNull(expr);
try {
return switch (expr) {
case StringLiteral(var location, var value) -> value;
case ExtensibleStringExpr e -> e.evaluate(scope);
case StringUnpack(var inner) -> {
try {
Dynamic iv = evaluate(inner, scope);
if (Dynamic.isNull(iv)) throw locationalException(expr, "Cannot unpack null");
yield iv.asString().getValue();
} catch (DynamicTypeConversionException e) {
throw locationalException(e, expr);
}
}
case StringCoerce(var inner) -> DynamicSerializer.INSTANCE.serialize(evaluate(inner, scope));
case StringAssign(var location, var variable, var value) -> {
var data = evaluate(value, scope);
scope.set(variable, data);
yield data;
}
case StringConditional(var location, var condition, var ifTrue, var ifFalse) -> evaluate(condition, scope) ? evaluate(ifTrue, scope) : evaluate(ifFalse, scope);
case Concatenate(var location, var left, var right) -> evaluate(left, scope) + evaluate(right, scope);
};
} catch (DynamicTypeConversionException e) {
throw locationalException(e, expr);
} catch (NullPointerException e) {
throw locationalException(expr, e);
}
}
public static double evaluate(NumberExpr expr, Scope scope) {
Objects.requireNonNull(expr);
try {
return switch (expr) {
case NumberLiteral(var location, var value) -> value;
case ExtensibleNumberExpr e -> e.evaluate(scope);
case NumberUnpack(var inner) -> {
try {
Dynamic iv = evaluate(inner, scope);
if (Dynamic.isNull(iv)) throw locationalException(expr, "Cannot unpack null");
yield iv.asNumber().getValue();
} catch (DynamicTypeConversionException e) {
throw locationalException(e, expr);
}
}
case NumberAssign(var location, var variable, var value) -> {
var data = evaluate(value, scope);
scope.set(variable, data);
yield data;
}
case NumberConditional(var location, var condition, var ifTrue, var ifFalse) -> evaluate(condition, scope) ? evaluate(ifTrue, scope) : evaluate(ifFalse, scope);
case Add(var location, var augend, var addend) -> evaluate(augend, scope) + evaluate(addend, scope);
case Subtract(var location, var minuend, var subtrahend) -> evaluate(minuend, scope) - evaluate(subtrahend, scope);
case Negate(var location, var inner) -> -evaluate(inner, scope);
case Multiply(var location, var multiplier, var multiplicand) -> evaluate(multiplier, scope) * evaluate(multiplicand, scope);
case Divide(var location, var dividend, var divisor) -> evaluate(dividend, scope) / evaluate(divisor, scope);
case Modulo(var location, var dividend, var divisor) -> evaluate(dividend, scope) % evaluate(divisor, scope);
case Power(var location, var base, var exponent) -> Math.pow(evaluate(base, scope), evaluate(exponent, scope));
};
} catch (DynamicTypeConversionException e) {
throw locationalException(e, expr);
} catch (NullPointerException | ArithmeticException e) {
throw locationalException(expr, e);
}
}
public static boolean evaluate(BoolExpr expr, Scope scope) {
Objects.requireNonNull(expr);
try {
return switch (expr) {
case BoolLiteral(var location, var value) -> value;
case ExtensibleBoolExpr e -> e.evaluate(scope);
case BoolUnpack(var inner) -> {
try {
Dynamic iv = evaluate(inner, scope);
if (Dynamic.isNull(iv)) throw locationalException(expr, "Cannot unpack null");
yield iv.asBool().getValue();
} catch (DynamicTypeConversionException e) {
throw locationalException(e, expr);
}
}
case BoolAssign(var location, var variable, var value) -> {
var data = evaluate(value, scope);
scope.set(variable, data);
yield data;
}
case BoolConditional(var location, var condition, var ifTrue, var ifFalse) -> evaluate(condition, scope) ? evaluate(ifTrue, scope) : evaluate(ifFalse, scope);
case And(var location, var left, var right) -> evaluate(left, scope) && evaluate(right, scope);
case Or(var location, var left, var right) -> evaluate(left, scope) || evaluate(right, scope);
case Not(var location, var inner) -> !evaluate(inner, scope);
case Equals(var location, var left, var right) -> Objects.equals(evaluate(left, scope), evaluate(right, scope));
case GreaterThan(var location, var left, var right) -> evaluate(left, scope) > evaluate(right, scope);
};
} catch (DynamicTypeConversionException e) {
throw locationalException(e, expr);
} catch (NullPointerException e) {
throw locationalException(expr, e);
}
}
public static Dynamic evaluate(DynamicExpr expr, Scope scope) {
Objects.requireNonNull(expr);
try {
return switch (expr) {
case DynamicLiteral(var location, var value) -> coerce(expr, value);
case ExtensibleDynamicExpr e -> coerce(e, e.evaluate(scope));
case DynamicCoerce(StringExpr inner) -> of(evaluate(inner, scope));
case DynamicCoerce(NumberExpr inner) -> of(evaluate(inner, scope));
case DynamicCoerce(BoolExpr inner) -> of(evaluate(inner, scope));
case DynamicCoerce(DynamicExpr inner) -> evaluate(inner, scope);
case DynamicCoerce(NullLiteral inner) -> new DNull();
case DynamicAssign(var location, var variable, var value) -> {
var data = evaluate(value, scope);
scope.set(variable, data.isCallable() ? data.asCallable().named(variable) : data);
yield data;
}
case DynamicConditional(var location, var condition, var ifTrue, var ifFalse) -> evaluate(condition, scope) ? evaluate(ifTrue, scope) : evaluate(ifFalse, scope);
case This e -> scope;
case Variable(var location, var name) -> {
if (scope.has(name)) yield scope.get(name);
else throw locationalException(expr, "Variable " + name + " not found");
}
case Get e -> {
var left = evaluate(e.left(), scope);
if (Dynamic.isNull(left)) throw locationalException(expr, "Could not get \"" + evaluate(e.name(), scope) + "\" because left is null");
if (left.isObject()) {
var o = left.asObject();
var n = evaluate(asString(e.name()), scope);
if (!o.has(n)) throw locationalException(expr, "Object does not contain \"" + n + "\"");
yield o.get(n);
} else if (left.isList()) {
var l = left.asList();
int idx = (int) evaluate(asNumber(e.name()), scope);
if (idx < 0 || idx >= l.size()) throw locationalException(expr, "Index " + idx + " is out of range for list with size " + l.size());
yield l.get(idx);
}
throw new DynamicTypeConversionException("object or list", left);
}
case Bind(var location, var callable, var parameter) -> {
var param = evaluate(parameter, scope);
var clb = evaluate(callable, scope).asCallable();
yield of("<bind>", args -> {
var argsWithParameter = new LinkedList<Dynamic>(args.getValue());
argsWithParameter.addFirst(param);
return clb.call(of(argsWithParameter));
}, () -> expr);
}
case Call e -> {
DCallable dc;
DList arg;
try {
Dynamic lv = evaluate(e.callable(), scope);
if (Dynamic.isNull(lv)) throw locationalException(expr, "Cannot invoke null");
dc = lv.asCallable();
arg = of(e.arguments().stream().map(a -> evaluate(a, scope)).toArray(Dynamic[]::new));
} catch (RuntimeException ex) {
throw locationalException(expr, "Could not perform call successfully", ex);
}
try {
yield dc.call(arg);
} catch (LocationalException le) {
throw le.appendStack(new StackFrame.Raw(expr.location().file(), dc.getName(), e.callable().location().chStart()));
} catch (RuntimeException ex) {
throw locationalException(expr, ex.getMessage(), ex);
}
}
case Closure(var location, var boundArgs, var variadic, var steps, var finish) -> of(getSignature(boundArgs, variadic), null, args -> {
int ac = args.size();
int ae = boundArgs.size();
if (variadic) ae--;
if (ac < ae) throw locationalException(expr, "Invoked with too few arguments (expected " + ae + " but got " + ac + ")");
if (!variadic && ac > ae) throw locationalException(expr, "Invoked with too many arguments (expected " + ae + " but got " + ac + ")");
var fork = scope.fork();
for (int i = 0; i < ae; i++) fork.set(boundArgs.get(i), args.get(i));
if (variadic) {
fork.set(boundArgs.getLast(), IntStream.range(ae, ac).mapToObj(args::get).toList());
}
for (var step : steps) {
evaluate(step, fork);
}
return evaluate(finish, fork);
}, () -> expr);
case ExprGroup(var location, var steps, var finish, var packedArgs, var shouldFork) -> {
var fork = shouldFork ? scope.fork() : scope;
if (shouldFork && packedArgs instanceof ExprGroup.PackedArgs(var sourceArgs, var boundArgs, var variadic)) {
var from = new LinkedList<Dynamic>();
for (var arg : sourceArgs) {
var data = evaluate(arg.value(), scope);
if (arg.variadic()) from.addAll(data.asList().getValue());
else from.add(data);
}
int ac = from.size();
int ae = boundArgs.size();
if (variadic) ae--;
if (ac < ae) throw locationalException(expr, "Invoked with too few arguments (expected " + ae + " but got " + ac + ")");
if (!variadic && ac > ae) throw locationalException(expr, "Invoked with too many arguments (expected " + ae + " but got " + ac + ")");
for (int i = 0; i < ae; i++) fork.set(boundArgs.get(i), from.get(i));
if (variadic) {
fork.set(boundArgs.getLast(), IntStream.range(ae, ac).mapToObj(from::get).toList());
}
}
for (var step : steps) {
evaluate(step, fork);
}
yield evaluate(finish, fork);
}
case ListLiteral(var location, var elements) -> of(elements.stream().map(e -> evaluate(e, scope)).toList());
case ObjectLiteral(var location, var content) -> {
var result = new LinkedHashMap<String, Dynamic>();
content.forEach((k, v) -> result.put(k, evaluate(v, scope)));
yield of(result);
}
};
} catch (DynamicTypeConversionException e) {
throw locationalException(e, expr);
} catch (NullPointerException e) {
throw locationalException(expr, e);
}
}
private static DTypeCallable getSignature(List<String> boundArgs, boolean variadic) {
var args = new LinkedList<DTypeCallable.Arg>();
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);
}
private static Stream<? extends Dynamic> evaluate(Call.Argument argument, Scope scope) {
return argument.variadic()
? evaluate(argument.value(), scope).asList().getValue().stream()
: Stream.of(evaluate(argument.value(), scope));
}
public static Dynamic coerce(DynamicExpr expr, IDynamic value) {
return switch (value) {
case null -> null;
case Dynamic d -> d;
default -> throw locationalException(expr, "Unsupported implementation of Dynamic");
};
}
public static Scope coerce(Expr expr, IScope scope) {
return switch (scope) {
case null -> null;
case Scope s -> s;
default -> throw locationalException(expr, "Unsupported implementation of Scope");
};
}
public static DCallable bind(Script script, Scope scope) {
return of("<root>", args -> {
scope.set("args", args);
return run(script, scope);
}, script::content);
}
}

View File

@ -0,0 +1,16 @@
package io.gitlab.jfronny.muscript.runtime.context;
import io.gitlab.jfronny.muscript.ast.context.IScope;
import io.gitlab.jfronny.muscript.ast.extensible.ExtensibleBoolExpr;
import io.gitlab.jfronny.muscript.data.additional.context.Scope;
import static io.gitlab.jfronny.muscript.runtime.Runtime.coerce;
public interface CustomBoolExpr extends ExtensibleBoolExpr {
@Override
default boolean evaluate(IScope scope) {
return evaluate(coerce(this, scope));
}
boolean evaluate(Scope scope);
}

View File

@ -0,0 +1,18 @@
package io.gitlab.jfronny.muscript.runtime.context;
import io.gitlab.jfronny.muscript.ast.context.IScope;
import io.gitlab.jfronny.muscript.ast.extensible.ExtensibleDynamicExpr;
import io.gitlab.jfronny.muscript.core.IDynamic;
import io.gitlab.jfronny.muscript.data.dynamic.Dynamic;
import io.gitlab.jfronny.muscript.data.additional.context.Scope;
import static io.gitlab.jfronny.muscript.runtime.Runtime.coerce;
public interface CustomDynamicExpr extends ExtensibleDynamicExpr {
@Override
default IDynamic evaluate(IScope scope) {
return evaluate(coerce(this, scope));
}
Dynamic evaluate(Scope scope);
}

View File

@ -0,0 +1,16 @@
package io.gitlab.jfronny.muscript.runtime.context;
import io.gitlab.jfronny.muscript.ast.context.IScope;
import io.gitlab.jfronny.muscript.ast.extensible.ExtensibleNumberExpr;
import io.gitlab.jfronny.muscript.data.additional.context.Scope;
import static io.gitlab.jfronny.muscript.runtime.Runtime.coerce;
public interface CustomNumberExpr extends ExtensibleNumberExpr {
@Override
default double evaluate(IScope scope) {
return evaluate(coerce(this, scope));
}
double evaluate(Scope scope);
}

View File

@ -0,0 +1,16 @@
package io.gitlab.jfronny.muscript.runtime.context;
import io.gitlab.jfronny.muscript.ast.context.IScope;
import io.gitlab.jfronny.muscript.ast.extensible.ExtensibleStringExpr;
import io.gitlab.jfronny.muscript.data.additional.context.Scope;
import static io.gitlab.jfronny.muscript.runtime.Runtime.coerce;
public interface CustomStringExpr extends ExtensibleStringExpr {
@Override
default String evaluate(IScope scope) {
return evaluate(coerce(this, scope));
}
String evaluate(Scope scope);
}

View File

@ -0,0 +1,14 @@
package io.gitlab.jfronny.muscript.runtime.impl;
import io.gitlab.jfronny.muscript.ast.context.Script;
import io.gitlab.jfronny.muscript.data.additional.context.IExprBinder;
import io.gitlab.jfronny.muscript.data.additional.context.Scope;
import io.gitlab.jfronny.muscript.data.dynamic.DCallable;
import io.gitlab.jfronny.muscript.runtime.Runtime;
public class ExprBinderImpl implements IExprBinder {
@Override
public DCallable bind(Script script, Scope scope) {
return Runtime.bind(script, scope);
}
}

View File

@ -0,0 +1,10 @@
module io.gitlab.jfronny.commons.muscript.runtime {
requires io.gitlab.jfronny.commons;
requires static org.jetbrains.annotations;
requires io.gitlab.jfronny.commons.muscript.ast;
requires io.gitlab.jfronny.commons.muscript.core;
requires io.gitlab.jfronny.commons.muscript.data;
requires io.gitlab.jfronny.commons.muscript.data.additional;
exports io.gitlab.jfronny.muscript.runtime;
exports io.gitlab.jfronny.muscript.runtime.context;
}

View File

@ -0,0 +1 @@
io.gitlab.jfronny.muscript.runtime.impl.ExprBinderImpl

View File

@ -18,6 +18,7 @@ include("muscript-data-additional")
include("muscript-parser")
include("muscript-serialize")
include("muscript-optimizer")
include("muscript-runtime")
// legacy muscript
include("muscript")
include("muscript-gson")