Rust ยท scripting language
Sprite is a tiny, embeddable scripting language in Rust. A one-pass lexer, a
recursive-descent parser, and a tree-walking interpreter, with one eval call to
drop into any Rust program. Try it below, right in your browser.
let, print, if/else, while, and arithmetic.This box runs a small JavaScript
reimplementation of a subset of Sprite (expressions, let, print,
if/else, while) so you can poke at the syntax here. The real
interpreter is the Rust crate.
the demo is a subset in JavaScript, the real engine is the Rust crate behind the CLI and the eval API
The crate ships a CLI. Point it at a script file, or start a REPL. This is the actual output of the bundled examples, captured from a release build.
factorial.sprite prints fact(1) through fact(10); shown abridged
Sprite is deliberately at one end of the spectrum. The README says it plainly: if you want the capable option, reach for Lua or Rhai. If you want to understand every line of the interpreter running your script, reach for Sprite.
A mature, fast embeddable language with decades of features, coroutines, metatables, and a large C runtime. Far more capable, and far more to learn.
A capable Rust-native scripting engine with a rich type system and deep Rust interop. Excellent when you need that breadth.
The other end: a handful of value types, no standard library beyond print, no bytecode, no VM. Small enough to read start to finish, with one eval call and a Session for persistent state.
The pieces that matter
One pass over the source, no lookahead beyond one character, no external lexer generator. Skips whitespace and # comments, reports a typed Lex error on bad input, never panics.
Recursive descent producing a plain AST. Standard precedence from assignment down to calls and primaries. Returns a typed Parse error rather than crashing.
Walks the AST directly. No bytecode, no intermediate representation, no VM. A block evaluates to its last expression so a REPL prints something useful.
Five value types: numbers (f64), strings, booleans, nil, and functions. Functions close over their defining scope via a reference-counted, interior-mutable environment.
A recursion counter (MAX_DEPTH = 200) turns pathological input like thousands of nested parens into a Parse error instead of a native stack overflow.
A call-depth counter (MAX_CALL_DEPTH = 512) turns runaway recursion in a Sprite script into a catchable Runtime("stack overflow"), not a crashed host process.
Variables, arithmetic, comparisons, booleans, control flow, functions, closures, and string concatenation.
# variables and arithmetic let x = 2 + 3 * 4 print(x) # 14 # recursive functions fn fact(n) { if n <= 1 { return 1 } return n * fact(n - 1) } print(fact(5)) # 120 # while loops let i = 1 let sum = 0 while i <= 10 { sum = sum + i i = i + 1 } print(sum) # 55 # closures fn make_counter() { let count = 0 fn increment() { count = count + 1 return count } return increment } let counter = make_counter() print(counter()) # 1 print(counter()) # 2
A clean API for dropping a scripting layer into any Rust program. Errors are typed results, never panics on bad input.
let value = sprite::eval(
"2 + 3 * 4"
).unwrap();
assert_eq!(
value.to_string(),
"14"
);
let mut s = sprite::Session::new();
s.run("let score = 0").unwrap();
s.run("score = score + 10").unwrap();
let v = s.run("score").unwrap();
assert_eq!(v.to_string(), "10");
A library API, a CLI, and a test suite. That is the whole surface.
sprite::eval(source) for one-shot evaluation, or sprite::Session::new() when variables and functions should survive across calls. Both return Result<Value, SpriteError>.
sprite run FILE executes a script; running sprite with no arguments starts a REPL. Example scripts live in examples/.
One SpriteError type with three variants, Lex, Parse, and Runtime, each carrying a message. No stage panics, unwraps, or indexes out of bounds on attacker-controlled input.
The suite covers precedence, control flow, closures, string concatenation, and error paths, including a 10,000-deep paren expression that must return a Parse error rather than crash.
# build, run an example, test
git clone https://github.com/pavanchow/sprite
cd sprite
cargo build --release
cargo run --release -- run examples/factorial.sprite
cargo test