The Jest Programming Language
Rust 82%
EJS 16%
Lean 2%
Python <1%
Shell <1%
<1%

README.md

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 as ns
  • {"local": "exported"} — rename on import
  • null — 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.