From 4d7fc1a69f8a870d24898958deb960848fef1a94 Mon Sep 17 00:00:00 2001 From: Brandon Davis Date: Thu, 1 Jan 2026 14:08:07 -0600 Subject: [PATCH 1/5] Initial code dump --- .../engine/client/input/InputHandler.java | 14 +- .../engine/client/renderer/model/Mesh.java | 2 +- .../engine/client/renderer/shader/Shader.java | 2 +- .../client/renderer/shader/ShaderProgram.java | 2 +- .../engine/ecs/Entity.java | 2 +- .../engine/registry/Identifiable.java | 7 + .../engine/registry/Registry.java | 21 ++- .../engine/registry/RegistryPair.java | 2 +- .../engine/scripting/api/ActionContext.java | 11 ++ .../engine/scripting/api/PropertyAccess.java | 26 ++++ .../engine/scripting/api/ScriptAction.java | 68 ++++++++++ .../scripting/api/ScriptActionExecutor.java | 7 + .../engine/scripting/api/ScriptConstant.java | 7 + .../engine/scripting/api/ScriptEvent.java | 85 +++++++++++++ .../scripting/api/ScriptEventValue.java | 10 ++ .../engine/scripting/api/ScriptProperty.java | 20 +++ .../engine/scripting/api/ScriptType.java | 60 +++++++++ .../scripting/api/ScriptVisibility.java | 9 ++ .../api/registry/ScriptActionRegistry.java | 7 + .../api/registry/ScriptConstantRegistry.java | 7 + .../api/registry/ScriptEventRegistry.java | 7 + .../api/registry/ScriptPropertyRegistry.java | 7 + .../api/registry/ScriptTypeRegistry.java | 8 ++ .../scripting/api/syntax/ArgumentElement.java | 8 ++ .../scripting/api/syntax/LiteralElement.java | 7 + .../engine/scripting/api/syntax/Syntax.java | 19 +++ .../scripting/api/syntax/SyntaxElement.java | 4 + .../scripting/api/syntax/SyntaxPattern.java | 11 ++ .../engine/scripting/core/CoreActions.java | 32 +++++ .../engine/scripting/core/CoreConstants.java | 26 ++++ .../engine/scripting/core/CoreEvents.java | 13 ++ .../engine/scripting/core/CoreLibrary.java | 25 ++++ .../engine/scripting/core/CoreProperties.java | 36 ++++++ .../engine/scripting/core/CoreTypes.java | 36 ++++++ .../parser/CallActionInstruction.java | 24 ++++ .../scripting/parser/ExecutionContext.java | 19 +++ .../parser/LoadEventValueInstruction.java | 22 ++++ .../parser/LoadPropertyInstruction.java | 28 ++++ .../scripting/parser/ScriptInstruction.java | 7 + .../test/ecs/ScriptsTest.java | 120 ++++++++++++++++++ 40 files changed, 815 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/registry/Identifiable.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ActionContext.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/PropertyAccess.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptAction.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptActionExecutor.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptConstant.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptEvent.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptEventValue.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptProperty.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptType.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptVisibility.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptActionRegistry.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptConstantRegistry.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptEventRegistry.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptPropertyRegistry.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptTypeRegistry.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/ArgumentElement.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/LiteralElement.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/Syntax.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxElement.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxPattern.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreActions.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreConstants.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreEvents.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreLibrary.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreProperties.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreTypes.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/CallActionInstruction.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ExecutionContext.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/LoadEventValueInstruction.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/LoadPropertyInstruction.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ScriptInstruction.java create mode 100644 src/test/java/com/terminalvelocitycabbage/test/ecs/ScriptsTest.java diff --git a/src/main/java/com/terminalvelocitycabbage/engine/client/input/InputHandler.java b/src/main/java/com/terminalvelocitycabbage/engine/client/input/InputHandler.java index 2b8b5c0..09b839b 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/client/input/InputHandler.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/client/input/InputHandler.java @@ -124,12 +124,12 @@ public long getMousedOverWindow() { */ public Control registerControlListener(Control control) { return switch (control) { - case KeyboardKeyControl kkc -> controlRegistry.register(ClientBase.getInstance().identifierOf("keyboardKey_control_" + kkc.getKey().name()), kkc).getValue(); - case GamepadButtonControl gpbc -> controlRegistry.register(ClientBase.getInstance().identifierOf("gamepadButton_control_" + gpbc.getButton().name()), gpbc).getValue(); - case MouseButtonControl mbc -> controlRegistry.register(ClientBase.getInstance().identifierOf("mouseButton_control_" + mbc.getButton().name()), mbc).getValue(); - case GamepadAxisControl gpac -> controlRegistry.register(ClientBase.getInstance().identifierOf("gamepadAxis_control_" + gpac.getAxis().name()), gpac).getValue(); - case MouseMovementControl mmc -> controlRegistry.register(ClientBase.getInstance().identifierOf("mouseMovement_control_" + mmc.getAxis().name()), mmc).getValue(); - case MouseScrollControl msc -> controlRegistry.register(ClientBase.getInstance().identifierOf("mouseScroll_control_" + msc.getDirection().name()), msc).getValue(); + case KeyboardKeyControl kkc -> controlRegistry.register(ClientBase.getInstance().identifierOf("keyboardKey_control_" + kkc.getKey().name()), kkc).getElement(); + case GamepadButtonControl gpbc -> controlRegistry.register(ClientBase.getInstance().identifierOf("gamepadButton_control_" + gpbc.getButton().name()), gpbc).getElement(); + case MouseButtonControl mbc -> controlRegistry.register(ClientBase.getInstance().identifierOf("mouseButton_control_" + mbc.getButton().name()), mbc).getElement(); + case GamepadAxisControl gpac -> controlRegistry.register(ClientBase.getInstance().identifierOf("gamepadAxis_control_" + gpac.getAxis().name()), gpac).getElement(); + case MouseMovementControl mmc -> controlRegistry.register(ClientBase.getInstance().identifierOf("mouseMovement_control_" + mmc.getAxis().name()), mmc).getElement(); + case MouseScrollControl msc -> controlRegistry.register(ClientBase.getInstance().identifierOf("mouseScroll_control_" + msc.getDirection().name()), msc).getElement(); }; } @@ -141,6 +141,6 @@ public Control registerControlListener(Control control) { * @return The Controller which was registered */ public Controller registerController(Identifier identifier, Controller controller) { - return controllerRegistry.register(identifier, controller).getValue(); + return controllerRegistry.register(identifier, controller).getElement(); } } diff --git a/src/main/java/com/terminalvelocitycabbage/engine/client/renderer/model/Mesh.java b/src/main/java/com/terminalvelocitycabbage/engine/client/renderer/model/Mesh.java index 3cc0496..a6076b2 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/client/renderer/model/Mesh.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/client/renderer/model/Mesh.java @@ -215,7 +215,7 @@ private int getNumIndices() { } /** - * @return The opengl id given to this mesh + * @return The opengl identifier given to this mesh */ public final int getVaoId() { return vaoId; diff --git a/src/main/java/com/terminalvelocitycabbage/engine/client/renderer/shader/Shader.java b/src/main/java/com/terminalvelocitycabbage/engine/client/renderer/shader/Shader.java index beeec7c..4b6acec 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/client/renderer/shader/Shader.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/client/renderer/shader/Shader.java @@ -50,7 +50,7 @@ private String parseInclusions(String source) { } /** - * @return The shader id for this shader once created + * @return The shader identifier for this shader once created */ public int create() { diff --git a/src/main/java/com/terminalvelocitycabbage/engine/client/renderer/shader/ShaderProgram.java b/src/main/java/com/terminalvelocitycabbage/engine/client/renderer/shader/ShaderProgram.java index 5b8a0c4..a4d3898 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/client/renderer/shader/ShaderProgram.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/client/renderer/shader/ShaderProgram.java @@ -73,7 +73,7 @@ public void cleanup() { } /** - * @return The OpenGL id of this shader program + * @return The OpenGL identifier of this shader program */ public int getProgramID() { return programID; diff --git a/src/main/java/com/terminalvelocitycabbage/engine/ecs/Entity.java b/src/main/java/com/terminalvelocitycabbage/engine/ecs/Entity.java index f19cda3..34dc63d 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/ecs/Entity.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/ecs/Entity.java @@ -46,7 +46,7 @@ protected void setManager(Manager manager) { public T addComponent(Class componentClass) { if (containsComponent(componentClass)) { - Log.warn("Tried to add component " + componentClass.getName() + " to entity with id " + getID() + " which already contains it"); + Log.warn("Tried to add component " + componentClass.getName() + " to entity with identifier " + getID() + " which already contains it"); } components.put(componentClass, manager.obtainComponent(componentClass, this)); manager.invalidateQueryCacheForComponents(componentClass); diff --git a/src/main/java/com/terminalvelocitycabbage/engine/registry/Identifiable.java b/src/main/java/com/terminalvelocitycabbage/engine/registry/Identifiable.java new file mode 100644 index 0000000..e22cde6 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/registry/Identifiable.java @@ -0,0 +1,7 @@ +package com.terminalvelocitycabbage.engine.registry; + +public interface Identifiable { + + public Identifier getIdentifier(); + +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/registry/Registry.java b/src/main/java/com/terminalvelocitycabbage/engine/registry/Registry.java index 3641ba7..5f7d846 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/registry/Registry.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/registry/Registry.java @@ -2,7 +2,7 @@ import com.terminalvelocitycabbage.engine.debug.Log; -import java.util.*; +import java.util.LinkedHashMap; public class Registry { @@ -26,6 +26,25 @@ public Registry() { this(null); } + /** + * Registers an item to this registry for retrieval later by its identifier or name + * @param item The item to be registered (if it extends Identifiable) + */ + public RegistryPair register(T item) { + if (item instanceof Identifiable identifiable) { + var identifier = identifiable.getIdentifier(); + if (registryContents.containsKey(identifier)) { + Log.warn("Tried to register item of same identifier " + identifier.toString() + " twice, the second addition has been ignored."); + return null; + } + registryContents.put(identifier, item); + return new RegistryPair<>(identifier, item); + } else { + Log.warn("Tried to register item of type " + item.getClass().getName() + " which does not implement Identifiable, the item will not be registered. Try registering with a declared Identifier instead of implicit with this method"); + return null; + } + } + /** * Registers an item to this registry for retrieval later by its identifier or name * @param identifier The identifier of this registered item diff --git a/src/main/java/com/terminalvelocitycabbage/engine/registry/RegistryPair.java b/src/main/java/com/terminalvelocitycabbage/engine/registry/RegistryPair.java index 73d5542..a442013 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/registry/RegistryPair.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/registry/RegistryPair.java @@ -26,7 +26,7 @@ public Identifier getIdentifier() { /** * @return The value associated with this registration */ - public T getValue() { + public T getElement() { return getValue1(); } } diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ActionContext.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ActionContext.java new file mode 100644 index 0000000..60dc154 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ActionContext.java @@ -0,0 +1,11 @@ +package com.terminalvelocitycabbage.engine.scripting.api; + +import java.util.Map; + +public record ActionContext(Map arguments) { + + @SuppressWarnings("unchecked") + public T get(String name) { + return (T) arguments.get(name); + } +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/PropertyAccess.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/PropertyAccess.java new file mode 100644 index 0000000..3a1a223 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/PropertyAccess.java @@ -0,0 +1,26 @@ +package com.terminalvelocitycabbage.engine.scripting.api; + +import java.util.function.BiConsumer; +import java.util.function.Function; + +public sealed interface PropertyAccess permits PropertyAccess.ReadOnly, PropertyAccess.ReadWrite { + + Object get(T owner); + + record ReadOnly(Function getter) implements PropertyAccess { + + @Override + public Object get(T owner) { + return getter.apply(owner); + } + } + + record ReadWrite(Function getter, BiConsumer setter) implements PropertyAccess { + + @Override + public Object get(T owner) { + return getter.apply(owner); + } + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptAction.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptAction.java new file mode 100644 index 0000000..aae1b76 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptAction.java @@ -0,0 +1,68 @@ +package com.terminalvelocitycabbage.engine.scripting.api; + +import com.terminalvelocitycabbage.engine.registry.Identifiable; +import com.terminalvelocitycabbage.engine.registry.Identifier; +import com.terminalvelocitycabbage.engine.scripting.api.syntax.SyntaxPattern; + +import java.util.List; +import java.util.function.Consumer; + +public record ScriptAction(Identifier identifier, List patterns, ScriptType returnType, + Consumer executor, String documentation) implements Identifiable { + + public static Builder builder(Identifier id) { + return new Builder(id); + } + + public static Builder builder(String namespace, String name) { + return new Builder(new Identifier(namespace, name)); + } + + public void invoke(ActionContext arguments) { + executor.accept(arguments); + } + + @Override + public Identifier getIdentifier() { + return identifier; + } + + public static final class Builder { + private final Identifier identifier; + private List patterns; + private ScriptType returnType; + private Consumer executor; + private String documentation = ""; + + private Builder(Identifier id) { + this.identifier = id; + } + + public Builder patterns(List patterns) { + this.patterns = patterns; + return this; + } + + public Builder returns(ScriptType returnType) { + this.returnType = returnType; + return this; + } + + public Builder exec(Consumer executor) { + this.executor = executor; + return this; + } + + public Builder doc(String documentation) { + this.documentation = documentation; + return this; + } + + public ScriptAction build() { + if (patterns == null || executor == null) { + throw new IllegalStateException("Action must have patterns and executor"); + } + return new ScriptAction(identifier, patterns, returnType, executor, documentation); + } + } +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptActionExecutor.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptActionExecutor.java new file mode 100644 index 0000000..bd35bcc --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptActionExecutor.java @@ -0,0 +1,7 @@ +package com.terminalvelocitycabbage.engine.scripting.api; + +@FunctionalInterface +public interface ScriptActionExecutor { + void execute(Object[] arguments); +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptConstant.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptConstant.java new file mode 100644 index 0000000..435451a --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptConstant.java @@ -0,0 +1,7 @@ +package com.terminalvelocitycabbage.engine.scripting.api; + +import com.terminalvelocitycabbage.engine.registry.Identifier; + +public record ScriptConstant(Identifier id, ScriptType type, Object value, String documentation) { + +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptEvent.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptEvent.java new file mode 100644 index 0000000..89b7aad --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptEvent.java @@ -0,0 +1,85 @@ +package com.terminalvelocitycabbage.engine.scripting.api; + +import com.terminalvelocitycabbage.engine.registry.Identifiable; +import com.terminalvelocitycabbage.engine.registry.Identifier; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; + +public record ScriptEvent(Identifier identifier, + Class eventClass, + Map exposedValues, + ScriptVisibility visibility, + String documentation) implements Identifiable { + + public static ScriptEventBuilder builder() { + return new ScriptEventBuilder<>(); + } + + public static final class ScriptEventBuilder { + + private Identifier id; + private Class eventClass; + private final Map values = new LinkedHashMap<>(); + private ScriptVisibility visibility = ScriptVisibility.PUBLIC; + private String documentation = ""; + + public ScriptEventBuilder id(Identifier id) { + this.id = id; + return this; + } + + public ScriptEventBuilder eventClass(Class eventClass) { + this.eventClass = eventClass; + return this; + } + + public ScriptEventBuilder exposedValue( + String name, + ScriptType type, + Function extractor + ) { + values.put( + name, + new ScriptEventValue( + name, + type, + (Function) extractor + ) + ); + return this; + } + + public ScriptEventBuilder visibility(ScriptVisibility visibility) { + this.visibility = visibility; + return this; + } + + public ScriptEventBuilder doc(String documentation) { + this.documentation = documentation; + return this; + } + + public ScriptEvent build() { + if (id == null) + throw new IllegalStateException("ScriptEvent id is required"); + if (eventClass == null) + throw new IllegalStateException("ScriptEvent eventClass is required"); + + return new ScriptEvent( + id, + eventClass, + Map.copyOf(values), + visibility, + documentation + ); + } + } + + + @Override + public Identifier getIdentifier() { + return identifier; + } +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptEventValue.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptEventValue.java new file mode 100644 index 0000000..5ddcfc8 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptEventValue.java @@ -0,0 +1,10 @@ +package com.terminalvelocitycabbage.engine.scripting.api; + +import java.util.function.Function; + +public record ScriptEventValue( + String name, + ScriptType type, + Function extractor +) {} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptProperty.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptProperty.java new file mode 100644 index 0000000..12a87b9 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptProperty.java @@ -0,0 +1,20 @@ +package com.terminalvelocitycabbage.engine.scripting.api; + +import com.terminalvelocitycabbage.engine.registry.Identifiable; +import com.terminalvelocitycabbage.engine.registry.Identifier; + +public record ScriptProperty ( + Identifier identifier, + ScriptType owner, + ScriptType valueType, + PropertyAccess access, + ScriptVisibility visibility, + String documentation +) implements Identifiable { + + @Override + public Identifier getIdentifier() { + return identifier; + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptType.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptType.java new file mode 100644 index 0000000..92c5a56 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptType.java @@ -0,0 +1,60 @@ +package com.terminalvelocitycabbage.engine.scripting.api; + +import com.terminalvelocitycabbage.engine.registry.Identifiable; +import com.terminalvelocitycabbage.engine.registry.Identifier; + +import java.util.Objects; + +public final class ScriptType implements Identifiable { + + private final Identifier identifier; + private final ScriptType parent; + + private ScriptType(String namespace, String name, ScriptType parent) { + this.identifier = new Identifier(namespace, name); + this.parent = parent; + } + + public static ScriptType of(String namespace, String name) { + return new ScriptType(namespace, name, null); + } + + public static ScriptType of(String namespace, String name, ScriptType parent) { + return new ScriptType(namespace, name, parent); + } + + @Override + public Identifier getIdentifier() { + return identifier; + } + + public ScriptType getParent() { + return parent; + } + + public boolean isAssignableFrom(ScriptType other) { + if (this.equals(other)) return true; + ScriptType current = other.parent; + while (current != null) { + if (this.equals(current)) return true; + current = current.parent; + } + return false; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof ScriptType other)) return false; + return identifier.equals(other.getIdentifier()); + } + + @Override + public int hashCode() { + return Objects.hash(identifier); + } + + @Override + public String toString() { + return identifier.toString(); + } +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptVisibility.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptVisibility.java new file mode 100644 index 0000000..90b6b1c --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptVisibility.java @@ -0,0 +1,9 @@ +package com.terminalvelocitycabbage.engine.scripting.api; + +public enum ScriptVisibility { + PRIVATE, //Script private - only this script has access to this function/const/etc. + MODULE, //Module private - only the scripts in the defining script's module have access. + MOD, //Mod private - only this mod has access to this function/const/etc. + PUBLIC //Anyone can access +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptActionRegistry.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptActionRegistry.java new file mode 100644 index 0000000..84063c6 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptActionRegistry.java @@ -0,0 +1,7 @@ +package com.terminalvelocitycabbage.engine.scripting.api.registry; + +import com.terminalvelocitycabbage.engine.registry.Registry; +import com.terminalvelocitycabbage.engine.scripting.api.ScriptAction; + +public class ScriptActionRegistry extends Registry { +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptConstantRegistry.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptConstantRegistry.java new file mode 100644 index 0000000..0297e21 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptConstantRegistry.java @@ -0,0 +1,7 @@ +package com.terminalvelocitycabbage.engine.scripting.api.registry; + +import com.terminalvelocitycabbage.engine.registry.Registry; +import com.terminalvelocitycabbage.engine.scripting.api.ScriptConstant; + +public class ScriptConstantRegistry extends Registry { +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptEventRegistry.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptEventRegistry.java new file mode 100644 index 0000000..8c1d8aa --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptEventRegistry.java @@ -0,0 +1,7 @@ +package com.terminalvelocitycabbage.engine.scripting.api.registry; + +import com.terminalvelocitycabbage.engine.registry.Registry; +import com.terminalvelocitycabbage.engine.scripting.api.ScriptEvent; + +public class ScriptEventRegistry extends Registry { +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptPropertyRegistry.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptPropertyRegistry.java new file mode 100644 index 0000000..4d4a18a --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptPropertyRegistry.java @@ -0,0 +1,7 @@ +package com.terminalvelocitycabbage.engine.scripting.api.registry; + +import com.terminalvelocitycabbage.engine.registry.Registry; +import com.terminalvelocitycabbage.engine.scripting.api.ScriptProperty; + +public class ScriptPropertyRegistry extends Registry> { +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptTypeRegistry.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptTypeRegistry.java new file mode 100644 index 0000000..4f8c4e0 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptTypeRegistry.java @@ -0,0 +1,8 @@ +package com.terminalvelocitycabbage.engine.scripting.api.registry; + +import com.terminalvelocitycabbage.engine.registry.Registry; +import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; + +public class ScriptTypeRegistry extends Registry { + +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/ArgumentElement.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/ArgumentElement.java new file mode 100644 index 0000000..426eb89 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/ArgumentElement.java @@ -0,0 +1,8 @@ +package com.terminalvelocitycabbage.engine.scripting.api.syntax; + +import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; + +public record ArgumentElement(String name, ScriptType type) implements SyntaxElement { + +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/LiteralElement.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/LiteralElement.java new file mode 100644 index 0000000..83128ca --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/LiteralElement.java @@ -0,0 +1,7 @@ +package com.terminalvelocitycabbage.engine.scripting.api.syntax; + +import java.util.Set; + +public record LiteralElement(Set literals) implements SyntaxElement { + +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/Syntax.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/Syntax.java new file mode 100644 index 0000000..832fc59 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/Syntax.java @@ -0,0 +1,19 @@ +package com.terminalvelocitycabbage.engine.scripting.api.syntax; + +import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; + +import java.util.Set; + +public final class Syntax { + + private Syntax() {} + + public static LiteralElement literal(String... literals) { + return new LiteralElement(Set.of(literals)); + } + + public static ArgumentElement argument(String name, ScriptType type) { + return new ArgumentElement(name, type); + } + +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxElement.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxElement.java new file mode 100644 index 0000000..48a60ed --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxElement.java @@ -0,0 +1,4 @@ +package com.terminalvelocitycabbage.engine.scripting.api.syntax; + +public sealed interface SyntaxElement permits ArgumentElement, LiteralElement { +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxPattern.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxPattern.java new file mode 100644 index 0000000..d6f9fad --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxPattern.java @@ -0,0 +1,11 @@ +package com.terminalvelocitycabbage.engine.scripting.api.syntax; + +import java.util.List; + +public record SyntaxPattern(List elements) { + + public SyntaxPattern(List elements) { + this.elements = List.copyOf(elements); + } + +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreActions.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreActions.java new file mode 100644 index 0000000..65afd15 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreActions.java @@ -0,0 +1,32 @@ +package com.terminalvelocitycabbage.engine.scripting.core; + +import com.terminalvelocitycabbage.engine.registry.Identifier; +import com.terminalvelocitycabbage.engine.scripting.api.ScriptAction; +import com.terminalvelocitycabbage.engine.scripting.api.registry.ScriptActionRegistry; +import com.terminalvelocitycabbage.engine.scripting.api.syntax.Syntax; +import com.terminalvelocitycabbage.engine.scripting.api.syntax.SyntaxPattern; + +import java.util.List; + +public final class CoreActions { + + public static void register(ScriptActionRegistry actions) { + actions.register( + ScriptAction.builder(new Identifier(CoreLibrary.CORE_NAMESPACE, "print")) + .patterns(List.of( + new SyntaxPattern(List.of( + Syntax.literal("print"), + Syntax.argument("value", CoreTypes.ANY) + )) + )) + .returns(CoreTypes.VOID) + .exec(ctx -> { + Object value = ctx.get("value"); + System.out.println(value); + }) + .doc("Prints a value to the console.") + .build() + ); + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreConstants.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreConstants.java new file mode 100644 index 0000000..8db596f --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreConstants.java @@ -0,0 +1,26 @@ +package com.terminalvelocitycabbage.engine.scripting.core; + +import com.terminalvelocitycabbage.engine.registry.Identifier; +import com.terminalvelocitycabbage.engine.scripting.api.ScriptConstant; +import com.terminalvelocitycabbage.engine.scripting.api.registry.ScriptConstantRegistry; + +public final class CoreConstants { + + public static void register(ScriptConstantRegistry constants) { + + constants.register(new ScriptConstant( + new Identifier(CoreLibrary.CORE_NAMESPACE, "true"), + CoreTypes.BOOLEAN, + Boolean.TRUE, + "Boolean true" + )); + + constants.register(new ScriptConstant( + new Identifier(CoreLibrary.CORE_NAMESPACE, "false"), + CoreTypes.BOOLEAN, + Boolean.FALSE, + "Boolean false" + )); + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreEvents.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreEvents.java new file mode 100644 index 0000000..ba30ae6 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreEvents.java @@ -0,0 +1,13 @@ +package com.terminalvelocitycabbage.engine.scripting.core; + +import com.terminalvelocitycabbage.engine.scripting.api.registry.ScriptEventRegistry; + +public final class CoreEvents { + + public static void register( + ScriptEventRegistry events + ) { + + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreLibrary.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreLibrary.java new file mode 100644 index 0000000..c3c3778 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreLibrary.java @@ -0,0 +1,25 @@ +package com.terminalvelocitycabbage.engine.scripting.core; + +import com.terminalvelocitycabbage.engine.scripting.api.registry.*; + +public final class CoreLibrary { + + public static final String CORE_NAMESPACE = "core"; + + private CoreLibrary() {} + + public static void registerAll( + ScriptTypeRegistry types, + ScriptActionRegistry actions, + ScriptPropertyRegistry properties, + ScriptEventRegistry events, + ScriptConstantRegistry constants + ) { + CoreTypes.register(types); + CoreActions.register(actions); + CoreProperties.register(properties); + CoreConstants.register(constants); + CoreEvents.register(events); + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreProperties.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreProperties.java new file mode 100644 index 0000000..ae727f7 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreProperties.java @@ -0,0 +1,36 @@ +package com.terminalvelocitycabbage.engine.scripting.core; + +import com.terminalvelocitycabbage.engine.registry.Identifier; +import com.terminalvelocitycabbage.engine.scripting.api.PropertyAccess; +import com.terminalvelocitycabbage.engine.scripting.api.ScriptProperty; +import com.terminalvelocitycabbage.engine.scripting.api.ScriptVisibility; +import com.terminalvelocitycabbage.engine.scripting.api.registry.ScriptPropertyRegistry; + +public final class CoreProperties { + + public static void register(ScriptPropertyRegistry properties) { + + properties.register( + new ScriptProperty<>( + new Identifier("core", "identifier.namespace"), + CoreTypes.IDENTIFIER, + CoreTypes.TEXT, + new PropertyAccess.ReadOnly<>(Identifier::getNamespace), + ScriptVisibility.PUBLIC, + "The namespace portion of the identifier." + ) + ); + + properties.register( + new ScriptProperty<>( + new Identifier("core", "identifier.name"), + CoreTypes.IDENTIFIER, + CoreTypes.TEXT, + new PropertyAccess.ReadOnly<>(Identifier::getName), + ScriptVisibility.PUBLIC, + "The name/path portion of the identifier." + ) + ); + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreTypes.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreTypes.java new file mode 100644 index 0000000..4b5318a --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreTypes.java @@ -0,0 +1,36 @@ +package com.terminalvelocitycabbage.engine.scripting.core; + +import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; +import com.terminalvelocitycabbage.engine.scripting.api.registry.ScriptTypeRegistry; + +public final class CoreTypes { + + public static ScriptType ANY; + public static ScriptType VOID; + + public static ScriptType NUMBER; + public static ScriptType INTEGER; + public static ScriptType BOOLEAN; + public static ScriptType TEXT; + + public static ScriptType IDENTIFIER; + + public static ScriptType LIST; + public static ScriptType MAP; + + public static void register(ScriptTypeRegistry registry) { + ANY = registry.register(ScriptType.of(CoreLibrary.CORE_NAMESPACE, "any")).getElement(); + VOID = registry.register(ScriptType.of(CoreLibrary.CORE_NAMESPACE, "void")).getElement(); + + NUMBER = registry.register(ScriptType.of(CoreLibrary.CORE_NAMESPACE, "number", ANY)).getElement(); + INTEGER = registry.register(ScriptType.of(CoreLibrary.CORE_NAMESPACE, "integer", NUMBER)).getElement(); + BOOLEAN = registry.register(ScriptType.of(CoreLibrary.CORE_NAMESPACE, "boolean", ANY)).getElement(); + TEXT = registry.register(ScriptType.of(CoreLibrary.CORE_NAMESPACE, "text", ANY)).getElement(); + + IDENTIFIER = registry.register(ScriptType.of(CoreLibrary.CORE_NAMESPACE, "identifier", ANY)).getElement(); + + LIST = registry.register(ScriptType.of(CoreLibrary.CORE_NAMESPACE, "list", ANY)).getElement(); + MAP = registry.register(ScriptType.of(CoreLibrary.CORE_NAMESPACE, "map", ANY)).getElement(); + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/CallActionInstruction.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/CallActionInstruction.java new file mode 100644 index 0000000..0b5e528 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/CallActionInstruction.java @@ -0,0 +1,24 @@ +package com.terminalvelocitycabbage.engine.scripting.parser; + +import com.terminalvelocitycabbage.engine.scripting.api.ScriptAction; + +public final class CallActionInstruction implements ScriptInstruction { + + private final ScriptAction action; + private final int[] argumentSlots; + + public CallActionInstruction(ScriptAction action, int[] argumentSlots) { + this.action = action; + this.argumentSlots = argumentSlots; + } + + @Override + public void execute(ExecutionContext context) { + Object[] args = new Object[argumentSlots.length]; + for (int i = 0; i < argumentSlots.length; i++) { + args[i] = context.getLocal(argumentSlots[i]); + } + action.invoke(args); + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ExecutionContext.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ExecutionContext.java new file mode 100644 index 0000000..3311823 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ExecutionContext.java @@ -0,0 +1,19 @@ +package com.terminalvelocitycabbage.engine.scripting.parser; + +public final class ExecutionContext { + + private final Object[] locals; + + public ExecutionContext(Object[] locals) { + this.locals = locals; + } + + public Object getLocal(int index) { + return locals[index]; + } + + public void setLocal(int index, Object value) { + locals[index] = value; + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/LoadEventValueInstruction.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/LoadEventValueInstruction.java new file mode 100644 index 0000000..c8b2674 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/LoadEventValueInstruction.java @@ -0,0 +1,22 @@ +package com.terminalvelocitycabbage.engine.scripting.parser; + +import com.terminalvelocitycabbage.engine.scripting.api.ScriptEventValue; + +public final class LoadEventValueInstruction implements ScriptInstruction { + + private final int targetSlot; + private final ScriptEventValue value; + + public LoadEventValueInstruction(int targetSlot, ScriptEventValue value) { + this.targetSlot = targetSlot; + this.value = value; + } + + @Override + public void execute(ExecutionContext context) { + Object event = context.getLocal(0); // slot 0 = event instance + Object extracted = value.extract(event); + context.setLocal(targetSlot, extracted); + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/LoadPropertyInstruction.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/LoadPropertyInstruction.java new file mode 100644 index 0000000..923561b --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/LoadPropertyInstruction.java @@ -0,0 +1,28 @@ +package com.terminalvelocitycabbage.engine.scripting.parser; + +import com.terminalvelocitycabbage.engine.scripting.api.ScriptProperty; + +public final class LoadPropertyInstruction implements ScriptInstruction { + + private final int sourceSlot; + private final int targetSlot; + private final ScriptProperty property; + + public LoadPropertyInstruction( + int sourceSlot, + int targetSlot, + ScriptProperty property + ) { + this.sourceSlot = sourceSlot; + this.targetSlot = targetSlot; + this.property = property; + } + + @Override + public void execute(ExecutionContext context) { + Object source = context.getLocal(sourceSlot); + Object value = property.get(source); + context.setLocal(targetSlot, value); + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ScriptInstruction.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ScriptInstruction.java new file mode 100644 index 0000000..59e0a68 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ScriptInstruction.java @@ -0,0 +1,7 @@ +package com.terminalvelocitycabbage.engine.scripting.parser; + +public sealed interface ScriptInstruction permits LoadEventValueInstruction, LoadPropertyInstruction, CallActionInstruction { + + void execute(ExecutionContext context); +} + diff --git a/src/test/java/com/terminalvelocitycabbage/test/ecs/ScriptsTest.java b/src/test/java/com/terminalvelocitycabbage/test/ecs/ScriptsTest.java new file mode 100644 index 0000000..e0283be --- /dev/null +++ b/src/test/java/com/terminalvelocitycabbage/test/ecs/ScriptsTest.java @@ -0,0 +1,120 @@ +package com.terminalvelocitycabbage.test.ecs; + +import com.terminalvelocitycabbage.engine.TerminalVelocityEngine; +import com.terminalvelocitycabbage.engine.event.Event; +import com.terminalvelocitycabbage.engine.registry.Identifier; +import com.terminalvelocitycabbage.engine.scripting.api.*; +import com.terminalvelocitycabbage.engine.scripting.api.registry.*; +import com.terminalvelocitycabbage.engine.scripting.core.CoreLibrary; +import com.terminalvelocitycabbage.engine.scripting.core.CoreTypes; +import com.terminalvelocitycabbage.engine.scripting.parser.*; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class ScriptsTest { + + public final class Game { + + private final String name; + + public Game(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + public final class GameStartEvent extends Event { + + public static final Identifier EVENT = TerminalVelocityEngine.identifierOf("game_started"); + + private final Game game; + + public GameStartEvent(Game game) { + super(EVENT); + this.game = game; + } + + public Game getGame() { + return game; + } + } + + @Test + void testHelloWorldScript() { + + ScriptTypeRegistry scriptTypeRegistry = new ScriptTypeRegistry(); + ScriptActionRegistry scriptActionRegistry = new ScriptActionRegistry(); + ScriptPropertyRegistry scriptPropertyRegistry = new ScriptPropertyRegistry(); + ScriptEventRegistry scriptEventRegistry = new ScriptEventRegistry(); + ScriptConstantRegistry scriptConstantRegistry = new ScriptConstantRegistry(); + + CoreLibrary.registerAll( + scriptTypeRegistry, + scriptActionRegistry, + scriptPropertyRegistry, + scriptEventRegistry, + scriptConstantRegistry + ); + + ScriptType GAME_TYPE = scriptTypeRegistry.register(ScriptType.of(CoreLibrary.CORE_NAMESPACE, "game")).getElement(); + + scriptEventRegistry.register( + ScriptEvent.builder() + .id(new Identifier("test", "game_start")) + .eventClass(GameStartEvent.class) + .exposedValue("game", GAME_TYPE, GameStartEvent::getGame) + .doc("Fires when the game has fully started.") + .build() + ); + + scriptPropertyRegistry.register( + new ScriptProperty<>( + new Identifier("test", "game.name"), + GAME_TYPE, + CoreTypes.TEXT, + new PropertyAccess.ReadOnly<>(Game::getName), + ScriptVisibility.PUBLIC, + "The name/path portion of the identifier." + ) + ); + + String scriptText = + """ + on game_started: + print(game.name) + """; + + //TEMP + + List instructions = List.of( + // Load event.game → slot 1 + new LoadEventValueInstruction( + 1, + scriptEventRegistry.get(new Identifier("test", "game_start")).exposedValues().get("game") + ), + + // Load game.name → slot 2 + new LoadPropertyInstruction( + 1, + 2, + scriptPropertyRegistry.get(new Identifier("test", "game.name")) + ), + + // print(slot 2) + new CallActionInstruction(scriptActionRegistry.get(new Identifier(CoreLibrary.CORE_NAMESPACE, "print")), new int[]{2}) + ); + + + ExecutionContext context = new ExecutionContext(new Object[3]); + context.setLocal(0, new GameStartEvent(new Game("test name"))); + + for (ScriptInstruction instruction : instructions) { + instruction.execute(context); + } + + } +} From 884cef90d0289cb0cae075ed49a54556349939d2 Mon Sep 17 00:00:00 2001 From: Brandon Davis Date: Thu, 1 Jan 2026 14:38:44 -0600 Subject: [PATCH 2/5] It compiles now - next to generate script instructions instead of it being manual --- .../engine/scripting/api/ActionContext.java | 46 +++++++++- .../engine/scripting/api/PropertyAccess.java | 34 ++++++-- .../engine/scripting/api/ScriptAction.java | 4 + .../engine/scripting/api/ScriptConstant.java | 7 +- .../engine/scripting/api/ScriptEvent.java | 19 ++-- .../scripting/api/ScriptEventValue.java | 35 ++++++-- .../engine/scripting/api/ScriptProperty.java | 38 ++++++-- .../api/registry/ScriptPropertyRegistry.java | 2 +- .../parser/CallActionInstruction.java | 29 +++++-- .../scripting/parser/ExecutionContext.java | 19 ++-- .../parser/LoadEventValueInstruction.java | 11 ++- .../parser/LoadPropertyInstruction.java | 18 ++-- .../test/ecs/ScriptsTest.java | 87 ++++++++++++------- 13 files changed, 252 insertions(+), 97 deletions(-) diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ActionContext.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ActionContext.java index 60dc154..b0604a1 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ActionContext.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ActionContext.java @@ -1,11 +1,49 @@ package com.terminalvelocitycabbage.engine.scripting.api; +import com.terminalvelocitycabbage.engine.scripting.parser.ExecutionContext; + import java.util.Map; -public record ActionContext(Map arguments) { +public final class ActionContext { + + private final ExecutionContext execution; + private final int[] argumentSlots; + private final Map nameToIndex; + + public ActionContext( + ExecutionContext execution, + int[] argumentSlots, + Map nameToIndex + ) { + this.execution = execution; + this.argumentSlots = argumentSlots; + this.nameToIndex = nameToIndex; + } - @SuppressWarnings("unchecked") - public T get(String name) { - return (T) arguments.get(name); + public Object get(int index) { + return execution.getLocal(argumentSlots[index]); + } + + public Object get(String name) { + Integer index = nameToIndex.get(name); + if (index == null) { + throw new RuntimeException("Unknown argument: " + name); + } + return get(index); + } + + public boolean isPresent(String name) { + return nameToIndex.containsKey(name); + } + + public Object getEvent() { + return execution.getEvent(); + } + + public void setResult(Object value, int resultSlot) { + if (resultSlot >= 0) { + execution.setLocal(resultSlot, value); + } } } + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/PropertyAccess.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/PropertyAccess.java index 3a1a223..b9549d4 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/PropertyAccess.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/PropertyAccess.java @@ -3,24 +3,42 @@ import java.util.function.BiConsumer; import java.util.function.Function; -public sealed interface PropertyAccess permits PropertyAccess.ReadOnly, PropertyAccess.ReadWrite { +public sealed interface PropertyAccess + permits PropertyAccess.ReadOnly, PropertyAccess.ReadWrite { - Object get(T owner); + V get(O instance); - record ReadOnly(Function getter) implements PropertyAccess { + final class ReadOnly implements PropertyAccess { + + private final Function getter; + + public ReadOnly(Function getter) { + this.getter = getter; + } @Override - public Object get(T owner) { - return getter.apply(owner); + public V get(O instance) { + return getter.apply(instance); } } - record ReadWrite(Function getter, BiConsumer setter) implements PropertyAccess { + final class ReadWrite implements PropertyAccess { + + private final Function getter; + private final BiConsumer setter; + + public ReadWrite(Function getter, BiConsumer setter) { + this.getter = getter; + this.setter = setter; + } @Override - public Object get(T owner) { - return getter.apply(owner); + public V get(O instance) { + return getter.apply(instance); } + + // setter omitted for now } } + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptAction.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptAction.java index aae1b76..2a41422 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptAction.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptAction.java @@ -10,6 +10,10 @@ public record ScriptAction(Identifier identifier, List patterns, ScriptType returnType, Consumer executor, String documentation) implements Identifiable { + public void execute(ActionContext context) { + executor.accept(context); + } + public static Builder builder(Identifier id) { return new Builder(id); } diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptConstant.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptConstant.java index 435451a..81ca0a3 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptConstant.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptConstant.java @@ -1,7 +1,12 @@ package com.terminalvelocitycabbage.engine.scripting.api; +import com.terminalvelocitycabbage.engine.registry.Identifiable; import com.terminalvelocitycabbage.engine.registry.Identifier; -public record ScriptConstant(Identifier id, ScriptType type, Object value, String documentation) { +public record ScriptConstant(Identifier identifier, ScriptType type, Object value, String documentation) implements Identifiable { + @Override + public Identifier getIdentifier() { + return identifier; + } } diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptEvent.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptEvent.java index 89b7aad..9577aea 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptEvent.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptEvent.java @@ -7,11 +7,11 @@ import java.util.Map; import java.util.function.Function; -public record ScriptEvent(Identifier identifier, - Class eventClass, - Map exposedValues, - ScriptVisibility visibility, - String documentation) implements Identifiable { +public record ScriptEvent( + Identifier identifier, + Class eventClass, + Map> exposedValues, + String documentation) implements Identifiable { public static ScriptEventBuilder builder() { return new ScriptEventBuilder<>(); @@ -22,7 +22,6 @@ public static final class ScriptEventBuilder { private Identifier id; private Class eventClass; private final Map values = new LinkedHashMap<>(); - private ScriptVisibility visibility = ScriptVisibility.PUBLIC; private String documentation = ""; public ScriptEventBuilder id(Identifier id) { @@ -51,11 +50,6 @@ public ScriptEventBuilder exposedValue( return this; } - public ScriptEventBuilder visibility(ScriptVisibility visibility) { - this.visibility = visibility; - return this; - } - public ScriptEventBuilder doc(String documentation) { this.documentation = documentation; return this; @@ -63,7 +57,7 @@ public ScriptEventBuilder doc(String documentation) { public ScriptEvent build() { if (id == null) - throw new IllegalStateException("ScriptEvent id is required"); + throw new IllegalStateException("ScriptEvent identifier is required"); if (eventClass == null) throw new IllegalStateException("ScriptEvent eventClass is required"); @@ -71,7 +65,6 @@ public ScriptEvent build() { id, eventClass, Map.copyOf(values), - visibility, documentation ); } diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptEventValue.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptEventValue.java index 5ddcfc8..2e93ba7 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptEventValue.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptEventValue.java @@ -2,9 +2,34 @@ import java.util.function.Function; -public record ScriptEventValue( - String name, - ScriptType type, - Function extractor -) {} +public final class ScriptEventValue { + + private final String name; + private final ScriptType type; + private final Function extractor; + + public ScriptEventValue( + String name, + ScriptType type, + Function extractor + ) { + this.name = name; + this.type = type; + this.extractor = extractor; + } + + @SuppressWarnings("unchecked") + public Object extract(Object eventInstance) { + return extractor.apply((E) eventInstance); + } + + public ScriptType getType() { + return type; + } + + public String getName() { + return name; + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptProperty.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptProperty.java index 12a87b9..d253c53 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptProperty.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptProperty.java @@ -3,14 +3,35 @@ import com.terminalvelocitycabbage.engine.registry.Identifiable; import com.terminalvelocitycabbage.engine.registry.Identifier; -public record ScriptProperty ( - Identifier identifier, - ScriptType owner, - ScriptType valueType, - PropertyAccess access, - ScriptVisibility visibility, - String documentation -) implements Identifiable { +public final class ScriptProperty implements Identifiable { + + private final Identifier identifier; + private final ScriptType ownerType; + private final ScriptType valueType; + private final PropertyAccess access; + private final ScriptVisibility visibility; + private final String documentation; + + public ScriptProperty( + Identifier id, + ScriptType ownerType, + ScriptType valueType, + PropertyAccess access, + ScriptVisibility visibility, + String documentation + ) { + this.identifier = id; + this.ownerType = ownerType; + this.valueType = valueType; + this.access = access; + this.visibility = visibility; + this.documentation = documentation; + } + + @SuppressWarnings("unchecked") + public Object get(Object instance) { + return access.get((O) instance); + } @Override public Identifier getIdentifier() { @@ -18,3 +39,4 @@ public Identifier getIdentifier() { } } + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptPropertyRegistry.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptPropertyRegistry.java index 4d4a18a..0f4eee0 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptPropertyRegistry.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptPropertyRegistry.java @@ -3,5 +3,5 @@ import com.terminalvelocitycabbage.engine.registry.Registry; import com.terminalvelocitycabbage.engine.scripting.api.ScriptProperty; -public class ScriptPropertyRegistry extends Registry> { +public class ScriptPropertyRegistry extends Registry> { } diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/CallActionInstruction.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/CallActionInstruction.java index 0b5e528..834b4b7 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/CallActionInstruction.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/CallActionInstruction.java @@ -1,24 +1,39 @@ package com.terminalvelocitycabbage.engine.scripting.parser; +import com.terminalvelocitycabbage.engine.scripting.api.ActionContext; import com.terminalvelocitycabbage.engine.scripting.api.ScriptAction; +import java.util.Map; + public final class CallActionInstruction implements ScriptInstruction { private final ScriptAction action; private final int[] argumentSlots; + private final Map argumentNameToIndex; + private final int resultSlot; // -1 for void - public CallActionInstruction(ScriptAction action, int[] argumentSlots) { + public CallActionInstruction( + ScriptAction action, + int[] argumentSlots, + Map argumentNameToIndex, + int resultSlot + ) { this.action = action; this.argumentSlots = argumentSlots; + this.argumentNameToIndex = argumentNameToIndex; + this.resultSlot = resultSlot; } @Override - public void execute(ExecutionContext context) { - Object[] args = new Object[argumentSlots.length]; - for (int i = 0; i < argumentSlots.length; i++) { - args[i] = context.getLocal(argumentSlots[i]); - } - action.invoke(args); + public void execute(ExecutionContext executionContext) { + + ActionContext actionContext = new ActionContext( + executionContext, + argumentSlots, + argumentNameToIndex + ); + + action.execute(actionContext); } } diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ExecutionContext.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ExecutionContext.java index 3311823..8397214 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ExecutionContext.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ExecutionContext.java @@ -2,18 +2,25 @@ public final class ExecutionContext { + private final Object event; private final Object[] locals; - public ExecutionContext(Object[] locals) { - this.locals = locals; + public ExecutionContext(Object event, int localCount) { + this.event = event; + this.locals = new Object[localCount]; } - public Object getLocal(int index) { - return locals[index]; + public Object getEvent() { + return event; } - public void setLocal(int index, Object value) { - locals[index] = value; + public Object getLocal(int slot) { + return locals[slot]; + } + + public void setLocal(int slot, Object value) { + locals[slot] = value; } } + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/LoadEventValueInstruction.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/LoadEventValueInstruction.java index c8b2674..1268179 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/LoadEventValueInstruction.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/LoadEventValueInstruction.java @@ -4,17 +4,20 @@ public final class LoadEventValueInstruction implements ScriptInstruction { + private final ScriptEventValue value; private final int targetSlot; - private final ScriptEventValue value; - public LoadEventValueInstruction(int targetSlot, ScriptEventValue value) { - this.targetSlot = targetSlot; + public LoadEventValueInstruction( + ScriptEventValue value, + int targetSlot + ) { this.value = value; + this.targetSlot = targetSlot; } @Override public void execute(ExecutionContext context) { - Object event = context.getLocal(0); // slot 0 = event instance + Object event = context.getEvent(); Object extracted = value.extract(event); context.setLocal(targetSlot, extracted); } diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/LoadPropertyInstruction.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/LoadPropertyInstruction.java index 923561b..9b43cc1 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/LoadPropertyInstruction.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/LoadPropertyInstruction.java @@ -4,24 +4,20 @@ public final class LoadPropertyInstruction implements ScriptInstruction { - private final int sourceSlot; + private final ScriptProperty property; + private final int ownerSlot; private final int targetSlot; - private final ScriptProperty property; - public LoadPropertyInstruction( - int sourceSlot, - int targetSlot, - ScriptProperty property - ) { - this.sourceSlot = sourceSlot; - this.targetSlot = targetSlot; + public LoadPropertyInstruction(ScriptProperty property, int ownerSlot, int targetSlot) { this.property = property; + this.ownerSlot = ownerSlot; + this.targetSlot = targetSlot; } @Override public void execute(ExecutionContext context) { - Object source = context.getLocal(sourceSlot); - Object value = property.get(source); + Object owner = context.getLocal(ownerSlot); + Object value = property.get(owner); context.setLocal(targetSlot, value); } } diff --git a/src/test/java/com/terminalvelocitycabbage/test/ecs/ScriptsTest.java b/src/test/java/com/terminalvelocitycabbage/test/ecs/ScriptsTest.java index e0283be..cd9b546 100644 --- a/src/test/java/com/terminalvelocitycabbage/test/ecs/ScriptsTest.java +++ b/src/test/java/com/terminalvelocitycabbage/test/ecs/ScriptsTest.java @@ -11,6 +11,7 @@ import org.junit.jupiter.api.Test; import java.util.List; +import java.util.Map; public class ScriptsTest { @@ -29,7 +30,8 @@ public String getName() { public final class GameStartEvent extends Event { - public static final Identifier EVENT = TerminalVelocityEngine.identifierOf("game_started"); + public static final Identifier EVENT = + TerminalVelocityEngine.identifierOf("game_started"); private final Game game; @@ -46,6 +48,7 @@ public Game getGame() { @Test void testHelloWorldScript() { + // --- registries --- ScriptTypeRegistry scriptTypeRegistry = new ScriptTypeRegistry(); ScriptActionRegistry scriptActionRegistry = new ScriptActionRegistry(); ScriptPropertyRegistry scriptPropertyRegistry = new ScriptPropertyRegistry(); @@ -60,61 +63,87 @@ void testHelloWorldScript() { scriptConstantRegistry ); - ScriptType GAME_TYPE = scriptTypeRegistry.register(ScriptType.of(CoreLibrary.CORE_NAMESPACE, "game")).getElement(); + // --- types --- + ScriptType GAME_TYPE = + scriptTypeRegistry + .register(ScriptType.of(CoreLibrary.CORE_NAMESPACE, "game")) + .getElement(); - scriptEventRegistry.register( + // --- event --- + ScriptEvent gameStartEvent = ScriptEvent.builder() .id(new Identifier("test", "game_start")) .eventClass(GameStartEvent.class) .exposedValue("game", GAME_TYPE, GameStartEvent::getGame) .doc("Fires when the game has fully started.") - .build() - ); + .build(); + + scriptEventRegistry.register(gameStartEvent); - scriptPropertyRegistry.register( + // --- property --- + ScriptProperty gameNameProperty = new ScriptProperty<>( new Identifier("test", "game.name"), GAME_TYPE, CoreTypes.TEXT, new PropertyAccess.ReadOnly<>(Game::getName), ScriptVisibility.PUBLIC, - "The name/path portion of the identifier." - ) - ); - - String scriptText = - """ - on game_started: - print(game.name) - """; - - //TEMP + "The game name." + ); + + scriptPropertyRegistry.register(gameNameProperty); + + // --- actions --- + ScriptAction printAction = + scriptActionRegistry.get( + new Identifier(CoreLibrary.CORE_NAMESPACE, "print") + ); + + /* + * Script: + * + * on game_started: + * print(game.name) + * + * Slot layout: + * 0 -> game + * 1 -> game.name + */ List instructions = List.of( - // Load event.game → slot 1 + + // load event.game -> slot 0 new LoadEventValueInstruction( - 1, - scriptEventRegistry.get(new Identifier("test", "game_start")).exposedValues().get("game") + gameStartEvent.exposedValues().get("game"), + 0 ), - // Load game.name → slot 2 + // load game.name -> slot 1 new LoadPropertyInstruction( - 1, - 2, - scriptPropertyRegistry.get(new Identifier("test", "game.name")) + gameNameProperty, + 0, + 1 ), - // print(slot 2) - new CallActionInstruction(scriptActionRegistry.get(new Identifier(CoreLibrary.CORE_NAMESPACE, "print")), new int[]{2}) + // print(value = slot 1) + new CallActionInstruction( + printAction, + new int[]{1}, // argument slots + Map.of("value", 0), // name -> argument index + -1 // void + ) ); + // --- execution --- + GameStartEvent event = + new GameStartEvent(new Game("test name")); - ExecutionContext context = new ExecutionContext(new Object[3]); - context.setLocal(0, new GameStartEvent(new Game("test name"))); + ExecutionContext context = + new ExecutionContext(event, 2); for (ScriptInstruction instruction : instructions) { instruction.execute(context); } - } } + From 9954aedb6b61944e6ea37d7d1b449de69493e048 Mon Sep 17 00:00:00 2001 From: Brandon Davis Date: Thu, 1 Jan 2026 16:12:22 -0600 Subject: [PATCH 3/5] Start on some intermediate representation stuff --- .../scripting/parser/data/ActionCommand.java | 16 ++++ .../scripting/parser/data/EventBlock.java | 16 ++++ .../scripting/parser/data/EventValue.java | 13 +++ .../scripting/parser/data/LiteralValue.java | 11 +++ .../scripting/parser/data/PropertyValue.java | 13 +++ .../scripting/parser/data/ScriptBlock.java | 6 ++ .../scripting/parser/data/ScriptCommand.java | 4 + .../scripting/parser/data/ScriptFile.java | 7 ++ .../scripting/parser/data/ScriptValue.java | 4 + .../parser/data/intermediate/BlockKind.java | 8 ++ .../parser/data/intermediate/IRAction.java | 8 ++ .../parser/data/intermediate/IRArgument.java | 4 + .../parser/data/intermediate/IRBlock.java | 8 ++ .../data/intermediate/IREventValue.java | 10 +++ .../parser/data/intermediate/IRLiteral.java | 7 ++ .../parser/data/intermediate/IRNode.java | 6 ++ .../parser/data/intermediate/IRPrinter.java | 85 +++++++++++++++++++ .../parser/data/intermediate/IRProperty.java | 11 +++ .../parser/data/intermediate/IRValue.java | 9 ++ .../parser/data/intermediate/IRVariable.java | 10 +++ 20 files changed, 256 insertions(+) create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ActionCommand.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/EventBlock.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/EventValue.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/LiteralValue.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/PropertyValue.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptBlock.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptCommand.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptFile.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptValue.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/BlockKind.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRAction.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRArgument.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRBlock.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IREventValue.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRLiteral.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRNode.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRPrinter.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRProperty.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRValue.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRVariable.java diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ActionCommand.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ActionCommand.java new file mode 100644 index 0000000..dc13b41 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ActionCommand.java @@ -0,0 +1,16 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data; + +import com.terminalvelocitycabbage.engine.registry.Identifiable; + +import java.util.Map; + +public final class ActionCommand implements ScriptCommand { + + private final Identifiable actionIdentifier; + private final Map arguments; + + public ActionCommand(Identifiable actionIdentifier, Map arguments) { + this.actionIdentifier = actionIdentifier; + this.arguments = arguments; + } +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/EventBlock.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/EventBlock.java new file mode 100644 index 0000000..4035448 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/EventBlock.java @@ -0,0 +1,16 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data; + +import com.terminalvelocitycabbage.engine.registry.Identifier; + +import java.util.List; + +public final class EventBlock implements ScriptBlock { + + private final Identifier eventIdentifier; + private final List commands; + + public EventBlock(Identifier eventIdentifier, List commands) { + this.eventIdentifier = eventIdentifier; + this.commands = commands; + } +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/EventValue.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/EventValue.java new file mode 100644 index 0000000..6013c07 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/EventValue.java @@ -0,0 +1,13 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data; + +public final class EventValue implements ScriptValue { + + private final String name; // "game" + //TODO convert to identifier and guess what the namespace is if not explicit + + + public EventValue(String name) { + this.name = name; + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/LiteralValue.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/LiteralValue.java new file mode 100644 index 0000000..401f90f --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/LiteralValue.java @@ -0,0 +1,11 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data; + +public final class LiteralValue implements ScriptValue { + + private final Object value; // string, number, boolean + + public LiteralValue(Object value) { + this.value = value; + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/PropertyValue.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/PropertyValue.java new file mode 100644 index 0000000..955d2a0 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/PropertyValue.java @@ -0,0 +1,13 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data; + +import java.util.List; + +public final class PropertyValue implements ScriptValue { + + private final List path; // game.name → ["game", "name"] + + public PropertyValue(List path) { + this.path = path; + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptBlock.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptBlock.java new file mode 100644 index 0000000..48542cd --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptBlock.java @@ -0,0 +1,6 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data; + +//Represents a root level block of code in a script +public sealed interface ScriptBlock permits EventBlock { //TODO FunctionBlock, ConstBlock + +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptCommand.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptCommand.java new file mode 100644 index 0000000..58e5421 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptCommand.java @@ -0,0 +1,4 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data; + +public sealed interface ScriptCommand permits ActionCommand { +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptFile.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptFile.java new file mode 100644 index 0000000..9ec82ce --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptFile.java @@ -0,0 +1,7 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data; + +public final class ScriptFile { + + private final List blocks; + +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptValue.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptValue.java new file mode 100644 index 0000000..f9dbb59 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptValue.java @@ -0,0 +1,4 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data; + +public sealed interface ScriptValue permits PropertyValue, LiteralValue, EventValue { +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/BlockKind.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/BlockKind.java new file mode 100644 index 0000000..9ecd4b6 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/BlockKind.java @@ -0,0 +1,8 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate; + +public enum BlockKind { + EVENT, + FUNCTION, + CONTROL +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRAction.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRAction.java new file mode 100644 index 0000000..8660a65 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRAction.java @@ -0,0 +1,8 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate; + +import com.terminalvelocitycabbage.engine.registry.Identifier; +import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; + +public record IRAction(Identifier actionId, ScriptType returnType, List arguments) implements IRNode { +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRArgument.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRArgument.java new file mode 100644 index 0000000..cf1d3f4 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRArgument.java @@ -0,0 +1,4 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate; + +public record IRArgument (String name, IRValue value) { +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRBlock.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRBlock.java new file mode 100644 index 0000000..81930df --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRBlock.java @@ -0,0 +1,8 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate; + +import com.terminalvelocitycabbage.engine.registry.Identifier; + +import java.util.List; + +public record IRBlock(BlockKind kind, Identifier identifier, List body) implements IRNode { +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IREventValue.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IREventValue.java new file mode 100644 index 0000000..ea001c9 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IREventValue.java @@ -0,0 +1,10 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate; + +import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; + +public record IREventValue( + ScriptType type, + String name +) implements IRValue { +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRLiteral.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRLiteral.java new file mode 100644 index 0000000..aa346b9 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRLiteral.java @@ -0,0 +1,7 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate; + +import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; + +public record IRLiteral(ScriptType type, Object value) implements IRValue { +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRNode.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRNode.java new file mode 100644 index 0000000..9bb5eb9 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRNode.java @@ -0,0 +1,6 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate; + +public sealed interface IRNode + permits IRBlock, IRAction { +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRPrinter.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRPrinter.java new file mode 100644 index 0000000..d584e35 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRPrinter.java @@ -0,0 +1,85 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate; + +import com.terminalvelocitycabbage.engine.registry.Identifier; +import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; + +public final class IRPrinter { + + public static String print(IRBlock block) { + StringBuilder sb = new StringBuilder(); + printBlock(block, sb, 0); + return sb.toString(); + } + + private static void printBlock(IRBlock block, StringBuilder sb, int indent) { + indent(sb, indent); + sb.append("BLOCK ") + .append(block.kind()) + .append(" ") + .append(block.identifier()) + .append("\n"); + + for (IRNode node : block.body()) { + if (node instanceof IRAction action) { + printAction(action, sb, indent + 1); + } + } + } + + private static void printAction(IRAction action, StringBuilder sb, int indent) { + indent(sb, indent); + sb.append("ACTION ") + .append(action.actionId()) + .append(" -> ") + .append(action.returnType().getIdentifier()) + .append("\n"); + + for (IRArgument arg : action.arguments()) { + indent(sb, indent + 1); + sb.append("ARG ") + .append(arg.name()) + .append(" = "); + printValue(arg.value(), sb); + sb.append("\n"); + } + } + + private static void printValue(IRValue value, StringBuilder sb) { + switch (value) { + case IRLiteral(ScriptType type, Object value1) -> sb + .append("LITERAL<") + .append(type.getIdentifier()) + .append(">(") + .append(value1) + .append(")"); + case IRProperty(ScriptType type, Identifier propertyId, String accessPath) -> sb + .append("PROPERTY<") + .append(type.getIdentifier()) + .append(">(") + .append(accessPath) + .append(")"); + case IREventValue(ScriptType type, String name) -> sb + .append("EVENT<") + .append(type.getIdentifier()) + .append(">(") + .append(name) + .append(")"); + case IRVariable(ScriptType type, String name) -> sb + .append("VARIABLE<") + .append(type.getIdentifier()) + .append(">(") + .append(name) + .append(")"); + case null, default -> { + } + } + } + + + + private static void indent(StringBuilder sb, int level) { + sb.append(" ".repeat(level)); + } + +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRProperty.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRProperty.java new file mode 100644 index 0000000..235d749 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRProperty.java @@ -0,0 +1,11 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate; + +import com.terminalvelocitycabbage.engine.registry.Identifier; +import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; + +public record IRProperty( + ScriptType type, + Identifier propertyId, + String accessPath // ex. "game.name" +) implements IRValue { +} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRValue.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRValue.java new file mode 100644 index 0000000..87b4371 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRValue.java @@ -0,0 +1,9 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate; + +import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; + +public sealed interface IRValue permits IRLiteral, IRProperty, IREventValue, IRVariable { + + ScriptType type(); +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRVariable.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRVariable.java new file mode 100644 index 0000000..dc3123b --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRVariable.java @@ -0,0 +1,10 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate; + +import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; + +public record IRVariable( + ScriptType type, + String name +) implements IRValue { +} + From 9884f45bde3f49141b506f38d52fd352a22914c8 Mon Sep 17 00:00:00 2001 From: Brandon Davis Date: Thu, 1 Jan 2026 20:58:13 -0600 Subject: [PATCH 4/5] IR for header blocks --- .../engine/registry/Identifier.java | 5 + .../engine/registry/Registry.java | 26 +++++ .../scripting/parser/BlockHeaderParser.java | 15 +++ .../parser/BlockHeaderParserRegistry.java | 27 +++++ .../scripting/parser/ParsingContext.java | 27 +++++ .../scripting/parser/ScriptBlockParser.java | 67 ++++++++++++ .../scripting/parser/SentenceParser.java | 56 ++++++++++ .../scripting/parser/SentenceToIRAction.java | 57 ++++++++++ .../parser/core/EventBlockHeaderParser.java | 69 ++++++++++++ .../scripting/parser/data/ActionCommand.java | 16 --- .../scripting/parser/data/EventBlock.java | 16 --- .../scripting/parser/data/EventValue.java | 13 --- .../scripting/parser/data/LiteralValue.java | 11 -- .../scripting/parser/data/PropertyValue.java | 13 --- .../scripting/parser/data/ScriptBlock.java | 9 +- .../scripting/parser/data/ScriptCommand.java | 2 +- .../scripting/parser/data/ScriptFile.java | 5 + .../scripting/parser/data/ScriptLine.java | 7 ++ .../scripting/parser/data/ScriptValue.java | 4 - .../scripting/parser/data/SentenceNode.java | 10 ++ .../parser/data/intermediate/IRAction.java | 2 + .../parser/data/intermediate/IRPrinter.java | 2 - .../test/ecs/ScriptsTest.java | 101 ++++++++++++++++++ 23 files changed, 482 insertions(+), 78 deletions(-) create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/BlockHeaderParser.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/BlockHeaderParserRegistry.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ParsingContext.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ScriptBlockParser.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/SentenceParser.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/SentenceToIRAction.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/core/EventBlockHeaderParser.java delete mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ActionCommand.java delete mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/EventBlock.java delete mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/EventValue.java delete mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/LiteralValue.java delete mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/PropertyValue.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptLine.java delete mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptValue.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/SentenceNode.java diff --git a/src/main/java/com/terminalvelocitycabbage/engine/registry/Identifier.java b/src/main/java/com/terminalvelocitycabbage/engine/registry/Identifier.java index ff0a05e..3930078 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/registry/Identifier.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/registry/Identifier.java @@ -45,6 +45,11 @@ public String getName() { return name; } + public static boolean isValidIdentifierString(String identifier) { + if (!identifier.contains(":")) return false; + return identifier.split(":").length == 2; + } + @Override public String toString() { return namespace + ':' + name; diff --git a/src/main/java/com/terminalvelocitycabbage/engine/registry/Registry.java b/src/main/java/com/terminalvelocitycabbage/engine/registry/Registry.java index 5f7d846..e1a7674 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/registry/Registry.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/registry/Registry.java @@ -3,6 +3,8 @@ import com.terminalvelocitycabbage.engine.debug.Log; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Set; public class Registry { @@ -83,6 +85,30 @@ public T get(Identifier identifier) { return registryContents.get(identifier); } + /** + * @param identifier The key identifier that is requested + * @return a boolean of true if it exists in this registry or false if not + */ + public boolean contains(Identifier identifier) { + return registryContents.containsKey(identifier); + } + + public Set getIdentifiersWithNamespace(String namespace) { + Set identifiers = new LinkedHashSet<>(); + registryContents.keySet().forEach(identifier -> { + if (identifier.getNamespace().equals(namespace)) identifiers.add(identifier); + }); + return identifiers; + } + + public Set getIdentifiersWithName(String name) { + Set identifiers = new LinkedHashSet<>(); + registryContents.keySet().forEach(identifier -> { + if (identifier.getName().equals(name)) identifiers.add(identifier); + }); + return identifiers; + } + public LinkedHashMap getRegistryContents() { return registryContents; } diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/BlockHeaderParser.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/BlockHeaderParser.java new file mode 100644 index 0000000..0a56473 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/BlockHeaderParser.java @@ -0,0 +1,15 @@ +package com.terminalvelocitycabbage.engine.scripting.parser; + +import com.terminalvelocitycabbage.engine.scripting.parser.data.ScriptBlock; +import com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate.IRBlock; + +public interface BlockHeaderParser { + + boolean matches(String header); + + IRBlock parse( + ScriptBlock block, + ParsingContext context + ); +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/BlockHeaderParserRegistry.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/BlockHeaderParserRegistry.java new file mode 100644 index 0000000..06f47fb --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/BlockHeaderParserRegistry.java @@ -0,0 +1,27 @@ +package com.terminalvelocitycabbage.engine.scripting.parser; + +import com.terminalvelocitycabbage.engine.registry.Registry; +import com.terminalvelocitycabbage.engine.scripting.parser.data.ScriptBlock; +import com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate.IRBlock; + +public final class BlockHeaderParserRegistry extends Registry { + + public IRBlock parse( + ScriptBlock block, + ParsingContext context + ) { + for (BlockHeaderParser parser : registryContents.values()) { + if (parser.matches(block.headerLine())) { + return parser.parse(block, context); + } + } + + throw new RuntimeException( + "Unknown block header '" + + block.headerLine() + + "' at line " + + block.headerLineNumber() + ); + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ParsingContext.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ParsingContext.java new file mode 100644 index 0000000..d80d2e0 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ParsingContext.java @@ -0,0 +1,27 @@ +package com.terminalvelocitycabbage.engine.scripting.parser; + +import com.terminalvelocitycabbage.engine.scripting.api.registry.ScriptEventRegistry; +import com.terminalvelocitycabbage.engine.scripting.api.registry.ScriptTypeRegistry; + +public final class ParsingContext { + + private final ScriptEventRegistry eventRegistry; + private final ScriptTypeRegistry typeRegistry; + + public ParsingContext( + ScriptEventRegistry eventRegistry, + ScriptTypeRegistry typeRegistry + ) { + this.eventRegistry = eventRegistry; + this.typeRegistry = typeRegistry; + } + + public ScriptEventRegistry events() { + return eventRegistry; + } + + public ScriptTypeRegistry types() { + return typeRegistry; + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ScriptBlockParser.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ScriptBlockParser.java new file mode 100644 index 0000000..c8b3fee --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ScriptBlockParser.java @@ -0,0 +1,67 @@ +package com.terminalvelocitycabbage.engine.scripting.parser; + +import com.terminalvelocitycabbage.engine.scripting.parser.data.ScriptBlock; +import com.terminalvelocitycabbage.engine.scripting.parser.data.ScriptLine; + +import java.util.ArrayList; +import java.util.List; + +public final class ScriptBlockParser { + + public static List parse(String scriptText) { + + List blocks = new ArrayList<>(); + List lines = scriptText.lines().toList(); + + ScriptBlock currentBlock = null; + List currentBody = null; + int lineNumber = 0; + String rawLine = ""; + + for (int i = 0; i < lines.size(); i++) { + + rawLine = lines.get(i); + lineNumber = i + 1; + + if (rawLine.isBlank()) continue; + + int indent = countLeadingSpaces(rawLine); + String trimmed = rawLine.trim(); + + if (indent == 0) { + // New block header + if (currentBlock != null) { + blocks.add(currentBlock); + } + + currentBody = new ArrayList<>(); + currentBlock = new ScriptBlock( + trimmed, + currentBody, + lineNumber + ); + } else { + if (currentBlock == null) { + throw new RuntimeException("Indented line without a block header at line " + lineNumber); + } + + currentBody.add(new ScriptLine(trimmed, lineNumber)); + } + } + + if (currentBlock != null) { + blocks.add(currentBlock); + } + + return blocks; + } + + private static int countLeadingSpaces(String line) { + int count = 0; + while (count < line.length() && line.charAt(count) == ' ') { + count++; + } + return count; + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/SentenceParser.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/SentenceParser.java new file mode 100644 index 0000000..78e7881 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/SentenceParser.java @@ -0,0 +1,56 @@ +package com.terminalvelocitycabbage.engine.scripting.parser; + +import com.terminalvelocitycabbage.engine.scripting.parser.data.ScriptLine; +import com.terminalvelocitycabbage.engine.scripting.parser.data.SentenceNode; + +import java.util.ArrayList; +import java.util.List; + +public final class SentenceParser { + + public static SentenceNode parse(ScriptLine line) { + List tokens = tokenize(line.text()); + + if (tokens.isEmpty()) { + throw new RuntimeException("Empty sentence at line " + line.lineNumber()); + } + + String verb = tokens.get(0); + List args = tokens.subList(1, tokens.size()); + + return new SentenceNode(verb, args, line.lineNumber()); + } + + private static List tokenize(String text) { + List tokens = new ArrayList<>(); + StringBuilder current = new StringBuilder(); + boolean inQuotes = false; + + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + + if (c == '"') { + inQuotes = !inQuotes; + current.append(c); + } else if (c == ' ' && !inQuotes) { + if (!current.isEmpty()) { + tokens.add(current.toString()); + current.setLength(0); + } + } else { + current.append(c); + } + } + + if (!current.isEmpty()) { + tokens.add(current.toString()); + } + + if (inQuotes) { + throw new RuntimeException("Unterminated string literal: " + text); + } + + return tokens; + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/SentenceToIRAction.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/SentenceToIRAction.java new file mode 100644 index 0000000..e7992e1 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/SentenceToIRAction.java @@ -0,0 +1,57 @@ +package com.terminalvelocitycabbage.engine.scripting.parser; + +import com.terminalvelocitycabbage.engine.registry.Identifier; +import com.terminalvelocitycabbage.engine.scripting.core.CoreLibrary; +import com.terminalvelocitycabbage.engine.scripting.core.CoreTypes; +import com.terminalvelocitycabbage.engine.scripting.parser.data.SentenceNode; +import com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate.*; + +import java.util.List; + +public final class SentenceToIRAction { + + public static IRAction parse( + SentenceNode sentence, + ParsingContext context + ) { + if (sentence.verb().equals("print")) { + + if (sentence.arguments().size() != 1) { + throw new RuntimeException( + "print expects exactly 1 argument at line " + + sentence.lineNumber() + ); + } + + String rawArg = sentence.arguments().get(0); + + IRValue value; + + if (rawArg.startsWith("\"")) { + value = new IRLiteral( + CoreTypes.TEXT, + rawArg.substring(1, rawArg.length() - 1) + ); + } else { + // TODO property access for now + value = new IRProperty( + CoreTypes.TEXT, + Identifier.of("test:game.name"), + rawArg + ); + } + + return new IRAction( + new Identifier(CoreLibrary.CORE_NAMESPACE, "print"), + CoreTypes.VOID, + List.of(new IRArgument("value", value)) + ); + } + + throw new RuntimeException( + "Unknown action '" + sentence.verb() + + "' at line " + sentence.lineNumber() + ); + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/core/EventBlockHeaderParser.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/core/EventBlockHeaderParser.java new file mode 100644 index 0000000..5099cae --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/core/EventBlockHeaderParser.java @@ -0,0 +1,69 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.core; + +import com.terminalvelocitycabbage.engine.registry.Identifier; +import com.terminalvelocitycabbage.engine.scripting.parser.BlockHeaderParser; +import com.terminalvelocitycabbage.engine.scripting.parser.ParsingContext; +import com.terminalvelocitycabbage.engine.scripting.parser.data.ScriptBlock; +import com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate.BlockKind; +import com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate.IRBlock; + +import java.util.ArrayList; + +public final class EventBlockHeaderParser implements BlockHeaderParser { + + @Override + public boolean matches(String header) { + return header.startsWith("on ") || header.startsWith("when "); + } + + @Override + public IRBlock parse(ScriptBlock block, ParsingContext context) { + + String header = block.headerLine(); + + // Remove trailing colon if present + if (header.endsWith(":")) { + header = header.substring(0, header.length() - 1); + } + + String[] parts = header.split("\\s+", 2); + if (parts.length != 2) { + throw new RuntimeException( + "Invalid event block header at line " + + block.headerLineNumber() + ); + } + + String eventName = parts[1]; + Identifier eventId; + + if (Identifier.isValidIdentifierString(eventName)) { + eventId = Identifier.of(eventName); + } else { + + var events = context.events().getIdentifiersWithName(eventName); + + if (events.size() > 1) { + throw new RuntimeException( + "Ambiguous event name '" + eventName + "' at line " + block.headerLineNumber() + ); + } + + eventId = events.iterator().next(); + + if (!context.events().contains(eventId)) { + throw new RuntimeException( + "Unknown event '" + eventName + + "' at line " + block.headerLineNumber() + ); + } + } + + return new IRBlock( + BlockKind.EVENT, + eventId, + new ArrayList<>() // body filled later + ); + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ActionCommand.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ActionCommand.java deleted file mode 100644 index dc13b41..0000000 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ActionCommand.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.terminalvelocitycabbage.engine.scripting.parser.data; - -import com.terminalvelocitycabbage.engine.registry.Identifiable; - -import java.util.Map; - -public final class ActionCommand implements ScriptCommand { - - private final Identifiable actionIdentifier; - private final Map arguments; - - public ActionCommand(Identifiable actionIdentifier, Map arguments) { - this.actionIdentifier = actionIdentifier; - this.arguments = arguments; - } -} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/EventBlock.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/EventBlock.java deleted file mode 100644 index 4035448..0000000 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/EventBlock.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.terminalvelocitycabbage.engine.scripting.parser.data; - -import com.terminalvelocitycabbage.engine.registry.Identifier; - -import java.util.List; - -public final class EventBlock implements ScriptBlock { - - private final Identifier eventIdentifier; - private final List commands; - - public EventBlock(Identifier eventIdentifier, List commands) { - this.eventIdentifier = eventIdentifier; - this.commands = commands; - } -} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/EventValue.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/EventValue.java deleted file mode 100644 index 6013c07..0000000 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/EventValue.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.terminalvelocitycabbage.engine.scripting.parser.data; - -public final class EventValue implements ScriptValue { - - private final String name; // "game" - //TODO convert to identifier and guess what the namespace is if not explicit - - - public EventValue(String name) { - this.name = name; - } -} - diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/LiteralValue.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/LiteralValue.java deleted file mode 100644 index 401f90f..0000000 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/LiteralValue.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.terminalvelocitycabbage.engine.scripting.parser.data; - -public final class LiteralValue implements ScriptValue { - - private final Object value; // string, number, boolean - - public LiteralValue(Object value) { - this.value = value; - } -} - diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/PropertyValue.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/PropertyValue.java deleted file mode 100644 index 955d2a0..0000000 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/PropertyValue.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.terminalvelocitycabbage.engine.scripting.parser.data; - -import java.util.List; - -public final class PropertyValue implements ScriptValue { - - private final List path; // game.name → ["game", "name"] - - public PropertyValue(List path) { - this.path = path; - } -} - diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptBlock.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptBlock.java index 48542cd..5a02002 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptBlock.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptBlock.java @@ -1,6 +1,11 @@ package com.terminalvelocitycabbage.engine.scripting.parser.data; +import java.util.List; + //Represents a root level block of code in a script -public sealed interface ScriptBlock permits EventBlock { //TODO FunctionBlock, ConstBlock +public record ScriptBlock( + String headerLine, + List body, + int headerLineNumber +) {} -} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptCommand.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptCommand.java index 58e5421..46b1576 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptCommand.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptCommand.java @@ -1,4 +1,4 @@ package com.terminalvelocitycabbage.engine.scripting.parser.data; -public sealed interface ScriptCommand permits ActionCommand { +public interface ScriptCommand { } diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptFile.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptFile.java index 9ec82ce..428af5d 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptFile.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptFile.java @@ -1,7 +1,12 @@ package com.terminalvelocitycabbage.engine.scripting.parser.data; +import java.util.List; + public final class ScriptFile { private final List blocks; + public ScriptFile(List blocks) { + this.blocks = blocks; + } } diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptLine.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptLine.java new file mode 100644 index 0000000..a5069d5 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptLine.java @@ -0,0 +1,7 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data; + +public record ScriptLine( + String text, + int lineNumber +) {} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptValue.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptValue.java deleted file mode 100644 index f9dbb59..0000000 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptValue.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.terminalvelocitycabbage.engine.scripting.parser.data; - -public sealed interface ScriptValue permits PropertyValue, LiteralValue, EventValue { -} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/SentenceNode.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/SentenceNode.java new file mode 100644 index 0000000..b0048f8 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/SentenceNode.java @@ -0,0 +1,10 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data; + +import java.util.List; + +public record SentenceNode( + String verb, + List arguments, + int lineNumber +) {} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRAction.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRAction.java index 8660a65..8056120 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRAction.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRAction.java @@ -3,6 +3,8 @@ import com.terminalvelocitycabbage.engine.registry.Identifier; import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; +import java.util.List; + public record IRAction(Identifier actionId, ScriptType returnType, List arguments) implements IRNode { } diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRPrinter.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRPrinter.java index d584e35..5939bcd 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRPrinter.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRPrinter.java @@ -75,8 +75,6 @@ case IRVariable(ScriptType type, String name) -> sb } } - - private static void indent(StringBuilder sb, int level) { sb.append(" ".repeat(level)); } diff --git a/src/test/java/com/terminalvelocitycabbage/test/ecs/ScriptsTest.java b/src/test/java/com/terminalvelocitycabbage/test/ecs/ScriptsTest.java index cd9b546..aa8f742 100644 --- a/src/test/java/com/terminalvelocitycabbage/test/ecs/ScriptsTest.java +++ b/src/test/java/com/terminalvelocitycabbage/test/ecs/ScriptsTest.java @@ -8,8 +8,16 @@ import com.terminalvelocitycabbage.engine.scripting.core.CoreLibrary; import com.terminalvelocitycabbage.engine.scripting.core.CoreTypes; import com.terminalvelocitycabbage.engine.scripting.parser.*; +import com.terminalvelocitycabbage.engine.scripting.parser.core.EventBlockHeaderParser; +import com.terminalvelocitycabbage.engine.scripting.parser.data.ScriptBlock; +import com.terminalvelocitycabbage.engine.scripting.parser.data.ScriptLine; +import com.terminalvelocitycabbage.engine.scripting.parser.data.SentenceNode; +import com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate.IRAction; +import com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate.IRBlock; +import com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate.IRPrinter; import org.junit.jupiter.api.Test; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -45,6 +53,99 @@ public Game getGame() { } } + @Test + void testIRPrintScript() { + + // --- registries --- + ScriptTypeRegistry scriptTypeRegistry = new ScriptTypeRegistry(); + ScriptActionRegistry scriptActionRegistry = new ScriptActionRegistry(); + ScriptPropertyRegistry scriptPropertyRegistry = new ScriptPropertyRegistry(); + ScriptEventRegistry scriptEventRegistry = new ScriptEventRegistry(); + ScriptConstantRegistry scriptConstantRegistry = new ScriptConstantRegistry(); + + CoreLibrary.registerAll( + scriptTypeRegistry, + scriptActionRegistry, + scriptPropertyRegistry, + scriptEventRegistry, + scriptConstantRegistry + ); + + // --- types --- + ScriptType GAME_TYPE = + scriptTypeRegistry + .register(ScriptType.of(CoreLibrary.CORE_NAMESPACE, "game")) + .getElement(); + + // --- event --- + ScriptEvent gameStartEvent = + ScriptEvent.builder() + .id(new Identifier("test", "game_start")) + .eventClass(GameStartEvent.class) + .exposedValue("game", GAME_TYPE, GameStartEvent::getGame) + .doc("Fires when the game has fully started.") + .build(); + + scriptEventRegistry.register(gameStartEvent); + + // --- property --- + ScriptProperty gameNameProperty = + new ScriptProperty<>( + new Identifier("test", "game.name"), + GAME_TYPE, + CoreTypes.TEXT, + new PropertyAccess.ReadOnly<>(Game::getName), + ScriptVisibility.PUBLIC, + "The game name." + ); + + scriptPropertyRegistry.register(gameNameProperty); + + // --- actions --- + ScriptAction printAction = + scriptActionRegistry.get( + new Identifier(CoreLibrary.CORE_NAMESPACE, "print") + ); + + String scriptText = """ + on game_start: + print game.name + """; + + BlockHeaderParserRegistry headerParsers = new BlockHeaderParserRegistry(); + + headerParsers.register(new Identifier("test", "event_parser"), new EventBlockHeaderParser()); + + List blocks = ScriptBlockParser.parse(scriptText); + + ParsingContext context = new ParsingContext(scriptEventRegistry, scriptTypeRegistry); + + List irBlocks = new ArrayList<>(); + + for (ScriptBlock block : blocks) { + + IRBlock irBlock = + headerParsers.parse(block, context); + + for (ScriptLine line : block.body()) { + SentenceNode sentence = + SentenceParser.parse(line); + + IRAction action = + SentenceToIRAction.parse(sentence, context); + + irBlock.body().add(action); + } + + irBlocks.add(irBlock); + } + + for (IRBlock ir : irBlocks) { + System.out.println(IRPrinter.print(ir)); + } + + } + @Test void testHelloWorldScript() { From e13ac36180696d57e83c00a4cce8e1948101de10 Mon Sep 17 00:00:00 2001 From: Brandon Davis Date: Fri, 2 Jan 2026 15:00:32 -0600 Subject: [PATCH 5/5] A bunch of other stuff that is broken - not probably going to finish this --- .../engine/registry/Registry.java | 8 ++ .../engine/scripting/api/ActionContext.java | 43 +++------- ...ctionExecutor.java => ActionExecutor.java} | 4 +- .../engine/scripting/api/ScriptAction.java | 84 +++++++------------ .../engine/scripting/api/ScriptProperty.java | 32 ++----- .../api/registry/ScriptPropertyRegistry.java | 40 +++++++++ .../scripting/api/syntax/LiteralElement.java | 7 -- .../engine/scripting/api/syntax/Syntax.java | 10 +-- ...gumentElement.java => SyntaxArgument.java} | 7 +- .../scripting/api/syntax/SyntaxElement.java | 4 - .../scripting/api/syntax/SyntaxLiteral.java | 4 + .../scripting/api/syntax/SyntaxPart.java | 6 ++ .../scripting/api/syntax/SyntaxPattern.java | 18 +++- .../engine/scripting/core/CoreActions.java | 39 +++++---- .../parser/CallActionInstruction.java | 34 ++++---- .../scripting/parser/ExecutionContext.java | 30 +++---- .../scripting/parser/IRValueEvaluator.java | 38 +++++++++ .../scripting/parser/IRValueResolver.java | 32 +++++++ .../scripting/parser/LiteralParser.java | 23 +++++ .../scripting/parser/MatchedSyntax.java | 9 ++ .../scripting/parser/ParsingContext.java | 27 ++---- .../scripting/parser/SentenceParser.java | 44 +--------- .../scripting/parser/SentenceToIRAction.java | 67 +++++++-------- .../scripting/parser/SyntaxMatcher.java | 53 ++++++++++++ .../scripting/parser/data/SentenceNode.java | 10 +-- .../parser/data/intermediate/IRAction.java | 4 +- .../{IRLiteral.java => IRLiteralValue.java} | 2 +- .../parser/data/intermediate/IRPrinter.java | 4 +- .../parser/data/intermediate/IRProperty.java | 11 --- .../data/intermediate/IRPropertyValue.java | 19 +++++ .../parser/data/intermediate/IRValue.java | 2 +- .../test/ecs/ScriptsTest.java | 12 ++- 32 files changed, 424 insertions(+), 303 deletions(-) rename src/main/java/com/terminalvelocitycabbage/engine/scripting/api/{ScriptActionExecutor.java => ActionExecutor.java} (51%) delete mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/LiteralElement.java rename src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/{ArgumentElement.java => SyntaxArgument.java} (57%) delete mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxElement.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxLiteral.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxPart.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/IRValueEvaluator.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/IRValueResolver.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/LiteralParser.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/MatchedSyntax.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/SyntaxMatcher.java rename src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/{IRLiteral.java => IRLiteralValue.java} (65%) delete mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRProperty.java create mode 100644 src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRPropertyValue.java diff --git a/src/main/java/com/terminalvelocitycabbage/engine/registry/Registry.java b/src/main/java/com/terminalvelocitycabbage/engine/registry/Registry.java index e1a7674..20b53f7 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/registry/Registry.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/registry/Registry.java @@ -93,6 +93,10 @@ public boolean contains(Identifier identifier) { return registryContents.containsKey(identifier); } + /** + * @param namespace the namespace (portion before the : in an identifier) to search this registry for + * @return All identifiers in this registry with that namespace + */ public Set getIdentifiersWithNamespace(String namespace) { Set identifiers = new LinkedHashSet<>(); registryContents.keySet().forEach(identifier -> { @@ -101,6 +105,10 @@ public Set getIdentifiersWithNamespace(String namespace) { return identifiers; } + /** + * @param name the name (portion after the : in an identifier) to search this registry for + * @return All identifiers in this registry with that name + */ public Set getIdentifiersWithName(String name) { Set identifiers = new LinkedHashSet<>(); registryContents.keySet().forEach(identifier -> { diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ActionContext.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ActionContext.java index b0604a1..9b272d6 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ActionContext.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ActionContext.java @@ -6,44 +6,25 @@ public final class ActionContext { - private final ExecutionContext execution; - private final int[] argumentSlots; - private final Map nameToIndex; + private final Map arguments; + private final ExecutionContext executionContext; public ActionContext( - ExecutionContext execution, - int[] argumentSlots, - Map nameToIndex + Map arguments, + ExecutionContext executionContext ) { - this.execution = execution; - this.argumentSlots = argumentSlots; - this.nameToIndex = nameToIndex; + this.arguments = arguments; + this.executionContext = executionContext; } - public Object get(int index) { - return execution.getLocal(argumentSlots[index]); + @SuppressWarnings("unchecked") + public T get(String name) { + return (T) arguments.get(name); } - public Object get(String name) { - Integer index = nameToIndex.get(name); - if (index == null) { - throw new RuntimeException("Unknown argument: " + name); - } - return get(index); - } - - public boolean isPresent(String name) { - return nameToIndex.containsKey(name); - } - - public Object getEvent() { - return execution.getEvent(); - } - - public void setResult(Object value, int resultSlot) { - if (resultSlot >= 0) { - execution.setLocal(resultSlot, value); - } + public ExecutionContext execution() { + return executionContext; } } + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptActionExecutor.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ActionExecutor.java similarity index 51% rename from src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptActionExecutor.java rename to src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ActionExecutor.java index bd35bcc..df91d60 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptActionExecutor.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ActionExecutor.java @@ -1,7 +1,7 @@ package com.terminalvelocitycabbage.engine.scripting.api; @FunctionalInterface -public interface ScriptActionExecutor { - void execute(Object[] arguments); +public interface ActionExecutor { + void execute(ActionContext context); } diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptAction.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptAction.java index 2a41422..9328dda 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptAction.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptAction.java @@ -1,72 +1,46 @@ package com.terminalvelocitycabbage.engine.scripting.api; -import com.terminalvelocitycabbage.engine.registry.Identifiable; import com.terminalvelocitycabbage.engine.registry.Identifier; import com.terminalvelocitycabbage.engine.scripting.api.syntax.SyntaxPattern; import java.util.List; -import java.util.function.Consumer; -public record ScriptAction(Identifier identifier, List patterns, ScriptType returnType, - Consumer executor, String documentation) implements Identifiable { - - public void execute(ActionContext context) { - executor.accept(context); - } - - public static Builder builder(Identifier id) { - return new Builder(id); +public final class ScriptAction { + + private final Identifier id; + private final List patterns; + private final ScriptType returnType; + private final ActionExecutor executor; + private final String documentation; + + public ScriptAction( + Identifier id, + List patterns, + ScriptType returnType, + ActionExecutor executor, + String documentation + ) { + this.id = id; + this.patterns = patterns; + this.returnType = returnType; + this.executor = executor; + this.documentation = documentation; } - public static Builder builder(String namespace, String name) { - return new Builder(new Identifier(namespace, name)); + public Identifier id() { + return id; } - public void invoke(ActionContext arguments) { - executor.accept(arguments); + public List patterns() { + return patterns; } - @Override - public Identifier getIdentifier() { - return identifier; + public ScriptType returnType() { + return returnType; } - public static final class Builder { - private final Identifier identifier; - private List patterns; - private ScriptType returnType; - private Consumer executor; - private String documentation = ""; - - private Builder(Identifier id) { - this.identifier = id; - } - - public Builder patterns(List patterns) { - this.patterns = patterns; - return this; - } - - public Builder returns(ScriptType returnType) { - this.returnType = returnType; - return this; - } - - public Builder exec(Consumer executor) { - this.executor = executor; - return this; - } - - public Builder doc(String documentation) { - this.documentation = documentation; - return this; - } - - public ScriptAction build() { - if (patterns == null || executor == null) { - throw new IllegalStateException("Action must have patterns and executor"); - } - return new ScriptAction(identifier, patterns, returnType, executor, documentation); - } + public ActionExecutor executor() { + return executor; } } + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptProperty.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptProperty.java index d253c53..c902a0f 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptProperty.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptProperty.java @@ -3,30 +3,14 @@ import com.terminalvelocitycabbage.engine.registry.Identifiable; import com.terminalvelocitycabbage.engine.registry.Identifier; -public final class ScriptProperty implements Identifiable { - - private final Identifier identifier; - private final ScriptType ownerType; - private final ScriptType valueType; - private final PropertyAccess access; - private final ScriptVisibility visibility; - private final String documentation; - - public ScriptProperty( - Identifier id, - ScriptType ownerType, - ScriptType valueType, - PropertyAccess access, - ScriptVisibility visibility, - String documentation - ) { - this.identifier = id; - this.ownerType = ownerType; - this.valueType = valueType; - this.access = access; - this.visibility = visibility; - this.documentation = documentation; - } +public record ScriptProperty( + Identifier identifier, + ScriptType ownerType, + ScriptType valueType, + PropertyAccess access, + ScriptVisibility visibility, + String documentation +) implements Identifiable { @SuppressWarnings("unchecked") public Object get(Object instance) { diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptPropertyRegistry.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptPropertyRegistry.java index 0f4eee0..67cd3fa 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptPropertyRegistry.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptPropertyRegistry.java @@ -1,7 +1,47 @@ package com.terminalvelocitycabbage.engine.scripting.api.registry; +import com.terminalvelocitycabbage.engine.registry.Identifier; import com.terminalvelocitycabbage.engine.registry.Registry; import com.terminalvelocitycabbage.engine.scripting.api.ScriptProperty; +import java.util.List; + public class ScriptPropertyRegistry extends Registry> { + + public ScriptProperty resolve(String raw) { + + // 1. Fully-qualified identifier + if (Identifier.isValidIdentifierString(raw)) { + Identifier id = Identifier.of(raw); + + ScriptProperty property = get(id); + if (property == null) { + throw new RuntimeException("Unknown property '" + raw + "'"); + } + + return property; + } + + // 2. Unqualified name → search by name + List> matches = + getRegistryContents().values().stream() + .filter(p -> p.getIdentifier().getName().equals(raw)) + .toList(); + + if (matches.isEmpty()) { + throw new RuntimeException("Unknown property '" + raw + "'"); + } + + if (matches.size() > 1) { + throw new RuntimeException( + "Ambiguous property '" + raw + "': " + + matches.stream() + .map(p -> p.getIdentifier().toString()) + .toList() + ); + } + + return matches.get(0); + } + } diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/LiteralElement.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/LiteralElement.java deleted file mode 100644 index 83128ca..0000000 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/LiteralElement.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.terminalvelocitycabbage.engine.scripting.api.syntax; - -import java.util.Set; - -public record LiteralElement(Set literals) implements SyntaxElement { - -} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/Syntax.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/Syntax.java index 832fc59..2539135 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/Syntax.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/Syntax.java @@ -2,18 +2,16 @@ import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; -import java.util.Set; - public final class Syntax { private Syntax() {} - public static LiteralElement literal(String... literals) { - return new LiteralElement(Set.of(literals)); + public static SyntaxLiteral literal(String literal) { + return new SyntaxLiteral(literal); } - public static ArgumentElement argument(String name, ScriptType type) { - return new ArgumentElement(name, type); + public static SyntaxArgument argument(String name, ScriptType type) { + return new SyntaxArgument(name, type); } } diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/ArgumentElement.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxArgument.java similarity index 57% rename from src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/ArgumentElement.java rename to src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxArgument.java index 426eb89..ca28a97 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/ArgumentElement.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxArgument.java @@ -2,7 +2,8 @@ import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; -public record ArgumentElement(String name, ScriptType type) implements SyntaxElement { - -} +public record SyntaxArgument( + String name, + ScriptType type +) implements SyntaxPart {} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxElement.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxElement.java deleted file mode 100644 index 48a60ed..0000000 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxElement.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.terminalvelocitycabbage.engine.scripting.api.syntax; - -public sealed interface SyntaxElement permits ArgumentElement, LiteralElement { -} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxLiteral.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxLiteral.java new file mode 100644 index 0000000..8c19035 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxLiteral.java @@ -0,0 +1,4 @@ +package com.terminalvelocitycabbage.engine.scripting.api.syntax; + +public record SyntaxLiteral(String text) implements SyntaxPart {} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxPart.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxPart.java new file mode 100644 index 0000000..bdb7f4a --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxPart.java @@ -0,0 +1,6 @@ +package com.terminalvelocitycabbage.engine.scripting.api.syntax; + +public sealed interface SyntaxPart permits SyntaxLiteral, SyntaxArgument { + +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxPattern.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxPattern.java index d6f9fad..88b834e 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxPattern.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxPattern.java @@ -2,10 +2,22 @@ import java.util.List; -public record SyntaxPattern(List elements) { +public final class SyntaxPattern { - public SyntaxPattern(List elements) { - this.elements = List.copyOf(elements); + private final List parts; + + public SyntaxPattern(List parts) { + this.parts = parts; + } + + public List parts() { + return parts; } + public static SyntaxPattern of(SyntaxPart... parts) { + return new SyntaxPattern(List.of(parts)); + } } + + + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreActions.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreActions.java index 65afd15..40014a4 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreActions.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreActions.java @@ -3,30 +3,35 @@ import com.terminalvelocitycabbage.engine.registry.Identifier; import com.terminalvelocitycabbage.engine.scripting.api.ScriptAction; import com.terminalvelocitycabbage.engine.scripting.api.registry.ScriptActionRegistry; -import com.terminalvelocitycabbage.engine.scripting.api.syntax.Syntax; +import com.terminalvelocitycabbage.engine.scripting.api.syntax.SyntaxArgument; +import com.terminalvelocitycabbage.engine.scripting.api.syntax.SyntaxLiteral; import com.terminalvelocitycabbage.engine.scripting.api.syntax.SyntaxPattern; import java.util.List; public final class CoreActions { - public static void register(ScriptActionRegistry actions) { - actions.register( - ScriptAction.builder(new Identifier(CoreLibrary.CORE_NAMESPACE, "print")) - .patterns(List.of( - new SyntaxPattern(List.of( - Syntax.literal("print"), - Syntax.argument("value", CoreTypes.ANY) - )) - )) - .returns(CoreTypes.VOID) - .exec(ctx -> { - Object value = ctx.get("value"); + public static void register(ScriptActionRegistry registry) { + + ScriptAction print = + new ScriptAction( + new Identifier(CoreLibrary.CORE_NAMESPACE, "print"), + List.of( + SyntaxPattern.of( + new SyntaxLiteral("print"), + new SyntaxArgument("value", CoreTypes.TEXT) + ) + ), + CoreTypes.VOID, + context -> { + String value = context.get("value"); System.out.println(value); - }) - .doc("Prints a value to the console.") - .build() - ); + }, + "Prints text to the console." + ); + + registry.register(print); } } + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/CallActionInstruction.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/CallActionInstruction.java index 834b4b7..dba0ecc 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/CallActionInstruction.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/CallActionInstruction.java @@ -2,38 +2,40 @@ import com.terminalvelocitycabbage.engine.scripting.api.ActionContext; import com.terminalvelocitycabbage.engine.scripting.api.ScriptAction; +import com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate.IRValue; +import java.util.HashMap; import java.util.Map; public final class CallActionInstruction implements ScriptInstruction { private final ScriptAction action; - private final int[] argumentSlots; - private final Map argumentNameToIndex; - private final int resultSlot; // -1 for void + private final Map arguments; public CallActionInstruction( ScriptAction action, - int[] argumentSlots, - Map argumentNameToIndex, - int resultSlot + Map arguments ) { this.action = action; - this.argumentSlots = argumentSlots; - this.argumentNameToIndex = argumentNameToIndex; - this.resultSlot = resultSlot; + this.arguments = arguments; } @Override - public void execute(ExecutionContext executionContext) { + public void execute(ExecutionContext context) { - ActionContext actionContext = new ActionContext( - executionContext, - argumentSlots, - argumentNameToIndex - ); + Map evaluatedArgs = new HashMap<>(); - action.execute(actionContext); + for (Map.Entry entry : arguments.entrySet()) { + Object value = + IRValueEvaluator.evaluate(entry.getValue(), context); + evaluatedArgs.put(entry.getKey(), value); + } + + ActionContext actionContext = + new ActionContext(evaluatedArgs, context); + + action.executor().execute(actionContext); } } + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ExecutionContext.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ExecutionContext.java index 8397214..6efc33d 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ExecutionContext.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ExecutionContext.java @@ -1,26 +1,28 @@ package com.terminalvelocitycabbage.engine.scripting.parser; -public final class ExecutionContext { +import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; - private final Object event; - private final Object[] locals; +import java.util.HashMap; +import java.util.Map; - public ExecutionContext(Object event, int localCount) { - this.event = event; - this.locals = new Object[localCount]; - } +public final class ExecutionContext { - public Object getEvent() { - return event; - } + private final Map scopedValues = new HashMap<>(); - public Object getLocal(int slot) { - return locals[slot]; + public void setScopeValue(ScriptType type, Object value) { + scopedValues.put(type, value); } - public void setLocal(int slot, Object value) { - locals[slot] = value; + public Object getCurrentScopeValue(ScriptType type) { + Object value = scopedValues.get(type); + if (value == null) { + throw new RuntimeException( + "No value in scope for type " + type.getIdentifier() + ); + } + return value; } } + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/IRValueEvaluator.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/IRValueEvaluator.java new file mode 100644 index 0000000..6dd02a2 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/IRValueEvaluator.java @@ -0,0 +1,38 @@ +package com.terminalvelocitycabbage.engine.scripting.parser; + +import com.terminalvelocitycabbage.engine.scripting.api.ScriptProperty; +import com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate.IRLiteralValue; +import com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate.IRPropertyValue; +import com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate.IRValue; + +public final class IRValueEvaluator { + + public static Object evaluate( + IRValue value, + ExecutionContext context + ) { + if (value instanceof IRLiteralValue literal) { + return literal.value(); + } + + if (value instanceof IRPropertyValue propertyValue) { + return evaluateProperty(propertyValue, context); + } + + throw new IllegalStateException( + "Unknown IRValue: " + value.getClass() + ); + } + + private static Object evaluateProperty( + IRPropertyValue propertyValue, + ExecutionContext context + ) { + ScriptProperty property = propertyValue.property(); + + Object base = context.getCurrentScopeValue(property.ownerType()); + + return property.access().get(base); + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/IRValueResolver.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/IRValueResolver.java new file mode 100644 index 0000000..b11d152 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/IRValueResolver.java @@ -0,0 +1,32 @@ +package com.terminalvelocitycabbage.engine.scripting.parser; + +import com.terminalvelocitycabbage.engine.scripting.api.ScriptProperty; +import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; +import com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate.IRLiteralValue; +import com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate.IRPropertyValue; +import com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate.IRValue; + +import java.util.Optional; + +public final class IRValueResolver { + + public static IRValue resolve( + String token, + ScriptType expectedType, + ParsingContext context + ) { + // property? + Optional> property = Optional.ofNullable(context.properties().resolve(token)); + + if (property.isPresent()) { + return new IRPropertyValue(property.get()); + } + + // literal + Object literal = + LiteralParser.parse(token, expectedType); + + return new IRLiteralValue(expectedType, literal); + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/LiteralParser.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/LiteralParser.java new file mode 100644 index 0000000..a64ce60 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/LiteralParser.java @@ -0,0 +1,23 @@ +package com.terminalvelocitycabbage.engine.scripting.parser; + +import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; +import com.terminalvelocitycabbage.engine.scripting.core.CoreTypes; + +public final class LiteralParser { + + public static Object parse(String token, ScriptType type) { + + if (type.equals(CoreTypes.TEXT)) { + return token.replace("\"", ""); + } + + if (type.equals(CoreTypes.NUMBER)) { + return Double.parseDouble(token); + } + + throw new RuntimeException( + "Cannot parse literal '" + token + "' as " + type.getIdentifier() + ); + } +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/MatchedSyntax.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/MatchedSyntax.java new file mode 100644 index 0000000..812e64b --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/MatchedSyntax.java @@ -0,0 +1,9 @@ +package com.terminalvelocitycabbage.engine.scripting.parser; + +import com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate.IRValue; + +import java.util.Map; + +public record MatchedSyntax(Map arguments) { +} + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ParsingContext.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ParsingContext.java index d80d2e0..1f81f55 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ParsingContext.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ParsingContext.java @@ -1,27 +1,12 @@ package com.terminalvelocitycabbage.engine.scripting.parser; -import com.terminalvelocitycabbage.engine.scripting.api.registry.ScriptEventRegistry; -import com.terminalvelocitycabbage.engine.scripting.api.registry.ScriptTypeRegistry; +import com.terminalvelocitycabbage.engine.scripting.api.registry.*; -public final class ParsingContext { +public record ParsingContext( + ScriptActionRegistry actions, + ScriptPropertyRegistry properties, + ScriptEventRegistry events, + ScriptTypeRegistry types) { - private final ScriptEventRegistry eventRegistry; - private final ScriptTypeRegistry typeRegistry; - - public ParsingContext( - ScriptEventRegistry eventRegistry, - ScriptTypeRegistry typeRegistry - ) { - this.eventRegistry = eventRegistry; - this.typeRegistry = typeRegistry; - } - - public ScriptEventRegistry events() { - return eventRegistry; - } - - public ScriptTypeRegistry types() { - return typeRegistry; - } } diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/SentenceParser.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/SentenceParser.java index 78e7881..29bca26 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/SentenceParser.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/SentenceParser.java @@ -3,54 +3,18 @@ import com.terminalvelocitycabbage.engine.scripting.parser.data.ScriptLine; import com.terminalvelocitycabbage.engine.scripting.parser.data.SentenceNode; -import java.util.ArrayList; import java.util.List; public final class SentenceParser { public static SentenceNode parse(ScriptLine line) { - List tokens = tokenize(line.text()); - if (tokens.isEmpty()) { - throw new RuntimeException("Empty sentence at line " + line.lineNumber()); - } + String raw = line.text().trim(); - String verb = tokens.get(0); - List args = tokens.subList(1, tokens.size()); + List tokens = + List.of(raw.split("\\s+")); - return new SentenceNode(verb, args, line.lineNumber()); - } - - private static List tokenize(String text) { - List tokens = new ArrayList<>(); - StringBuilder current = new StringBuilder(); - boolean inQuotes = false; - - for (int i = 0; i < text.length(); i++) { - char c = text.charAt(i); - - if (c == '"') { - inQuotes = !inQuotes; - current.append(c); - } else if (c == ' ' && !inQuotes) { - if (!current.isEmpty()) { - tokens.add(current.toString()); - current.setLength(0); - } - } else { - current.append(c); - } - } - - if (!current.isEmpty()) { - tokens.add(current.toString()); - } - - if (inQuotes) { - throw new RuntimeException("Unterminated string literal: " + text); - } - - return tokens; + return new SentenceNode(raw, tokens, line.lineNumber()); } } diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/SentenceToIRAction.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/SentenceToIRAction.java index e7992e1..6c42804 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/SentenceToIRAction.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/SentenceToIRAction.java @@ -1,11 +1,11 @@ package com.terminalvelocitycabbage.engine.scripting.parser; -import com.terminalvelocitycabbage.engine.registry.Identifier; -import com.terminalvelocitycabbage.engine.scripting.core.CoreLibrary; -import com.terminalvelocitycabbage.engine.scripting.core.CoreTypes; +import com.terminalvelocitycabbage.engine.scripting.api.ScriptAction; +import com.terminalvelocitycabbage.engine.scripting.api.syntax.SyntaxPattern; import com.terminalvelocitycabbage.engine.scripting.parser.data.SentenceNode; -import com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate.*; +import com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate.IRAction; +import java.util.ArrayList; import java.util.List; public final class SentenceToIRAction { @@ -14,44 +14,39 @@ public static IRAction parse( SentenceNode sentence, ParsingContext context ) { - if (sentence.verb().equals("print")) { - - if (sentence.arguments().size() != 1) { - throw new RuntimeException( - "print expects exactly 1 argument at line " - + sentence.lineNumber() - ); + List matches = new ArrayList<>(); + + for (ScriptAction action : context.actions().getRegistryContents().values()) { + for (SyntaxPattern pattern : action.patterns()) { + + SyntaxMatcher.match(pattern, sentence, context) + .ifPresent(match -> { + matches.add( + new IRAction( + action.id(), + action.returnType(), + match.arguments() + ) + ); + }); } + } - String rawArg = sentence.arguments().get(0); - - IRValue value; - - if (rawArg.startsWith("\"")) { - value = new IRLiteral( - CoreTypes.TEXT, - rawArg.substring(1, rawArg.length() - 1) - ); - } else { - // TODO property access for now - value = new IRProperty( - CoreTypes.TEXT, - Identifier.of("test:game.name"), - rawArg - ); - } + if (matches.isEmpty()) { + throw new RuntimeException( + "No matching action for: " + sentence.rawText() + ); + } - return new IRAction( - new Identifier(CoreLibrary.CORE_NAMESPACE, "print"), - CoreTypes.VOID, - List.of(new IRArgument("value", value)) + if (matches.size() > 1) { + throw new RuntimeException( + "Ambiguous action for: " + sentence.rawText() ); } - throw new RuntimeException( - "Unknown action '" + sentence.verb() - + "' at line " + sentence.lineNumber() - ); + return matches.get(0); } } + + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/SyntaxMatcher.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/SyntaxMatcher.java new file mode 100644 index 0000000..800ee77 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/SyntaxMatcher.java @@ -0,0 +1,53 @@ +package com.terminalvelocitycabbage.engine.scripting.parser; + +import com.terminalvelocitycabbage.engine.scripting.api.syntax.SyntaxArgument; +import com.terminalvelocitycabbage.engine.scripting.api.syntax.SyntaxLiteral; +import com.terminalvelocitycabbage.engine.scripting.api.syntax.SyntaxPart; +import com.terminalvelocitycabbage.engine.scripting.api.syntax.SyntaxPattern; +import com.terminalvelocitycabbage.engine.scripting.parser.data.SentenceNode; +import com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate.IRValue; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public final class SyntaxMatcher { + + public static Optional match( + SyntaxPattern pattern, + SentenceNode sentence, + ParsingContext context + ) { + List tokens = sentence.tokens(); + List parts = pattern.parts(); + + if (tokens.size() != parts.size()) { + return Optional.empty(); + } + + Map arguments = new HashMap<>(); + + for (int i = 0; i < parts.size(); i++) { + + SyntaxPart part = parts.get(i); + String token = tokens.get(i); + + if (part instanceof SyntaxLiteral literal) { + if (!literal.text().equalsIgnoreCase(token)) { + return Optional.empty(); + } + } + + if (part instanceof SyntaxArgument arg) { + IRValue value = + IRValueResolver.resolve(token, arg.type(), context); + arguments.put(arg.name(), value); + } + } + + return Optional.of(new MatchedSyntax(arguments)); + } +} + + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/SentenceNode.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/SentenceNode.java index b0048f8..5677e78 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/SentenceNode.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/SentenceNode.java @@ -2,9 +2,9 @@ import java.util.List; -public record SentenceNode( - String verb, - List arguments, - int lineNumber -) {} +public record SentenceNode(String rawText, List tokens, int lineNumber) { + +} + + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRAction.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRAction.java index 8056120..6ef34d3 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRAction.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRAction.java @@ -3,8 +3,8 @@ import com.terminalvelocitycabbage.engine.registry.Identifier; import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; -import java.util.List; +import java.util.Map; -public record IRAction(Identifier actionId, ScriptType returnType, List arguments) implements IRNode { +public record IRAction(Identifier actionId, ScriptType returnType, Map arguments) implements IRNode { } diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRLiteral.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRLiteralValue.java similarity index 65% rename from src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRLiteral.java rename to src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRLiteralValue.java index aa346b9..38145d3 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRLiteral.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRLiteralValue.java @@ -2,6 +2,6 @@ import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; -public record IRLiteral(ScriptType type, Object value) implements IRValue { +public record IRLiteralValue(ScriptType type, Object value) implements IRValue { } diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRPrinter.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRPrinter.java index 5939bcd..6cd650e 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRPrinter.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRPrinter.java @@ -46,13 +46,13 @@ private static void printAction(IRAction action, StringBuilder sb, int indent) { private static void printValue(IRValue value, StringBuilder sb) { switch (value) { - case IRLiteral(ScriptType type, Object value1) -> sb + case IRLiteralValue(ScriptType type, Object value1) -> sb .append("LITERAL<") .append(type.getIdentifier()) .append(">(") .append(value1) .append(")"); - case IRProperty(ScriptType type, Identifier propertyId, String accessPath) -> sb + case IRPropertyValue(ScriptType type, Identifier propertyId, String accessPath) -> sb .append("PROPERTY<") .append(type.getIdentifier()) .append(">(") diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRProperty.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRProperty.java deleted file mode 100644 index 235d749..0000000 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRProperty.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate; - -import com.terminalvelocitycabbage.engine.registry.Identifier; -import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; - -public record IRProperty( - ScriptType type, - Identifier propertyId, - String accessPath // ex. "game.name" -) implements IRValue { -} diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRPropertyValue.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRPropertyValue.java new file mode 100644 index 0000000..281529e --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRPropertyValue.java @@ -0,0 +1,19 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate; + +import com.terminalvelocitycabbage.engine.scripting.api.ScriptProperty; +import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; + +public record IRPropertyValue(ScriptProperty property) implements IRValue { + + @Override + public ScriptType type() { + return property.valueType(); + } + + @Override + public String toString() { + return "PROPERTY<" + type().getIdentifier() + ">(" + property.getIdentifier() + ")"; + } +} + + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRValue.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRValue.java index 87b4371..3f0f181 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRValue.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRValue.java @@ -2,7 +2,7 @@ import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; -public sealed interface IRValue permits IRLiteral, IRProperty, IREventValue, IRVariable { +public sealed interface IRValue permits IREventValue, IRLiteralValue, IRPropertyValue, IRVariable { ScriptType type(); } diff --git a/src/test/java/com/terminalvelocitycabbage/test/ecs/ScriptsTest.java b/src/test/java/com/terminalvelocitycabbage/test/ecs/ScriptsTest.java index aa8f742..62c1c62 100644 --- a/src/test/java/com/terminalvelocitycabbage/test/ecs/ScriptsTest.java +++ b/src/test/java/com/terminalvelocitycabbage/test/ecs/ScriptsTest.java @@ -118,7 +118,15 @@ void testIRPrintScript() { List blocks = ScriptBlockParser.parse(scriptText); - ParsingContext context = new ParsingContext(scriptEventRegistry, scriptTypeRegistry); + ParsingContext context = + new ParsingContext( + scriptEventRegistry, + scriptTypeRegistry, + scriptActionRegistry, + scriptPropertyRegistry, + scriptConstantRegistry + ); + List irBlocks = new ArrayList<>(); @@ -132,7 +140,7 @@ void testIRPrintScript() { SentenceParser.parse(line); IRAction action = - SentenceToIRAction.parse(sentence, context); + ActionToIR.parse(sentence, context); irBlock.body().add(action); }