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/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 3641ba7..20b53f7 100644 --- a/src/main/java/com/terminalvelocitycabbage/engine/registry/Registry.java +++ b/src/main/java/com/terminalvelocitycabbage/engine/registry/Registry.java @@ -2,7 +2,9 @@ import com.terminalvelocitycabbage.engine.debug.Log; -import java.util.*; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Set; public class Registry { @@ -26,6 +28,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 @@ -64,6 +85,38 @@ 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); + } + + /** + * @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 -> { + if (identifier.getNamespace().equals(namespace)) identifiers.add(identifier); + }); + 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 -> { + if (identifier.getName().equals(name)) identifiers.add(identifier); + }); + return identifiers; + } + public LinkedHashMap getRegistryContents() { return registryContents; } 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..9b272d6 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ActionContext.java @@ -0,0 +1,30 @@ +package com.terminalvelocitycabbage.engine.scripting.api; + +import com.terminalvelocitycabbage.engine.scripting.parser.ExecutionContext; + +import java.util.Map; + +public final class ActionContext { + + private final Map arguments; + private final ExecutionContext executionContext; + + public ActionContext( + Map arguments, + ExecutionContext executionContext + ) { + this.arguments = arguments; + this.executionContext = executionContext; + } + + @SuppressWarnings("unchecked") + public T get(String name) { + return (T) arguments.get(name); + } + + public ExecutionContext execution() { + return executionContext; + } +} + + diff --git a/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ActionExecutor.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ActionExecutor.java new file mode 100644 index 0000000..df91d60 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ActionExecutor.java @@ -0,0 +1,7 @@ +package com.terminalvelocitycabbage.engine.scripting.api; + +@FunctionalInterface +public interface ActionExecutor { + void execute(ActionContext context); +} + 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..b9549d4 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/PropertyAccess.java @@ -0,0 +1,44 @@ +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 { + + V get(O instance); + + final class ReadOnly implements PropertyAccess { + + private final Function getter; + + public ReadOnly(Function getter) { + this.getter = getter; + } + + @Override + public V get(O instance) { + return getter.apply(instance); + } + } + + 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 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 new file mode 100644 index 0000000..9328dda --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptAction.java @@ -0,0 +1,46 @@ +package com.terminalvelocitycabbage.engine.scripting.api; + +import com.terminalvelocitycabbage.engine.registry.Identifier; +import com.terminalvelocitycabbage.engine.scripting.api.syntax.SyntaxPattern; + +import java.util.List; + +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 Identifier id() { + return id; + } + + public List patterns() { + return patterns; + } + + public ScriptType returnType() { + return returnType; + } + + public ActionExecutor executor() { + return executor; + } +} + 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..81ca0a3 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptConstant.java @@ -0,0 +1,12 @@ +package com.terminalvelocitycabbage.engine.scripting.api; + +import com.terminalvelocitycabbage.engine.registry.Identifiable; +import com.terminalvelocitycabbage.engine.registry.Identifier; + +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 new file mode 100644 index 0000000..9577aea --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptEvent.java @@ -0,0 +1,78 @@ +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, + 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 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 doc(String documentation) { + this.documentation = documentation; + return this; + } + + public ScriptEvent build() { + if (id == null) + throw new IllegalStateException("ScriptEvent identifier is required"); + if (eventClass == null) + throw new IllegalStateException("ScriptEvent eventClass is required"); + + return new ScriptEvent( + id, + eventClass, + Map.copyOf(values), + 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..2e93ba7 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptEventValue.java @@ -0,0 +1,35 @@ +package com.terminalvelocitycabbage.engine.scripting.api; + +import java.util.function.Function; + +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 new file mode 100644 index 0000000..c902a0f --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/ScriptProperty.java @@ -0,0 +1,26 @@ +package com.terminalvelocitycabbage.engine.scripting.api; + +import com.terminalvelocitycabbage.engine.registry.Identifiable; +import com.terminalvelocitycabbage.engine.registry.Identifier; + +public record ScriptProperty( + Identifier identifier, + ScriptType ownerType, + ScriptType valueType, + PropertyAccess access, + ScriptVisibility visibility, + String documentation +) implements Identifiable { + + @SuppressWarnings("unchecked") + public Object get(Object instance) { + return access.get((O) instance); + } + + @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..67cd3fa --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/registry/ScriptPropertyRegistry.java @@ -0,0 +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/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/Syntax.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/Syntax.java new file mode 100644 index 0000000..2539135 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/Syntax.java @@ -0,0 +1,17 @@ +package com.terminalvelocitycabbage.engine.scripting.api.syntax; + +import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; + +public final class Syntax { + + private Syntax() {} + + public static SyntaxLiteral literal(String literal) { + return new SyntaxLiteral(literal); + } + + 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/SyntaxArgument.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxArgument.java new file mode 100644 index 0000000..ca28a97 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxArgument.java @@ -0,0 +1,9 @@ +package com.terminalvelocitycabbage.engine.scripting.api.syntax; + +import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; + +public record SyntaxArgument( + String name, + ScriptType type +) implements SyntaxPart {} + 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 new file mode 100644 index 0000000..88b834e --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/api/syntax/SyntaxPattern.java @@ -0,0 +1,23 @@ +package com.terminalvelocitycabbage.engine.scripting.api.syntax; + +import java.util.List; + +public final class SyntaxPattern { + + 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 new file mode 100644 index 0000000..40014a4 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/core/CoreActions.java @@ -0,0 +1,37 @@ +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.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 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); + }, + "Prints text to the console." + ); + + registry.register(print); + } +} + + 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/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/CallActionInstruction.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/CallActionInstruction.java new file mode 100644 index 0000000..dba0ecc --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/CallActionInstruction.java @@ -0,0 +1,41 @@ +package com.terminalvelocitycabbage.engine.scripting.parser; + +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 Map arguments; + + public CallActionInstruction( + ScriptAction action, + Map arguments + ) { + this.action = action; + this.arguments = arguments; + } + + @Override + public void execute(ExecutionContext context) { + + Map evaluatedArgs = new HashMap<>(); + + 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 new file mode 100644 index 0000000..6efc33d --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ExecutionContext.java @@ -0,0 +1,28 @@ +package com.terminalvelocitycabbage.engine.scripting.parser; + +import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; + +import java.util.HashMap; +import java.util.Map; + +public final class ExecutionContext { + + private final Map scopedValues = new HashMap<>(); + + public void setScopeValue(ScriptType type, Object value) { + scopedValues.put(type, 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/LoadEventValueInstruction.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/LoadEventValueInstruction.java new file mode 100644 index 0000000..1268179 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/LoadEventValueInstruction.java @@ -0,0 +1,25 @@ +package com.terminalvelocitycabbage.engine.scripting.parser; + +import com.terminalvelocitycabbage.engine.scripting.api.ScriptEventValue; + +public final class LoadEventValueInstruction implements ScriptInstruction { + + private final ScriptEventValue value; + private final int targetSlot; + + public LoadEventValueInstruction( + ScriptEventValue value, + int targetSlot + ) { + this.value = value; + this.targetSlot = targetSlot; + } + + @Override + public void execute(ExecutionContext context) { + 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 new file mode 100644 index 0000000..9b43cc1 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/LoadPropertyInstruction.java @@ -0,0 +1,24 @@ +package com.terminalvelocitycabbage.engine.scripting.parser; + +import com.terminalvelocitycabbage.engine.scripting.api.ScriptProperty; + +public final class LoadPropertyInstruction implements ScriptInstruction { + + private final ScriptProperty property; + private final int ownerSlot; + private final int 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 owner = context.getLocal(ownerSlot); + Object value = property.get(owner); + context.setLocal(targetSlot, value); + } +} + 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 new file mode 100644 index 0000000..1f81f55 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/ParsingContext.java @@ -0,0 +1,12 @@ +package com.terminalvelocitycabbage.engine.scripting.parser; + +import com.terminalvelocitycabbage.engine.scripting.api.registry.*; + +public record ParsingContext( + ScriptActionRegistry actions, + ScriptPropertyRegistry properties, + ScriptEventRegistry events, + ScriptTypeRegistry types) { + +} + 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/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/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..29bca26 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/SentenceParser.java @@ -0,0 +1,20 @@ +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.List; + +public final class SentenceParser { + + public static SentenceNode parse(ScriptLine line) { + + String raw = line.text().trim(); + + List tokens = + List.of(raw.split("\\s+")); + + 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 new file mode 100644 index 0000000..6c42804 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/SentenceToIRAction.java @@ -0,0 +1,52 @@ +package com.terminalvelocitycabbage.engine.scripting.parser; + +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.IRAction; + +import java.util.ArrayList; +import java.util.List; + +public final class SentenceToIRAction { + + public static IRAction parse( + SentenceNode sentence, + ParsingContext context + ) { + 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() + ) + ); + }); + } + } + + if (matches.isEmpty()) { + throw new RuntimeException( + "No matching action for: " + sentence.rawText() + ); + } + + if (matches.size() > 1) { + throw new RuntimeException( + "Ambiguous action for: " + sentence.rawText() + ); + } + + 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/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/ScriptBlock.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptBlock.java new file mode 100644 index 0000000..5a02002 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptBlock.java @@ -0,0 +1,11 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data; + +import java.util.List; + +//Represents a root level block of code in a script +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 new file mode 100644 index 0000000..46b1576 --- /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 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 new file mode 100644 index 0000000..428af5d --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/ScriptFile.java @@ -0,0 +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/SentenceNode.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/SentenceNode.java new file mode 100644 index 0000000..5677e78 --- /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 rawText, List tokens, int lineNumber) { + +} + + + 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..6ef34d3 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRAction.java @@ -0,0 +1,10 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate; + +import com.terminalvelocitycabbage.engine.registry.Identifier; +import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; + +import java.util.Map; + +public record IRAction(Identifier actionId, ScriptType returnType, Map 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/IRLiteralValue.java b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRLiteralValue.java new file mode 100644 index 0000000..38145d3 --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRLiteralValue.java @@ -0,0 +1,7 @@ +package com.terminalvelocitycabbage.engine.scripting.parser.data.intermediate; + +import com.terminalvelocitycabbage.engine.scripting.api.ScriptType; + +public record IRLiteralValue(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..6cd650e --- /dev/null +++ b/src/main/java/com/terminalvelocitycabbage/engine/scripting/parser/data/intermediate/IRPrinter.java @@ -0,0 +1,83 @@ +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 IRLiteralValue(ScriptType type, Object value1) -> sb + .append("LITERAL<") + .append(type.getIdentifier()) + .append(">(") + .append(value1) + .append(")"); + case IRPropertyValue(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/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 new file mode 100644 index 0000000..3f0f181 --- /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 IREventValue, IRLiteralValue, IRPropertyValue, 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 { +} + 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..62c1c62 --- /dev/null +++ b/src/test/java/com/terminalvelocitycabbage/test/ecs/ScriptsTest.java @@ -0,0 +1,258 @@ +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 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; + +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 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, + scriptActionRegistry, + scriptPropertyRegistry, + scriptConstantRegistry + ); + + + 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 = + ActionToIR.parse(sentence, context); + + irBlock.body().add(action); + } + + irBlocks.add(irBlock); + } + + for (IRBlock ir : irBlocks) { + System.out.println(IRPrinter.print(ir)); + } + + } + + @Test + void testHelloWorldScript() { + + // --- 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") + ); + + /* + * Script: + * + * on game_started: + * print(game.name) + * + * Slot layout: + * 0 -> game + * 1 -> game.name + */ + + List instructions = List.of( + + // load event.game -> slot 0 + new LoadEventValueInstruction( + gameStartEvent.exposedValues().get("game"), + 0 + ), + + // load game.name -> slot 1 + new LoadPropertyInstruction( + gameNameProperty, + 0, + 1 + ), + + // 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(event, 2); + + for (ScriptInstruction instruction : instructions) { + instruction.execute(context); + } + } +} +