# SandScript
A minimal, sandboxed JavaScript subset designed for LLM code generation and transparent execution.
## What is SandScript?
SandScript is a strict subset of JavaScript — every valid SandScript program is a valid JavaScript program with identical semantics. It trades flexibility for predictability, designed for scenarios where you need:
- Sandboxed execution — No host access except through explicit FFI
- LLM-friendly syntax — Predictable structure, one way to do everything
- Observable execution — Every step can be paused, inspected, replayed
- Fuel-based execution — Bounded computation with explicit fuel budgets
### Design Principle: Subset, Not Superset
SandScript only restricts JavaScript — it never adds new semantics. If something works in SandScript, it works identically in a standard JavaScript runtime. This means:
- No new operators or syntax
- No expression forms that don't exist in JS (e.g.,
ifremains a statement) - No implicit conversions beyond what JS does
## Try It Now
Online Playground: sandscriptmath.ai/playground — write code, step through execution, inspect memory.
CLI Tool: Install the sand command for local sessions:
curl -fsSL https://sandscriptmath.ai/sand/install.sh | sh
Then run:
sand script "let x = 1 + 2"
sand get "x" # → 3
## Quick Start (Deno/Browser)
Import directly from the CDN:
import { createSession } from 'https://sandscriptmath.ai/src/fuel/session.js';
const session = await createSession();
// Parse and run code
session.parse('let x = 1 + 2');
const result = session.run(1000); // 1000 fuel units
console.log(result.status); // 'done'
console.log(session.state().scope.x); // 3
Run with Deno:
deno run --allow-net your-script.js
## Language Overview
SandScript looks like JavaScript with some constraints:
// Variables
let name = "Claude";
let count = 42;
let active = true;
// Functions (arrow only)
let square = (x) => x * x;
let greet = (name) => "Hello, " + name + "!";
// Operators work normally
let sum = 1 + 2;
let isEqual = a === b;
let isGreater = x > 10;
// Arrays and objects
let numbers = [1, 2, 3];
let person = { name: "Alice", age: 30 };
// Property access
let first = numbers[0];
let personName = person.name;
// Control flow
let max = (a, b) => {
if (a > b) {
return a;
} else {
return b;
}
};
// Loops
for (let i = 0; i < 10; i++) {
Console.log(i);
}
// Higher-order functions
let doubled = numbers.map((x) => x * 2);
let total = numbers.reduce((acc, x) => acc + x, 0);
// Error handling
try {
throw { type: "Error", message: "Something went wrong" };
} catch (e) {
Console.log(e.message);
}
### What's Not Supported
| Feature | Status |
|---|---|
class |
Parses but fails at runtime |
async/await |
Parses but fails at runtime |
Destructuring [a, b] = ... |
Parse error |
for...of |
Parse error |
Spread ...arr |
Parse error |
Note: new, this, and ternary ? : all work normally.
## Standard Library
### Math
Constants: PI, E, LN2, LN10, LOG2E, LOG10E, SQRT2, SQRT1_2
// Basic
Math.abs(x) Math.floor(x) Math.ceil(x)
Math.round(x) Math.trunc(x) Math.sign(x)
Math.min(a, b) Math.max(a, b) Math.pow(x, y)
Math.sqrt(x) Math.cbrt(x) Math.hypot(a, b)
// Trigonometric
Math.sin(x) Math.cos(x) Math.tan(x)
Math.asin(x) Math.acos(x) Math.atan(x)
Math.atan2(y, x) Math.sinh(x) Math.cosh(x)
Math.tanh(x) Math.asinh(x) Math.acosh(x)
Math.atanh(x)
// Exponential/Logarithmic
Math.log(x) Math.log10(x) Math.log2(x)
Math.log1p(x) Math.exp(x) Math.expm1(x)
// Other
Math.random() Math.clz32(x) Math.imul(a, b)
Math.fround(x)
### Array
// Instance methods
arr.length arr.push(item) arr.pop()
arr.shift() arr.unshift(item)
arr.map(fn) arr.filter(fn) arr.reduce(fn, init)
arr.forEach(fn) arr.find(fn) arr.findIndex(fn)
arr.some(fn) arr.every(fn) arr.includes(item)
arr.indexOf(item) arr.slice(start, end)
arr.concat(other) arr.join(sep) arr.reverse()
arr.sort(fn)
// Static methods
Array.isArray(x)
### String
// Instance methods
str.length str.charAt(idx) str.charCodeAt(idx)
str.indexOf(sub) str.includes(sub) str.startsWith(sub)
str.endsWith(sub) str.slice(start, end)
str.split(sep) str.replace(from, to)
str.toUpperCase() str.toLowerCase() str.trim()
// Static methods
String.fromCharCode(code) String.fromCodePoint(code)
### Object
Object.keys(obj) Object.values(obj) Object.entries(obj)
Object.assign(target, source)
### Number
Constants: MAXVALUE, MINVALUE, MAXSAFEINTEGER, MINSAFEINTEGER, POSITIVEINFINITY, NEGATIVEINFINITY, NaN, EPSILON
Number.isNaN(x) Number.isFinite(x) Number.isInteger(x)
Number.parseInt(s) Number.parseFloat(s)
### Global Functions
parseInt(s) parseFloat(s) isNaN(x)
isFinite(x) Boolean(x) String(x)
Number(x)
## Error Handling
SandScript uses throw signals for all exceptional conditions. Error values are just values — no special Error class.
// Throwing
throw "Simple error message";
throw { type: "ValidationError", message: "Invalid input" };
// Catching
try {
riskyOperation();
} catch (e) {
Console.log(e.message);
}
### Why No Stack Traces
SandScript does not provide stack traces inside the language:
1. Mutability — Code lives in linear memory and can be mutated during execution. A stack trace captured at throw time could contain stale references.
2. Synthetic code — Code causing an error might not come from parsed text at all. Line numbers are meaningless for dynamically constructed code.
Stack traces are a host/debugger concern, not a language feature.
## Session API
### createSession(options)
const session = await createSession({
// Standalone: create new memory (default 1MB)
memorySize: 1024 * 1024,
// Or segment model: attach to existing memory at offset
memory: existingWebAssemblyMemory,
offset: 1024 * 1024,
// Initial FFI bindings
ffi: {
Console: { log: (...args) => console.log(...args) },
Timer: { now: () => Date.now() },
},
});
### session.parse(source)
Parse source code and prepare for execution. Appends to existing code block.
const result = session.parse('let x = 10');
// { instructions: 4, startIndex: 0, complete: true, error: null }
### session.run(fuel)
Execute until fuel exhausted, done, FFI request, or error.
const result = session.run(1000);
// {
// status: 'done' | 'running' | 'ffi' | 'error',
// instruction: 5,
// fuelRemaining: 847,
// request: { ... }, // if status === 'ffi'
// error: { ... }, // if status === 'error'
// }
### session.get(name)
Get a variable from global scope.
session.get('greeting'); // "hello"
### session.state()
Get current execution state snapshot.
const state = session.state();
// {
// instruction: 5,
// instructionCount: 12,
// status: 'running',
// scope: { x: 10, y: 'hello' },
// pending: [{ type: 'integer', value: 42 }],
// callStackDepth: 1,
// }
### session.reset()
Soft reset: clears execution state but keeps parsed code. Resets instruction pointer, stacks, scope, and status.
### session.resetCode()
Discard all parsed code and start with an empty code block. Invalidates any closures created from previous code.
### session.gc()
Run garbage collection. Returns stats about what was collected.
### session.bindFFI(name, implementation)
Register an FFI namespace at runtime.
session.bindFFI('Math2', {
clamp: (val, min, max) => Math.min(Math.max(val, min), max),
});
### FFI Handling
FFI calls pause execution and return control to the caller:
while (true) {
const result = session.run(1000);
if (result.status === 'done') break;
if (result.status === 'error') throw new Error(result.error.code);
if (result.status === 'ffi') {
const { namespace, method, args } = result.request;
const returnValue = yourFFIHandler(namespace, method, args);
session.resumeWithFFIResult(returnValue);
// Or on error: session.resumeWithFFIError(new Error('denied'));
}
}
### Debugging
// Get instruction details
session.instruction(0);
// { opcode: 'LIT_FLOAT', operand1: 0, operand2: 1072693248, sourceStart: 8, sourceEnd: 10 }
// Find instructions for source range
session.instructionsForRange(8, 15); // [3, 4, 5]
// Jump to instruction
session.setInstruction(0);
### Segment Model
Multiple interpreters can share a single WebAssembly.Memory:
const sharedMemory = new WebAssembly.Memory({ initial: 64 }); // 4MB
const session1 = await createSession({ memory: sharedMemory, offset: 0 });
const session2 = await createSession({ memory: sharedMemory, offset: 1024 * 1024 });
## License
MIT License. Download source code.