From f1fd9e5bc8b9f6f16c5a272b2ebafd0996c129b3 Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Mon, 18 Dec 2023 17:38:07 +0100 Subject: [PATCH] Construct variables for scripts This allows scripts to be used as values. --- .../api/language/ILanguageHandler.java | 4 ++ .../api/network/IScript.java | 19 +++++++ .../api/network/IScriptFactory.java | 23 ++++++++ .../api/network/IScriptMember.java | 18 ++++++ .../api/network/IScriptingData.java | 4 +- .../api/network/IScriptingNetwork.java | 37 ++++++++++++ .../core/item/ScriptVariableFacade.java | 45 ++++++++------- .../language/LanguageHandlerJavaScript.java | 11 ++++ .../language/LanguageHandlerRegistry.java | 7 +-- .../core/network/GraalScript.java | 37 ++++++++++++ .../core/network/GraalScriptFactory.java | 42 ++++++++++++++ .../core/network/ScriptingNetwork.java | 57 +++++++++++++++++++ .../evaluate/ScriptHelpers.java | 12 ++++ .../translator/ValueTranslatorOperator.java | 4 +- .../integratedscripting/lang/en_us.json | 5 ++ 15 files changed, 299 insertions(+), 26 deletions(-) create mode 100644 src/main/java/org/cyclops/integratedscripting/api/network/IScript.java create mode 100644 src/main/java/org/cyclops/integratedscripting/api/network/IScriptFactory.java create mode 100644 src/main/java/org/cyclops/integratedscripting/api/network/IScriptMember.java create mode 100644 src/main/java/org/cyclops/integratedscripting/core/network/GraalScript.java create mode 100644 src/main/java/org/cyclops/integratedscripting/core/network/GraalScriptFactory.java diff --git a/src/main/java/org/cyclops/integratedscripting/api/language/ILanguageHandler.java b/src/main/java/org/cyclops/integratedscripting/api/language/ILanguageHandler.java index 0aca2e06..38057475 100644 --- a/src/main/java/org/cyclops/integratedscripting/api/language/ILanguageHandler.java +++ b/src/main/java/org/cyclops/integratedscripting/api/language/ILanguageHandler.java @@ -2,6 +2,8 @@ import net.minecraft.network.chat.Style; import org.apache.commons.lang3.tuple.Pair; +import org.cyclops.integrateddynamics.api.evaluate.EvaluationException; +import org.cyclops.integratedscripting.api.network.IScriptFactory; import java.util.List; @@ -17,4 +19,6 @@ public interface ILanguageHandler { public List> markupLine(String line); + public IScriptFactory getScriptFactory() throws EvaluationException; + } diff --git a/src/main/java/org/cyclops/integratedscripting/api/network/IScript.java b/src/main/java/org/cyclops/integratedscripting/api/network/IScript.java new file mode 100644 index 00000000..8c8de51f --- /dev/null +++ b/src/main/java/org/cyclops/integratedscripting/api/network/IScript.java @@ -0,0 +1,19 @@ +package org.cyclops.integratedscripting.api.network; + +import javax.annotation.Nullable; + +/** + * A script that contains executable members. + * @author rubensworks + */ +public interface IScript { + + /** + * Get the given script member. + * @param memberName A member name. + * @return The member, or null if it does not exist. + */ + @Nullable + public IScriptMember getMember(String memberName); + +} diff --git a/src/main/java/org/cyclops/integratedscripting/api/network/IScriptFactory.java b/src/main/java/org/cyclops/integratedscripting/api/network/IScriptFactory.java new file mode 100644 index 00000000..95a50f5f --- /dev/null +++ b/src/main/java/org/cyclops/integratedscripting/api/network/IScriptFactory.java @@ -0,0 +1,23 @@ +package org.cyclops.integratedscripting.api.network; + +import org.cyclops.integrateddynamics.api.evaluate.EvaluationException; + +import javax.annotation.Nullable; +import java.nio.file.Path; + +/** + * Instantiates scripts for paths in disks. + * @author rubensworks + */ +public interface IScriptFactory { + + /** + * Get the script by the given path in the given disk. + * @param disk A disk id. + * @param path A script path. + * @return The script or null. + */ + @Nullable + public IScript getScript(int disk, Path path) throws EvaluationException; + +} diff --git a/src/main/java/org/cyclops/integratedscripting/api/network/IScriptMember.java b/src/main/java/org/cyclops/integratedscripting/api/network/IScriptMember.java new file mode 100644 index 00000000..1bedde78 --- /dev/null +++ b/src/main/java/org/cyclops/integratedscripting/api/network/IScriptMember.java @@ -0,0 +1,18 @@ +package org.cyclops.integratedscripting.api.network; + +import org.cyclops.integrateddynamics.api.evaluate.EvaluationException; +import org.cyclops.integrateddynamics.api.evaluate.variable.IValue; + +/** + * A script member that contains a value. + * @author rubensworks + */ +public interface IScriptMember { + + /** + * @return The member value as Integrated Dynamics value. + * @throws EvaluationException If an error occurred. + */ + public IValue getValue() throws EvaluationException; + +} diff --git a/src/main/java/org/cyclops/integratedscripting/api/network/IScriptingData.java b/src/main/java/org/cyclops/integratedscripting/api/network/IScriptingData.java index a4322380..b7940360 100644 --- a/src/main/java/org/cyclops/integratedscripting/api/network/IScriptingData.java +++ b/src/main/java/org/cyclops/integratedscripting/api/network/IScriptingData.java @@ -1,12 +1,14 @@ package org.cyclops.integratedscripting.api.network; + import javax.annotation.Nullable; import java.nio.file.Path; import java.util.Collection; import java.util.Map; /** - * Capability for handling scripts inside a world. + * Server-side singleton for handling scripts inside the game. + * @see {@link IntegratedScripting#scriptingData} * @author rubensworks */ public interface IScriptingData { diff --git a/src/main/java/org/cyclops/integratedscripting/api/network/IScriptingNetwork.java b/src/main/java/org/cyclops/integratedscripting/api/network/IScriptingNetwork.java index 46c1a39b..9692a533 100644 --- a/src/main/java/org/cyclops/integratedscripting/api/network/IScriptingNetwork.java +++ b/src/main/java/org/cyclops/integratedscripting/api/network/IScriptingNetwork.java @@ -1,5 +1,11 @@ package org.cyclops.integratedscripting.api.network; +import org.cyclops.integrateddynamics.api.evaluate.EvaluationException; +import org.cyclops.integrateddynamics.api.evaluate.variable.IValue; +import org.cyclops.integrateddynamics.api.evaluate.variable.IVariable; + +import javax.annotation.Nullable; +import java.nio.file.Path; import java.util.Set; /** @@ -8,10 +14,41 @@ */ public interface IScriptingNetwork { + /** + * Indicate that the given disk is contained in this network. + * @param disk A disk id. + */ public void addDisk(int disk); + /** + * Indicate that the given disk is not contained in this network. + * @param disk A disk id. + */ public void removeDisk(int disk); + /** + * @return All disks contained in this network. + */ public Set getDisks(); + /** + * Get the script by the given path in the given disk. + * @param disk A disk id. + * @param path A script path. + * @return The script or null. + */ + @Nullable + public IScript getScript(int disk, Path path) throws EvaluationException; + + /** + * Get a variable containing the value of the given script member. + * This variable can be cached and invalidated if the underlying script is modified. + * @param disk A disk id. + * @param path A script path. + * @param member A script member name. + * @param The value type. + * @return A variable. + */ + public IVariable getOrCreateVariable(int disk, Path path, String member); + } diff --git a/src/main/java/org/cyclops/integratedscripting/core/item/ScriptVariableFacade.java b/src/main/java/org/cyclops/integratedscripting/core/item/ScriptVariableFacade.java index 7b079f40..10a31a47 100644 --- a/src/main/java/org/cyclops/integratedscripting/core/item/ScriptVariableFacade.java +++ b/src/main/java/org/cyclops/integratedscripting/core/item/ScriptVariableFacade.java @@ -10,14 +10,19 @@ import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.client.model.data.ModelData; import org.cyclops.integrateddynamics.api.client.model.IVariableModelBaked; +import org.cyclops.integrateddynamics.api.evaluate.EvaluationException; 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.api.network.INetwork; import org.cyclops.integrateddynamics.api.network.IPartNetwork; import org.cyclops.integrateddynamics.core.evaluate.variable.ValueTypes; +import org.cyclops.integrateddynamics.core.helper.L10NValues; import org.cyclops.integrateddynamics.core.item.VariableFacadeBase; import org.cyclops.integratedscripting.api.item.IScriptVariableFacade; +import org.cyclops.integratedscripting.api.network.IScriptingNetwork; import org.cyclops.integratedscripting.core.client.model.ScriptingVariableModelProviders; +import org.cyclops.integratedscripting.core.network.ScriptingNetworkHelpers; import java.nio.file.Path; import java.util.List; @@ -49,11 +54,11 @@ public ScriptVariableFacade(int id, int disk, Path path, String member) { } @Override - public IVariable getVariable(IPartNetwork network) { + public IVariable getVariable(INetwork network, IPartNetwork partNetwork) { if(isValid()) { - // TODO: get variable of script from scripting network -// IVariable variable = getTargetVariable(network).orElse(null); -// return variable; + return ScriptingNetworkHelpers.getScriptingNetwork(network) + .map(scriptingNetwork -> scriptingNetwork.getOrCreateVariable(this.disk, this.path, this.member)) + .orElse(null); } return null; } @@ -64,21 +69,23 @@ public boolean isValid() { } @Override - public void validate(IPartNetwork network, IValidator validator, IValueType containingValueType) { - // TODO: validation: check if disk is present and such... -// Optional targetVariable = getTargetVariable(network); -// if (!isValid()) { -// validator.addError(Component.translatable(L10NValues.VARIABLE_ERROR_INVALIDITEM)); -// } else if (network.getScript(proxyId) == null) { -// validator.addError(getScriptNotInNetworkError()); -// } else if (!targetVariable.isPresent()) { -// validator.addError(getScriptInvalidError()); -// } else if (!ValueHelpers.correspondsTo(containingValueType, targetVariable.get().getType())) { -// validator.addError(getScriptInvalidTypeError(network, containingValueType, -// targetVariable.get().getType())); -// } - - getVariable(network); + public void validate(INetwork network, IPartNetwork partNetwork, IValidator validator, IValueType containingValueType) { + IScriptingNetwork scriptingNetwork = ScriptingNetworkHelpers.getScriptingNetwork(network).orElse(null); + try { + if (!isValid()) { + validator.addError(Component.translatable(L10NValues.VARIABLE_ERROR_INVALIDITEM)); + } else if (scriptingNetwork == null) { + validator.addError(Component.translatable("script.integratedscripting.error.invalid_network")); + } else if (!scriptingNetwork.getDisks().contains(this.disk)) { + validator.addError(Component.translatable("script.integratedscripting.error.disk_not_in_network", this.disk)); + } else if (scriptingNetwork.getScript(this.disk, this.path) == null) { + validator.addError(Component.translatable("script.integratedscripting.error.path_not_in_network", this.disk, this.path.toString())); + } else if (scriptingNetwork.getScript(this.disk, this.path).getMember(this.member) == null) { + validator.addError(Component.translatable("script.integratedscripting.error.member_not_in_network", this.disk, this.path.toString(), this.member)); + } + } catch (EvaluationException e) { + validator.addError(e.getErrorMessage()); + } } @Override diff --git a/src/main/java/org/cyclops/integratedscripting/core/language/LanguageHandlerJavaScript.java b/src/main/java/org/cyclops/integratedscripting/core/language/LanguageHandlerJavaScript.java index 4a1ddb41..1c9a8ee5 100644 --- a/src/main/java/org/cyclops/integratedscripting/core/language/LanguageHandlerJavaScript.java +++ b/src/main/java/org/cyclops/integratedscripting/core/language/LanguageHandlerJavaScript.java @@ -6,7 +6,12 @@ import org.apache.commons.compress.utils.Lists; import org.apache.commons.lang3.tuple.Pair; import org.cyclops.cyclopscore.helper.Helpers; +import org.cyclops.integrateddynamics.api.evaluate.EvaluationException; import org.cyclops.integratedscripting.api.language.ILanguageHandler; +import org.cyclops.integratedscripting.api.network.IScriptFactory; +import org.cyclops.integratedscripting.core.network.GraalScriptFactory; +import org.cyclops.integratedscripting.evaluate.ScriptHelpers; +import org.graalvm.polyglot.Context; import java.util.Arrays; import java.util.List; @@ -89,4 +94,10 @@ public List> markupLine(String line) { return segments; } + + @Override + public IScriptFactory getScriptFactory() throws EvaluationException { + Context context = ScriptHelpers.createPopulatedContext(); + return new GraalScriptFactory(context, context.getBindings("js"), "js"); + } } diff --git a/src/main/java/org/cyclops/integratedscripting/core/language/LanguageHandlerRegistry.java b/src/main/java/org/cyclops/integratedscripting/core/language/LanguageHandlerRegistry.java index 4e10606a..c13da62c 100644 --- a/src/main/java/org/cyclops/integratedscripting/core/language/LanguageHandlerRegistry.java +++ b/src/main/java/org/cyclops/integratedscripting/core/language/LanguageHandlerRegistry.java @@ -3,6 +3,7 @@ import com.google.common.collect.Maps; import org.cyclops.integratedscripting.api.language.ILanguageHandler; import org.cyclops.integratedscripting.api.language.ILanguageHandlerRegistry; +import org.cyclops.integratedscripting.evaluate.ScriptHelpers; import javax.annotation.Nullable; import java.nio.file.Path; @@ -37,10 +38,8 @@ public void register(ILanguageHandler translator) { @Nullable @Override public ILanguageHandler getProvider(Path filePath) { - String filePathString = filePath.toString(); - int dotPos = filePathString.lastIndexOf('.'); - if (dotPos >= 0 && dotPos + 1 < filePathString.length()) { - String extension = filePathString.substring(dotPos + 1); + String extension = ScriptHelpers.getPathExtension(filePath); + if (extension != null) { return extensionToHandlerMap.get(extension); } return null; diff --git a/src/main/java/org/cyclops/integratedscripting/core/network/GraalScript.java b/src/main/java/org/cyclops/integratedscripting/core/network/GraalScript.java new file mode 100644 index 00000000..09b17d37 --- /dev/null +++ b/src/main/java/org/cyclops/integratedscripting/core/network/GraalScript.java @@ -0,0 +1,37 @@ +package org.cyclops.integratedscripting.core.network; + +import org.cyclops.integrateddynamics.api.evaluate.EvaluationException; +import org.cyclops.integrateddynamics.api.evaluate.variable.IValue; +import org.cyclops.integratedscripting.api.network.IScript; +import org.cyclops.integratedscripting.api.network.IScriptMember; +import org.cyclops.integratedscripting.evaluate.translation.ValueTranslators; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Value; +import org.jetbrains.annotations.Nullable; + +/** + * A script referencing a Graal value. + * @author rubensworks + */ +public class GraalScript implements IScript, IScriptMember { + + private final Context graalContext; + private final Value graalValue; + + public GraalScript(Context graalContext, Value graalValue) { + this.graalContext = graalContext; + this.graalValue = graalValue; + } + + @Nullable + @Override + public IScriptMember getMember(String memberName) { + Value member = this.graalValue.getMember(memberName); + return member == null ? null : new GraalScript(this.graalContext, member); + } + + @Override + public IValue getValue() throws EvaluationException { + return ValueTranslators.REGISTRY.translateFromGraal(this.graalContext, graalValue); + } +} diff --git a/src/main/java/org/cyclops/integratedscripting/core/network/GraalScriptFactory.java b/src/main/java/org/cyclops/integratedscripting/core/network/GraalScriptFactory.java new file mode 100644 index 00000000..4ace4fbc --- /dev/null +++ b/src/main/java/org/cyclops/integratedscripting/core/network/GraalScriptFactory.java @@ -0,0 +1,42 @@ +package org.cyclops.integratedscripting.core.network; + +import net.minecraft.network.chat.Component; +import org.cyclops.integrateddynamics.api.evaluate.EvaluationException; +import org.cyclops.integratedscripting.api.network.IScript; +import org.cyclops.integratedscripting.api.network.IScriptFactory; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.Source; +import org.graalvm.polyglot.Value; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.nio.file.Path; + +/** + * Instantiates scripts using the GraalVM. + * @author rubensworks + */ +public class GraalScriptFactory implements IScriptFactory { + + private final Context graalContext; + private final Value languageBinding; + private final String languageId; + + public GraalScriptFactory(Context graalContext, Value languageBinding, String languageId) { + this.graalContext = graalContext; + this.languageBinding = languageBinding; + this.languageId = languageId; + } + + @Nullable + @Override + public IScript getScript(int disk, Path path) throws EvaluationException { + try { + Source source = Source.newBuilder(this.languageId, ScriptingNetworkHelpers.getScriptingData().getScripts(disk).get(path), path.toString()).build(); + this.graalContext.eval(source); + return new GraalScript(this.graalContext, this.languageBinding); + } catch (IOException e) { + throw new EvaluationException(Component.literal(e.getMessage())); + } + } +} diff --git a/src/main/java/org/cyclops/integratedscripting/core/network/ScriptingNetwork.java b/src/main/java/org/cyclops/integratedscripting/core/network/ScriptingNetwork.java index 4ee449cc..3227d057 100644 --- a/src/main/java/org/cyclops/integratedscripting/core/network/ScriptingNetwork.java +++ b/src/main/java/org/cyclops/integratedscripting/core/network/ScriptingNetwork.java @@ -1,8 +1,23 @@ package org.cyclops.integratedscripting.core.network; import com.google.common.collect.Sets; +import net.minecraft.network.chat.Component; +import org.cyclops.cyclopscore.datastructure.Wrapper; +import org.cyclops.integrateddynamics.api.evaluate.EvaluationException; +import org.cyclops.integrateddynamics.api.evaluate.expression.VariableAdapter; +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.ValueTypes; +import org.cyclops.integratedscripting.api.language.ILanguageHandler; +import org.cyclops.integratedscripting.api.network.IScript; +import org.cyclops.integratedscripting.api.network.IScriptFactory; +import org.cyclops.integratedscripting.api.network.IScriptMember; import org.cyclops.integratedscripting.api.network.IScriptingNetwork; +import org.cyclops.integratedscripting.core.language.LanguageHandlers; +import org.jetbrains.annotations.Nullable; +import java.nio.file.Path; import java.util.Collections; import java.util.Set; @@ -28,4 +43,46 @@ public Set getDisks() { return Collections.unmodifiableSet(this.disks); } + @Nullable + @Override + public IScript getScript(int disk, Path path) throws EvaluationException { + // TODO: cache the following + + ILanguageHandler languageHandler = LanguageHandlers.REGISTRY.getProvider(path); + if (languageHandler == null) { + throw new EvaluationException(Component.translatable("script.integratedscripting.error.unsupported_language", path.toString())); + } + IScriptFactory scriptFactory = languageHandler.getScriptFactory(); + return scriptFactory.getScript(disk, path); + } + + @Override + public IVariable getOrCreateVariable(int disk, Path path, String member) { + // TODO: variable caching and invalidation (listen to file changes via IScript to ScriptingData) + + Wrapper script = new Wrapper<>(); + VariableAdapter variable = new VariableAdapter() { + + @Override + public IValueType getType() { + return ValueTypes.CATEGORY_ANY; + } + + @Override + public IValue getValue() throws EvaluationException { + if (script.get() == null) { + // TODO: cache error? + script.set(getScript(disk, path)); + } + IScriptMember scriptMember = script.get().getMember(member); + if (scriptMember == null) { + throw new EvaluationException(Component.translatable("script.integratedscripting.error.member_not_in_network", member, path.toString())); + } + return scriptMember.getValue(); + } + }; + + return (IVariable) variable; + } + } diff --git a/src/main/java/org/cyclops/integratedscripting/evaluate/ScriptHelpers.java b/src/main/java/org/cyclops/integratedscripting/evaluate/ScriptHelpers.java index 24dad672..f6787e63 100644 --- a/src/main/java/org/cyclops/integratedscripting/evaluate/ScriptHelpers.java +++ b/src/main/java/org/cyclops/integratedscripting/evaluate/ScriptHelpers.java @@ -9,6 +9,8 @@ import org.graalvm.polyglot.Engine; import org.graalvm.polyglot.Value; +import javax.annotation.Nullable; +import java.nio.file.Path; import java.util.Map; /** @@ -43,4 +45,14 @@ public static Context createPopulatedContext() throws EvaluationException { return context; } + @Nullable + public static String getPathExtension(Path path) { + String filePathString = path.toString(); + int dotPos = filePathString.lastIndexOf('.'); + if (dotPos >= 0 && dotPos + 1 < filePathString.length()) { + return filePathString.substring(dotPos + 1); + } + return null; + } + } diff --git a/src/main/java/org/cyclops/integratedscripting/evaluate/translation/translator/ValueTranslatorOperator.java b/src/main/java/org/cyclops/integratedscripting/evaluate/translation/translator/ValueTranslatorOperator.java index 5aa90b5f..f8eb049e 100644 --- a/src/main/java/org/cyclops/integratedscripting/evaluate/translation/translator/ValueTranslatorOperator.java +++ b/src/main/java/org/cyclops/integratedscripting/evaluate/translation/translator/ValueTranslatorOperator.java @@ -78,12 +78,12 @@ public static interface JavaRunnableFromGraal { public static class GraalOperator extends OperatorBase { protected GraalOperator(IValueType[] inputTypes, IFunction function) { - super("GRAAL", "GRAAL", "GRAAL", null, false, inputTypes, ValueTypes.CATEGORY_ANY, function, null); + super("graal", "graal", "graal", null, false, inputTypes, ValueTypes.CATEGORY_ANY, function, null); } @Override protected String getUnlocalizedType() { - return "GRAAL"; + return "graal"; } } diff --git a/src/main/resources/assets/integratedscripting/lang/en_us.json b/src/main/resources/assets/integratedscripting/lang/en_us.json index 4b6b7831..6be2d43e 100644 --- a/src/main/resources/assets/integratedscripting/lang/en_us.json +++ b/src/main/resources/assets/integratedscripting/lang/en_us.json @@ -23,6 +23,11 @@ "script.integratedscripting.tooltip.disk": "§e§oDisk ID: §r§o%s", "script.integratedscripting.tooltip.path": "§e§oScript Path: §r§o%s", "script.integratedscripting.tooltip.member": "§e§oMember: §r§o%s", + "script.integratedscripting.error.invalid_network": "No valid scripting network could be found.", + "script.integratedscripting.error.disk_not_in_network": "Disk with id %s could not be found within the current network.", + "script.integratedscripting.error.path_not_in_network": "Disk with id %s does not contain the script \"%s\".", + "script.integratedscripting.error.member_not_in_network": "Disk with id %s does not contain the member \"%s\" in the script \"%s\".", + "script.integratedscripting.error.unsupported_language": "The language of script \"%s\" is not supported.", "_comment": "Items", "item.integratedscripting.scripting_disk": "Scripting Disk",