Jest #
Jest is a dynamically-typed, functional programming language whose syntax is plain JSON. A Jest program is a JSON value; evaluation transforms it into another JSON value, or produces a runtime error.
Jest is pure: no mutation, no I/O, no side effects. It is designed to express portable logic that can be embedded in data, transmitted over a network, stored in a database, and evaluated anywhere a JSON parser exists.
Quick start #
cargo build --release
./target/release/jest program.jst # run a program
./target/release/jest program.jst '{"x": 42}' # pass arguments as JSON
Jest files are JSON with // line comments permitted. Multi-expression programs use JSONL (one JSON value per line); the CLI wraps them in an implicit do.
The core idea #
Singleton JSON objects — objects with exactly one key — are procedure calls. The key is the procedure name; the value is the argument (or argument list).
{"+" : [1, 2]} // addition → 3
{"str-upper": "hello"} // string op → "HELLO"
{"if": [true, "yes", "no"]} // conditional → "yes"
Everything else evaluates to itself (primitives) or element-wise (arrays and multi-key objects):
42 // → 42
[1, {"+" : [1, 1]}, 3] // → [1, 2, 3]
{"a": {"+" : [1, 1]}, "b": false} // → {"a": 2, "b": false}
Variables #
let* binds variables sequentially; var looks them up:
{"let*": [
[["name", "world"]],
{"str-join": [["Hello", {"var": "name"}], ", "]}
]}
→ "Hello, world"
var also traverses nested structures:
{"var": ["user", "address", "city"]}
Functions #
Functions are first-class values. fn creates a closure that eagerly evaluates its arguments:
{"let*": [
[["add", {"fn": [["x", "y"], {"+": [{"var": "x"}, {"var": "y"}]}]}]],
{"add": [3, 4]}
]}
→ 7
Recursive functions use defrec:
{"do": [
{"defrec": [["fact", [{"var": "n"}],
{"if": [{"==": [{"var": "n"}, 0]},
1,
{"*": [{"var": "n"}, {"fact": [{"-": [{"var": "n"}, 1]}]}]}]}]]},
{"fact": [10]}
]}
macro creates Lisp-style syntax macros — the body constructs code that is auto-evaluated in the caller's scope.
Mutual recursion #
letrec binds mutually recursive functions:
{"letrec": [
[["even", {"fn": [["n"],
{"if": [{"==": [{"var": "n"}, 0]}, true, {"odd": [{"-": [{"var": "n"}, 1]}]}]}]}],
["odd", {"fn": [["n"],
{"if": [{"==": [{"var": "n"}, 0]}, false, {"even": [{"-": [{"var": "n"}, 1]}]}]}]}]],
{"apply": [{"var": "even"}, [10]]}
]}
→ true
Program arguments #
The args variable holds the value passed on the command line:
{"str-upper": {"var": "args"}}
./jest greet.jst '"hello"' # → "HELLO"
Programs and modules #
do sequences expressions, threads def/defn/defmacro bindings, and returns the last non-definition value:
{"def": ["pi", 3.14159]}
{"defn": ["circle-area", [{"var": "r"}], {"*": [{"var": "pi"}, {"var": "r"}, {"var": "r"}]}]}
{"circle-area": [5]}
defn is shorthand for def + fn (non-recursive). Use defrec for recursive functions:
{"defrec": [["fact", [{"var": "n"}],
{"if": [{"==": [{"var": "n"}, 0]},
1,
{"*": [{"var": "n"}, {"fact": [{"-": [{"var": "n"}, 1]}]}]}]}]]}
import loads another .jst file (requires Runtime::with_file_loader):
{"import": ["./utils.jst", ["helper-fn", "other-fn"]]}
names can be:
["a", "b"]— import specific names flat"ns"— bind entire module asns{"local": "exported"}— rename on importnull— wildcard flat import (module must return an object)
Procedures as values #
All Jest values are plain JSON — including procedures. A function value is a JSON object:
{"fn": [["x"], {"+": [{"var": "x"}, 1]}, {"captured-var": 42}]}
This means functions can be stored, transmitted, and re-evaluated in any scope. The captured environment is embedded directly in the value.
Built-ins #
| Category | Procedures |
|---|---|
| Arithmetic | + - * / % |
| Comparison | == != < <= > >= |
| Math | abs floor ceil round pow sqrt min max |
| Logic | not if |
| Variables | var |
| Binding | let let* letrec |
| Procedures | fn macro apply eval |
| Sequencing | do def defn defmacro defrec deftype import |
| Arrays | count nth first rest cons concat slice |
| Objects | keys values merge assoc dissoc has? |
| Strings | str str-length str-slice str-split str-join str-contains? str-starts-with? str-ends-with? str-index-of str-replace str-upper str-lower str-trim |
| Types | null? bool? number? int? float? string? array? object? proc? typeof type-of |
| Conversion | number int float |
| Scope | get-scope |
See SPEC.md for complete documentation of every built-in.