luma-lang
v1.0.4
Published
The Embeddable Luma Language Compiler and Runtime
Maintainers
Readme
Introduction to Luma
Welcome to Luma!
Luma is a lightweight, high-performance scripting language designed to be embedded within any JavaScript or TypeScript application. It works seamlessly on both the server and client-side and in (Web)Workers, making it an ideal solution for game logic, modding systems, and rule engines.
Core Philosophy
- Embeddable: Designed to live inside your host application, not replace it.
- Secure: Runs in a strictly isolated sandbox. No access to the host's
window,process, or global prototypes unless explicitly granted. - Familiar: Syntax inspired by Python (indentation-based) and JavaScript (dynamic typing).
- Resumable: Scripts can be paused (
wait), saved to disk, and resumed later. - Budgeted: Prevent infinite loops from freezing your app with tick-based execution limits.
Intuitive Syntax
Luma’s syntax utilizes an indentation-based structure to reduce visual clutter. It supports modern features like string interpolation, array comprehensions, and classes.
// A simple Luma script
class Greeter(name):
name = name
fn greet(times):
// String interpolation
print("Hello, {this.name}!")
// Python-style array comprehension
return [i * 10 for i in 0..times]
greeter = new Greeter("World")
// Call methods
result = greeter.greet(3)Security & Sandboxing
One of Luma's strongest features is its security model. Luma scripts run in a virtualized environment that is completely isolated from the host.
- No Global Leakage: Scripts cannot pollute the host's global scope.
- Prototype Protection: Access to
__proto__andconstructoris blocked at the VM level, preventing common sandbox escapes. - Controlled Interop: The script can only access functions and classes (fine-tuned to individual properties and methods) you explicitly expose.
Compilation & Binary Serialization
Scripts in Luma are compiled into bytecode before execution. This model ensures the scripts run efficiently and allows for binary caching.
Basic Compilation
To run a script, you compile source code into a Program object.
import { Compiler } from 'luma-lang';
// Compile a Luma script into a Program
const program = Compiler.compile(`print("Hello, Luma!")`);Binary Export (Pre-compilation)
You can pre-compile Luma scripts to binary formats (Uint8Array) using the
Writer and Reader APIs. This allows you to ship compiled assets and skip
parsing at runtime.
import { Compiler, Reader, Writer } from 'luma-lang';
// 1. Serialize the Program to binary
const binary = Writer.write(program);
// 2. Deserialize from binary later
const loadedProgram = Reader.read(binary);Performance Tip:
Although completely optional, pre-compiling scripts to binary format can significantly reduce load times, especially for large scripts or when loading multiple scripts at once. This becomes critical if you load scripts during a game loop.
Tick-based Execution & Time Travel
Luma uses a tick-based Virtual Machine. This allows for tight integration
with host applications (like game loops) and enables the wait keyword directly
in your scripts.
const vm = new VirtualMachine(program, {
budget: 100, // Optional: Limit instructions per tick to prevent freezing
});
// In your application loop:
function gameLoop() {
// Advances the VM by a frame (deltaTime in milliseconds)
vm.run(deltaTime);
requestAnimationFrame(gameLoop);
}In your Luma script, you can pause execution without blocking the host:
print("Start")
wait(1000) // Pauses this script for 1 second, host keeps running!
print("End")Native Async Interoperability
Luma supports asynchronous host functions out of the box. If you expose a host
function that returns a Promise (such as a database query or a fetch request), Luma will pause the script execution
until that Promise resolves.
This creates an "automatic await" behavior, allowing you to write synchronous-looking code in Luma that handles asynchronous tasks.
// Expose an async function to Luma
const vm = new VirtualMachine(program, {
functions: {
// The VM detects that this returns a Promise
async fetchData(url: string): Promise<string> {
const response = await fetch(url);
return response.text();
}
}
});In your Luma script, you call this function normally. The script halts at the function call:
// The script pauses here automatically
data = fetchData("https://example.com/data")
// This line runs only after the Promise resolves AND the host calls vm.run()
print("Fetched Data: " + data)[!WARNING] The VM is Passive
When the script invokes an async function, the VM pauses and
vm.run()returns immediately. The VM does not automatically resume itself when the Promise resolves. You must continue to callvm.run()in your host application's update loop (e.g., every frame).
- If the Promise is still pending,
vm.run()does nothing (returns immediately).- Once the Promise resolves, the next call to
vm.run()will resume the script where it left off.
State Persistence
Luma allows you to snapshot the entire state of the Virtual Machine. This is critical for features like ** Save / Load ** in games or session resumption in interactive apps.
// Save the full state (variables, stack, instruction pointer)
const serializedState = vm.save();
// Restore the state later - the script continues exactly where it left off
vm.load(serializedState);[!WARNING] Versioning Warning: Saved states are tightly coupled to the structure of the compiled
Program. If you recompile the source code, the VM may not be able to load a state saved from a previous version.
Contributing
To set up the development environment, clone the repository and install dependencies:
npm installBuild the project using npm run build or npm run watch for continuous builds.
Once built, you have two options to run the tests:
- Run the entire suite once:
npm run test - Run tests in watch mode:
npm run watch:test
