luma-lang
Version:
The Embeddable Luma Language Compiler and Runtime
206 lines (150 loc) • 7.06 kB
Markdown
<p align="center">
<a href="https://github.com/haroldiedema/luma-language/actions/workflows/test.yml"><img src="https://github.com/haroldiedema/luma-language/actions/workflows/test.yml/badge.svg" alt="Tests" /></a>
<a href="https://bundlephobia.com/package/luma-lang"><img src="https://img.shields.io/bundlephobia/minzip/luma-lang" alt="Bundle Size" /></a>
</p>
<p align="center">
<a href="https://www.npmjs.com/package/luma-lang"><img src="https://img.shields.io/npm/v/luma-lang?color=red" alt="NPM Version" /></a>
<a href="https://esm.sh/luma-language"><img src="https://img.shields.io/badge/esm.sh-luma-lang-f39c12" alt="esm.sh" /></a>
<a href="https://unpkg.com/browse/luma-lang/"><img src="https://img.shields.io/badge/unpkg-luma-lang-3498db" alt="unpkg" /></a>
</p>
# 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.
```luma
// 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__` and `constructor` is 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.
```ts
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.
```ts
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.
```ts
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:
```luma
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.
```ts
// 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:
```luma
// 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 call `vm.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.
```ts
// 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:
```bash
npm install
```
Build 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`
---