snaq-lite is a reactive arithmetic language for quantities, units, and uncertainty. Expressions form a computation graph: when an argument changes, only dependent operations are recomputed (via Salsa). It runs natively in Rust or from WebAssembly.
The language combines numeric quantities with units (SI and common derivatives), symbolic math (e.g. keep π in expressions or substitute for numbers), uncertainty (implicit from decimal places or explicit with ~), and statistical comparisons that yield crisp or uncertain booleans. You can write variable bindings, blocks, vectors, and conditionals (if … then … else), and call built-ins like sin, cos, tan, max, and min. By default evaluation is symbolic; use run_numeric() when you want a single numeric result.
- Quantities and units — Literals like
100 m,1 km/hour,2 pi rad; unit conversion withas(e.g.10 km as m). SI base and derived units, time, angle, area, volume, and Numbat-style units (mile, parsec, eV, etc.); plural names accepted (e.g. meters → meter). STEM-friendly: volume (L, mL), pressure (bar, atm, psi), imperial mass (lb, oz), Fahrenheit, calories/Wh/kWh, concentration (molar, M), percent/ppm/ppb, arcmin/arcsec, knot, and physical constants (c, h, R) — see docs/UNITS_AND_QUANTITIES.md. - Numbers and precision — Values carry mean and variance. Implicit variance from decimal places (e.g.
10.5); explicit error withvalue ~ error(e.g.10 ~ 2). - Symbols — Unknown identifiers are symbols (
pi,π,eor custom). Default evaluation keeps them symbolic; substitute viarun_numeric()or a symbol registry. - Comparisons —
==,!=,<,<=,>,>=(same dimension). Result is a FuzzyBool:true,false, oruncertain(prob)when variance matters. Vectors: element-wise, outer, or row×column reduction. - Variables and blocks — Immutable bindings
name = expression; blocks{ ... }with multiple expressions (newline or;). Result = last expression (or last binding’s RHS). See docs/VARIABLE_BINDINGS.md and docs/BLOCKS_AND_EXPRESSIONS.md. - Conditionals —
if condition then expr else expr; condition must be FuzzyBool. Crisp true/false evaluate one branch; uncertain conditions blend both branches (numeric or symbolic). - Vectors — Literals
[ expr, ... ], postfix transpose'. Property.length; methods.map(fn (x) => body)or.map(sqrt)(single-arg built-in), and.take(start, length). Vector×scalar maps; vector×vector by orientation (element-wise, outer, dot). Display as[e1, e2, ...]. - Functions — Built-ins:
sin,cos,tan(one argument, angle);max,min(two same-dimension args). You can define your own withfn (params) => bodyorfn name(params) => body; optional default parameters, block bodies, and closures. Positional and named arguments (e.g.max(a: 1, b: 2)). See docs/FUNCTIONS.md.
run(expression)— Parse and evaluate; returnsResult<Value, RunError>. Result is symbolic by default (e.g.1 + pi→1 + π).Valuecan beNumeric(Quantity),Symbolic(...),FuzzyBool,Vector(...),Function(...), orUndefined. Multiple expressions and blocks allowed; result is the last value (orUndefinedif empty).run_numeric(expression)— Substitute symbols and returnResult<Quantity, RunError>. Errors on missing symbol, comparison result, or undefined result.run_scalar(expression)— Likerun_numericbut returnsResult<f64, RunError>; errors if not dimensionless or undefined.
Use run_with_registry(input, &UnitRegistry) (and optional symbol registry) for custom units/symbols.
| Input | Result (conceptually) |
|---|---|
1 + 2 |
3 |
x = 10 then x + 1 |
11 |
{ a = 2; b = 3; a * b } |
6 |
1 + pi |
1 + π (symbolic) or numeric with run_numeric |
100 km / hour |
100 km/hour |
10 km as m |
10000 m |
sin(pi rad) |
0 |
1 < 2 |
true; with variance, may be uncertain(prob) |
[1, 2, 3] |
vector [1, 2, 3] |
fn mysum(a, b) => (a + b) then mysum(1, 2) |
3 |
CLI: snaq-lite "1 + pi" (symbolic); snaq-lite --numeric "1 + pi" (numeric). With --numeric (or -n), if the result is not a single quantity (e.g. a date, a comparison, a map), the CLI still prints the result in display form instead of erroring.
- snaq-lite-lang — Core library (parser, AST, eval). Platform-agnostic.
- snaq-lite-cli — Native CLI binary.
- snaq-lite-wasm — WASM build for the web.
- apps/frontend — TanStack Start SPA; LSP in a Web Worker (WASM). Multiple projects (create, rename, delete, export/import); each project is a canvas of computation and presentation blocks. Uses pnpm.
- All (from root):
pnpm run install:frontendonce, thenpnpm test(backend then frontend). - Backend only:
cargo test --workspace; Clippy:cargo clippy --workspace -- -D warnings; WASM (Node):wasm-pack test --node crates/snaq-lite-wasm(requires wasm-pack and Node). - Frontend only:
pnpm run install:frontendthenpnpm run test:frontend(from root) orpnpm testinapps/frontend. - Frontend E2E (Playwright): From root,
pnpm smoketestis the single command: it ensures the environment is ready (frontend deps + Playwright Chromium), builds the frontend, starts the preview server, runs the E2E tests, and stops the server when done. No separate bootstrap or sandbox is needed. For local iteration against an already-running dev server, usepnpm smoketest:dev(no server is started or stopped). - Lint:
pnpm run lint(backend clippy + frontend ESLint; from root).
- Native CLI:
cargo run -p snaq-lite-cli - WASM:
rustup target add wasm32-unknown-unknown, install wasm-pack, then
wasm-pack build crates/snaq-lite-wasm --target web(output incrates/snaq-lite-wasm/pkg/) - Frontend: From root,
pnpm run install:frontendthenpnpm run dev(dev server) orpnpm run build:frontendthenpnpm run preview.