6 KiB
SURFACE — what js/mpl.js actually implements
Audited from the source of js/mpl.js at Stage 2 (C3, head ea66a2a) and
pinned by the corpus of that stage. Historical record: Stage 3 ratified
the 26 judgment calls (2026-07-09) and changed the surface where rulings
overrode it — rationals, boolean conditions, binding discipline, depth
limit, composition, and parser alignment. Where this file and
JUDGMENT_CALLS.md disagree, the rulings govern; the ratified corpus is
the executable truth.
Lexer
- Whitespace: space, tab, CR, LF.
- Comments: line
-- …; block{- … -}, nesting tracked (unterminated →err_comment). - Strings:
"…", escape\n→ newline,\t→ tab, any other\x→x(the backslash is silently dropped —\qbecomesq); unterminated →err_string. - ASCII escapes:
\wordfor word ∈ {lambda, forall, in, coloneq, leftarrow, implies, and, or, neq, leq, geq, times, div, trace, bot, parallel, circ, ast}; unknown word →err_escape. - Numbers:
[0-9]+(.[0-9]+)?viaparseFloat(all numbers are JS doubles; no scientific notation, no leading.). - Identifiers:
[a-zA-Z][a-zA-Z0-9_]*;true/falseare boolean literals. A leading_is not an identifier start (err_char). - Type symbols
ℕ ℤ ℚ ℝ ℂ 𝔹lex as identifiers (code-point-safe; 𝔹 is supplementary-plane). - Single-char tokens:
✎ λ ∀ ∈ ≜ ← ⟹ ∧ ∨ ≠ ≤ ≥ × ÷ ∗ ∘ ‖ ⊥ + - / = < > | ; : , ( ) [ ] { }. - Anything else →
err_char.
Parser (loosest to tightest)
; sequence → ‖ (desugars to sequence!) → ≜ (right-assoc, id target
only) → ← (right-assoc, id target only) → | (left-assoc) → ⟹
(right-assoc) → ∨ → ∧ → comparison (= ≠ < > ≤ ≥, non-associative —
1 < 2 < 3 is a parse error) → + - → × ∗ ÷ / → unary (✎, -) →
call f(a, …) (postfix, nullary f() parses) → atom.
Atoms: number/string/bool literals, ⊥, identifiers,
λ params [∈ constraint] : body (≥ 1 parameter; the constraint is parsed
and discarded unevaluated), ∀ id ∈ expr : body, list […], parens
( expr ) (a single expression — ; inside parens is a parse error),
braces { seq } ({} evaluates to ⊥).
Parse-class error keys: err_char, err_escape, err_string,
err_comment, err_expect, err_unexpected, err_def_target,
err_assign_target.
Lexed but unusable: ∘ (compose) has a token and an escape but no
parser rule — any use is err_expect.
Evaluator
- Values: number (double), string, boolean, list, closure,
⊥. ✎ eprintsshow(value)and returns the value.show:⊥→⊥; strings bare at top level but quoted inside lists; lists[a, b]; closures →λ; booleanstrue/false; numbers via JSString()(integral doubles print without.0).+is numeric addition unless either side is a string — then it is concatenation ofshow-rendered operands.- × ∗ ÷ /are numeric only (err_num),÷ 0→err_div0.- Guards:
c ⟹ efires iffcistrueor a non-zero number; strings/lists/⊥never fire. A non-firing guard yields NOMATCH, which|catches; NOMATCH surfacing anywhere else becomes⊥. ∧ ∨: short-circuit; operands are tested with=== true, so non-boolean operands behave as false (1 ∧ true→false— contrast with⟹).- Comparison:
=/≠via JSON serialization (structural for lists, cross-type →false,⊥ = ⊥→true, comparing a self-referential closure crashes keyless);< > ≤ ≥are raw JS comparisons (mixed-type coercion applies). ≜binds in the current scope;←mutates the nearest enclosing binding, creating one in the current scope if none exists. Both return the value; both may rebind.- Braces
{…}do not create a scope (bindings leak out). λ bodies and each∀iteration do (the∀variable shadows and is restored). - Closures capture the defining environment (later mutations are
visible), enabling recursion via
≜. ∀ x ∈ list : bodyrequires a list (err_iter), evaluates the body per element, and yields the last body value (⊥for an empty list).- Errors carry a key + 1-based line:col. Runtime keys:
err_undef,err_num,err_div0,err_notfn,err_arity,err_iter,err_steps(evaluation-step budget: 500 000). - Deep recursion overflows the host stack before the step budget — a keyless RangeError, unpinnable by the corpus (see JUDGMENT_CALLS).
Grammar-only surface (no ruling, Stage 4+ material)
The grammar accepts these; the Stage-3 interpreter does not. No ruling covers them and the fuzzer's generator does not emit them — they are on the record here so the gap is a listed fact, not a surprise:
≈(APPROX) and∼(SIM) comparison operators — interpreter:err_char."""raw strings"""(RAWSTRING) — interpreter:err_charat the second quote pair's content orerr_string/err_expectdepending on context.- Hex (
0x1F), binary (0b101) and exponent (1.5e3) number literals — interpreter lexes the leading digits as a plain number and fails to parse the rest (err_expect). _as a λ pattern (patternAtom UNDERSCORE) — interpreter:err_char.
Dead key: err_comment can no longer be raised — since the comment lexer
matched the grammar (a {- without its -} is a brace, not an
unterminated comment), no code path produces it. It stays in the error-key
list pending removal at the next legitimate interpreter change (no hash
churn for hygiene alone).
Explicitly out of corpus scope
‖— parses, and the evaluator runs it as plain sequencing, but its semantics are an M1 design question (locked decision 6): no corpus entry pins it.- Everything the grammar has that
js/mpl.jsdoes not implement (modules, resources, channels, exceptions, metaprogramming, records, sets, choice types, …) — Stage 4/5 artifacts, not Stage 2 ones.