Skip to content

djinilabs/snaq-lite

Repository files navigation

snaq-lite

Tests

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.


Language at a glance

  • Quantities and units — Literals like 100 m, 1 km/hour, 2 pi rad; unit conversion with as (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 with value ~ error (e.g. 10 ~ 2).
  • Symbols — Unknown identifiers are symbols (pi, π, e or custom). Default evaluation keeps them symbolic; substitute via run_numeric() or a symbol registry.
  • Comparisons==, !=, <, <=, >, >= (same dimension). Result is a FuzzyBool: true, false, or uncertain(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.
  • Conditionalsif 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 with fn (params) => body or fn 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.

API

  • run(expression) — Parse and evaluate; returns Result<Value, RunError>. Result is symbolic by default (e.g. 1 + pi1 + π). Value can be Numeric(Quantity), Symbolic(...), FuzzyBool, Vector(...), Function(...), or Undefined. Multiple expressions and blocks allowed; result is the last value (or Undefined if empty).
  • run_numeric(expression) — Substitute symbols and return Result<Quantity, RunError>. Errors on missing symbol, comparison result, or undefined result.
  • run_scalar(expression) — Like run_numeric but returns Result<f64, RunError>; errors if not dimensionless or undefined.

Use run_with_registry(input, &UnitRegistry) (and optional symbol registry) for custom units/symbols.


Examples

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.


Structure

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

Run tests

  • All (from root): pnpm run install:frontend once, then pnpm 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:frontend then pnpm run test:frontend (from root) or pnpm test in apps/frontend.
  • Frontend E2E (Playwright): From root, pnpm smoketest is 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, use pnpm smoketest:dev (no server is started or stopped).
  • Lint: pnpm run lint (backend clippy + frontend ESLint; from root).

Build and run

  • 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 in crates/snaq-lite-wasm/pkg/)
  • Frontend: From root, pnpm run install:frontend then pnpm run dev (dev server) or pnpm run build:frontend then pnpm run preview.

About

A simple arithmetic-like language

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages