# 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:

### 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:

## 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.