A high-performance JavaScript-like scripting language with native code execution
Featuring a self-hosting compiler and Rust-inspired memory safety
This project is AI assisted, with some chunks of the code being generated by AI, marjority of the code still were generated by a Human, mainly the buggy part, and inconsistent functions. Most of the documentation were generate by AI , because documentation will be properly addressed when the project gets more mature.
This project accepts and encourage AI to be used. If you're against AI please consider contributing to another project.
Script is a scripting language that combines JavaScript-like syntax with Rust-inspired memory safety and native code performance.
function fib(n) {
if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
}
console.log(fib(35)); // Compiled to native code!- Native Execution — SSA-based IR compiled to native code via Cranelift/LLVM
- Link-Time Optimization — ThinLTO and Full LTO for maximum performance
- Standalone Binaries — Self-contained executables with runtime stubs in LLVM IR
- Memory Safety — Ownership model with compile-time borrow checking
- Self-Hosting — Bootstrap compiler written in Script itself
- Type Inference — Flow-sensitive type analysis for optimization
- JavaScript Syntax — Familiar syntax with ES6+ features
- Classes & Inheritance — ES6 classes with extends, super(), and private fields
- Error Handling — try/catch/finally with exception propagation
Script Core is like C without libc — a minimal, self-contained language that runs without dependencies. Everything else is optional.
┌─────────────────────────────────────────────────────────────────────────┐
│ User App Code │
└───────────────────────────────────┬─────────────────────────────────────┘
│
┌───────────────┴───────────────┐
│ │
▼ ▼
┌───────────────────────────────────┐ ┌──────────────────────────────────┐
│ SCRIPT CORE │ │ ROLLS │
│ ✅ Always available │ │ ⚡ Optional system libraries │
│ │ │ │
│ • Compiler (scriptc) │ │ • @rolls/http HTTP server │
│ • Runtime (NaN-boxing, heap) │ │ • @rolls/tls TLS encryption │
│ • Primitives (number, string...) │ │ • @rolls/fs File system │
│ • console.log │ │ • @rolls/db Databases │
│ │ │ • @rolls/async Event loop │
│ Like C without libc: │ │ │
│ Can run, can't do HTTP │ │ Batteries for real apps │
└───────────────────────────────────┘ └──────────────────────────────────┘
│ │
└───────────────┬───────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ UNROLL │
│ Build system & package manager │
│ │
│ • Resolves dependencies (Rolls + NPM → .nroll) │
│ • Produces single static binary │
│ • Lockfile for reproducible builds │
└───────────────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ ./myapp (Single Binary) │
│ ✅ No runtime required ✅ No node_modules ✅ Deploy: copy one file │
└─────────────────────────────────────────────────────────────────────────┘
See docs/ARCHITECTURE.md for detailed diagrams and philosophy.
┌─────────────────────────────────────────────────────────────────┐
│ Script Source │
└───────────────────────────┬─────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────┐
│ Script Compiler │
│ ┌─────────────┐ ┌──────────────┐ ┌────────────────────────┐ │
│ │ Parser │─▶│ Borrow Check │─▶│ SSA IR Generation │ │
│ │ (SWC AST) │ │ (Ownership) │ │ (Type Inference, Opts) │ │
│ └─────────────┘ └──────────────┘ └────────────────────────┘ │
└───────────────────────────┬─────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────┐
│ Native Backend │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Cranelift JIT │ │ LLVM AOT │ │ VM (Debug) │ │
│ │ (Fast) │ │ (LTO, Native) │ │ (Interpreter) │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
└───────────────────────────┬─────────────────────────────────────┘
▼
CPU
Required for LLVM AOT backend:
# Install LLVM 18 (required for AOT compilation)
brew install llvm@18
# Install zstd (required for linking)
brew install zstd
# Set LLVM environment variable (add to ~/.zshrc or ~/.bashrc for persistence)
export LLVM_SYS_180_PREFIX=$(brew --prefix llvm@18)Note: The Cranelift JIT backend works without LLVM. LLVM is only required if you want to use the AOT compilation backend.
# Build
cargo build --release
# Run a script
./target/release/script myprogram.tscl
# Dump SSA IR (for debugging)
./target/release/script ir myprogram.tscl
# Run with VM (debug mode)
./target/release/script --run-binary output.tscl.bc
# Build to native binary (requires LLVM)
./target/release/script build myprogram.tscl --release -o myprogram
# Run the compiled binary
./myprogramlet x = 42; // Number
let name = "script"; // String
let active = true; // Boolean
let data = { key: 1 }; // Object
let items = [1, 2, 3]; // Array// Function declaration
function greet(name) {
return "Hello, " + name + "!";
}
// Arrow functions
let double = (x) => x * 2;
let add = (a, b) => a + b;
// Closures
function counter() {
let count = 0;
return () => {
count = count + 1;
return count;
};
}if (condition) {
// ...
} else {
// ...
}
for (let i = 0; i < 10; i++) {
// ...
if (done) break;
if (skip) continue;
}
while (condition) {
// ...
}
do {
// ...
} while (condition);let obj = { x: 10, y: 20 };
obj.z = 30;
console.log(obj["x"]);
let arr = [1, 2, 3];
arr.push(4);
let first = arr[0];class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
speak() {
console.log(this.name + " makes a sound");
}
}
class Dog extends Animal {
breed: string;
constructor(name: string, breed: string) {
super(name);
this.breed = breed;
}
speak() {
console.log(this.name + " barks!");
}
}
let dog = new Dog("Buddy", "Golden Retriever");
dog.speak(); // "Buddy barks!"Script supports JavaScript-style private fields using the # prefix:
class Counter {
#count = 0; // Private field (only accessible within class)
increment() {
this.#count++;
}
getCount() {
return this.#count; // Can access private field from methods
}
}
let c = new Counter();
c.increment();
console.log(c.getCount()); // 1
// c.#count; // ERROR: Private field not accessible outside class
// c["#count"]; // Returns undefined (encapsulation works)try {
riskyOperation();
} catch (e) {
console.log("Error: " + e);
} finally {
cleanup();
}Script uses a Rust-inspired ownership system:
let a = { value: 42 };
let b = a; // 'a' is MOVED to 'b'
// console.log(a.value); // ERROR: use after move!
console.log(b.value); // OK: 42
// Primitives are copied
let x = 10;
let y = x; // 'x' is COPIED
console.log(x); // OK: 10- Each value has exactly one owner
- Assigning objects moves ownership
- Primitives (numbers, booleans) are copied
- Variables are freed when their scope ends
Script compiles to an SSA (Static Single Assignment) intermediate representation:
// Source: let x = 1 + 2; let y = x * 3;
fn main() -> any {
bb0:
v0 = const 1
v1 = const 2
v2 = add.num v0, v1 // Specialized to numeric add
store.local $0, v2
v3 = load.local $0
v4 = const 3
v5 = mul.any v3, v4
return
}
// After optimization:
bb0:
v2 = const 3 // 1+2 constant-folded!
store.local $0, v2
...
The type inference pass specializes dynamic operations:
| Before | After | Speedup |
|---|---|---|
add.any v0, v1 |
add.num v0, v1 |
~10x |
mul.any v0, v1 |
mul.num v0, v1 |
~10x |
Script core provides only essential primitives:
console.log("Hello", 42, true);
console.error("Error message");Used by the bootstrap compiler for bytecode emission:
let stream = ByteStream.create();
ByteStream.writeU8(stream, 0xff);
ByteStream.writeU32(stream, 12345);
ByteStream.writeF64(stream, 3.14159);
ByteStream.writeString(stream, "hello");
let bytes = ByteStream.toArray(stream);Basic file operations for the bootstrap compiler:
let fs = require("fs");
let content = fs.readFileSync("file.txt");
fs.writeFileSync("out.txt", "Hello!");Note: Full standard library functionality (Math, Date, JSON, comprehensive fs/path, etc.) will be provided by the Rolls ecosystem in a separate repository. See
docs/future/rolls-design.mdfor the planned architecture.
script/
├── Cargo.toml # Minimal dependencies
├── README.md # This file
├── PROGRESS.md # Development status
├── compiler/ # Self-hosted compiler (modular, target)
│ ├── main.tscl # CLI entry point
│ ├── lexer/ # Tokenization module
│ ├── parser/ # AST generation module
│ ├── ast/ # AST type definitions
│ ├── ir/ # IR system module
│ ├── codegen/ # Code generation module
│ └── stdlib/ # Runtime declarations
├── bootstrap/ # Bootstrap compiler (working reference)
│ └── *.tscl # 11 files (~5,000 lines)
├── src/
│ ├── main.rs # Entry point
│ ├── lib.rs # Library target
│ ├── compiler/ # Rust compiler (production)
│ │ ├── mod.rs # Parser → Bytecode
│ │ └── borrow_ck.rs # Borrow checker
│ ├── ir/
│ │ ├── mod.rs # SSA IR types
│ │ ├── lower.rs # Bytecode → IR
│ │ ├── typecheck.rs # Type inference
│ │ ├── verify.rs # Validation
│ │ ├── opt.rs # Optimizations
│ │ └── format.rs # IR serialization
│ ├── backend/
│ │ ├── mod.rs # Backend trait
│ │ ├── cranelift.rs # JIT backend
│ │ ├── jit.rs # JIT runtime
│ │ ├── layout.rs # Memory layout
│ │ └── llvm/ # AOT backend
│ ├── runtime/
│ │ ├── mod.rs # Runtime module
│ │ ├── abi.rs # NaN-boxed values
│ │ ├── heap.rs # Memory allocation
│ │ ├── stubs.rs # FFI bridge
│ │ └── async/
│ │ ├── mod.rs # Core async traits
│ │ ├── task.rs # Task abstraction
│ │ ├── reactor.rs # Basic epoll/kqueue
│ │ └── runtime_impl.rs # Simple executor
│ ├── vm/ # Debug interpreter
│ │ ├── mod.rs # VM implementation
│ │ ├── value.rs # Runtime values
│ │ ├── opcodes.rs # Bytecode opcodes
│ │ └── stdlib_setup.rs # Minimal setup
│ └── stdlib/
│ └── mod.rs # console, ByteStream only
├── docs/
│ ├── docs/self-hosting.md # Self-hosting roadmap
│ └── docs/future/ # Future architecture docs
│ ├── rolls-design.md # Rolls (system libraries)
│ └── unroll-design.md # Unroll (tooling)
└── tests/
Script has three compiler implementations working toward full self-hosting:
| Compiler | Location | Status | Purpose |
|---|---|---|---|
| Rust | src/compiler/ |
✅ Production | Native binaries via LLVM/Cranelift |
| Bootstrap | bootstrap/*.tscl |
✅ Reference | Self-compiling bytecode compiler |
| Modular | compiler/*.tscl |
✅ Complete | Full compiler with LLVM IR output |
The self-hosted compiler is complete and can generate native binaries:
# Generate LLVM IR using self-hosted compiler (runs on Rust VM)
./target/release/script compiler/main.tscl llvm myapp.tscl
# Compile to native binary
clang myapp.tscl.ll -o myapp
./myappCurrent requirements:
- Rust: Still needed to build the VM that runs the self-hosted compiler
- LLVM/Clang: Required to compile generated LLVM IR to native code
Roadmap to full independence:
Current: Rust VM runs compiler/*.tscl → LLVM IR → clang → Native
Future: Prebuilt scriptc binary → LLVM IR → clang → Native (no Rust!)
See docs/docs/self-hosting.md for detailed roadmap.
Core language complete. 113 tests passing. Self-hosted compiler generates native binaries ~30x faster than VM.
| Component | Status |
|---|---|
| Runtime kernel (NaN-boxing, allocator) | ✅ Complete |
| SSA IR + optimizations | ✅ Complete |
| Native backends (Cranelift JIT, LLVM AOT) | ✅ Complete |
| Language features (classes, async, modules) | ✅ Complete |
| Self-hosting compiler | ✅ Complete |
| Rolls ecosystem (HTTP, TLS, fs) | 📋 Planned |
See PROGRESS.md for detailed implementation notes.
# Run all tests
cargo test --release
# Run specific IR tests
cargo test --release ir::The Script ecosystem will eventually include:
- Rolls: Official system libraries (
@rolls/http,@rolls/tls,@rolls/fs, etc.) - Unroll: Package manager, build system, and developer tooling
See docs/future/ for detailed architecture designs.
Contributions are welcome! Please feel free to submit a Pull Request.
Script is distributed under the terms of the Apache License (Version 2.0).
See LICENSE for details.