ClearScript Documentation
A type-safe scripting language with a Rust runtime, embeddable in Node.js and the browser. This page covers installation, the language tour, the standard library, and every embedding API.
Installation
Three ways to run ClearScript depending on what you're building:
1. The CLI (native Rust binary)
git clone https://github.com/clearscript/clearscript
cd clearscript
cargo build --release
./target/release/clear-cli run examples/hello.clear
2. Node.js embedding (npm)
npm install clearscript
Single platform-independent WASM bundle — no native compilation, no node-gyp. Works on macOS, Linux, Windows.
3. Browser (WASM)
import init, { run_clearscript } from './wasm/clear_wasm.js';
await init();
const result = JSON.parse(run_clearscript('fn main() { print("hi") }'));
Hello, World
Save the following as hello.clear:
fn main() {
print("Hello, ClearScript!")
}
Run it:
clear-cli run hello.clear
# or via Node:
npx clear-node hello.clear
The main() function is invoked automatically if defined.
The CLI
| Command | Description |
|---|---|
clear-cli run <file> | Parse and execute a .clear file |
clear-cli check <file> | Validate syntax, print diagnostics, no execution |
clear-cli repl | Start the interactive REPL |
clear-cli fmt <file> | Format source (placeholder — coming soon) |
clear-cli build <file> | Build to WASM or native (placeholder) |
clear-cli version | Print version |
Syntax overview
ClearScript is expression-oriented. Almost everything returns a value. Statements end at newlines (no semicolons required).
// Single-line comment
/* Block
comment */
let name = "world" // immutable
let mut count = 0 // mutable
fn greet(n: String) {
print("Hello, ", n)
}
fn main() {
greet(name)
}
Types & values
| Type | Literal example | Notes |
|---|---|---|
Int | 42, 0xFF, 0b1010, 1_000_000 | 64-bit signed |
Float | 3.14, 1.5e-3 | IEEE 754 64-bit |
String | "hi", "Hello, ${name}!" | UTF-8, supports interpolation |
Bool | true, false | No coercion to/from int |
Nil | nil | Single null value (no undefined) |
List | [1, 2, 3] | Heterogeneous allowed |
Map | {x: 1, y: 2} | String keys |
Function | fn(x) { x * 2 } | First-class, captures lexically |
Variables
let x = 5 // immutable, type inferred
let mut y = 10 // mutable
let z: Int = 15 // explicit type annotation
y = y + 1 // ok, mut
// x = x + 1 // compile error: x is immutable
Functions & lambdas
fn add(a: Int, b: Int) -> Int {
a + b // last expression is the return value
}
// Lambdas — first-class functions
let double = |x| x * 2
let result = map([1, 2, 3], double) // [2, 4, 6]
// Inline:
filter(nums, |n| n > 10)
Control flow
// if/else (expressions, return values)
let tier = if score >= 90 { "A" } else if score >= 75 { "B" } else { "C" }
// while
while count < 10 { count = count + 1 }
// for-in (works on lists, ranges, strings, maps)
for i in 0..10 { print(i) }
for ch in "hello" { print(ch) }
// loop / break / continue
loop {
if done { break }
}
Pattern matching
match value {
0 => "zero",
1 => "one",
n if n > 0 => "positive",
_ => "other",
}
// Bind values:
match result {
0 => print("empty"),
n => print("got", n), // `n` binds to value
}
Collections
let nums = [1, 2, 3]
nums[0] // 1 — index access
len(nums) // 3
let user = {name: "Ada", age: 36}
user["name"] // "Ada"
String interpolation
Any expression can be spliced into a string with ${...}. The hole is parsed and evaluated at runtime; the result is converted to a string and concatenated.
let name = "Ada"
let age = 36
print("Hello, ${name}! You are ${age} years old.")
// Hello, Ada! You are 36 years old.
// Arbitrary expressions — not just identifiers:
let nums = [1, 2, 3]
print("Sum = ${sum(nums)}, doubled = ${sum(nums) * 2}")
// Sum = 6, doubled = 12
${...}: arithmetic, function calls, method chains, conditionals, etc. Balanced braces inside the hole are tracked correctly.
Operators
| Category | Operators |
|---|---|
| Arithmetic | + - * / % ** |
| Comparison | == != < > <= >= |
| Logical | && || ! (short-circuit) |
| Bitwise | & | ^ ~ << >> |
| Pipe | |> — data |> transform |> print |
| Null coalesce | ?? — x ?? default |
| Range | 0..10 (exclusive), 0..=10 (inclusive) |
| Assignment | = += -= *= /= %= **= |
== is strict — there is no loose-equality == like JavaScript. 0 == false is a type error.
Standard Library — I/O
| Function | Description |
|---|---|
print(...args) | Space-joins args, emits one line. |
println(...args) | Alias for print. |
Standard Library — String
| Function | Example |
|---|---|
len(s) | len("hi") → 2 |
upper(s) | upper("rust") → "RUST" |
lower(s) | lower("RUST") → "rust" |
trim(s) | trim(" x ") → "x" |
split(s, sep) | split("a,b,c", ",") → ["a","b","c"] |
join(arr, sep) | join(["a","b"], "-") → "a-b" |
replace(s, from, to) | String find/replace |
contains(s, sub) | Substring check |
starts_with(s, p) | Prefix check |
ends_with(s, p) | Suffix check |
repeat(s, n) | repeat("ab", 3) → "ababab" |
chars(s) | String → array of single-char strings |
pad_left / pad_right | Pad to length with char |
Standard Library — Array
| Function | Example |
|---|---|
len(arr) | Number of items |
push(arr, v) | Append |
pop(arr) | Remove + return last |
first(arr) / last(arr) | Element access |
reverse(arr) | Reverse |
sort(arr) | Ascending sort |
unique(arr) | Deduplicate, preserving order |
flatten(arr) | One level of nested → flat |
sum(arr) / product(arr) | Numeric reduction |
range(start, end) | Generate [start..end) |
enumerate(arr) | Pairs of [index, value] |
zip(a, b) | Pair elements |
Standard Library — Math
| Function | Example |
|---|---|
abs(n) | Absolute value |
sqrt(n) | Square root |
pow(base, exp) | Exponentiation |
floor(n) / ceil(n) / round(n) | Rounding |
min(a, b) / max(a, b) | Two-arg min/max |
clamp(n, lo, hi) | Restrict to range |
random() | Float in [0.0, 1.0) |
sin(n) / cos(n) / tan(n) | Trigonometry (radians) |
log(n) / log2(n) / log10(n) | Natural / base-2 / base-10 logarithm |
exp(n) | e^n |
Standard Library — Functional
map([1,2,3], |x| x * 2) // [2, 4, 6]
filter([1,2,3,4], |x| x % 2 == 0) // [2, 4]
reduce([1,2,3], |acc, x| acc + x, 0) // 6 — args: (list, fn, init)
Standard Library — Map
| Function | Example |
|---|---|
keys(m) | Sorted array of keys (deterministic) |
values(m) | Values in key-sorted order |
has_key(m, k) | Bool — key membership |
Standard Library — JSON
| Function | Example |
|---|---|
json_parse(s) | json_parse("[1,2,3]") → [1, 2, 3] |
json_stringify(v) | json_stringify({"a": 1}) → "{\"a\":1}" |
Round-trips through serde_json. Maps, arrays, strings, numbers, bools, and null are all supported.
Standard Library — Filesystem
| Function | Description |
|---|---|
fs_read(path) | Read file → string |
fs_write(path, contents) | Write string → file |
fs_exists(path) | Bool — file exists |
Native only. In WASM (browser playground) these throw a clear "filesystem not available" error.
Standard Library — Env & Time
| Function | Description |
|---|---|
env_get(name) | Read environment variable, or empty string if unset |
env_args() | Array of CLI args passed after the script |
time_now() | Unix epoch seconds (float) |
Standard Library — Type & assertion
| Function | Description |
|---|---|
type_of(v) | Returns type name string |
to_string(v) / to_int(v) / to_float(v) / to_bool(v) | Explicit conversions |
assert(cond) | Throws on false |
assert_eq(a, b) | Throws if a != b |
Embedding in Node.js
Install:
npm install clearscript
Run source
const cs = require('clearscript');
// Returns { ok, output, error } — never throws on script errors
const result = cs.run(`
fn main() {
let nums = [1, 2, 3, 4, 5]
print("sum:", sum(nums))
}
`);
console.log(result.output); // ['sum: 15']
// execute() throws on failure
try {
const out = cs.execute('fn main() { print("hi") }');
} catch (e) {
if (e instanceof cs.ClearScriptError) {
console.error(e.message, e.output);
}
}
require() .clear files
require('clearscript/register'); // install loader hook
require('./hello.clear'); // runs the file, output → stdout
Reusable context (REPL / notebook)
const ctx = cs.createContext();
ctx.eval('fn greet(name: String) { print("hi", name) }');
ctx.eval('fn main() { greet("world") }'); // → ['hi world']
ctx.reset();
ESM
import { run, execute, createContext } from 'clearscript';
CLI
npx clear-node hello.clear
npx clear-node --eval 'fn main() { print("hi") }'
npx clear-node --repl
npx clear-node --version
Node API reference
| API | Returns | Throws |
|---|---|---|
run(source) | { ok, output, error } | Only on TypeError |
execute(source) | string[] (output lines) | ClearScriptError |
runFile(path) | { ok, output, error, file } | fs errors |
executeFile(path) | string[] | ClearScriptError |
createContext() | { eval, reset, source } | — |
version() | string | — |
ClearScriptError | class extending Error with .output | — |
Embedding in the browser (WASM)
<script type="module">
import init, { run_clearscript } from './wasm/clear_wasm.js';
await init();
const { ok, output, error } = JSON.parse(
run_clearscript('fn main() { print("hi from wasm") }')
);
document.body.textContent = output.join('\n');
</script>
Diagnostic codes
| Code | Phase | Meaning |
|---|---|---|
E0001 | Lexer | Unexpected character |
E0002 | Lexer | Unterminated string |
E0003 | Lexer | Unterminated comment |
E0004 | Lexer | Invalid number literal |
E0010 | Parser | Unexpected token |
E0011 | Parser | Unexpected end of input |
E0012 | Parser | Missing token |
E0013 | Parser | Invalid expression |
E0020 | Semantic | Undefined variable (with "did you mean?" suggestion) |
E0021 | Semantic | Undefined function |
E0022 | Semantic | Undefined type |
E0023 | Semantic | Duplicate definition |
E0024 | Type checker | Type mismatch (e.g. let x: Int = "s") |
E0025 | Type checker | Invalid operation for types |
E0026 | Type checker | Argument count / type mismatch in call |
Current limitations
ClearScript is pre-1.0. The compiler frontend, tree-walking interpreter, type checker, and module resolver are complete. Still in progress:
- Trait system — declarations parse; method dispatch via traits is in design.
- Async / await — syntax reserved, runtime not yet implemented.
- Generics — parsed; full inference + monomorphisation in design.
- Bytecode VM / JIT — currently a tree-walking interpreter. Bytecode planned for v0.3.
- String interpolation in patterns —
${expr}works in normal strings but match-arm patterns cannot contain interpolated strings yet.
✅ Shipped in v0.1.3: bidirectional type checker, module resolution with cycle detection, JSON / fs / env / time stdlib, "did you mean?" suggestions.
✅ Shipped in v0.1.5: full ${expr} string interpolation, O(n) unique(), zero clippy warnings.
FAQ
Why a new language?
JavaScript's footguns (coercion, hoisting, this, ==) are all fixable, but only if the compiler refuses the bad code. ClearScript bakes those refusals into the language as the 10 Laws.
How fast is the WASM interpreter?
The current tree-walking interpreter is suitable for scripts and config DSLs — roughly 5-20× slower than V8 for compute, comparable for I/O-bound work. A bytecode VM is on the roadmap.
Can I use it in production?
Not yet. v0.1 is for early experimentation. Open source launch on June 25, 2026.
Is there an N-API native addon?
Not yet — the npm package uses WASM for portability. N-API is on the roadmap for hot-path embedding scenarios.