# JENNIFER.md - the Jennifer language, for coding assistants Drop this file into a project where you write **Jennifer** (`.j` files) and point your AI coding assistant at it ("we code in Jennifer, see JENNIFER.md"). It is a self-contained reference to the language so an assistant with no prior knowledge of Jennifer can write correct code. It describes the *language*, not the interpreter's internals. Jennifer is a batteries-included, interpreted language (tree-walking interpreter written in Go/TinyGo). Source files use the `.j` extension. Run a program with `jennifer run program.j`, start a REPL with `jennifer repl`. **Full documentation** - guides, the complete library reference, and an alphabetical cheatsheet of every builtin - is hosted at . If an assistant has web access, fetch the exact signature of any function there. Source and issues: . > This file mirrors the authoritative spec. If something here conflicts with the > [hosted docs](https://jennifer-lang.dev/), the docs win - tell the > maintainer. > **Keep this file in sync with the installed interpreter - it is your job.** > This is a copy dropped into a project, so it silently goes stale as the local > `jennifer` is upgraded (or was already older than this copy). A JENNIFER.md > that does not match the installed release is the most common reason an > assistant writes `.j` code the interpreter then rejects. So **you, the > assistant, are responsible for keeping this file aligned with the installed > release** - do this before you rely on it (and again whenever code you wrote > fails with an "unknown"/"not defined"/pragma-version error). > > **Cheap self-check first.** Once you have synced this file you leave a marker on > its last line recording the exact `jennifer version` string it was fetched for > (``). Run `jennifer version` and compare it > to that marker: **equal -> this copy is current, do nothing**; **different, or > no marker yet (a pristine copy just dropped into the project) -> run the sync > below**. That is one command and a string compare, so it is cheap enough to do > at the start of any `.j` work; you only fetch over the network when the marker > actually disagrees. > > **Sync:** > > 1. Read the installed version: `jennifer version` (e.g. `0.24.0`, or > `0.24.0-dev+N.`). > 2. Fetch the JENNIFER.md that matches it from the source repo: > - a **development build** - any version containing `-dev` (e.g. > `0.24.0-dev+N.`) or the bare string `dev` - is ahead of the last > release, so **always** fetch the `main` branch, regardless of the `X.Y.Z` > it prints: > `https://raw.githubusercontent.com/jennifer-language/jennifer/main/JENNIFER.md`; > - a **clean release** `X.Y.Z` (no `-dev` suffix) fetches its own tag: > `https://raw.githubusercontent.com/jennifer-language/jennifer/X.Y.Z/JENNIFER.md`. > 3. If the fetched file differs from this local copy, **replace this local copy > with it**. > 4. **Stamp the marker.** Whether or not step 3 replaced anything, set the last > line of the local copy to the full `jennifer version` string you read in > step 1, so the next self-check is a cheap compare: > ``. Record the whole > string verbatim - for a dev build it carries the commit (`+N.`), so the > marker changes the moment the installed interpreter is rebuilt and your check > catches it. (A dev build always re-fetches `main` per step 2, since `main` > advances even when the local commit has not; the marker still tells you the > interpreter itself moved.) > > Match the **installed interpreter**, not the newest release: if the local > `jennifer` is `0.24.0` while `0.25.0` is out, fetch the `0.24.0` JENNIFER.md - > this file must describe the features the interpreter you actually run has, no > more and no less. (No web access? Then note in your reply that you could not > verify JENNIFER.md against the installed `jennifer version`, so some guidance > here may not match it.) --- ## The 10 rules that trip people up Read these first; they are where Jennifer differs from Python/JS/Go and where an assistant usually guesses wrong: 1. **Variables are referenced with a `$` sigil: `$x`.** But the *declaration* uses a bare name: `def x as int init 5;` then use `$x`. Writing `def $x` is an error; using bare `x` in an expression is an error. 2. **Constants are referenced bare (no `$`): `MAX`.** They are `UPPER_CASE`. Reading one *with* `$` (`$MAX`) is a parse error - the sigil is for mutable variables only. A method may not share a name with a top-level variable or constant either (`def foo ...; func foo() {}` is rejected). 3. **Method calls are bare and take `()`: `greet()`.** The parser tells a call from a constant by the `(`. 4. **`/` is true division and always returns `float`** (like Python 3). `5 / 2 == 2.5`. Use `//` for integer/floor division: `5 // 2 == 2`. `%` is **floored** to match `//` (`-7 % 3 == 2`, `7 % -3 == -2`). Integer arithmetic that overflows `int64` is a runtime error (no silent wrap, including `-(MinInt64)`), and **float arithmetic that overflows to a non-finite value is an error too** (`1e308 * 10.0` raises, never `+Inf`/`NaN`); a mixed `int`/`float` comparison is exact (no lossy promotion). 5. **Identifiers are letter-initial, then letters + digits, <= 64 chars.** No underscores in variable/method/parameter/library names. `myVar`, `var2`, `sha256` are fine; `my_var` is not, and a name cannot start with a digit. (Constants are the *only* names that take `_`: `MAX_RETRIES`.) 6. **Statements end with `;`.** Whitespace (including newlines) is insignificant everywhere. 7. **Comments are `#` (line) and `/* */` (block, nests).** Not `//` - that is the floor-division operator. 8. **No `++`, `--`, `+=`, or any compound assignment.** Only `$x = EXPR;`. 9. **Value semantics: assignment and argument passing copy.** `$b = $a;` then mutating `$b` never touches `$a`. Same for lists, maps, structs, bytes. 10. **Logical operators are words: `and`, `or`, `not`** (not `&&`/`||`/`!`). `&` `|` `^` `~` are the *bitwise* operators. --- ## Lexical basics - **Identifiers** (variables, methods, parameters, library names): `[A-Za-z][A-Za-z0-9]*`, <= 64 chars. Letter-initial (a digit-initial token is a number), then letters and digits; no underscores. Legal: `myVar`, `sha256`, `x2`, `toUtf8`. - **Constant names**: uppercase chunks joined by single `_`, with in-chunk digits: `[A-Z][A-Z0-9]*(_[A-Z][A-Z0-9]*)*` (each chunk starts with a letter). Legal: `MAX`, `MAX_RETRIES`, `HTTP_OK`, `SHA256`, `HTTP2`, `SCRAM_SHA256`. Illegal: `_MAX`, `MAX_`, `MAX__INT`, `maxInt`, `AES_256` (write `AES256`). - **`.j` import paths** are strings and may contain digits, `_`, `/`. - A leading `#!` line is allowed (shebang): `#!/usr/bin/env -S jennifer run`. A file with a shebang may also be run **without a `.j` extension** (as an executable installed under a bare command name); `import` / `include` targets still require `.j`. ## Types Primitive: `null`, `int`, `float`, `string`, `bool`, `bytes`. Compound: `list of T`, `map of K to V`, user `struct`s, user `enum`s (sum types), `task of T` (a handle to a `spawn`ed computation), `func` (a first-class function value), `channel of T` (a CSP channel between goroutines). - **int** literals: `42`, `0xff`, `0o755`, `0b1010`, with `_` digit separators (`1_000_000`, `0xDEAD_BEEF`). - **float** literals: a `.` (`3.14`, `0.5`) or an `[eE][+-]?` exponent (`6.022e23`, `1.6e-19`, `1e10` - the exponent alone makes it a float); `_` separators in the mantissa only. Overflow (`1e400`) is an error, not `Infinity`; underflow (`1e-400`) rounds to `0.0` (a finite value; only the non-finite is banned). - **string** literals: two delimiters, one job each. `"..."` is **cooked** - escape sequences `\n \r \t \\ \" \' \0 \{ \}`, plus Unicode `\uXXXX` (exactly 4 hex, the BMP) and `\UXXXXXXXX` (exactly 8 hex, any plane, e.g. `\U0001F600`), are processed; a surrogate, an out-of-range code point, or the wrong digit count is a lex error. A cooked string also **interpolates**: an unescaped `{expr}` is a slot (see below). `'...'` is **raw** - no escape processing at all and no interpolation: every byte to the next `'` is literal (backslashes, braces, and newlines included), so `'\d+\.\d+'` is an 8-char string, `'{"port": 8080}'` is literal JSON, and a multi-line block is just a `'...'` that spans newlines. To put a `'` inside a string, use the cooked form: `"it's"`. There is no `r"..."` prefix. **Reach for raw `'...'` by default when the text carries backslashes or braces** - regex patterns (`'\d+\.\d+'`, `'\bword\b'`), Windows paths (`'C:\Users\me'`), JSON / template / brace-heavy literals (`'{"a": 1}'`, `'${VAR}'`), and any escape-dense blob. Raw needs **zero** escaping and reads exactly as written, so it is shorter and clearer than the cooked equivalent (`"\\d+\\.\\d+"`, `"C:\\Users\\me"`, `"\{\"a\": 1\}"` - error-prone and hard to read). Only pick cooked `"..."` when you actually need one of its three features: an escape sequence (`\n`, `\t`, `\uXXXX`), a `'` inside the string, or `{expr}` interpolation. Rule of thumb: **if you find yourself typing `\\`, `\{`, or `\}`, switch the whole literal to raw `'...'`.** - **string interpolation**: inside a cooked `"..."` string, each unescaped `{expr}` is a slot - one Jennifer **expression** evaluated in the current scope and stringified in place (the `convert.toString` form; no `use convert` needed). `"total: {$sum}, next {$n + 1}, up {strings.upper($s)}"`. A slot holds a single expression (a variable, constant, field / index access, arithmetic, a call), not a statement - a `;`, `def`, `if`, or assignment in a slot is a parse error, and an empty `{}` is an error. Write a literal brace as `\{` / `\}`; a bare unescaped `}` is a lex error. A **raw** `'...'` string never interpolates (it is the "no interpolation" form). No `f"..."` prefix - the cooked / raw split is the opt-in. Style: keep slots to variables, field / index access, and arithmetic; a side-effecting or expensive **call** in a slot is flagged by `lint` (L204) - compute it into a variable first. `undefined` variables in a slot are caught at parse time, exactly like any other reference. - **bool**: `true`, `false`. **null**: `null`. - **bytes** has no literal: build with `convert.bytesFromString(s, "utf-8")` or append into `def b as bytes;` with `$b[] = 65;`. - **list** literals: `[1, 2, 3]`, `[]`. Lists are homogeneous (one element type). - **map** literals: `{"a": 1, "b": 2}`, `{}`. Insertion-ordered. - **struct** literals: `Point{x: 1, y: 2}` after `def struct Point { x as int, y as int };`. Every field must be named. - **enum** (sum type): `def enum Shape { Circle { r as float }, Empty };` at top level. A value is one variant: construct with `Shape.Circle{r: 2.0}` or the payload-less `Shape.Empty` (cross-module: `alias.Shape.Circle{...}`). Value semantics + equality like structs. `def s as Shape;` (no init) zeroes to the **first** variant, payload zeroed. Read the payload only through `match` (no `$enum.field`). Names may be any case (an all-`UPPERCASE` name is a constant, so don't name a type all-caps). - **func** (first-class function value): a bare method name in expression position **is** the value; a name followed by `(` is a call. `def f as func init greet;` binds the method `greet` into a value; call it through a variable or any function-valued expression: `$f(args)`, `$fns[0](x)`, `makeAdder(1)(2)`. Pass and return them like any value (`func apply(fn as func, x as int) { return $fn($x); }`). Arity + argument types are checked at the call site (the `func` type has no signature). Immutable (copies share the method; value semantics hold). Zero value (`def f as func;`) is a **null** function - calling it errors. No `&NAME` sigil and no anonymous-function / closure literal (yet). Powers the higher-order `lists` layer (`map` / `filter` / `reduce` / `find` / `any` / `all` / `sortBy`). ```jennifer use lists; func dbl(n as int) { return $n * 2; } func isEven(n as int) { return $n % 2 == 0; } def xs as list of int init [1, 2, 3, 4]; def doubled as list of int init lists.map($xs, dbl); # [2, 4, 6, 8] def evens as list of int init lists.filter($xs, isEven); # [2, 4] ``` ## Variables and constants ```jennifer def x as int; # declare, zero value (0) def y as int init 5; # declare + initialize def const MAX as int init 10; # constant, must be initialized, never reassigned $x = 7; # assignment uses the $ sigil ``` - The name at the `def` site is bare (`def x`), never `def $x`. - `const` is deep: a const list/map/struct rejects mutation at any depth. ## Operators - Arithmetic: `+ - * / // %`. `/` is float division; `//` is floor. - Unary `-` (negation). `+` also concatenates two strings. - Comparison: `< > <= >= == !=` -> `bool`. `!=` is `not (a == b)`. The ordering operators (`< > <= >=`) work on two numbers or two strings (strings compare lexicographically by UTF-8 bytes); a string/number mix is a type error. There is no bare `!` (logical negation is the word `not`); a lone `!` is a lex error. - Logical (words, short-circuit): `and`, `or`, `not`. Operands must be `bool`. - Bitwise (int only): `& | ^ ~ << >>`. - Mixed int/float arithmetic promotes to `float`. - Range `..`: `lo..hi` is a half-open range `[lo, hi)`, int bounds only, non-associative, looser than every other operator. Builds a `list of int` (`def r as list of int init 0..n;`), or iterates lazily as a for-each source (`for (def i in 0..n)`), or slices (`$xs[a..b]`). `lo > hi` errors; `lo == hi` is empty. Always a fresh copy, never a view. - Precedence, low to high: `..` < `or` < `and` < `not` < comparison < `|` < `^` < `&` < shifts < `+ -` < `* / // %` < unary `- ~`. So `$x & 0xff == 0` parses as `($x & 0xff) == 0`, and `1+1..2*3` as `(1+1)..(2*3)`. ## Control flow ```jennifer if ($n > 0) { ... } elseif ($n < 0) { ... } else { ... } while ($i < 10) { ... } for (def i as int init 0; $i < 10; $i = $i + 1) { ... } # C-style for (def x in $xs) { ... } # for-each over a list (elements) for (def k in $m) { ... } # for-each over a map (keys, insertion order) for (def i in 0..10) { ... } # for-each over a half-open range (lazy) repeat { ... } until ($done); # post-test loop; body runs at least once match ($cmd) { # multi-way value dispatch (subject evaluated once) when "start" { start(); } when "stop", "halt" { # several values per arm (an OR of equality) stop(); } else { # optional default, must be last unknown(); } } # Over an enum subject, `match` dispatches on the variant and binds its payload. # It must be exhaustive (cover every variant) or carry an `else`. match ($shape) { when Circle(c) { area($c.r); } # $c is the variant's payload (a mini-struct) when Rect(rc) { area2($rc.w, $rc.h); } when Empty { } # payload-less variant: no binder } break; # exit innermost loop continue; # next iteration exit; # terminate the whole program (exit 0); exit EXPR sets the code (Unix: 0..255, masked to 8 bits) ``` Conditions must be `bool` (there is no truthiness). `and` / `or` **short-circuit** (`true or f()` never calls `f`). `break`/`continue` do not cross a method-call or `spawn` boundary. `match` compares the subject to each `when` value by strict `==`; the first matching arm runs and its values evaluate left-to-right only until a match. There is **no fall-through**, and `match` is **not** a `break` target - `break` / `continue` in an arm act on the enclosing loop. A bare `when Name { }` reads `Name` as a value and `{` as the block, so a struct-literal value needs parens: `when (Point{x: 1}) { ... }`. No matching arm and no `else` is a no-op. ### Errors ```jennifer use io; try { throw Error{kind: "bad", message: "nope", file: "", line: 0, col: 0}; } catch (e) { io.printf("%s\n", $e.message); } ``` `throw EXPR;` raises any value; convention is the auto-provided `Error` struct `{kind, message, file, line, col}`. `catch` also catches the runtime errors builtins raise (out-of-range, missing key, etc.), wrapped into `Error`. `exit`/`return`/`break`/`continue` are control flow, not catchable. ### Cleanup with `defer` ```jennifer use fs; func write(path as string) { def f as fs.File init fs.open($path, "write"); defer fs.close($f); # runs when the block exits, however it exits fs.writeString($f, "data\n"); } ``` `defer CALL(args);` schedules a **call** (method / namespaced / module call - a non-call is a parse error) to run when the **enclosing block** exits, on every path (`return`/`break`/`continue`/`throw`/`exit`/fall-through), **LIFO**. Arguments are evaluated at the `defer` line; the call runs at block exit. Block-scoped (a `defer` in a loop body runs each iteration); does not cross a method or `spawn` boundary. A deferred throw propagates and supersedes a pending error (never an `exit`). There is no `finally`. `errdefer CALL(args);` is the error-path variant: same form, same LIFO stack, but the call runs **only when the block exits with a propagating error** (a `throw` or a runtime error) - skipped on fall-through, `return`, `break`, `continue`, and `exit`. It is the undo half of an acquire whose resource must survive on success: ```jennifer func connect(addr as string) { def c as net.Conn init net.connect($addr); errdefer net.close($c); # a failed handshake closes; success keeps it open handshake($c); return Session{conn: $c}; } ``` ## Methods ```jennifer use io; func greet(name as string) { io.printf("hi %s\n", $name); # parameters referenced as $name return; # bare return -> null; or return EXPR; } greet("ada"); ``` - Bare parameter names (`name as string`), referenced inside as `$name`. - No return type is declared; the caller's `def x as T init f();` checks it. - Methods are **top-level only** (not nested). Recursion works. - Method bodies see global variables/constants. A method may not shadow a global name, nor share a name with a builtin from an imported library. - A program has **no required entry point**: top-level statements run in order. ## Compound types: indexing and iteration ```jennifer def xs as list of int init [1, 2, 3]; $xs[0]; # read -> 1 $xs[0] = 9; # write $xs[] = 4; # append (write-only; lists and bytes only) def m as map of string to int init {"a": 1}; $m["a"]; # read (missing key is an error - test with maps.has) $m["b"] = 2; # write def p as Point init Point{x: 1, y: 2}; $p.x; # field read $p.x = 5; # field write $grid[i][j] = v; # chains nest and mix [index] and .field def mid as list of int init $xs[1..3]; # slice: half-open [1, 3) copy $xs[2..]; $xs[..3]; $xs[..]; # open ends default to 0 / len ``` **Slicing (`$xs[a..b]`)** returns a fresh, value-semantic copy of a half-open `[a, b)` sub-range of a `list`, `bytes`, or `string` (rune-indexed). Open ends default to the extremes (`$xs[a..]`, `$xs[..b]`, `$xs[..]`). Bounds are strict (`0 <= a <= b <= len`). A slice is **read-only**: `$xs[a..b] = ...` is a parse error (it is a copy, so a write through it would do nothing). **Prefer `$xs[]` over `lists.push` in loops.** The `$xs[] = item;` append sugar (lists and bytes) mutates in place via copy-on-write - amortized O(N) to append N items. `$xs = lists.push($xs, item)` returns a *new* list each pass and copies the whole list, so the same loop is O(N^2). Use `$xs[]` to build a list element by element (a raster, a buffer, a big result set); use `lists.push` only when you want a fresh list and keep the original. `len(EXPR)` is a language built-in (not a library): rune count of a string, element count of a list, entry count of a map, byte count of bytes. ## Concurrency ```jennifer use task; def t as task of int init spawn { return expensiveThing(); }; def result as int init task.wait($t); # also poll / discard / waitAll / waitAny ``` `spawn { ... }` runs concurrently and evaluates to a `task of T`. It deep-copies its enclosing scope at launch, so there are no shared-memory data races. **Cancellation + timeouts.** `task.cancel($t)` requests cooperative cancellation: the body observes it at its next loop checkpoint, where the runtime raises a catchable "task cancelled" (so a runaway `spawn` can be stopped - this retires the exit-time hang). Catch it inside the body for a clean partial result; `task.cancel + task.discard` is stop-and-forget. `task.cancelled()` is a non-raising poll. `task.waitTimeout($t, ms)` / `task.waitAnyTimeout($ts, ms)` are bounded waits that throw a catchable "timed out" error. **Channels.** `channel of T` streams values between goroutines (the counterpart to `task`'s single result). `channel.make(capacity)` (0 = unbuffered), `send` (deep-copies the value in - the receiver gets its own copy), `recv` (blocks; throws a catchable error on a closed+drained channel - drain with `try { while (true) { process(channel.recv($ch)); } } catch (e) { }`), `close`, `select([...])` (fan-in: next value from any open channel), `len` / `capacity`. A channel is a shared handle but the values through it are copied, so no-shared-mutable-state holds. `channel` is a contextual keyword (a type only in `channel of T`; a valid identifier elsewhere). ## Imports ```jennifer use io; # enable a system library, addressed io.printf(...) use strings as s; # alias: only s.upper(...) works after this include "helpers.j"; # textual splice of another .j file (preprocessor) import "./util.j" as u; # load util.j as a module, addressed u.fn(...) / u.CONST ``` - `use NAME [as ALIAS];` - system library. Nothing auto-loads; every program states its imports. Aliasing is a rename (the canonical prefix stops working). - `include "path.j";` - textual file splice (path is a string literal ending in `.j`, resolved relative to the including file). - `import "PATH.j" [as NAME];` - **module** import (a real boundary, not a splice). Path forms: `./x.j` / `../x.j` local, `/x.j` absolute, bare `x.j` from the module search path. Loads once (run-once, cached), depth-first post-order; cycles error. Reach the module's surface as `NAME.fn(args)`, `NAME.CONST`, and `NAME.Struct` / `NAME.Struct{...}` (`NAME` is the `as` alias, else the file stem). A **module top level is declarations-only**: `def const`, `def struct`, `func`, `use`, `import` - no mutable `def`, no free-standing statements. `use` is not transitive across the boundary. - `export` publishes a top-level `def const` / `def struct` / `func` from a module; unmarked names are private (reaching one from outside errors). A module struct type keeps its identity `(module, name)` at the consumer, so `def p as NAME.Struct init NAME.make();` type-checks and `a.Point` / `b.Point` are distinct. An exported struct/func may not expose a private struct. `export` is only valid in a module (a parse error in a `run` script). A co-located `MODULE_test.j` white-box overlay runs under `jennifer test`. - **Requirement header** - a file may declare what it needs with typed comment pragmas in its header block, checked at read time (a program, a module, and each `include`d file self-checks): `# pragma-jennifer-version: >=0.25.0` (a minimum interpreter floor; any `-dev` build bypasses, only a release tag is compared - one floor per file) and `# pragma-jennifer-capability: net` (a host facility the build must have: `net` or `exec`, neither on `jennifer-tiny`; multiple accumulate). A mismatch aborts with a clear message; a malformed directive is a hard error. Query capabilities at runtime with `meta.hasCapability("net")` / `meta.CAPABILITIES`. ## Standard library (all namespaced, all opt-in via `use`) Call as `LIB.name(...)`. Enable with `use LIB;` first. Highlights: - **`io`** - `printf` / `sprintf` with verbs `%d %f %s %t %v %a` and `%verb[|key=value]` modifiers (`pad`, `align`, `base`, `prec`, `sign`, `group`, `case`, ...); `readLine`, `eof`, `readBytes`. - **`convert`** - `toInt toFloat toString toBool`, `typeOf`, `objectType`, `bytesFromString` / `stringFromBytes` (utf-8). Note: the callees are `toInt` etc. because `int`/`float`/`string`/`bool`/`bytes` are reserved type keywords (they appear only after `as`). - **`math`** - arithmetic `abs min max sqrt pow floor ceil round trunc sign cbrt hypot`; trig `sin cos tan asin acos atan atan2` + hyperbolic `sinh cosh tanh asinh acosh atanh`; exp/log `exp expm1 ln log10 log2 log1p log`(x, base); combinatorics `factorial comb perm gcd lcm`; random `rand randInt randSeed`; special functions `erf erfc gamma lgamma beta lbeta regGammaP regGammaQ regBetaI` (the distribution-CDF engine); constants `PI`, `E`, `TAU`. Angles in radians; floor/ceil/round/trunc return int; undefined results error (no NaN). - **`stats`** - 26 descriptive statistics over `list of int`/`float`: `mean median mode modes geometricMean harmonicMean weightedMean variance stddev sampleVariance sampleStddev range iqr mad skewness kurtosis percentile quartiles min max sum zscore correlation covariance sampleCovariance describe`. Real-valued reductions return `float`; `min`/`max`/`mode`/`modes`/`range`/`sum` keep the input kind; `quartiles`/`zscore` return `list of float`; `describe` returns a `stats.Summary` struct. Population (`variance`/`stddev`/`covariance`/moments) vs sample (`sample*`, `n-1`); `kurtosis` is excess. Undefined results (empty list, bad percentile, zero-variance, non-positive geometric/harmonic input) are catchable errors. Also **distributions** (flat R-style names, `float`): normal `normalPdf/normalCdf/normalQuantile/normalSample`, `tPdf/tCdf/tQuantile`, `chiSquareCdf/chiSquareQuantile`, `fCdf/fQuantile`, `binomialPmf/binomialCdf`, `poissonPmf/poissonCdf` (quantile `p` in `(0,1)`); and **inference**: `linearRegression`/`multipleRegression`, `confidenceInterval`, `proportionCi` (wald/wilson/clopper-pearson), `tTest`/`tTest2`, `chiSquareTest`, `fTest`, `anova`, `histogram`. Results are `stats.Regression`/`Interval`/`Test` structs. No separate `prob` library - distributions live in `stats` (like `scipy.stats`). - **`ml`** - classical / predictive machine learning on tabular data (scikit-learn-lite), over `stats`/`linalg`. **Fit/predict shape**: a fit function returns an opaque `ml.Model` handle, applied with `ml.predict` (labels for a classifier/cluster, values for a regressor) / `ml.transform` (scalers, PCA) / `ml.predictProba` (binary logistic). Models: regression `linearRegression` `ridge` `lasso` `kNNRegressor` `decisionTreeRegressor` `randomForestRegressor`; classifiers `kNN` `naiveBayes` `logisticRegression` (binary or multiclass one-vs-rest) `decisionTree` `randomForest`; `kMeans` `pca` `standardScaler` `minMaxScaler`. Introspection: `coefficients` `intercept` `centroids` `components` `explainedVariance` `featureImportances`. Selection / preprocessing: `trainTestSplit` -> `ml.Split`, `kFold` -> list of `ml.Fold`, `polynomialFeatures`. Metrics: `accuracy` `precision` `recall` `f1`(+ positive label) `confusionMatrix` `rocAuc` `logLoss` `rmse` `mse` `mae` `r2`. X is `list of list of float/int` (rows), y a `list of float/int`. Random models honor `math.randSeed`; a degenerate input is a catchable error. Not a deep-learning framework. - **`linalg`** - linear algebra, the companion to `stats`. Vectors are a `list of float`: `dot distance cross normalize`. Matrices are a `list of list of float`: `transpose trace determinant inverse solve identity zeros shape`. `norm`/`scale`/`add`/`sub` are polymorphic over a vector or a matrix (`norm` = L2 / Frobenius), and `matmul` covers matrix*matrix, matrix*vector, and vector*matrix (vector*vector errors - use `dot`). `dot`/`distance`/`norm`/`trace`/`determinant` return `float`; `shape` a `list of int`; the rest a vector or matrix. Strict like `math`/`stats`: a dimension mismatch, a non-rectangular matrix, a singular `inverse`/`solve`, the zero vector to `normalize`, or a non-finite (overflow) result is a catchable error, not a NaN. - **`strings`** - `upper lower fold contains startsWith endsWith indexOf trim trimLeft trimRight replace repeat substring split chars join`. Rune-indexed (`fold` = case-insensitive compare). - **`lists`** - `push pop first last head tail reverse sort contains concat slice shuffle range`, plus higher-order `map filter reduce find any all sortBy` (each takes a `func` value). Non-mutating (they return new lists). - **`binary`** - bulk operations on `bytes` (the byte-data counterpart to `strings`/`lists`): `concat slice find split startsWith endsWith`. Non-mutating, value-semantic; each pushes a per-byte loop into Go for throughput. `indexOf`/`split` scan at native speed (a MIME boundary, a delimiter). Named `binary` because `bytes` is a reserved type keyword. For building a buffer from a stream use `net.readAll`/`readN`, not `binary.concat` in a loop (O(n^2)). - **`maps`** - `keys values has delete merge`. `has` before a missing-key read. - **`os`** - `getEnv`, `hasFlag`/`flag`, `isTerminal`, `run`/`spawn`, `cwd`/`homeDir`/`tempDir`; `catchSignal(name)`/`gotSignal(name)` to trap and poll a Unix signal (`"int"`/`"term"`/`"hup"`/`"usr2"`; cooperative, opt-in, for graceful shutdown; `"usr1"` reserved for `kill -USR1` interpreter diagnostics). Constants `PLATFORM ARCH NCPU EOL DIRSEP PATHSEP ARGS`. - **`path`** - OS-aware filesystem path manipulation (the string layer paired with `fs`; no I/O): `path.base(p)`, `dir(p)`, `ext(p)`, `stem(p)`, `join(a, b, ...)`, `clean(p)`, `isAbs(p)`, `split(p)` -> `[dir, file]`. Uses the host separator, so `path.join` builds portable paths instead of hardcoding `/`. Not a filename sanitizer (OS-aware `base` does not strip a foreign `\`). - **`json`** - `encode`/`encodePretty`/`decode`. `decode` returns an opaque `json.Value` walked by JSON Pointer accessors (`get`/`asInt`/`asString`/ `typeOf`/`has`/`keys`/`length`/...) and edited by non-mutating writers (`set`/`insert`/`append`/`remove`/`move`, `map()`/`list()`). - **`asn1`** - ASN.1 BER decode / DER encode (the byte layer under LDAP / SNMP / PKI), designed like `json`. `decode(bytes)` returns an opaque `asn1.Value` walked by `(node, pointer)` accessors where the pointer's tokens are child indices (`typeOf`/`tagClass`/`tagNumber`/`isConstructed`/`get`/`has`/`length`/ `asInt`/`asBool`/`asString`/`asBytes`/`asOid`/`isNull`). Build with typed constructors (`integer`/`enumerated`/`boolean`/`null`/`octetString`/ `utf8String`/`printableString`/`ia5String`/`oid`/`sequence`/`set`, plus `tagged` EXPLICIT / `retag` IMPLICIT context tags) and `encode(v)` -> DER `bytes`. Malformed input and wrong-type leaf reads are catchable errors. - **`toml`** - RFC-conformant TOML 1.0 `encode`/`encodePretty`/`decode` with the **same opaque-value, read / walk / write surface as `json`, name for name** (JSON Pointer addressing), plus `asDatetime` (backed by `time.Time`) for TOML's native date-times. The config format Jennifer ships (not INI). - **`xml`** - `decode`/`encode`/`encodePretty` over an opaque `xml.Value`, designed like `json`/`toml` but an element tree (ordered attributes + children + mixed text). Read: `tag`/`text`/`attr`/`hasAttr`/`attrs`/`children`, `typeOf`; navigate with an XPath-style path (`name`, `name[k]` 1-based, `*`) via `get`/`findAll`/`has`; build with `element`/`setAttr`/`setText`/`append`. Entities + numeric refs decode; namespace prefixes kept verbatim. - **`yaml`** - YAML 1.2 `decode`/`decodeAll`/`encode`/`encodePretty` over an opaque `yaml.Value`, the **same opaque-value read / walk / write surface as `json`/`toml`, name for name** (JSON Pointer addressing), plus `asDatetime`/`isDatetime` for timestamps and `isNull`. `decode` is one document (a multi-doc stream errors); `decodeAll` returns a `list of yaml.Value`. Anchors / aliases resolve by value and `<<` merge keys apply (own key wins). `encode` is flow (compact `{a: 1}`); `encodePretty` is block (readable). Backed by `gopkg.in/yaml.v3` (the one library with a Go dependency; TinyGo-clean). - **`intl`** - internationalization: message catalogs + locale-aware translation. `intl.load(lang, catalog)` ingests a `map of string to string` (first language loaded is the default); `intl.setLocale(lang)` / `intl.locale()`; `intl.tr(key)` / `intl.tr(key, params)` translates with `%name%` placeholder interpolation (`%%` escapes a literal `%`) and a fallback chain (current locale -> its base language -> default language -> the key itself, so a missing translation is visible). Named `intl` (letters-only, like JS `Intl`), not `i18n`; there is no ambient `_()`. - **`httpd`** - HTTP/1.1 server engine over `net/http`. Pull loop (no handler callbacks): `httpd.listen(addr)` (or `listenTLS(addr, cert, key)`, TLS + HTTP/2) -> `Server`, then loop. `httpd.listenWith(addr, opts)` / `listenTLSWith(addr, cert, key, opts)` take an `httpd.Options{maxBodyBytes as int, maxInFlight as int}` for per-server limits (a `0` field selects its default: a 10 MiB request-body cap / 256 concurrent in-flight requests); the guard rejects a pair whose `maxInFlight x maxBodyBytes` worst-case buffered memory exceeds a process-wide budget (default 4 GiB, set with `httpd.setMaxBufferBudget(bytes)` to match the host's RAM, or opt into `httpd.setMaxBufferBudgetFromRAM(fraction)` -> the bytes set, cgroup-aware so a container uses its own limit). Then loop `httpd.accept($srv)` -> `Request` and `httpd.respond($req, status, body)`; request accessors `method`/`path`/`query`/`header`/`body`/`remoteAddr`, plus `setHeader`/`serveFile`/`serveDir`/`shutdown`. `httpd.setRequestValue($req, key, value)` / `requestValue($req, key)` are per-request scratch notes (`""` if unset; never sent to the client) for memoizing a compute-once-per-request value. `httpd.etag($req, tag)` sets an `ETag` and honours a conditional GET (returns `bool`; `true` = a `304` was sent, so stop) - parsing `If-None-Match` (list / `*` / weak `W/`) for you. `serveFileEtag` / `serveDirEtag` are `serveFile` / `serveDir` plus a cached content-hash `ETag` (stable across replicas, unlike an mtime). `spawn` several accept loops for a worker pool. Default binary only (`jennifer-tiny` stubs it). - **`term`** - terminal control for interactive TUIs: `term.makeRaw(stream)` -> `term.State` and `term.restore(state)` (raw mode: unbuffered, no-echo input), `term.size(stream)` -> `term.Size{rows, cols}`, `term.readByte()` -> int (one raw byte from stdin, `-1` at EOF; bytes, not decoded keys). Over `golang.org/x/term`; default binary only (`jennifer-tiny` stubs it). Refused in the REPL. Output-only TUIs need only `ansi` + `os.isTerminal`. - **`serial`** - serial ports: `serial.open(path, baud)` -> `serial.Port`, `read` / `write` / `flush` / `close`, `openWith` for full termios config. Linux-only, **default binary only** (stubs elsewhere and on `jennifer-tiny`). - **`spi`** - SPI devices: `spi.open(path)` -> `spi.Device`, `configure(dev, mode, speedHz)`, full-duplex `transfer(dev, bytes)`, `close`. Linux-only, **default binary only**. - **`i2c`** - the I2C bus: `i2c.open(path, addr)` -> `i2c.Bus`, `read` / `write` / `readReg(bus, reg, n)` / `writeReg` / `close`. Linux-only, **default binary only**. - **`gpio`** - GPIO over `/dev/gpiochipN` lines, pin-keyed `setup` / `read` / `write` / `release` + `gpio.IN` / `gpio.OUT` (mirrors the sysfs `gpio` module). Linux-only, **default binary only**. - **`sql`** - relational-database client over `database/sql`: MySQL / MariaDB + PostgreSQL (pure-Go drivers; SQLite excluded). `sql.open(driver, dsn)` -> `Connection`, `query`/`exec` (target is a Connection or Tx), pull cursor `next` + typed `asInt`/`asFloat`/`asString`/`asBool`/`asBytes`/`isNull`, `begin`/`commit`/`rollback`, prepared statements. Values bind **only through placeholders** (no string interpolation -> injection-safe). Default `jennifer` binary only; `jennifer-tiny` stubs it. - **`time`** - dates, durations, zones. Structs `time.Time` / `Duration` / `Zone`; constructors + accessors, arithmetic (`add` / `sub` / `before` / `after` / `equal`), `inZone`, `sleep`, strftime `format` / `parse`, ISO 8601 `iso` / `fromIso`. Constants `UTC`, `PROGRAM_START`. Fixed-offset zones (no IANA / DST yet). - **`fs`** - blocking filesystem I/O. Whole-file `read` / `write` / `append` (String / Bytes), plus `writeNew(path, content)` (create, failing if it exists - `O_EXCL`, the atomic test-and-set a file lock builds on); metadata `exists` / `isFile` / `isDir` / `stat` (-> `fs.Stat`) / `realpath` / `readlink` / `symlink(target, linkPath)`; permissions `chmod` / `chown` (Unix); dir ops `mkdir` / `mkdirAll` / `remove` / `removeAll` / `rename` / `list` / `walk`; temp entries `makeTempFile` / `makeTempDir`; buffered `fs.File` handles (`open` / `readLine` / ... / `sync` / `close`); and a polling `watch` -> `fs.Watcher` (`next` / `hasEvent` / `close`). Path- vs handle-form verbs dispatch on the first arg. - **`net`** - sockets. TCP `connect` / `listen` / `accept` / `readBytes` / `writeBytes`, bulk `readAll` / `readN`; TLS `connectTLS` / `startTLS` (opt-out verify via `net.TLSOptions{skipVerify, caCert}`); UDP `listenUDP` / `sendTo` / `recvFrom` / `setBroadcast` / `bindToDevice` (pin a UDP socket to one interface via `SO_BINDTODEVICE` - Linux-only, needs root; the multi-homed-host fix a wildcard bind can't give); DNS `lookup` / `reverseLookup`; polymorphic `close` / `address`. `connect` takes an optional trailing `timeoutMs`. **Default `jennifer` binary only** (`jennifer-tiny` stubs it). - **`regex`** - RE2 (linear-time): `matches` / `find` / `findAll` / `replace` / `split` / `escape`. A match is a `regex.Match{text, start, end, groups, groupsNamed}` (rune indices; `start == -1` means no match). Implicit 128-entry LRU pattern cache. - **`hash`** - MD5 / SHA-1 / SHA-256 digests over `bytes`: `hash.compute(b, algo)`, keyed `hmac(key, message, algo)`, constant-time `equal(a, b)` (MAC / token checks), and streaming (`stream` / `update` / `finalize` via `hash.Stream`). The algorithm is always a value (`"sha256"`, ...), never a per-digest shortcut. - **`crc`** - CRC-32 / CRC-64 checksums over `bytes`, the same codec-table shape as `hash`. - **`crypto`** - security primitives above `hash`: crypto-grade random `randBytes(n)` / `randInt(lo, hi)`, constant-time `hmacEqual`, key derivation `hkdf` / `pbkdf2`, AES-256-GCM `encrypt` / `decrypt`, Ed25519 `signKeypair` / `sign` / `verify`, and PEM-key RSA / ECDSA `rsaSign` / `rsaVerify` / `ecdsaSign` / `ecdsaVerify` plus key generation / CSR / JWK `rsaGenerateKey` / `ecGenerateKey` / `jwkPublic` / `jwkToPem` / `csr` (for JWT RS\* / ES\* and ACME). TinyGo-clean except the RSA / ECDSA surface, which is **default binary only**. - **`compress`** - byte-stream compression: `pack` / `unpack` for `"gzip"` / `"zlib"` / `"deflate"` (`bytes` in/out, optional `"fast"` / `"default"` / `"best"` level), plus streaming via a `compress.Stream` handle. Both binaries. - **`archive`** - tar / zip containers over `bytes` (no `fs`): `pack` / `unpack` for `"tar"` / `"zip"` / `"tar.gz"`; a bundle is a `list of archive.Entry{name, data, mode, mtime}`. Both binaries. - **`encoding`** - introspection (`isAscii` / `lenBytes` / `lenRunes`), binary-to-text `toText` / `fromText` (`hex` / `base32` / `base64` / `base64-url` / `ascii85` / `z85` / `quoted-printable`, plus `uri-percent` (RFC 3986) and `uri-form` (form-urlencoded) that the `uri` module builds on), and character `encode` / `decode` (`ascii` / `iso-8859-*` / `windows-*` / `ebcdic`). Codec names are exact-match. - **`uuid`** - RFC 9562 UUIDs: `uuid.v4()` (random) / `v7()` (time-ordered), `parse` / `isValid` / `version`, constant `NIL`. Randomness is `crypto`-grade, so a generated UUID is safe as a security token. - **`meta`** - interpreter self-identity: constants `VERSION` / `BUILD` / `SYSMODDIR` / `CAPABILITIES` + `hasCapability(name)`, and a small reflection surface - `call(name, args...)` / `defined(name)` (a top-level method by string name) and `callMain` / `definedMain` (resolve against the entry program, so a module can dispatch to handlers its host defined). - **`testing`** - test-runner primitives: `run(name)` (invoke a user method by name; the one place `exit` is caught), assertions `assertEqual` / ... / `assertThrows` (throw `Error{kind: "assertion"}`), `results` / `reset`, and `report` (`text` / `tap` / `junit`). The `.j` test framework and the `jennifer test` subcommand build on top. - **`kv`** - in-process key/value store with per-key TTL (the no-server local counterpart to the `memcache` / `redis` modules): `kv.open()` (in-memory) / `kv.openFile(path)` (persisted across `jennifer run` invocations) -> `kv.Store`, then `set(store, k, v, ttl)` / `add` / `get` / `has` / `delete` / `touch` / `incr(store, k, delta)` (memcache-shape: new value, `-1` when absent, does not create; `delta` is signed so a negative value decrements - no separate `decr` - and it does not floor at 0) / `close`. A `kv.Store` is an integer handle into a per-interpreter registry, so it shares its backing map across value-copies and `spawn`ed tasks (per-store mutex; `incr` atomic) - the shared mutable state a pure `.j` module cannot hold. Backs `kvstore`'s in-process option. TinyGo-clean (both binaries). For the exact signature of any function, see the hosted library reference - the [cheatsheet](https://jennifer-lang.dev/libraries/cheatsheet.html) (every builtin in one table) or the [per-library pages](https://jennifer-lang.dev/libraries/index.html) (e.g. `.../libraries/json.html`). ## Module library (Jennifer-coded, brought in with `import`) Distributable `.j` modules that ship with Jennifer - ordinary Jennifer source you can read, fork, or replace, distinct from the Go libraries above. Installed to the system module dir, so `import "NAME.j";` resolves with no path (or `import "./NAME.j" as NAME;` for a local copy); addressed `NAME.fn(...)` / `NAME.Struct` like a library. - **`acme`** - ACME (RFC 8555) client: obtain / renew TLS certificates from Let's Encrypt and compatible CAs. `acme.connect(directoryUrl, accountKey)` / `register(client, email)`, `order(client, domains)`, `authorization` + `challenge(authz, kind)`, compute HTTP-01 `keyAuthorization(client, token)` / DNS-01 `dnsRecord(client, token)`, `accept` + `pollAuthorization`, then `finalize(client, order, csr, ...)` with a `crypto.csr` + `downloadCertificate`. Every request a JWS (`RS256` / `ES256`) over `http` + `json`; keys / CSR / JWK from `crypto`. Test against a CA **staging** endpoint first. Needs the default binary. - **`ansi`** - terminal styling as string wrappers: `ansi.color(s, name)` / `bgColor` / `style(s, name)` (bold / dim / italic / underline / reverse) / `rgb` / `strip`, plus per-colour and per-style shortcuts (`ansi.red(s)`, `ansi.bold(s)`). TTY-aware: styling suppresses itself off a terminal or under `NO_COLOR`, and is forced on by `FORCE_COLOR`. - **`args`** - a declarative CLI argument parser (argparse-style) over `os.ARGS`. Build a value-semantic `Parser` with copy-returning builders: `args.parser(prog, help)` then `args.flag` / `intFlag` / `floatFlag` / `boolFlag` (long + short, default, help), `countFlag` (`-vvv` -> 3), `listFlag` (repeatable -> list), `positional` / `positionalOpt` / `positionalList` / `positionalList1` / `positionalN` (`nargs` `?` / `*` / `+` / N), post-modifiers `required(p)` / `choices(p, allowed)`, `command(p, name, help, sub)` (subcommands), `version(p, ver)`. `args.parse($p, os.ARGS)` -> a `Result` read with `asString` / `asInt` / `asFloat` / `asBool` / `asList` / `count` / `has` (+ `$r.command` / `$r.done` / `$r.helpText`). Accepts `--flag=value` / `--flag value` / `-abc` bundling / `--`; an unknown flag / missing required / bad type / bad choice throws `Error{kind: "args"}`, while `-h` / `--help` / `--version` set `$r.done` with `helpText` to print. `args.dispatch($r, handlers)` routes the chosen subcommand to its handler (`handlers` a `map of string to func` of `func(r as Result)` values, run in the entry program's context), returning the handler's value. Pure `.j` over `strings` + `convert` + `lists` + `maps`; both binaries. - **`csv`** - RFC 4180: `csv.parse(s)` / `format(rows)` (`parseWith` / `formatWith` for any single-character delimiter, e.g. TSV), plus `toRecords` / `fromRecords` for header-keyed `map of string to string`. Quoting-aware. `csv.formatSafe(rows)` neutralises spreadsheet-formula injection (CWE-1236); a `csv.Dialect` (`dialect(delim)` -> `parseDialect` / `formatDialect`) groups delimiter / quote / comment / trim knobs; streaming `csv.reader` / `writer` wrap an open `fs.File`. - **`docblock`** - the Jennifer doc-comment format (`/**` ... `*/` with a summary, description, and `@param`/`@field`/`@return`/`@throws`/`@since`/ `@deprecated`/`@see`/`@example`/`@internal`/`@module` tags; types in `{...}`) and its parser. `docblock.parse(source)` -> a typed `FileDoc` tree (module preamble + per-construct docs + `Diagnostic`s for doc drift / orphans). Data, not rendering. Over `regex` + `strings`; both binaries. - **`feed`** - RSS 2.0 and Atom 1.0 web syndication in one module (format chosen on `build`, detected on `parse`). Value-semantic `feed.Feed{title, link, updated, entries, author, categories}` of `feed.Entry{..., enclosure}` (an `feed.Enclosure{url, length, type}`) with builders `feed.feed(title, link)` / `entry(title, link)` / `add(f, e)` / `feedUpdated` / `entryId` / `entryPublished` / `entrySummary` / `entryContent` / `entryAuthor` / `entryCategory` / `entryEnclosure` / `hasEnclosure`; `feed.build(f, "rss"|"atom")` / `parse(text)` / `kind(text)`, and `feed.fetch(url)` over `http`. Enclosures make it a podcast feed; author + categories round-trip. Over `xml` + `time` (build / parse both binaries, `fetch` the default). Hardened for untrusted feeds (nesting cap, 64 MiB body cap). - **`font`** - a pure-Jennifer TrueType / OpenType font parser (no Go; `bytes` + bitwise ops + `fs`, so **both binaries**). `font.parse(b)` / `open(path)` -> `font.Font` (`.ttf` or `.otf`); `font.unitsPerEm` / `name` / `advance(f, cp)` / `kern(f, left, right)` / `ascender` / `descender` / `lineGap` / `capHeight` / `xHeight` (OS/2 metrics) / `glyphPath(f, cp)` -> an SVG path `d` / `glyph(f, cp)` -> `font.Glyph` (contours of `font.Point`s + advance + bbox). Both outline backends ship: TrueType `glyf` (quadratic) and CFF for OpenType `OTTO` (a Type2 charstring interpreter with subrs + CID-keyed FDArray, so CJK too; cubic curves emit native `C` in `glyphPath`). GPOS / GSUB shaping, `CFF2` / variable axes, and hinting are out of scope. - **`flatdb`** - a file-backed JSON store over `json` + `fs`. `flatdb.open(path)` -> value-semantic `DB` (empty if absent), or `flatdb.openString(text)` for a **read-only** DB from an in-memory JSON string (`save` throws). Query / edit by JSON Pointer (`get` / `has` / `keys` / `length`; fresh-`DB`-returning `set` / `append` / `remove`); `flatdb.save(db)` writes back crash-atomically (temp + `rename`) and returns a rebound `DB` (thread it - `$db = flatdb.save($db)` - to save again), `flatdb.saveAs(db, path)` writes to a new file and returns a fresh `DB` bound to it. Values are `json.Value`s. **Concurrent writers:** `save` throws a `flatdb` conflict if the file changed since `open` (no silent lost update), and `flatdb.update(path, transform)` runs a locked read-modify-write (a `func(db as DB) -> DB`, cross-process advisory lock over `fs.writeNew`) so concurrent writers (processes or `spawn`ed tasks) serialize with no loss - prefer it when more than one writer touches a store (`flatdb.lock` / `unlock` are the raw primitives). Transport-agnostic (never imports `http` / `net`), so both binaries. Not a database engine - crash-atomic snapshotting of small data. - **`dotenv`** - read `.env` config files with layered profiles + `${VAR}` interpolation. Single-file: `dotenv.parse(text)` / `read(path)` -> `map of string to string`; `dotenv.load(path)` also `os.setEnv`s each. Cascade loaders merge `.env` -> `.env.local` -> `.env.` -> `.env..local` from one dir, a real OS env var always winning: `readCascade(dir, profile)` (no mutation) / `resolve(dir, profile)` (effective map) / `loadCascade(dir, profile)` (setEnv only unset keys) / `autoload(dir)` (profile from `JENNIFER_ENV`). Handles `#` comments, a leading `export`, single (literal) / double (`\n` etc., multi-line) quotes, and backward-reference `${VAR}` interpolation (earlier keys -> OS env -> ""; no command substitution). Profile labels validated (no traversal). Over `fs` + `strings` + `os` + `path` + `regex` + `maps`; both binaries. - **`cron`** - parse and evaluate cron expressions. `cron.parse(expr) -> Schedule`; `cron.matches(schedule, t) -> bool`; `cron.next(schedule, after)` -> the next `time.Time` at or after a time. Five fields (minute / hour / day-of-month / month / day-of-week) with `*` / `,` / `-` / `/n` steps, three-letter month / day names, and `@`-nickname macros (`@daily` / `@hourly` / `@reboot` / ...). A pure calculator over `time` (no clock) - a scheduler is your own `spawn` + `time.sleep` loop. Both binaries. - **`html`** - build an HTML element tree and render escaped HTML5: `html.element(tag, attrs, children)` / `text(s)` / `raw(s)` / `attr(n, v)`, `render` / `renderAll`, `escape`, `safeUrl(url)` (an `http` / `https` / `mailto` allowlist, else `"#"`), `boolAttr(name)`. `element` / `attr` reject a tag / attribute name outside `[A-Za-z][A-Za-z0-9-]*`. Also a **tolerant `parse(src)`** that reads HTML back into the same `Node` tree (void / self-closing / mismatched-nesting / comment / DOCTYPE tolerant, budget-capped), walked by `get(node, sel)` / `findAll` / `has` (XPath-ish `/`-path selectors with `*` and `name[k]`) + `attrOf` / `hasAttr` - re-serialized with the same `render`, so build and parse round-trip through one model. - **`tengine`** - a text template engine (a subset of Go `text/template`) rendered over a `json.Value` tree. `tengine.newSet()` -> `Set`; `tengine.add(set, name, src)` (extracts `{{ define }}` blocks); `tengine.render(set, entry, data)` -> string. Addressing `.a.b` / `$` / `$var`; actions `{{ if }}` / `{{ else if }}` / `{{ else }}` (functions `eq` / `ne` / `lt` / `and` / `or` / `not`, parenthesised), `{{ range }}` / `{{ with }}` (each with `else`), `{{ $x := PIPE }}`, `{{ template }}` / `{{ block }}` layout inheritance, `{{/* comments */}}`, `{{- -}}` trim markers, and output pipes (`upper` / `lower` / `title` / `trim` / `html` / `urlize` / `default` / `truncate` / `join` / `len` / `printf`). Not auto-escaped (use the `html` pipe). Over `json` / `strings` / `lists` / `maps` / `convert`; both binaries. - **`http`** - an HTTP/1.1 client over `net` (`https://` via TLS). `http.request(method, url, headers, body)` (or `requestWith(..., timeoutMs)`; the verbs default to a 30 s idle timeout) plus `get` / `post(url, contentType, body, headers)` / `put` / `patch` / `delete` / `head` / `options` return an `http.Response` (`status` / `statusText` / lowercased `headers` / `body`; `http.header(resp, name)` reads case-insensitively). Handles Content-Length + chunked framing, text (UTF-8) bodies. For **binary** downloads use `http.getBytes` / `requestBytes` -> `http.BytesResponse` (raw `bytes` body); to **upload** raw bytes byte-for-byte, `http.requestRawBody(...)` / `requestRawBodyTls`. For a request **with a policy** use `http.send(method, url, headers, body, options)` with an `http.Options{timeoutMs, maxBytes, maxRedirects, maxRetries, backoffMs, tls}` (zero value = one-shot): it follows redirects, retries 429 / 5xx with backoff (honouring `Retry-After`), and carries cookies. `http.basic(user, pass)` builds a `Basic` auth value. For a **keep-alive** connection, `http.connect(url, options)` -> `http.Session` and `http.exchange(session, method, path, headers, body)` -> `http.Exchange{response, session}` (thread the session forward: `$s = $x.session;`), `http.close(session)`. For a self-signed / private-CA server, pass `http.TlsOptions{skipVerify, caCert}` via `http.requestTls` / `requestWithTls` (the zero value full-verifies). **Default `jennifer` binary only** (`net`). - **`gotify`** - push a notification to a [Gotify](https://gotify.net) server, on top of `http`: `gotify.push(cfg, title, message, priority)` POSTs the message form (`X-Gotify-Key` header) to `cfg.url + "/message"` and returns the `http.Response` (a bad token is a `4xx` value, not a crash). `gotify.pushMarkdown(...)` renders the body as markdown, `pushWith(..., url)` adds a tap/click action, and `pushExtras(..., ex)` attaches an arbitrary `Extras`. Value-semantic `gotify.Config{url, token}`, caller-supplied. **Default `jennifer` binary only** (`net`). - **`gpio`** - Raspberry-Pi / Linux-SBC GPIO over sysfs (`fs` backend). Stateless, pin-keyed: `gpio.setup(pin, "in"/"out")` / `write(pin, 0/1)` / `read(pin)` / `release(pin)`. Root `/sys/class/gpio`, overridable via the `JENNIFER_GPIO_BASE` env var (`os.setEnv`, e.g. for a mock). Off a GPIO-capable host, calls throw `Error{kind: "gpio"}` clearly. Both binaries. - **`rest`** - an ergonomic REST layer over `http` + `json`. Build a value-semantic `rest.client(baseUrl)` (`Client{baseUrl, headers, options}`, `options` an `http.Options` policy), then `rest.get(c, path, query)` / `post(c, path, contentType, body)` / `put` / `patch` / `delete` -> `rest.Response`, plus `getJson` (-> `json.Value`) / `postJson` / `putJson` / `patchJson`. Base-URL joining, form-encoded queries (via `uri`), auth (`rest.bearer` / `basic` / `withHeader`). Inherits the http policy via copy-returning builders (`withTimeout` / `withRedirects` / `withRetries` / `withBackoff`, TLS `withCA(c, pem)` / `insecure(c)`). For **paginated** collections, `rest.paginate(c, path, query, maxPages)` (Link-header `rel="next"`) / `paginateCursor(...)` -> `list of json.Value`. A 4xx/5xx is a `Response` value, not a crash. **Default `jennifer` binary only** (`net`). - **`uri`** - URL / URI parsing, building, and query strings (RFC 3986), the shared URL layer the network modules build on. `uri.parse(raw)` -> `Uri` (`scheme` / `user` / `host` / `port` / `path` / `query` / `fragment`) and `uri.build(u)` back; `uri.encode` / `decode` (RFC 3986 percent-encoding, space `%20`) and `uri.encodeForm` / `decodeForm` (`application/x-www-form-urlencoded`, space `+`); `uri.buildQuery(params)` / `uri.parseQuery(q)` between a `map of string to string` and a query string (form-encoded); and `uri.resolve(base, ref)` for RFC 3986 relative-reference resolution (`../img.png` against a base). Pure `.j` over `strings` + `encoding` + `convert`, so **both binaries**; sits on `encoding`'s `uri-percent` / `uri-form` codecs. - **`validate`** - declarative validation of a `map of string to string` (a form body, query, config) against a rule set, returning a structured failure list instead of ad-hoc `if` checks. Rules compose per field as value-semantic descriptors: `validate.required` / `isInt` / `isFloat` / `isBool` / `min(n)` / `max(n)` / `minLen(n)` / `maxLen(n)` / `pattern(re)` / `email` / `url` / `datetime(format)` / `oneOf(list)` / `noneOf(list)` / `password(schema)` / `custom(fn, msg)` (a `func` predicate) / `withMessage(r, m)`, grouped in a `map of string to list of validate.Rule`. `validate.check(data, rules)` -> `list of validate.Failure` (`{field, rule, param, message}`, `rule` a stable id); `validate.ok(...)` -> bool; `validate.messages` / `byField` render them, and `validate.localize(errs, templates)` re-messages via a `rule-id -> template` map (feed it `intl.tr` for non-English). An absent / blank field passes every rule but `required`; only named fields are checked. Pure `.j` over `regex` + `uri` + `time` + `password` + `convert` + `lists` + `strings` + `maps`; **both binaries**. - **`graphql`** - a thin GraphQL client over `http` / `rest`. `graphql.client(endpoint)`, layer auth / TLS with `bearer(c, token)` / `basic` / `header` / `withCA` / `insecure`, then `graphql.query(c, query, variables)` POSTs `{"query", "variables"}` and returns the decoded `json.Value` (result under `/data`; `variables` a `json.Value`, empty `json.map()` for none). The rule it gets right: a GraphQL execution error is an **HTTP 200 with a top-level `errors` array**, so `query` raises `Error{kind: "graphql"}` (a non-2xx also raises). To read partial data, `graphql.tryQuery(...)` returns the raw envelope (raises only on non-2xx); inspect with `hasErrors` / `errorMessages`. `queryNamed` / `tryQueryNamed` add a trailing `operationName`. **Default `jennifer` binary only** (`net`). - **`oauth`** - a generic OAuth2 client (the *get-a-token* half; `sasl` is the *use-a-token* half) over `http` + `json`: `oauth.clientCredentials(cfg)` / `refresh(cfg, refreshToken)` / device flow `deviceStart(cfg)` -> `deviceWait(cfg, dev)` -> `oauth.Token`. `google` / `microsoft` `Config` presets, `isExpired` + `save` / `load` token store; tokens feed `sasl.bearer` for mail XOAUTH2. Throws `Error` (kind `"oauth"`) on a token-endpoint error. Auth-Code+PKCE / JWT assertion gated on `httpd` / `crypto`. **Default `jennifer` binary only** (`net`). - **`web`** - a small HTTP framework over the `httpd` engine. Register routes against handler **`func` values** (`web.get($app, "/users/:id", showUser)` / `post` / `put` / `patch` / `delete` / `route`); patterns take `:param` captures and a trailing `*rest` wildcard (`/*path` an SPA fallback, registered last), plus `web.before` middleware, `web.notFound`, and `web.onError` (a throwing handler is contained as a logged 500, and handed to onError bound `as Error`). A handler is `func name(ctx as web.Context)`, passed by its bare name and called through its home interpreter (so an entry-program handler runs in the entry program's context); `HEAD` is served by the matching `GET`. **Requests are handled concurrently** (each in its own `spawn`ed worker, dispatch race-safe): reads of shared top-level state are safe, but do not have two handlers **write** one top-level `def` at overlapping times. `web.Context` helpers: `param` / `query` / `method` / `path` / `header` / `body` / `bodyJson` / `form` / `formValue` / `remoteAddr`, and `text` / `html` / `sendJson` / `redirect` / `respond` / `setHeader` / `serveFile` / `serveDir` / `sendGzip`. Plus cookies (`web.cookie` / `setCookie` + `CookieOptions`), sessions (`web.sessionId` mints a UUID id cookie, `web.renewSession` rotates it after login; the app owns the store), CORS (`web.cors` + `CorsOptions`), caching (`web.etag`; `serveFile` sets `ETag` / `Last-Modified`), auth (`web.basicAuth` -> `BasicCredentials`, `web.bearerToken`), CSRF (`web.csrfToken` / `csrfCheck`, HMAC double-submit, app owns the secret; `csrfToken` is idempotent per request, so calling it once per form - even in a loop - is safe), and mounting (`web.mount($app, prefix, sub)` / `joinRoute` composes a sub-router under a prefix). `web.run($app, addr)` owns the accept loop (`serveOn` to hold the server handle). Run with `jennifer serve app.j [--watch]`. **Default `jennifer` binary only** (`net`). - **`webapi`** - a JSON-API conventions layer over `web`. A value-semantic `webapi.Api` builder (`new` / `mount(a, version, path)` / `alias` / `deprecate` / `authenticator(a, fn)` / `limiter(a, fn)` / `get` / `post` / ... with a `Spec` / `install(a, app, guard)`). Routes and the authenticator / limiter are `func` values. The `Spec` carries `auth` (`Auth.None` / `Auth.Bearer`, an **enum**), `scopes`, `rules` (reusing `validate`), `rateLimit`, and `produces` (`Produces.Json` / `Html` / `Negotiate` enum); `webapi.public()` is the zero `Spec`. Enforcement is one `before` guard the app wires with a shim `func apiGuard(ctx) { return webapi.guard($api, $ctx); }` (the shim only binds the `Api`, since there are no closures yet to capture it). The guard authenticates, checks scopes (`403`), validates (`422`), rate-limits (`429`); `webapi.evaluate(spec, identity, data)` is the **pure**, testable core. Uniform error envelopes (`fail` / `notFound` / `denied` / `unauthorized`), request data (`queryData` / `jsonData` / `validated` / `identity`), content negotiation (`wants`), pagination (`page` / `sendPage`), and a drift-proof discovery `json.Value` (`webapi.discovery`). Over `web` + `validate` + `json`; **default `jennifer` binary only** (`net`). - **`markdown`** - render a small CommonMark subset (headings, emphasis, links, lists, code, GFM tables) to HTML (`markdown.toHtml`, via `html`) and styled terminal text (`toAnsi`, via `ansi`). `toHtml` is **safe by default** - raw HTML in the source is escaped, so untrusted Markdown (a README, a comment) cannot inject `