diff --git a/src/main/java/org/cyclops/integratedscripting/evaluate/translation/translator/OperatorProxyExecutable.java b/src/main/java/org/cyclops/integratedscripting/evaluate/translation/translator/OperatorProxyExecutable.java index fdb5b5004..c775ef813 100644 --- a/src/main/java/org/cyclops/integratedscripting/evaluate/translation/translator/OperatorProxyExecutable.java +++ b/src/main/java/org/cyclops/integratedscripting/evaluate/translation/translator/OperatorProxyExecutable.java @@ -1,9 +1,14 @@ package org.cyclops.integratedscripting.evaluate.translation.translator; import lombok.SneakyThrows; +import org.cyclops.integrateddynamics.api.evaluate.EvaluationException; +import org.cyclops.integrateddynamics.api.evaluate.operator.IOperator; import org.cyclops.integrateddynamics.api.evaluate.variable.IValue; +import org.cyclops.integrateddynamics.api.evaluate.variable.IValueType; import org.cyclops.integrateddynamics.api.evaluate.variable.IVariable; +import org.cyclops.integrateddynamics.core.evaluate.variable.ValueTypeNbt; import org.cyclops.integrateddynamics.core.evaluate.variable.ValueTypeOperator; +import org.cyclops.integrateddynamics.core.evaluate.variable.ValueTypes; import org.cyclops.integrateddynamics.core.evaluate.variable.Variable; import org.cyclops.integratedscripting.api.evaluate.translation.IEvaluationExceptionFactory; import org.cyclops.integratedscripting.evaluate.translation.ValueTranslators; @@ -11,6 +16,8 @@ import org.graalvm.polyglot.Value; import org.graalvm.polyglot.proxy.ProxyExecutable; +import javax.annotation.Nullable; + /** * A Graal proxy executable for operator values. * @author rubensworks @@ -33,10 +40,38 @@ public ValueTypeOperator.ValueOperator getValue() { @SneakyThrows @Override public Object execute(Value... args) { + IOperator operator = value.getRawValue(); + IValueType[] inputTypes = operator.getInputTypes(); IVariable[] variables = new IVariable[args.length]; for (int i = 0; i < args.length; i++) { - variables[i] = new Variable<>(ValueTranslators.REGISTRY.translateFromGraal(context, args[i], exceptionFactory)); + variables[i] = new Variable<>(translateArgument(args[i], i < inputTypes.length ? inputTypes[i] : null)); + } + return ValueTranslators.REGISTRY.translateToGraal(context, operator.evaluate(variables), exceptionFactory); + } + + /** + * Translate a single argument of this operator. + * + * Only compound tags keep their NBT type when they are translated to a script, + * all other tags become plain script values, and an absent NBT value becomes null. + * These are translated back to NBT here, so that they can be passed to operators again. + * + * @param arg A script value. + * @param inputType The value type the operator expects for this argument, if known. + * @return The translated value. + * @throws EvaluationException If translation failed. + */ + protected IValue translateArgument(Value arg, @Nullable IValueType inputType) throws EvaluationException { + if (inputType == ValueTypes.NBT) { + if (arg.isNull()) { + return ValueTypeNbt.ValueNbt.of(); + } + IValue value = ValueTranslators.REGISTRY.translateFromGraal(context, arg, exceptionFactory); + if (value.getType() != ValueTypes.NBT) { + return ValueTypeNbt.ValueNbt.of(ValueTranslators.REGISTRY.translateToNbt(context, value, exceptionFactory)); + } + return value; } - return ValueTranslators.REGISTRY.translateToGraal(context, value.getRawValue().evaluate(variables), exceptionFactory); + return ValueTranslators.REGISTRY.translateFromGraal(context, arg, exceptionFactory); } } diff --git a/src/test/java/org/cyclops/integratedscripting/evaluate/translation/NbtOperatorArgumentsJavaScriptTests.java b/src/test/java/org/cyclops/integratedscripting/evaluate/translation/NbtOperatorArgumentsJavaScriptTests.java new file mode 100644 index 000000000..32e7739d2 --- /dev/null +++ b/src/test/java/org/cyclops/integratedscripting/evaluate/translation/NbtOperatorArgumentsJavaScriptTests.java @@ -0,0 +1,117 @@ +package org.cyclops.integratedscripting.evaluate.translation; + +import net.minecraft.DetectedVersion; +import net.minecraft.SharedConstants; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.Bootstrap; +import org.cyclops.integrateddynamics.api.evaluate.EvaluationException; +import org.cyclops.integrateddynamics.core.evaluate.operator.Operators; +import org.cyclops.integrateddynamics.core.evaluate.variable.ValueTypeListProxyFactories; +import org.cyclops.integrateddynamics.core.evaluate.variable.ValueTypeNbt; +import org.cyclops.integratedscripting.api.evaluate.translation.IEvaluationExceptionFactory; +import org.cyclops.integratedscripting.evaluate.ScriptHelpers; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Value; +import org.junit.BeforeClass; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +/** + * Tests for passing values to operators that expect NBT. + * + * NBT values other than compound tags reach scripts as plain JS values, + * so they must be converted back to NBT when they are passed into an operator again. + * + * @author rubensworks + */ +public class NbtOperatorArgumentsJavaScriptTests { + + static { + SharedConstants.setVersion(DetectedVersion.BUILT_IN); + Bootstrap.bootStrap(); + } + + private static Context CTX = null; + private static IEvaluationExceptionFactory EF = ScriptHelpers.getDummyEvaluationExceptionFactory(); + + @BeforeClass + public static void beforeAll() throws EvaluationException { + ValueTypeListProxyFactories.load(); + Operators.load(); + ValueTranslators.load(); + + CTX = ScriptHelpers.createPopulatedContext(null); + } + + private static Value nbtValue() throws EvaluationException { + CompoundTag levels = new CompoundTag(); + levels.putInt("a", 5); + levels.putInt("b", 1); + levels.putInt("c", 7); + CompoundTag tag = new CompoundTag(); + tag.put("levels", levels); + return ValueTranslators.REGISTRY.translateToGraal(CTX, ValueTypeNbt.ValueNbt.of(tag), EF); + } + + @Test + public void testListTagMatchPassedBackToNbtOperator() throws EvaluationException { + // A filter expression matches into a list tag, which reaches the script as a plain array. + Value function = CTX.eval("js", """ + (function (nbt) { + const matched = idContext.ops.stringNbtPathMatchFirst('["levels"][?(@ >= 3)]', nbt); + return [Array.isArray(matched), idContext.ops.nbtAsTagList(matched).length]; + }) + """); + Value result = function.execute(nbtValue()); + assertThat(result.getArrayElement(0).asBoolean(), is(true)); + assertThat(result.getArrayElement(1).asInt(), is(2)); + } + + @Test + public void testArrayPassedToNbtOperator() { + Value function = CTX.eval("js", """ + (function () { + return idContext.ops.nbtAsTagList([1, 2, 3]).length; + }) + """); + assertThat(function.execute().asInt(), is(3)); + } + + @Test + public void testNullPassedToNbtOperator() throws EvaluationException { + // An NBT path that matches nothing yields null, which stays usable as an NBT value. + Value function = CTX.eval("js", """ + (function (nbt) { + const matched = idContext.ops.stringNbtPathMatchFirst('["absent"]', nbt); + return [matched === null, idContext.ops.nbtAsTagList(matched).length]; + }) + """); + Value result = function.execute(nbtValue()); + assertThat(result.getArrayElement(0).asBoolean(), is(true)); + assertThat(result.getArrayElement(1).asInt(), is(0)); + } + + @Test + public void testCompoundTagPassedBackToNbtOperator() throws EvaluationException { + // Compound tags were already passed back unchanged, and must keep working. + Value function = CTX.eval("js", """ + (function (nbt) { + const levels = idContext.ops.stringNbtPathMatchFirst('["levels"]', nbt); + return idContext.ops.nbtSize(levels); + }) + """); + assertThat(function.execute(nbtValue()).asInt(), is(3)); + } + + @Test + public void testNonNbtOperatorArgumentsAreUnaffected() { + Value function = CTX.eval("js", """ + (function () { + return idContext.ops.stringLength("abcd"); + }) + """); + assertThat(function.execute().asInt(), is(4)); + } +}