Status: tested
Last updated: 2026-08-02 · S48 B2 (review release blocker): a guard block mixed with statements and/or a tail is never filtered into a clean StageKind::Guard again — parse_classified_block retains the complete block as a StmtBlock (P0006 per mixed statement, P0006 now also names the tail case, P0115 out-of-Core), and lower rejects the stage loudly (L1000). The B2 probe { -true-> 1; -false-> 2; 999 } keeps its 999. · S29 time builtin: () now PARSES to ExprKind::Unit — the wire-less chain head, sole sanctioned use () -> time — replacing the P0001 "empty parentheses" rejection; the parser records the shape only, every other position is lower's L1301. · S22 ADR-0031: the ADR-0029 call-expression carve REMOVED — P0108 rejects every call expression uniformly again (iota(...)/fill(...) get the arrow-form teaching message: "write n -> iota / (x, n) -> fill"); ExprKind::Call remains as the rejection-recovery node only. S20 (carve, superseded) · S13 (ADR-0021 element-update bind c[i] <- x; P0013–P0015)
Spec references: user-guide.md §3 (Syntax reference) + ADR-0005 (a flow is a statement; E4) + ADR-0009 (map/fold postfix block) + ADR-0010 (guard-arrow lexing) + ADR-0011 (Core loops are loop only) as amended by ADR-0012 (labeled blocks :label { … } / jumps -> :label;; Ident { is always a struct literal) + ADR-0019 (seq { … } is a statement block StageKind::SeqBlock, not a fanout kind; P0117) + docs/components/lower/plans/plan-time-builtin.md (() → ExprKind::Unit, the () -> time head). Supporting: architecture.md §2.2.1–§2.2.2 (lexer + recursive-descent parser, minimal parse tree), ADR-0008 (error recovery + structured diagnostics, binding), ADR-0006 (type keyword, category reserved).
Depends on: (none) Depended on by: lower, cli
- P1 frontend complete: lexer + parser.
lex(source) -> LexOutputandparse(source) -> ParseOutput(total, pure, error-recovering; merged span-sorted diagnostics). - Lexer (Session 02): full v0.2-surface token set, spans +
LineIndex, L0001–L0008 with recovery, ADR-0010 single-lexeme guard arrows. - Parser (Session 03): two-tier grammar per ADR-0005 (expressions 1–7;
->/<-chains at statement level), thin spanned parse tree (DESIGN §15,Expr::Holefor operator shorthand), guard blocks/fanout/map/foldpostfix blocks (ADR-0009), loop statements with-> loop;back-edges, struct/array/tuple literals, typedmutbindings both directions. seqstatement block (ADR-0019, Session 11):seq { … }isStageKind::SeqBlock(Block)— the ordinary block production (statements + optional tail), no longer aFanoutKind::Seqfanout. Rebinds/loops insideseqare first-class (no silent drop); the old bare-chain branch form still parses; guard arms inseqare stray guards (P0004).- Unit literal
()(S29,timebuiltin):parse_paren_or_tuplebuildsExprKind::Unitwhere it used to emit P0001 +Expr::Error.() -> time -> t0;parses with zero diagnostics and aUnitchain head. The parser does not police where()may appear — that is a chain-context question it cannot answer locally, so()as a value or heading anything buttimeis lower's L1301 (no wire). - Element-update bind
c[i] <- x(ADR-0021, Session 13):BindStmtgainedindex: Option<Expr>— the optional[' expr ']after the name (parse_bind_stmt).Somemarks the indexed (element-write) form;mut/type-annotation/nested-[…]on it draw P0013/P0014/P0015 with recovery. The desugar toUpdateis lower's job, not syntax's. - Diagnostics: P0001–P0015 (syntax/recovery incl. ADR-0010's two guard hints with machine-applicable fixes; P0013–P0015 guard the ADR-0021 element-update bind
c[i] <- x—muton an indexed target / a type annotation on one / a nestedc[i][j]target, all recovered; P0001 no longer covers()— S29 made it theUnitliteral; its other triggers,(e,)/[]/missing-;/…, are untouched) + P0101–P0117 (out-of-Core rejections, each naming construct + horizon; P0117 is a structural reject — a non-chain statement dropped from a fanoutvoidblock, ADR-0019). All eightexamples/*.mapalparse with zero diagnostics;full_surface.mapalyields exactly {P0101×2, P0102×2, P0103×2, P0107, P0109, P0111, P0112, P0113} and zero L-codes — the C8 story end-to-end. - Recovery: panic-mode with sync sets, diagnostic cooldown, shared depth guard (128) over expressions/blocks/types, progress lemma —
parseis total on adversarial input (≈490K-case proptest run during review).
- Parser-level imprecision, deliberate (DESIGN §16/§17): expression-position generics surface as P0007/P0001 not P0103 (W16);
filterrecovery renders asmapin the tree (P0114 diagnostic carries the name); stage-position P0006 statements are reported then dropped from the arm-only Guard node. - Semantic/scope checks deferred to mapal-check by design (C10): scalar-type validity, string-as-data, mut/arity/exhaustiveness,
printplacement, recursion, named-param partial application (W21). - Char literals still unlexed (
'→ L0001) — corpus-empty soft spot (DESIGN §10).
- Lexer I1–I5 — unchanged (see Session 02 entry;
lexer.rs, proptests). - J1 parse total: depth guard (P0011, limit 128, shared incl. type recursion) + progress lemma
debug_asserts —parser.rs; proptests over arbitrary strings/unicode/flow-soup. - J2 span sanity (child ⊆ parent, in-bounds):
debug_asserts in node constructors + recursive walkers in unit & property tests (incl. Bind.ty, MapFold params, arm/discr spans). - J3 zero diagnostics ⟹ no Error nodes / rejected-kept forms: walkers in goldens + proptests.
- J4 acceptance: eight examples parse with zero diagnostics (
tests/golden_trees.rs; zip_demo + vector_add added with ADR-0018's zip form). - J5 presentation-free: no
Displayin crate (C3); render helpers live intests/support/. - J6 lex-diagnostic preservation:
parse(s).diagnostics ⊇ lex(s).diagnostics— unit + proptest.
201 tests, all green (cargo test -p mapal-syntax): 160 lib (102 parser units — precedence/§3.6 verbatim, W10–W24 ledger, F-matrix payloads, ADR-0011 scan, classification, the ADR-0019 seq statement-block suite [stmt-form/headless-compat/rebind+loop-kept/tail/empty/optional-;/guard-arm-P0004/fanout-P0117 + each-drop-P0117/unterminated-tail-branch-kept/seq-arm-mixed-P0006 regressions], the ADR-0021 element-update-bind suite [index_bind_top_level/index_bind_in_loop_body/index_bind_computed_index accept + index_bind_mut_rejected_p0013/index_bind_type_annotation_rejected_p0014/index_bind_nested_rejected_p0015], the ADR-0031 uniform-P0108 test iota_fill_calls_are_p0108_rejected_with_teaching_text — every call expression rejects, iota/fill with the arrow-form teaching text, and the arrow forms (4 -> iota, (1.0, 4) -> fill) parse clean, every P-code, design-review regression pins; 58 lexer) · 9 golden token streams · 10 golden parse trees (zero diags asserted; +zip_demo +vector_add, ADR-0018; +tree_unit_time_head, S29 — () -> time -> t0; parses clean and the chain head is asserted ExprKind::Unit before the snapshot, so a wrong-but-stable golden cannot hide a regression to P0001/Error) · 2+3+6 diagnostics/error/out-of-Core fixtures · 3 coverage · 3+3 proptests (lexer/parser, 2048 cases each). Golden trees were verified by independent re-derivation (one reviewer per example, node-by-node against source + grammar); implementation passed 2 adversarial reviews + a fix round (stack-overflow totality fix, P0007 climber path).
benches/lex_parse.rs (criterion 0.5.1), 2026-06-12, Apple-silicon dev machine: parse abs 1.24 µs · pipeline 1.07 µs · fanout 1.51 µs · sum_to_n 1.79 µs · fir 3.03 µs · sepia 7.45 µs · ~100× sepia synthetic 740 µs (≈100× single sepia ⇒ linear, no superlinear blowup from the ADR-0011 scan on real shapes). No baseline regressions to flag (first recording).
- ADR-0012 (decided with Sapir, Session 03): labeled blocks
:label { … }, jumps-> :label;, enclosing-targets-only; Core+1 lifts P0110. Break-to-after-a-loop deliberately undecided — the Core+1 ADR must decide or re-defer. - P0115 scope reading: anonymous block stages (user-guide §8.3 fanout form, §5.2
seqbranches) rejected as out-of-Core — HANDOFF §4.1 silence read as default-reject; flip = lift P0115. - W15 unary-minus/
!precedence: §3.6 table omits unary; bound tighter than*, looser than postfix (standard). Spec gap, no ADR. - W23:
?parsed as expression postfix (per exhibits) not §3.6 rank 9 — final call belongs to the Core+1 error-handling ADR. - Lexer items unchanged (string escape set; Core+1 pattern guards).