Ix Dev 4 - Ix Script
Ix Script
I started IxScript to have a simple, safe in-game scripting language for my projects - easy to extend in both plain TS and in the language itself. IxScript favors less code and syntax, with commas and parentheses optional in many cases.
IxScript uses the Ohm library to define its grammar and parse the text into in-memory program objects. The program can then be run step-by-step as a generator function, or all in one go, executing inside a small VM.
Standard variables and functions (including many core-to-the-language functions, such as :set variable assignment) are defined on the ScriptContext object. Built-in functions defined in JS are passed arguments in their low-level ValueSource form, enabling precise control over when variables or functions are evaluated.
As a convenience, a new ScriptProxyObject(myObject) can be bound to the context. This supports read/write access to properties, and the ability to call existing JS methods. Arguments are passed to bound functions as their final plain JS value, and the return value of functions are wrapped back into ValueSources for IxScript. This makes it very simple to wrap existing game objects and functions a slight performance penalty (and without support for named arguments).
Language overview
IxScript is a language implemented on top of JS, with easy interop between the two and syntax targeting short, readable scripts.
Scripts typically use the .ix extension.
Loading syntax highlighter...
Commands and messages
There are two ways to encapsulate functionality in IxScript - commands, and message passing.
Commands are invoked with :commandName and support both named and positional arguments
Loading syntax highlighter...
Messages can be sent to any variable or object, though it's up to that to handle it:
Loading syntax highlighter...
As you can see, objects can be given functionality via message handlers in a way similar to ad-hoc objects in JS. It's easy to then wrap object creation in a command and generate standardized objects with state and functionality - classes by another name.
Extending IxScript
Consumers of IxScript at the library level can add more functions, variables, and ScriptProxyObject instances to the context (or while) running their parsed IxProgram.
As an example, this is part of the setup for running tests written in .ix files (by mapping to vitest), adding support for :assert boolVal and :assert val1, val2:
Loading syntax highlighter...
Most tests need to support a deep comparison of two objects, so I map to vitest's built-in matcher where possible to give nice error messages.
Tests that need inequality/contains/some other condition currently use the :assert 1 != 2 form - not as nice error messages, but simple.
Using IxScript
Loading syntax highlighter...
Extra: ix-cli
I have a small npm script ix-cli that runs either a command or IxScript file and prints the response. I don't use it too often, but it makes the language feel a bit more "real" to see it in the terminal.
Editors
The ix-script package provides a monaco-based editor, with lightweight syntax highlighting and a relatively pleasant interface. It doesn't yet provide other niceties (autocomplete or error display). It also provides a set of components for a debugger display - showing stack frames, variables, and bound functions for a given script execution.
In-game
Scripts are loaded into the game as templates, and can be executed via a standard RunScriptEffect (which can itself be run from inside a script). This brings scripting support to items, abilities, buttons, and more - anything that uses effects.
I prefer to start with the stronger-typed, easier-to-validate JSON templates - spawning multiple entities with a spawn effect rather than a script that loops. But having drop-in script ability for any part of the game is a powerful feature to have at hand.
Scripts can be run using a single-line command prompt (bound to ```` and : for convenience, since commands start with :), or using the full monaco editor in a larger side-pane.
The full script editor supports ad-hoc scripts, loading any script template, or creating new (local and persisted) script templates.