WokML compiler code coverage report (LCOV)
Current view: top level - c - wok_parse.c (source / functions) Coverage Total Hit
Test: 084deda Lines: 99.2 % 1450 1439
Test Date: 2026-08-10 11:39:41 Functions: 100.0 % 102 102
Legend: Lines:     hit not hit

            Line data    Source code
       1              : // wok_parse -- stage 3. Recursive descent over the post-layout token stream.
       2              : //
       3              : // One function per production in section 7 of the frontend design spec, named
       4              : // after the production. Nothing here reads a token's bytes to make a decision:
       5              : // every choice is a kind or a word compare, so a misspelling fails to compile.
       6              : //
       7              : // Three rules of FORM are enforced (they are grammar facts, not analyses):
       8              : //   - a `handle` STATEMENT must write its label (P1/D13);
       9              : //   - a control clause binds exactly one bare lowercase name after its comma
      10              : //     (C8/D14), which is what lets a later pass compare pattern count to
      11              : //     arity for every clause kind alike;
      12              : //   - `once` at clause-head position gets the v1 migration diagnostic (D25).
      13              : //
      14              : // Operator chains stay FLAT. src/Wok/Reordering.hs owns fixity, so no
      15              : // precedence table lives here.
      16              : 
      17              : #include "wok_parse.h"
      18              : 
      19              : #include "wok_utf8.h"
      20              : 
      21              : #include <stdio.h>
      22              : 
      23              : #include "wok_layout.h"
      24              : #include "wok_trivia.h"
      25              : 
      26              : // ---------------------------------------------------------------- state
      27              : 
      28              : typedef struct {
      29              :   const WokToken *tok;
      30              :   usize n;
      31              :   usize i;
      32              :   const char *src;
      33              :   WokArena *a;
      34              :   WokDiagSink *d;
      35              :   u32 depth;
      36              :   // A NEWLINE that closed a nested block is also the enclosing block's item
      37              :   // separator; closing the inner block carries it out here.
      38              :   bool sep;
      39              :   // The current item is damaged. One message per item is the whole recovery
      40              :   // contract, so every report site is gated on this.
      41              :   bool panic;
      42              :   // `own`/`lend`/`copy` are contextual: they are transfer modes only at the
      43              :   // head of a foreign member's signature type, and ordinary names everywhere
      44              :   // else (spec.md section 2 writes `Bytes.copy` itself).
      45              :   bool foreign_sig;
      46              :   u32 last_end;
      47              :   char desc[64];
      48              : } P;
      49              : 
      50              : // ---------------------------------------------------------------- cursor
      51              : 
      52      6194243 : static const WokToken *cur(const P *p) { return &p->tok[p->i]; }
      53              : 
      54       159455 : static WokKind kind_at(const P *p, usize k) {
      55       159455 :   usize j = p->i + k;
      56       159455 :   if (j >= p->n) j = p->n - 1;
      57       159455 :   return (WokKind)p->tok[j].kind;
      58              : }
      59              : 
      60         2049 : static WokWord word_at(const P *p, usize k) {
      61         2049 :   usize j = p->i + k;
      62         2049 :   if (j >= p->n) j = p->n - 1;
      63         2049 :   return (WokWord)p->tok[j].word;
      64              : }
      65              : 
      66              : // The one place the parser reads a COLUMN: which inline body an indented
      67              : // continuation belongs to (see parse_block_body). It is a token field like
      68              : // any other -- the stage split forbids the LAYOUT filter from naming a
      69              : // keyword, not the parser from reading a position.
      70          498 : static u32 col_at(const P *p, usize k) {
      71          498 :   usize j = p->i + k;
      72          498 :   if (j >= p->n) j = p->n - 1;
      73          498 :   return p->tok[j].col;
      74              : }
      75              : 
      76      2310353 : static bool at(const P *p, WokKind k) { return (WokKind)cur(p)->kind == k; }
      77              : 
      78       788084 : static bool at_word(const P *p, WokWord w) { return (WokWord)cur(p)->word == w; }
      79              : 
      80      1101284 : static void bump(P *p) {
      81      1101284 :   const WokToken *t = cur(p);
      82      1101284 :   if (t->len > 0) p->last_end = t->off + t->len;
      83      1101284 :   if (p->i + 1 < p->n) p->i++;
      84      1101284 : }
      85              : 
      86       646530 : static u32 span_end(const P *p) { return p->last_end; }
      87              : 
      88       275649 : static WokSpan tok_span(const WokToken *t) { return wok_span(t->off, t->len); }
      89              : 
      90              : // Allocates and marks production coverage in one place, so a form no corpus
      91              : // file reaches names itself rather than sitting silently uncovered.
      92       646530 : static WokNode *mk(P *p, WokTag tag, u32 start, u32 end) {
      93       646530 :   wok_cover_mark(tag);
      94       646530 :   return wok_node(p->a, tag, start, end > start ? end - start : 0);
      95              : }
      96              : 
      97              : // ------------------------------------------------------------ diagnostics
      98              : 
      99         9388 : static const char *tok_desc(P *p, const WokToken *t) {
     100         9388 :   if ((WokKind)t->kind == WT_EOF) return "end of file";
     101         9388 :   if ((WokKind)t->kind == WT_NEWLINE) return "end of line";
     102         7636 :   if ((WokKind)t->kind == WT_INDENT) return "an indented block";
     103         7636 :   if ((WokKind)t->kind == WT_DEDENT) return "the end of a block";
     104              :   // A BYTE cap would cut a multi-byte character in half and put an
     105              :   // ill-formed sequence into the message -- on valid input.
     106         7621 :   usize len = wok_utf8_truncate((const unsigned char *)p->src + t->off,
     107         7621 :                                  t->len, 40);
     108         7621 :   snprintf(p->desc, sizeof p->desc, "`%.*s`", (int)len, p->src + t->off);
     109         7621 :   return p->desc;
     110              : }
     111              : 
     112              : // E1. A layout token carries the position of the token it PRECEDES --
     113              : // wok_layout.c builds it from the next token, which is right for the block
     114              : // rule and wrong for blame. Reporting one sends the reader to the line AFTER
     115              : // the broken one, which is usually a perfectly good declaration.
     116              : //
     117              : // So a fault on a layout token is blamed at the end of the last thing the
     118              : // author actually wrote, which is where a missing `=` or `->` belongs. All
     119              : // layout tokens have len 0, and `bump` only advances `last_end` past tokens
     120              : // with len > 0, so that field is already exactly what is wanted.
     121        12659 : static WokSpan blame(const P *p, const WokToken *t) {
     122        12659 :   if (t->len == 0) return wok_span(p->last_end, 0);
     123         7914 :   return wok_span(t->off, t->len);
     124              : }
     125              : 
     126          245 : static void perr_at(P *p, WokDiagCode code, const WokToken *t,
     127              :                     const char *msg) {
     128          245 :   if (p->panic) return;
     129          245 :   WokSpan b = blame(p, t);
     130          245 :   wok_diag_add(p->d, code, b.off, b.len, "%s", msg);
     131          245 :   p->panic = true;
     132              : }
     133              : 
     134              : // Reports without entering panic: the item is otherwise complete, so keeping
     135              : // its node is worth more than discarding it, and no second message can follow.
     136          333 : static void perr_form(P *p, WokDiagCode code, const WokToken *t,
     137              :                       const char *msg) {
     138          333 :   if (p->panic) return;
     139          296 :   WokSpan b = blame(p, t);
     140          296 :   wok_diag_add(p->d, code, b.off, b.len, "%s", msg);
     141              : }
     142              : 
     143        32536 : static void perr_expect(P *p, const char *what) {
     144        32536 :   if (p->panic) return;
     145        10753 :   const WokToken *t = cur(p);
     146        10753 :   if ((WokKind)t->kind == WT_INDENT) {
     147              :     // D-LAY-3. The corpus contains zero of these, which is what lets the
     148              :     // parser carry no continuation machinery at all.
     149         1365 :     wok_diag_add(p->d, WOK_E_LAY_INDENT, blame(p, t).off, blame(p, t).len,
     150              :                  "unexpected indentation; to continue a line, begin it with "
     151              :                  "an operator, or bracket the expression");
     152              :   } else {
     153         9388 :     WokSpan b = blame(p, t);
     154         9388 :     wok_diag_add(p->d, WOK_E_PARSE, b.off, b.len, "expected %s, found %s",
     155              :                  what, tok_desc(p, t));
     156              :   }
     157        10753 :   p->panic = true;
     158              : }
     159              : 
     160              : // TIER 1 (single-token insert / delete / substitute local repair, Diekmann &
     161              : // Tratt CPCT+ with a fixed budget) WOULD GO HERE, between the report and the
     162              : // caller's fallback. It is a later slice; today every fault falls straight
     163              : // through to tier 2 region discard in the block-item loop.
     164              : 
     165       115240 : static bool expect(P *p, WokKind k, const char *what) {
     166       115240 :   if (at(p, k)) {
     167       105973 :     bump(p);
     168       105973 :     return true;
     169              :   }
     170         9267 :   perr_expect(p, what);
     171         9267 :   return false;
     172              : }
     173              : 
     174        13048 : static bool expect_word(P *p, WokWord w, const char *what) {
     175        13048 :   if (at_word(p, w)) {
     176        12895 :     bump(p);
     177        12895 :     return true;
     178              :   }
     179          153 :   perr_expect(p, what);
     180          153 :   return false;
     181              : }
     182              : 
     183        69489 : static WokSpan take_kind(P *p, WokKind k, const char *what) {
     184        69489 :   const WokToken *t = cur(p);
     185        69489 :   if ((WokKind)t->kind == k) {
     186        68344 :     bump(p);
     187        68344 :     return tok_span(t);
     188              :   }
     189         1145 :   perr_expect(p, what);
     190         1145 :   return wok_span(t->off, 0);
     191              : }
     192              : 
     193         1572 : static WokSpan take_any_name(P *p, const char *what) {
     194         1572 :   const WokToken *t = cur(p);
     195         1572 :   if ((WokKind)t->kind == WT_VARID || (WokKind)t->kind == WT_CONID) {
     196         1557 :     bump(p);
     197         1557 :     return tok_span(t);
     198              :   }
     199           15 :   perr_expect(p, what);
     200           15 :   return wok_span(t->off, 0);
     201              : }
     202              : 
     203              : // Bounded recursion: `((((((...` exhausting the C stack is the second classic
     204              : // parser CVE after literal overflow, and <stdckdint.h> only covers the first.
     205       608542 : static bool enter(P *p) {
     206       608542 :   if (p->depth >= WOK_PARSE_MAX_DEPTH) {
     207            0 :     if (!p->panic) {
     208            0 :       const WokToken *t = cur(p);
     209            0 :       wok_diag_add(p->d, WOK_E_DEPTH, t->off, t->len,
     210              :                    "nested deeper than %d levels", WOK_PARSE_MAX_DEPTH);
     211            0 :       p->panic = true;
     212              :     }
     213            0 :     return false;
     214              :   }
     215       608542 :   p->depth++;
     216       608542 :   return true;
     217              : }
     218              : 
     219       608542 : static void leave(P *p) { p->depth--; }
     220              : 
     221        27038 : static WokNode *mk_error(P *p, WokTag tag, u32 start) {
     222        27038 :   WokNode *n = mk(p, tag, start, span_end(p));
     223        27038 :   WokSpan text = wok_span(start, n->len);
     224        27038 :   if (tag == D_Error)
     225         8991 :     D_Error_set_text(n, text);
     226              :   else
     227        18047 :     E_Error_set_text(n, text);
     228        27038 :   return n;
     229              : }
     230              : 
     231        16841 : static WokNode *mk_err(P *p) { return mk_error(p, E_Error, cur(p)->off); }
     232              : 
     233              : // -------------------------------------------------------------- the block
     234              : //
     235              : // BLOCK(x) := NEWLINE INDENT x (NEWLINE x)* DEDENT | x
     236              : //
     237              : // Called ONLY where the grammar already owes a block: after `=`, `of`, `->`,
     238              : // `where`, `in`, `:=`, and after an effect / class / instance / foreign module
     239              : // / handler head. A DEDENT is not always preceded by a NEWLINE -- a
     240              : // continuation line emits its DEDENTs with no item separator, which is the
     241              : // whole point of rule L4.
     242              : 
     243              : typedef struct {
     244              :   bool indented;
     245              : } Block;
     246              : 
     247        66742 : static Block block_begin(P *p) {
     248        66742 :   Block b = {.indented = false};
     249        66742 :   if (p->panic) return b;
     250        59144 :   if (at(p, WT_NEWLINE) && kind_at(p, 1) == WT_INDENT) {
     251        19892 :     bump(p);
     252        19892 :     bump(p);
     253        19892 :     b.indented = true;
     254        39252 :   } else if (at(p, WT_NEWLINE) || at(p, WT_DEDENT) || at(p, WT_EOF)) {
     255          245 :     perr_at(p, WOK_E_LAY_EXPECTED_BLOCK, cur(p),
     256              :             "expected a body here: write it on this line, or on the next line "
     257              :             "indented further");
     258              :   }
     259        59144 :   return b;
     260              : }
     261              : 
     262        38372 : static bool block_next(P *p, const Block *b) {
     263        38372 :   if (!b->indented) return false;
     264        37694 :   if (at(p, WT_DEDENT)) return false;
     265        34855 :   if (p->sep) {
     266         1392 :     p->sep = false;
     267         1392 :     return true;
     268              :   }
     269        33463 :   if (at(p, WT_NEWLINE)) {
     270        32917 :     if (kind_at(p, 1) == WT_DEDENT) return false;
     271        16250 :     bump(p);
     272        16250 :     return true;
     273              :   }
     274              :   // A continuation lead (`where`, `in`, ...) belongs to the caller.
     275              :   return false;
     276              : }
     277              : 
     278        66902 : static void block_end(P *p, const Block *b) {
     279        66902 :   if (!b->indented) return;
     280        20052 :   bool sep = p->sep;
     281        20052 :   p->sep = false;
     282        20052 :   if (at(p, WT_NEWLINE) && kind_at(p, 1) == WT_DEDENT) {
     283        16667 :     bump(p);
     284        16667 :     sep = true;
     285              :   }
     286        20052 :   if (at(p, WT_DEDENT))
     287        19746 :     bump(p);
     288          306 :   else if (!at(p, WT_EOF))
     289          282 :     perr_expect(p, "the end of this block");
     290        20052 :   p->sep = sep;
     291              : }
     292              : 
     293              : // ---------------------------------------------------------------- recovery
     294              : //
     295              : // TIER 2 -- region discard (de Jonge / Kats / Visser / Soderberg). Abandon the
     296              : // item, emit an error node so the block skeleton survives, skip to the next
     297              : // NEWLINE at this block's level or to its DEDENT. DEDENT is a free
     298              : // synchronisation point: it is arithmetic from columns, not a guess about what
     299              : // a closing brace meant, so a damaged item can never swallow its block.
     300              : 
     301        11083 : static void resync_to_boundary(P *p) {
     302        11083 :   int br = 0, ind = 0;
     303       347054 :   for (;;) {
     304       347054 :     WokKind k = (WokKind)cur(p)->kind;
     305       347054 :     if (k == WT_EOF) return;
     306       345770 :     if (br == 0) {
     307       198824 :       if (k == WT_INDENT) {
     308         1483 :         ind++;
     309         1483 :         bump(p);
     310         1483 :         continue;
     311              :       }
     312       197341 :       if (k == WT_DEDENT) {
     313           71 :         if (ind == 0) return;
     314           43 :         ind--;
     315           43 :         bump(p);
     316           43 :         continue;
     317              :       }
     318       197270 :       if (k == WT_NEWLINE) {
     319        10696 :         if (ind == 0) return;
     320              :         // The NEWLINE that precedes a run of DEDENTs is the separator of every
     321              :         // level it closes, including ours. Consume exactly the ones we skipped
     322              :         // into and hand the separator to the block loop.
     323              :         int d = 0;
     324         3635 :         while (kind_at(p, (usize)d + 1) == WT_DEDENT) d++;
     325         2192 :         if (d >= ind) {
     326         1267 :           bump(p);
     327         2618 :           for (int j = 0; j < ind; j++) bump(p);
     328         1267 :           ind = 0;
     329         1267 :           if ((WokKind)cur(p)->kind == WT_DEDENT) return;
     330         1204 :           p->sep = true;
     331         1204 :           return;
     332              :         }
     333          925 :         bump(p);
     334          925 :         continue;
     335              :       }
     336              :     }
     337       333520 :     if (wok_kind_is_open_bracket(k))
     338         5764 :       br++;
     339       327756 :     else if (wok_kind_is_close_bracket(k) && br > 0)
     340         3509 :       br--;
     341       333520 :     bump(p);
     342              :   }
     343              : }
     344              : 
     345              : // The words that BEGIN a declaration, and the production each one names.
     346              : //
     347              : // ONE roster, because two questions share this answer and must never drift
     348              : // apart: parse_decl asks WHICH PRODUCTION parses a declaration, and
     349              : // word_starts_decl asks whether a token is a safe place for recovery to
     350              : // RESUME. They agree by construction rather than by convention -- forward-only
     351              : // declarations (D23) are what makes a declaration keyword unambiguous enough
     352              : // to be an anchor, so the two questions have one answer by design.
     353              : //
     354              : // They were two hand-written lists 1,500 lines apart, and dropping a word from
     355              : // one of them compiled, passed every suite, and reported a fault on GOOD
     356              : // source: recovery ran past the declaration it should have stopped at and into
     357              : // its body. A wrong diagnostic on correct code is the worst kind, because a
     358              : // reader believes it.
     359              : #define WOK_DECL_WORDS(X)                                                    \
     360              :   X(WW_MODULE, parse_module_decl)     X(WW_IMPORT, parse_import_decl)        \
     361              :   X(WW_TYPE, parse_type_decl)         X(WW_ALIAS, parse_alias_decl)          \
     362              :   X(WW_EFFECT, parse_effect_decl)     X(WW_CLASS, parse_class_decl)          \
     363              :   X(WW_INSTANCE, parse_instance_decl) X(WW_FOREIGN, parse_foreign_decl)      \
     364              :   X(WW_EXTERN, parse_extern_decl)     X(WW_FIXITY, parse_fixity_decl)
     365              : 
     366          722 : WOK_PURE static bool word_starts_decl(WokWord w) {
     367              : #define WOK_X(word, fn) if (w == word) return true;
     368          722 :   WOK_DECL_WORDS(WOK_X)
     369              : #undef WOK_X
     370              :   return false;
     371              : }
     372              : 
     373              : // Forward-only declarations (D23) are what make a signature the strongest
     374              : // anchor available: `name :` at a block column can never be the tail of a
     375              : // damaged item.
     376         7035 : static bool at_decl_anchor(const P *p, usize j) {
     377         7035 :   if (j >= p->n) return false;
     378         7035 :   const WokToken *t = &p->tok[j];
     379              :   // E4. resync_to_boundary stops at a NEWLINE at the block's OWN level, and
     380              :   // the layout filter emits one exactly where an item begins -- so the
     381              :   // boundary is already exact and the anchor's only job is to reject
     382              :   // WRECKAGE. A `)` or `->` stranded at the block column cannot start a
     383              :   // declaration, and those are precisely the CONTINUATION LEADS, which is a
     384              :   // question the scanner already answers.
     385              :   //
     386              :   // The previous rule accepted only a declaration keyword or `name :`. That
     387              :   // made a damaged EQUATION not an anchor, so recovery walked over every one
     388              :   // it met: five broken equations in a row reported only the first, and the
     389              :   // batch stopped early exactly when a file was uniformly broken.
     390              :   // A KEYWORD still has to be a declaration keyword: `let` or `case` at the
     391              :   // top level is not a fresh declaration, it is wreckage from a damaged one.
     392         7035 :   if ((WokKind)t->kind == WT_KEYWORD) return word_starts_decl((WokWord)t->word);
     393         6313 :   return !wok_token_is_continuation_lead(t);
     394              : }
     395              : 
     396         9792 : static void resync_decl(P *p) {
     397         9962 :   for (;;) {
     398         9877 :     resync_to_boundary(p);
     399         9877 :     if (!at(p, WT_NEWLINE)) return;
     400         7426 :     if (kind_at(p, 1) == WT_DEDENT) return;
     401         7035 :     if (at_decl_anchor(p, p->i + 1)) return;
     402           85 :     bump(p);
     403              :   }
     404              : }
     405              : 
     406              : // ------------------------------------------------------------ predicates
     407              : 
     408        76205 : static bool starts_atom(const P *p) {
     409        76205 :   WokKind k = (WokKind)cur(p)->kind;
     410        76205 :   return k == WT_VARID || k == WT_CONID || k == WT_INT || k == WT_STRING ||
     411        76205 :          k == WT_CHAR || k == WT_LPAREN || k == WT_LBRACKET;
     412              : }
     413              : 
     414        51881 : static bool starts_atompat(const P *p) {
     415        51881 :   WokKind k = (WokKind)cur(p)->kind;
     416        51881 :   if (k == WT_VARID || k == WT_CONID || k == WT_UNDERSCORE || k == WT_INT ||
     417              :       k == WT_STRING || k == WT_CHAR || k == WT_LPAREN || k == WT_LBRACKET)
     418        18100 :     return true;
     419              :   // `-` Int is the only pattern an operator can begin.
     420          152 :   if (k == WT_VARSYM) return kind_at(p, 1) == WT_INT;
     421              :   return false;
     422              : }
     423              : 
     424       103210 : static bool starts_atomtype(const P *p) {
     425       103210 :   WokKind k = (WokKind)cur(p)->kind;
     426       103210 :   return k == WT_VARID || k == WT_CONID || k == WT_LPAREN || k == WT_LBRACKET;
     427              : }
     428              : 
     429        16457 : static bool starts_typaram(const P *p) {
     430        16457 :   if (at(p, WT_VARID)) return true;
     431        12264 :   return at(p, WT_LPAREN) && word_at(p, 1) == WW_ROW;
     432              : }
     433              : 
     434        64628 : static bool at_infixop(const P *p) {
     435        64628 :   WokKind k = (WokKind)cur(p)->kind;
     436        64628 :   if (k == WT_VARSYM || k == WT_COLONCOLON) return true;
     437        58333 :   return k == WT_BACKTICK && kind_at(p, 1) == WT_VARID &&
     438          312 :          kind_at(p, 2) == WT_BACKTICK;
     439              : }
     440              : 
     441              : // ------------------------------------------------------------- prototypes
     442              : 
     443              : static WokNode *parse_name(P *p);
     444              : static WokNode *parse_modpath(P *p);
     445              : static WokNode *parse_decl(P *p);
     446              : static WokNode *parse_sig_or_equation(P *p);
     447              : static WokNode *parse_type(P *p);
     448              : static WokNode *parse_arrow(P *p);
     449              : static WokNode *parse_typeapp(P *p);
     450              : static WokNode *parse_atomtype(P *p);
     451              : static WokSeq parse_row(P *p);
     452              : static WokNode *parse_rowentry(P *p);
     453              : static WokNode *parse_pat(P *p);
     454              : static WokNode *parse_patapp(P *p);
     455              : static WokNode *parse_atompat(P *p);
     456              : static WokNode *parse_expr(P *p);
     457              : static WokNode *parse_chain(P *p);
     458              : static WokNode *parse_app(P *p);
     459              : static WokNode *parse_atom(P *p);
     460              : static WokNode *parse_stmt(P *p);
     461              : static WokNode *parse_body(P *p);
     462              : static WokNode *parse_bind(P *p);
     463              : static WokNode *parse_alt(P *p);
     464              : static WokNode *parse_clause(P *p);
     465              : static WokSeq parse_where(P *p);
     466              : 
     467              : // ------------------------------------------------------------ block lists
     468              : 
     469              : typedef WokNode *(*ItemFn)(P *);
     470              : 
     471        20730 : static WokSeq parse_block_list(P *p, const Block *b, ItemFn item,
     472              :                                bool decl_block) {
     473        20730 :   WokNodeBuf buf;
     474        20730 :   wok_buf_init(&buf, p->a);
     475        38372 :   do {
     476        38372 :     usize before = p->i;
     477        38372 :     u32 start = cur(p)->off;
     478        38372 :     WokNode *n = item(p);
     479        38372 :     if (p->panic) {
     480         1602 :       if (decl_block)
     481          396 :         resync_decl(p);
     482              :       else
     483         1206 :         resync_to_boundary(p);
     484         2808 :       n = mk_error(p, decl_block ? D_Error : E_Error, start);
     485         1602 :       p->panic = false;
     486              :     }
     487        38372 :     wok_buf_push(&buf, n);
     488        38372 :     if (p->i == before && !at(p, WT_EOF) && !at(p, WT_NEWLINE) &&
     489            0 :         !at(p, WT_DEDENT))
     490            0 :       bump(p);
     491        38372 :   } while (block_next(p, b));
     492        20730 :   return wok_buf_seq(&buf);
     493              : }
     494              : 
     495              : // A statement that is not also an expression. It carries no value, so it is
     496              : // meaningful only as an ITEM of a block; standing alone as a body it is a
     497              : // `let` (or `handle`, or `use`) whose continuation was never written. Read
     498              : // from the schema rather than hand-listed: the STMT family IS this set, and
     499              : // a new statement form joins the check by joining the family.
     500        46012 : static bool is_statement_only(const WokNode *n) {
     501        46012 :   return wok_node_desc[n->tag].family == WFAM_STMT;
     502              : }
     503              : 
     504              : // Named per form, because the repair differs: three of them are missing an
     505              : // `in`, and the fourth cannot be repaired that way at all -- `_ = e` throws
     506              : // its value away, so it can only be one line of a block, never the thing a
     507              : // body evaluates to. An if-chain rather than a switch: this is four tags out
     508              : // of eighty-two, so -Wswitch has nothing to hold down here.
     509           41 : static void perr_unfinished_stmt(P *p, const WokNode *n) {
     510           41 :   if (n->tag == S_Let)
     511            7 :     perr_expect(p, "`in` after this binding");
     512           34 :   else if (n->tag == S_Handle)
     513           34 :     perr_expect(p, "`in` after the handler of this `handle`");
     514            0 :   else if (n->tag == S_Use)
     515            0 :     perr_expect(p, "`in` after these bridges");
     516              :   else
     517            0 :     perr_expect(p, "a value here; `_ =` discards a result");
     518           41 : }
     519              : 
     520              : // A body is `expr | E_Block`. E_Block is used whenever the block form was
     521              : // taken -- even for one statement -- so a printer can re-derive line breaks.
     522              : //
     523              : // The block form has TWO spellings. The first is the ordinary one: the body
     524              : // opens its own block under the head. The second is the HANGING body --
     525              : // the first statement shares the head's line and the rest are indented under
     526              : // it:
     527              : //
     528              : //     add x, k -> let t = t + x        False -> budget := budget - 1
     529              : //                 t := 9                        k (lookup q)
     530              : //
     531              : // Both are normative (spec.md 1.1's column-aware arm bodies; spec-min section
     532              : // 6's `race`; reject/13). The layout filter already emits NEWLINE INDENT for
     533              : // the continuation, so the whole difference is that the block's FIRST item was
     534              : // read before the block opened -- which is why this cannot be decided by
     535              : // lookahead: an inline `case` opens a block of its own on the same line, and
     536              : // only parsing tells the two INDENTs apart.
     537        50107 : static WokNode *parse_block_body(P *p, const Block *b) {
     538        50107 :   u32 start = cur(p)->off;
     539        50107 :   if (b->indented) {
     540         3935 :     WokSeq stmts = parse_block_list(p, b, parse_stmt, false);
     541         3935 :     WokNode *n = mk(p, E_Block, start, span_end(p));
     542         3935 :     E_Block_set_stmts(n, stmts);
     543         3935 :     return n;
     544              :   }
     545              : 
     546              :   // THE ANCHOR. Inline bodies nest on one line -- `let t = t + x` is a clause
     547              :   // body holding a binding whose own body is `t + x` -- and every one of them
     548              :   // is looking at the same INDENT. The block belongs to the body whose first
     549              :   // token stands in the block's column, which is the offside rule read
     550              :   // literally and the only reading that lets reject/13 mean what it says:
     551              :   //
     552              :   //     add x, k -> let t = t + x     the continuation is in `let`'s column,
     553              :   //                 t := 9            so it continues the CLAUSE body, and
     554              :   //                 k ()              `let t = t + x` is one statement of it.
     555              :   //
     556              :   // A column matching no inline body is not silently attached to the nearest
     557              :   // one: it falls through to D-LAY-3, which is what a misaligned line is.
     558              :   //
     559              :   // Limit, stated: the first statement must be SUB-BLOCK-FREE. If it opens
     560              :   // its own indented block (`set x -> case x of` with the alts deeper), that
     561              :   // block's DEDENT closes every level at or right of the anchor, so a
     562              :   // continuation standing in the anchor column arrives with no open level to
     563              :   // match and is D-LAY-3, not a clause-body line. Only a sub-block-free
     564              :   // first statement can share the arrow's line and still be continued
     565              :   // (README, Limits).
     566        46172 :   u32 anchor = cur(p)->col;
     567        46172 :   WokNode *first = parse_stmt(p);
     568              :   // The panic path takes the same statement guard as the fall-through below:
     569              :   // an enclosing block list may absorb and CLEAR the panic, and then a STMT
     570              :   // node returned here would stand in an EXPR slot of the final tree.
     571        46172 :   if (p->panic) return is_statement_only(first) ? mk_err(p) : first;
     572              : 
     573        38170 :   if (at(p, WT_NEWLINE) && kind_at(p, 1) == WT_INDENT && col_at(p, 2) == anchor) {
     574          160 :     Block hb = {.indented = true};
     575          160 :     bump(p);
     576          160 :     bump(p);
     577          160 :     WokNodeBuf buf;
     578          160 :     wok_buf_init(&buf, p->a);
     579          160 :     wok_buf_push(&buf, first);
     580          160 :     WokSeq rest = parse_block_list(p, &hb, parse_stmt, false);
     581          473 :     for (u32 i = 0; i < rest.n; i++) wok_buf_push(&buf, rest.items[i]);
     582          160 :     block_end(p, &hb);
     583          160 :     WokNode *n = mk(p, E_Block, start, span_end(p));
     584          160 :     E_Block_set_stmts(n, wok_buf_seq(&buf));
     585          160 :     return n;
     586              :   }
     587              : 
     588        38010 :   if (is_statement_only(first)) {
     589           41 :     perr_unfinished_stmt(p, first);
     590              :     // The node's FAMILY is STMT and an expression is due, so returning it
     591              :     // would put a statement where the schema demands an expression. Damage
     592              :     // is a wildcard family precisely so a damaged parse still dumps.
     593           41 :     return mk_err(p);
     594              :   }
     595              :   return first;
     596              : }
     597              : 
     598        17530 : static WokNode *parse_body(P *p) {
     599        17530 :   Block b = block_begin(p);
     600        17530 :   WokNode *n = parse_block_body(p, &b);
     601        17530 :   block_end(p, &b);
     602        17530 :   return n;
     603              : }
     604              : 
     605         2750 : static WokSeq parse_where(P *p) {
     606         2750 :   bump(p);  // `where`
     607         2750 :   Block b = block_begin(p);
     608         2750 :   WokSeq s = parse_block_list(p, &b, parse_sig_or_equation, true);
     609         2750 :   block_end(p, &b);
     610         2750 :   return s;
     611              : }
     612              : 
     613              : // ------------------------------------------------------------------ names
     614              : 
     615        70770 : static WokNode *parse_name(P *p) {
     616        70770 :   const WokToken *t = cur(p);
     617        70770 :   u32 start = t->off;
     618        70770 :   bool upper = (WokKind)t->kind == WT_CONID;
     619        70770 :   WokSpan text;
     620        70770 :   if (upper || (WokKind)t->kind == WT_VARID) {
     621        70650 :     text = tok_span(t);
     622        70650 :     bump(p);
     623              :   } else {
     624          120 :     perr_expect(p, "a name");
     625          120 :     text = wok_span(t->off, 0);
     626          120 :     upper = false;
     627              :   }
     628        70770 :   WokNode *n = mk(p, N_Name, start, span_end(p));
     629        70770 :   N_Name_set_text(n, text);
     630        70770 :   N_Name_set_upper(n, upper);
     631        70770 :   return n;
     632              : }
     633              : 
     634        65862 : static WokNode *parse_modpath(P *p) {
     635        65862 :   u32 start = cur(p)->off;
     636        65862 :   WokNodeBuf parts;
     637        65862 :   wok_buf_init(&parts, p->a);
     638        65862 :   wok_buf_push(&parts, parse_name(p));
     639        68930 :   while (at(p, WT_DOT) && kind_at(p, 1) == WT_CONID && !p->panic) {
     640         3068 :     bump(p);
     641         3068 :     wok_buf_push(&parts, parse_name(p));
     642              :   }
     643        65862 :   WokNode *n = mk(p, N_ModPath, start, span_end(p));
     644        65862 :   N_ModPath_set_parts(n, wok_buf_seq(&parts));
     645        65862 :   return n;
     646              : }
     647              : 
     648              : // ------------------------------------------------------------------ types
     649              : 
     650        55532 : static WokNode *parse_type(P *p) {
     651        55532 :   if (!enter(p)) return mk_err(p);
     652        55532 :   u32 start = cur(p)->off;
     653        55532 :   WokNode *lhs = parse_arrow(p);
     654        55532 :   WokNode *r = lhs;
     655        55532 :   if (at(p, WT_FATARROW)) {
     656         2453 :     bump(p);
     657         2453 :     WokNode *body = parse_type(p);
     658         2453 :     r = mk(p, T_Qual, start, span_end(p));
     659         2453 :     T_Qual_set_ctx(r, lhs);
     660         2453 :     T_Qual_set_body(r, body);
     661        53079 :   } else if (at_word(p, WW_WITH)) {
     662         4415 :     bump(p);
     663         4415 :     WokSeq row = parse_row(p);
     664         4415 :     r = mk(p, T_With, start, span_end(p));
     665         4415 :     T_With_set_body(r, lhs);
     666         4415 :     T_With_set_row(r, row);
     667              :   }
     668        55532 :   leave(p);
     669        55532 :   return r;
     670              : }
     671              : 
     672        70537 : static WokNode *parse_arrow(P *p) {
     673        70537 :   if (!enter(p)) return mk_err(p);
     674        70537 :   u32 start = cur(p)->off;
     675        70537 :   WokNode *l = parse_typeapp(p);
     676        70537 :   WokNode *r = l;
     677        70537 :   if (at(p, WT_ARROW)) {
     678        15005 :     bump(p);
     679        15005 :     WokNode *to = parse_arrow(p);
     680        15005 :     r = mk(p, T_Fun, start, span_end(p));
     681        15005 :     T_Fun_set_from(r, l);
     682        15005 :     T_Fun_set_to(r, to);
     683              :   }
     684        70537 :   leave(p);
     685        70537 :   return r;
     686              : }
     687              : 
     688        76507 : static WokNode *parse_typeapp(P *p) {
     689        76507 :   u32 start = cur(p)->off;
     690        76507 :   u64 mode = WOK_TRANSFER_OWN;
     691        76507 :   bool transfer = false;
     692        76507 :   if (p->foreign_sig && at(p, WT_VARID)) {
     693         1181 :     WokWord w = (WokWord)cur(p)->word;
     694         1181 :     if (w == WW_OWN) {
     695              :       mode = WOK_TRANSFER_OWN;
     696              :       transfer = true;
     697              :     } else if (w == WW_LEND) {
     698              :       mode = WOK_TRANSFER_LEND;
     699              :       transfer = true;
     700              :     } else if (w == WW_COPY) {
     701              :       mode = WOK_TRANSFER_COPY;
     702              :       transfer = true;
     703              :     }
     704          392 :     if (transfer) bump(p);
     705              :   }
     706        76507 :   WokNode *t = parse_atomtype(p);
     707        94642 :   while (starts_atomtype(p) && !p->panic) {
     708        18135 :     WokNode *arg = parse_atomtype(p);
     709        18135 :     WokNode *app = mk(p, T_App, start, span_end(p));
     710        18135 :     T_App_set_fn(app, t);
     711        18135 :     T_App_set_arg(app, arg);
     712        18135 :     t = app;
     713              :   }
     714        76507 :   if (transfer) {
     715          392 :     WokNode *tr = mk(p, T_Transfer, start, span_end(p));
     716          392 :     T_Transfer_set_mode(tr, mode);
     717          392 :     T_Transfer_set_body(tr, t);
     718          392 :     t = tr;
     719              :   }
     720        76507 :   return t;
     721              : }
     722              : 
     723        98037 : static WokNode *parse_atomtype(P *p) {
     724        98037 :   if (!enter(p)) return mk_err(p);
     725        98037 :   u32 start = cur(p)->off;
     726        98037 :   WokNode *r;
     727        98037 :   if (at(p, WT_VARID)) {
     728        18726 :     const WokToken *t = cur(p);
     729        18726 :     bump(p);
     730        18726 :     r = mk(p, T_Var, start, span_end(p));
     731        18726 :     T_Var_set_name(r, tok_span(t));
     732        79311 :   } else if (at(p, WT_CONID)) {
     733        48109 :     WokNode *path = parse_modpath(p);
     734        48109 :     r = mk(p, T_Con, start, span_end(p));
     735        48109 :     T_Con_set_path(r, path);
     736        31202 :   } else if (at(p, WT_LBRACKET)) {
     737         4403 :     bump(p);
     738         4403 :     WokNode *elem = parse_type(p);
     739         4403 :     expect(p, WT_RBRACKET, "`]` to close this list type");
     740         4403 :     r = mk(p, T_List, start, span_end(p));
     741         4403 :     T_List_set_elem(r, elem);
     742        26799 :   } else if (at(p, WT_LPAREN)) {
     743        25445 :     bump(p);
     744        25445 :     if (at(p, WT_RPAREN)) {
     745         9836 :       bump(p);
     746         9836 :       r = mk(p, T_Unit, start, span_end(p));
     747        15609 :     } else if (at_word(p, WW_ROW) && kind_at(p, 1) == WT_VARID &&
     748        12158 :                kind_at(p, 2) == WT_RPAREN) {
     749         6079 :       bump(p);
     750         6079 :       const WokToken *v = cur(p);
     751         6079 :       bump(p);
     752         6079 :       bump(p);
     753         6079 :       r = mk(p, T_RowArg, start, span_end(p));
     754         6079 :       T_RowArg_set_name(r, tok_span(v));
     755              :     } else {
     756         9530 :       WokNodeBuf items;
     757         9530 :       wok_buf_init(&items, p->a);
     758         9530 :       wok_buf_push(&items, parse_type(p));
     759        15158 :       while (at(p, WT_COMMA) && !p->panic) {
     760         5628 :         bump(p);
     761         5628 :         wok_buf_push(&items, parse_type(p));
     762              :       }
     763         9530 :       expect(p, WT_RPAREN, "`)` to close this type");
     764         9530 :       if (wok_buf_count(&items) == 1) {
     765         4567 :         r = wok_buf_at(&items, 0);
     766              :       } else {
     767         4963 :         r = mk(p, T_Tuple, start, span_end(p));
     768         4963 :         T_Tuple_set_items(r, wok_buf_seq(&items));
     769              :       }
     770              :     }
     771              :   } else {
     772         1354 :     perr_expect(p, "a type");
     773         1354 :     r = mk_err(p);
     774              :   }
     775        98037 :   leave(p);
     776        98037 :   return r;
     777              : }
     778              : 
     779         4415 : static WokSeq parse_row(P *p) {
     780         4415 :   WokNodeBuf buf;
     781         4415 :   wok_buf_init(&buf, p->a);
     782         4415 :   wok_buf_push(&buf, parse_rowentry(p));
     783         6794 :   while (at_word(p, WW_PLUS) && !p->panic) {
     784         2379 :     bump(p);
     785         2379 :     wok_buf_push(&buf, parse_rowentry(p));
     786              :   }
     787         4415 :   return wok_buf_seq(&buf);
     788              : }
     789              : 
     790         6794 : static WokNode *parse_rowentry(P *p) {
     791         6794 :   u32 start = cur(p)->off;
     792         6794 :   WokNode *n;
     793         6794 :   if (at(p, WT_LPAREN) && kind_at(p, 1) == WT_VARID &&
     794         2733 :       kind_at(p, 2) == WT_COLON) {
     795              :     // A role obligation (D20). The label is lowercase by construction.
     796         1129 :     bump(p);
     797         1129 :     const WokToken *label = cur(p);
     798         1129 :     bump(p);
     799         1129 :     bump(p);
     800         1129 :     WokNode *ty = parse_typeapp(p);
     801         1129 :     expect(p, WT_RPAREN, "`)` to close this role obligation");
     802         1129 :     n = mk(p, H_RowEntry, start, span_end(p));
     803         1129 :     H_RowEntry_set_kind(n, WOK_ROW_ROLE);
     804         1129 :     H_RowEntry_set_label(n, tok_span(label));
     805         1129 :     H_RowEntry_set_type(n, ty);
     806         6489 :   } else if (at_word(p, WW_EFF) && kind_at(p, 1) == WT_VARID) {
     807          824 :     bump(p);
     808          824 :     const WokToken *v = cur(p);
     809          824 :     bump(p);
     810          824 :     n = mk(p, H_RowEntry, start, span_end(p));
     811          824 :     H_RowEntry_set_kind(n, WOK_ROW_VAR);
     812          824 :     H_RowEntry_set_label(n, tok_span(v));
     813          824 :     H_RowEntry_set_type(n, nullptr);
     814              :   } else {
     815         4841 :     WokNode *ty = parse_typeapp(p);
     816         4841 :     n = mk(p, H_RowEntry, start, span_end(p));
     817         4841 :     H_RowEntry_set_kind(n, WOK_ROW_SLOT);
     818         4841 :     H_RowEntry_set_label(n, wok_span(start, 0));
     819         4841 :     H_RowEntry_set_type(n, ty);
     820              :   }
     821         6794 :   return n;
     822              : }
     823              : 
     824              : // --------------------------------------------------------------- patterns
     825              : 
     826          805 : static WokNode *parse_fieldpat(P *p) {
     827          805 :   u32 start = cur(p)->off;
     828          805 :   WokSpan name = take_kind(p, WT_VARID, "a field name");
     829          805 :   WokNode *pat;
     830          805 :   if (at(p, WT_EQUALS)) {
     831          607 :     bump(p);
     832          607 :     pat = parse_pat(p);
     833              :   } else {
     834              :     // Punned: `{ x }` binds the field to its own name.
     835          198 :     pat = mk(p, P_Var, start, span_end(p));
     836          198 :     P_Var_set_name(pat, name);
     837              :   }
     838          805 :   WokNode *n = mk(p, H_FieldPat, start, span_end(p));
     839          805 :   H_FieldPat_set_name(n, name);
     840          805 :   H_FieldPat_set_pat(n, pat);
     841          805 :   return n;
     842              : }
     843              : 
     844         1195 : static WokNode *parse_record_pat(P *p, u32 start, WokNode *path) {
     845         1195 :   bump(p);  // `{`
     846         1195 :   bool is_open = false;
     847         1195 :   WokSpan rest = wok_span(start, 0);
     848         1195 :   if (at(p, WT_DOTDOT)) {
     849          590 :     bump(p);
     850          590 :     is_open = true;
     851          590 :     if (at(p, WT_VARID)) {
     852          376 :       rest = tok_span(cur(p));
     853          376 :       bump(p);
     854              :     }
     855          590 :     if (at(p, WT_COMMA)) bump(p);
     856              :   }
     857         1195 :   WokNodeBuf fields;
     858         1195 :   wok_buf_init(&fields, p->a);
     859         1195 :   if (!at(p, WT_RBRACE) && !p->panic) {
     860          510 :     wok_buf_push(&fields, parse_fieldpat(p));
     861          805 :     while (at(p, WT_COMMA) && !p->panic) {
     862          295 :       bump(p);
     863          295 :       wok_buf_push(&fields, parse_fieldpat(p));
     864              :     }
     865              :   }
     866         1195 :   expect(p, WT_RBRACE, "`}` to close this record pattern");
     867         1195 :   WokNode *n = mk(p, P_Record, start, span_end(p));
     868         1195 :   P_Record_set_path(n, path);
     869         1195 :   P_Record_set_fields(n, wok_buf_seq(&fields));
     870         1195 :   P_Record_set_is_open(n, is_open);
     871         1195 :   P_Record_set_rest(n, rest);
     872         1195 :   return n;
     873              : }
     874              : 
     875        42200 : static WokNode *parse_as_tail(P *p, WokNode *inner, u32 start) {
     876        42701 :   while (at_word(p, WW_AS) && !p->panic) {
     877          501 :     bump(p);
     878          501 :     WokSpan name = take_kind(p, WT_VARID, "a name after `as`");
     879          501 :     WokNode *n = mk(p, P_As, start, span_end(p));
     880          501 :     P_As_set_pat(n, inner);
     881          501 :     P_As_set_name(n, name);
     882          501 :     inner = n;
     883              :   }
     884        42200 :   return inner;
     885              : }
     886              : 
     887        40515 : static WokNode *parse_atompat(P *p) {
     888        40515 :   if (!enter(p)) return mk_err(p);
     889        40515 :   u32 start = cur(p)->off;
     890        40515 :   WokNode *r;
     891        40515 :   if (at(p, WT_VARID)) {
     892        17998 :     const WokToken *t = cur(p);
     893        17998 :     bump(p);
     894        17998 :     r = mk(p, P_Var, start, span_end(p));
     895        17998 :     P_Var_set_name(r, tok_span(t));
     896        22517 :   } else if (at(p, WT_UNDERSCORE)) {
     897         1547 :     bump(p);
     898         1547 :     r = mk(p, P_Wild, start, span_end(p));
     899        20970 :   } else if (at(p, WT_INT)) {
     900         1095 :     const WokToken *t = cur(p);
     901         1095 :     u64 v = 0;
     902         1095 :     (void)wok_token_int_value(p->src, t, p->d, &v);
     903         1095 :     bump(p);
     904         1095 :     r = mk(p, P_Int, start, span_end(p));
     905         1095 :     P_Int_set_value(r, v);
     906         1095 :     P_Int_set_negative(r, false);
     907        20620 :   } else if (at(p, WT_VARSYM) && kind_at(p, 1) == WT_INT) {
     908          745 :     bump(p);
     909          745 :     const WokToken *t = cur(p);
     910          745 :     u64 v = 0;
     911          745 :     (void)wok_token_int_value(p->src, t, p->d, &v);
     912          745 :     bump(p);
     913          745 :     r = mk(p, P_Int, start, span_end(p));
     914          745 :     P_Int_set_value(r, v);
     915          745 :     P_Int_set_negative(r, true);
     916        19130 :   } else if (at(p, WT_STRING)) {
     917         1267 :     const WokToken *t = cur(p);
     918         1267 :     bump(p);
     919         1267 :     r = mk(p, P_Str, start, span_end(p));
     920         1267 :     P_Str_set_text(r, tok_span(t));
     921        17863 :   } else if (at(p, WT_CHAR)) {
     922         1231 :     const WokToken *t = cur(p);
     923         1231 :     bump(p);
     924         1231 :     r = mk(p, P_Char, start, span_end(p));
     925         1231 :     P_Char_set_text(r, tok_span(t));
     926        16632 :   } else if (at(p, WT_CONID)) {
     927         2096 :     WokNode *path = parse_modpath(p);
     928         2096 :     if (at(p, WT_LBRACE)) {
     929          703 :       r = parse_record_pat(p, start, path);
     930              :     } else {
     931         1393 :       r = mk(p, P_Con, start, span_end(p));
     932         1393 :       P_Con_set_path(r, path);
     933         1393 :       P_Con_set_args(r, wok_seq_empty());
     934              :     }
     935        14536 :   } else if (at(p, WT_LPAREN)) {
     936         5086 :     bump(p);
     937         5086 :     if (at(p, WT_RPAREN)) {
     938         2671 :       bump(p);
     939         2671 :       r = mk(p, P_Unit, start, span_end(p));
     940              :     } else {
     941         2415 :       WokNodeBuf items;
     942         2415 :       wok_buf_init(&items, p->a);
     943         2415 :       wok_buf_push(&items, parse_pat(p));
     944         3569 :       while (at(p, WT_COMMA) && !p->panic) {
     945         1154 :         bump(p);
     946         1154 :         wok_buf_push(&items, parse_pat(p));
     947              :       }
     948         2415 :       expect(p, WT_RPAREN, "`)` to close this pattern");
     949         2415 :       if (wok_buf_count(&items) == 1) {
     950         1375 :         r = wok_buf_at(&items, 0);
     951              :       } else {
     952         1040 :         r = mk(p, P_Tuple, start, span_end(p));
     953         1040 :         P_Tuple_set_items(r, wok_buf_seq(&items));
     954              :       }
     955              :     }
     956         9450 :   } else if (at(p, WT_LBRACKET)) {
     957         1107 :     bump(p);
     958         1107 :     WokNodeBuf items;
     959         1107 :     wok_buf_init(&items, p->a);
     960         1107 :     if (!at(p, WT_RBRACKET) && !p->panic) {
     961          239 :       wok_buf_push(&items, parse_pat(p));
     962          331 :       while (at(p, WT_COMMA) && !p->panic) {
     963           92 :         bump(p);
     964           92 :         wok_buf_push(&items, parse_pat(p));
     965              :       }
     966              :     }
     967         1107 :     expect(p, WT_RBRACKET, "`]` to close this list pattern");
     968         1107 :     r = mk(p, P_List, start, span_end(p));
     969         1107 :     P_List_set_items(r, wok_buf_seq(&items));
     970              :   } else {
     971         8343 :     perr_expect(p, "a pattern");
     972         8343 :     r = mk_err(p);
     973              :   }
     974        40515 :   r = parse_as_tail(p, r, start);
     975        40515 :   leave(p);
     976        40515 :   return r;
     977              : }
     978              : 
     979        13160 : static WokNode *parse_patapp(P *p) {
     980        13160 :   if (!at(p, WT_CONID)) return parse_atompat(p);
     981         3280 :   u32 start = cur(p)->off;
     982         3280 :   WokNode *path = parse_modpath(p);
     983         3280 :   if (at(p, WT_LBRACE))
     984          492 :     return parse_as_tail(p, parse_record_pat(p, start, path), start);
     985         2788 :   if (!starts_atompat(p)) {
     986         1193 :     WokNode *bare = mk(p, P_Con, start, span_end(p));
     987         1193 :     P_Con_set_path(bare, path);
     988         1193 :     P_Con_set_args(bare, wok_seq_empty());
     989         1193 :     return parse_as_tail(p, bare, start);
     990              :   }
     991         1595 :   WokNodeBuf args;
     992         1595 :   wok_buf_init(&args, p->a);
     993         3477 :   while (starts_atompat(p) && !p->panic) wok_buf_push(&args, parse_atompat(p));
     994         1595 :   WokNode *n = mk(p, P_Con, start, span_end(p));
     995         1595 :   P_Con_set_path(n, path);
     996         1595 :   P_Con_set_args(n, wok_buf_seq(&args));
     997         1595 :   return n;
     998              : }
     999              : 
    1000        13160 : static WokNode *parse_pat(P *p) {
    1001        13160 :   if (!enter(p)) return mk_err(p);
    1002        13160 :   u32 start = cur(p)->off;
    1003        13160 :   WokNode *head = parse_patapp(p);
    1004        13160 :   WokNode *r = head;
    1005        13160 :   if (at(p, WT_COLONCOLON)) {
    1006         1003 :     bump(p);
    1007         1003 :     WokNode *tail = parse_pat(p);
    1008         1003 :     r = mk(p, P_Cons, start, span_end(p));
    1009         1003 :     P_Cons_set_head(r, head);
    1010         1003 :     P_Cons_set_tail(r, tail);
    1011              :   }
    1012        13160 :   leave(p);
    1013        13160 :   return r;
    1014              : }
    1015              : 
    1016              : // ------------------------------------------------------------ expressions
    1017              : 
    1018          867 : static WokNode *parse_field(P *p) {
    1019          867 :   u32 start = cur(p)->off;
    1020          867 :   WokSpan name = take_kind(p, WT_VARID, "a field name");
    1021          867 :   WokNode *value;
    1022          867 :   if (at(p, WT_EQUALS)) {
    1023          641 :     bump(p);
    1024          641 :     value = parse_expr(p);
    1025              :   } else {
    1026          226 :     value = mk(p, E_Var, start, span_end(p));
    1027          226 :     E_Var_set_name(value, name);
    1028              :   }
    1029          867 :   WokNode *n = mk(p, H_Field, start, span_end(p));
    1030          867 :   H_Field_set_name(n, name);
    1031          867 :   H_Field_set_value(n, value);
    1032          867 :   return n;
    1033              : }
    1034              : 
    1035          688 : static WokNode *parse_record_expr(P *p, u32 start, WokNode *path) {
    1036          688 :   bump(p);  // `{`
    1037          688 :   WokNode *spread = nullptr;
    1038          688 :   if (at(p, WT_DOTDOT)) {
    1039          326 :     bump(p);
    1040          326 :     spread = parse_expr(p);
    1041          326 :     if (at(p, WT_COMMA)) bump(p);
    1042              :   }
    1043          688 :   WokNodeBuf fields;
    1044          688 :   wok_buf_init(&fields, p->a);
    1045          688 :   if (!at(p, WT_RBRACE) && !p->panic) {
    1046          475 :     wok_buf_push(&fields, parse_field(p));
    1047          867 :     while (at(p, WT_COMMA) && !p->panic) {
    1048          392 :       bump(p);
    1049          392 :       wok_buf_push(&fields, parse_field(p));
    1050              :     }
    1051              :   }
    1052          688 :   expect(p, WT_RBRACE, "`}` to close this record");
    1053          688 :   WokNode *n = mk(p, E_Record, start, span_end(p));
    1054          688 :   E_Record_set_path(n, path);
    1055          688 :   E_Record_set_spread(n, spread);
    1056          688 :   E_Record_set_fields(n, wok_buf_seq(&fields));
    1057          688 :   return n;
    1058              : }
    1059              : 
    1060          693 : static bool is_con_atom(const WokNode *n) {
    1061          693 :   if (n->tag == E_Con) return true;
    1062           85 :   return n->tag == E_Dot && E_Dot_upper(n);
    1063              : }
    1064              : 
    1065        76205 : static WokNode *parse_atom(P *p) {
    1066        76205 :   if (!enter(p)) return mk_err(p);
    1067        76205 :   u32 start = cur(p)->off;
    1068        76205 :   WokNode *r;
    1069        76205 :   if (at(p, WT_VARID)) {
    1070        31857 :     const WokToken *t = cur(p);
    1071        31857 :     bump(p);
    1072        31857 :     r = mk(p, E_Var, start, span_end(p));
    1073        31857 :     E_Var_set_name(r, tok_span(t));
    1074        44348 :   } else if (at(p, WT_CONID)) {
    1075         6046 :     const WokToken *t = cur(p);
    1076         6046 :     bump(p);
    1077         6046 :     r = mk(p, E_Con, start, span_end(p));
    1078         6046 :     E_Con_set_name(r, tok_span(t));
    1079        38302 :   } else if (at(p, WT_INT)) {
    1080        17101 :     const WokToken *t = cur(p);
    1081        17101 :     u64 v = 0;
    1082        17101 :     (void)wok_token_int_value(p->src, t, p->d, &v);
    1083        17101 :     bump(p);
    1084        17101 :     r = mk(p, E_Int, start, span_end(p));
    1085        17101 :     E_Int_set_value(r, v);
    1086        21201 :   } else if (at(p, WT_STRING)) {
    1087         1938 :     const WokToken *t = cur(p);
    1088         1938 :     bump(p);
    1089         1938 :     r = mk(p, E_Str, start, span_end(p));
    1090         1938 :     E_Str_set_text(r, tok_span(t));
    1091        19263 :   } else if (at(p, WT_CHAR)) {
    1092         1223 :     const WokToken *t = cur(p);
    1093         1223 :     bump(p);
    1094         1223 :     r = mk(p, E_Char, start, span_end(p));
    1095         1223 :     E_Char_set_text(r, tok_span(t));
    1096        18040 :   } else if (at(p, WT_LPAREN)) {
    1097         9509 :     bump(p);
    1098         9509 :     if (at(p, WT_RPAREN)) {
    1099         3199 :       bump(p);
    1100         3199 :       r = mk(p, E_Unit, start, span_end(p));
    1101         7621 :     } else if (at(p, WT_VARSYM) && kind_at(p, 1) == WT_RPAREN) {
    1102         1311 :       const WokToken *t = cur(p);
    1103         1311 :       bump(p);
    1104         1311 :       bump(p);
    1105         1311 :       r = mk(p, E_OpRef, start, span_end(p));
    1106         1311 :       E_OpRef_set_name(r, tok_span(t));
    1107              :     } else {
    1108         4999 :       WokNodeBuf items;
    1109         4999 :       wok_buf_init(&items, p->a);
    1110         4999 :       wok_buf_push(&items, parse_expr(p));
    1111         7255 :       while (at(p, WT_COMMA) && !p->panic) {
    1112         2256 :         bump(p);
    1113         2256 :         wok_buf_push(&items, parse_expr(p));
    1114              :       }
    1115         4999 :       expect(p, WT_RPAREN, "`)` to close this expression");
    1116         4999 :       if (wok_buf_count(&items) == 1) {
    1117         3080 :         r = wok_buf_at(&items, 0);
    1118              :       } else {
    1119         1919 :         r = mk(p, E_Tuple, start, span_end(p));
    1120         1919 :         E_Tuple_set_items(r, wok_buf_seq(&items));
    1121              :       }
    1122              :     }
    1123         8531 :   } else if (at(p, WT_LBRACKET)) {
    1124         1479 :     bump(p);
    1125         1479 :     WokNodeBuf items;
    1126         1479 :     wok_buf_init(&items, p->a);
    1127         1479 :     if (!at(p, WT_RBRACKET) && !p->panic) {
    1128          930 :       wok_buf_push(&items, parse_expr(p));
    1129         2833 :       while (at(p, WT_COMMA) && !p->panic) {
    1130         1903 :         bump(p);
    1131         1903 :         wok_buf_push(&items, parse_expr(p));
    1132              :       }
    1133              :     }
    1134         1479 :     expect(p, WT_RBRACKET, "`]` to close this list");
    1135         1479 :     r = mk(p, E_List, start, span_end(p));
    1136         1479 :     E_List_set_items(r, wok_buf_seq(&items));
    1137              :   } else {
    1138         7052 :     perr_expect(p, "an expression");
    1139         7052 :     r = mk_err(p);
    1140              :   }
    1141              :   // ONE Dot node for `M.f`, `st.get` and `p.x`: spec 1.5 resolves qualifier /
    1142              :   // label / projection later, and its collision rule needs them
    1143              :   // undistinguished at parse time.
    1144        80847 :   while (!p->panic) {
    1145        72741 :     if (at(p, WT_DOT) &&
    1146         3965 :         (kind_at(p, 1) == WT_VARID || kind_at(p, 1) == WT_CONID)) {
    1147         3954 :       bump(p);
    1148         3954 :       const WokToken *t = cur(p);
    1149         3954 :       bool upper = (WokKind)t->kind == WT_CONID;
    1150         3954 :       bump(p);
    1151         3954 :       WokNode *dot = mk(p, E_Dot, start, span_end(p));
    1152         3954 :       E_Dot_set_recv(dot, r);
    1153         3954 :       E_Dot_set_name(dot, tok_span(t));
    1154         3954 :       E_Dot_set_upper(dot, upper);
    1155         3954 :       r = dot;
    1156         3954 :       continue;
    1157              :     }
    1158        68787 :     if (at(p, WT_LBRACE) && is_con_atom(r)) {
    1159          688 :       r = parse_record_expr(p, start, r);
    1160          688 :       continue;
    1161              :     }
    1162              :     break;
    1163              :   }
    1164        76205 :   leave(p);
    1165        76205 :   return r;
    1166              : }
    1167              : 
    1168        65368 : static WokNode *parse_app(P *p) {
    1169        65368 :   if (!enter(p)) return mk_err(p);
    1170        65368 :   u32 start = cur(p)->off;
    1171        65368 :   WokNode *r;
    1172        65368 :   if (at(p, WT_VARSYM)) {
    1173              :     // In operand position the only prefix operator is `-` (D-LEX-1): negation
    1174              :     // is never glued to its literal, so this needs no text compare.
    1175          740 :     bump(p);
    1176          740 :     WokNode *body = parse_app(p);
    1177          740 :     r = mk(p, E_Neg, start, span_end(p));
    1178          740 :     E_Neg_set_body(r, body);
    1179              :   } else {
    1180        64628 :     r = parse_atom(p);
    1181        76205 :     while (starts_atom(p) && !p->panic) {
    1182        11577 :       WokNode *arg = parse_atom(p);
    1183        11577 :       WokNode *app = mk(p, E_App, start, span_end(p));
    1184        11577 :       E_App_set_fn(app, r);
    1185        11577 :       E_App_set_arg(app, arg);
    1186        11577 :       r = app;
    1187              :     }
    1188              :   }
    1189        65368 :   leave(p);
    1190        65368 :   return r;
    1191              : }
    1192              : 
    1193        57839 : static WokNode *parse_chain(P *p) {
    1194        57839 :   u32 start = cur(p)->off;
    1195        57839 :   WokNode *head = parse_app(p);
    1196        57839 :   WokNodeBuf ops;
    1197        57839 :   wok_buf_init(&ops, p->a);
    1198        64628 :   while (at_infixop(p) && !p->panic) {
    1199         6789 :     u32 ostart = cur(p)->off;
    1200         6789 :     WokSpan name;
    1201         6789 :     bool backtick = false;
    1202         6789 :     if (at(p, WT_BACKTICK)) {
    1203          256 :       bump(p);
    1204          256 :       name = tok_span(cur(p));
    1205          256 :       backtick = true;
    1206          256 :       bump(p);
    1207          256 :       expect(p, WT_BACKTICK, "a closing backtick");
    1208              :     } else {
    1209         6533 :       name = tok_span(cur(p));
    1210         6533 :       bump(p);
    1211              :     }
    1212         6789 :     WokNode *rhs = parse_app(p);
    1213         6789 :     WokNode *op = mk(p, H_ChainOp, ostart, span_end(p));
    1214         6789 :     H_ChainOp_set_op(op, name);
    1215         6789 :     H_ChainOp_set_backtick(op, backtick);
    1216         6789 :     H_ChainOp_set_rhs(op, rhs);
    1217         6789 :     wok_buf_push(&ops, op);
    1218              :   }
    1219        57839 :   if (wok_buf_count(&ops) == 0) return head;
    1220         5965 :   WokNode *n = mk(p, E_Chain, start, span_end(p));
    1221         5965 :   E_Chain_set_head(n, head);
    1222         5965 :   E_Chain_set_ops(n, wok_buf_seq(&ops));
    1223         5965 :   return n;
    1224              : }
    1225              : 
    1226         1313 : static WokNode *parse_lambda(P *p) {
    1227         1313 :   u32 start = cur(p)->off;
    1228         1313 :   bump(p);  // `\`
    1229         1313 :   WokNodeBuf params;
    1230         1313 :   wok_buf_init(&params, p->a);
    1231         2542 :   while (starts_atompat(p) && !p->panic)
    1232         1229 :     wok_buf_push(&params, parse_atompat(p));
    1233         1313 :   expect(p, WT_ARROW, "`->` after the lambda's parameters");
    1234         1313 :   WokNode *body = parse_body(p);
    1235         1313 :   WokNode *n = mk(p, E_Lambda, start, span_end(p));
    1236         1313 :   E_Lambda_set_params(n, wok_buf_seq(&params));
    1237         1313 :   E_Lambda_set_body(n, body);
    1238         1313 :   return n;
    1239              : }
    1240              : 
    1241          576 : static WokNode *parse_if(P *p) {
    1242          576 :   u32 start = cur(p)->off;
    1243          576 :   bump(p);  // `if`
    1244          576 :   WokNode *cond = parse_expr(p);
    1245          576 :   expect_word(p, WW_THEN, "`then` after the condition");
    1246          576 :   WokNode *then_ = parse_body(p);
    1247          576 :   expect_word(p, WW_ELSE, "`else` after the `then` branch");
    1248          576 :   WokNode *else_ = parse_body(p);
    1249          576 :   WokNode *n = mk(p, E_If, start, span_end(p));
    1250          576 :   E_If_set_cond(n, cond);
    1251          576 :   E_If_set_then_(n, then_);
    1252          576 :   E_If_set_else_(n, else_);
    1253          576 :   return n;
    1254              : }
    1255              : 
    1256         3100 : static WokNode *parse_case(P *p) {
    1257         3100 :   u32 start = cur(p)->off;
    1258         3100 :   bump(p);  // `case`
    1259         3100 :   WokNode *scrut = parse_expr(p);
    1260         3100 :   expect_word(p, WW_OF, "`of` after the scrutinee");
    1261         3100 :   Block b = block_begin(p);
    1262         3100 :   WokSeq alts = parse_block_list(p, &b, parse_alt, false);
    1263         3100 :   block_end(p, &b);
    1264         3100 :   WokNode *n = mk(p, E_Case, start, span_end(p));
    1265         3100 :   E_Case_set_scrut(n, scrut);
    1266         3100 :   E_Case_set_alts(n, alts);
    1267         3100 :   return n;
    1268              : }
    1269              : 
    1270         3461 : static WokNode *parse_handler(P *p) {
    1271         3461 :   u32 start = cur(p)->off;
    1272         3461 :   bump(p);  // `handler`
    1273              :   // `handler E` names its effect MANDATORILY (C3).
    1274         3461 :   WokSpan effect = take_kind(p, WT_CONID, "the effect this handler handles");
    1275         3461 :   Block b = block_begin(p);
    1276         3461 :   WokSeq clauses = parse_block_list(p, &b, parse_clause, false);
    1277         3461 :   block_end(p, &b);
    1278         3461 :   WokNode *n = mk(p, E_Handler, start, span_end(p));
    1279         3461 :   E_Handler_set_effect(n, effect);
    1280         3461 :   E_Handler_set_clauses(n, clauses);
    1281         3461 :   return n;
    1282              : }
    1283              : 
    1284          717 : static WokSeq parse_usebinds(P *p) {
    1285          717 :   WokNodeBuf buf;
    1286          717 :   wok_buf_init(&buf, p->a);
    1287           69 :   for (;;) {
    1288          786 :     u32 start = cur(p)->off;
    1289          786 :     WokSpan from = take_any_name(p, "the label to bridge from");
    1290          786 :     expect_word(p, WW_AS, "`as` between the two labels");
    1291          786 :     WokSpan to = take_any_name(p, "the label to bridge to");
    1292          786 :     WokNode *n = mk(p, H_UseBind, start, span_end(p));
    1293          786 :     H_UseBind_set_from(n, from);
    1294          786 :     H_UseBind_set_to(n, to);
    1295          786 :     wok_buf_push(&buf, n);
    1296          786 :     if (p->panic || !at(p, WT_COMMA)) break;
    1297           69 :     bump(p);
    1298              :   }
    1299          717 :   return wok_buf_seq(&buf);
    1300              : }
    1301              : 
    1302              : // A label is written only when a name is immediately followed by `=`.
    1303         3331 : static bool at_handle_label(const P *p) {
    1304         3331 :   return (at(p, WT_VARID) || at(p, WT_CONID)) && kind_at(p, 1) == WT_EQUALS;
    1305              : }
    1306              : 
    1307        66883 : static WokNode *parse_expr(P *p) {
    1308        66883 :   if (!enter(p)) return mk_err(p);
    1309        66883 :   u32 start = cur(p)->off;
    1310        66883 :   WokNode *r;
    1311        66883 :   if (at(p, WT_LAMBDA)) {
    1312         1313 :     r = parse_lambda(p);
    1313        65570 :   } else if (at_word(p, WW_LET)) {
    1314           90 :     bump(p);
    1315           90 :     WokNode *bind = parse_bind(p);
    1316           90 :     expect_word(p, WW_IN, "`in` after this binding");
    1317           90 :     WokNode *body = parse_body(p);
    1318           90 :     r = mk(p, E_LetIn, start, span_end(p));
    1319           90 :     E_LetIn_set_bind(r, bind);
    1320           90 :     E_LetIn_set_body(r, body);
    1321        65480 :   } else if (at_word(p, WW_HANDLE)) {
    1322          396 :     bump(p);
    1323              :     // The delimited inline form MAY elide the label (D13 two-tier); the
    1324              :     // default is then read off the handler's type.
    1325          396 :     WokSpan label = wok_span(start, 0);
    1326          396 :     if (at_handle_label(p)) {
    1327          235 :       label = tok_span(cur(p));
    1328          235 :       bump(p);
    1329          235 :       bump(p);
    1330              :     }
    1331          396 :     WokNode *handler = parse_expr(p);
    1332          396 :     expect_word(p, WW_IN, "`in` after the handler of this `handle`");
    1333          396 :     WokNode *body = parse_body(p);
    1334          396 :     r = mk(p, E_HandleIn, start, span_end(p));
    1335          396 :     E_HandleIn_set_label(r, label);
    1336          396 :     E_HandleIn_set_handler(r, handler);
    1337          396 :     E_HandleIn_set_body(r, body);
    1338        65084 :   } else if (at_word(p, WW_USE)) {
    1339          108 :     bump(p);
    1340          108 :     WokSeq binds = parse_usebinds(p);
    1341          108 :     expect_word(p, WW_IN, "`in` after these bridges");
    1342          108 :     WokNode *body = parse_body(p);
    1343          108 :     r = mk(p, E_UseIn, start, span_end(p));
    1344          108 :     E_UseIn_set_binds(r, binds);
    1345          108 :     E_UseIn_set_body(r, body);
    1346        64976 :   } else if (at_word(p, WW_IF)) {
    1347          576 :     r = parse_if(p);
    1348        64400 :   } else if (at_word(p, WW_CASE)) {
    1349         3100 :     r = parse_case(p);
    1350        61300 :   } else if (at_word(p, WW_HANDLER)) {
    1351         3461 :     r = parse_handler(p);
    1352              :   } else {
    1353        57839 :     r = parse_chain(p);
    1354        57839 :     if (at(p, WT_ASSIGN)) {
    1355         1447 :       bump(p);
    1356         1447 :       WokNode *value = parse_body(p);
    1357         1447 :       WokNode *asn = mk(p, E_Assign, start, span_end(p));
    1358         1447 :       E_Assign_set_target(asn, r);
    1359         1447 :       E_Assign_set_value(asn, value);
    1360         1447 :       r = asn;
    1361              :     }
    1362              :   }
    1363        66883 :   leave(p);
    1364        66883 :   return r;
    1365              : }
    1366              : 
    1367              : // --------------------------------------------------------------- statements
    1368              : 
    1369         3300 : static WokNode *parse_bind(P *p) {
    1370         3300 :   u32 start = cur(p)->off;
    1371         3300 :   WokNode *lhs;
    1372         3300 :   if (at(p, WT_VARID)) {
    1373         2619 :     const WokToken *t = cur(p);
    1374         2619 :     bump(p);
    1375         2619 :     WokNodeBuf args;
    1376         2619 :     wok_buf_init(&args, p->a);
    1377         2632 :     while (starts_atompat(p) && !p->panic)
    1378           13 :       wok_buf_push(&args, parse_atompat(p));
    1379         2619 :     lhs = mk(p, L_Prefix, start, span_end(p));
    1380         2619 :     L_Prefix_set_name(lhs, tok_span(t));
    1381         2619 :     L_Prefix_set_paren(lhs, false);
    1382         2619 :     L_Prefix_set_args(lhs, wok_buf_seq(&args));
    1383              :   } else {
    1384          681 :     lhs = parse_pat(p);
    1385              :   }
    1386         3300 :   expect(p, WT_EQUALS, "`=` in this binding");
    1387         3300 :   WokNode *body = parse_body(p);
    1388         3300 :   WokNode *n = mk(p, H_Bind, start, span_end(p));
    1389         3300 :   H_Bind_set_lhs(n, lhs);
    1390         3300 :   H_Bind_set_body(n, body);
    1391         3300 :   return n;
    1392              : }
    1393              : 
    1394        55792 : static WokNode *parse_stmt(P *p) {
    1395        55792 :   if (!enter(p)) return mk_err(p);
    1396        55792 :   u32 start = cur(p)->off;
    1397        55792 :   const WokToken *lead = cur(p);
    1398        55792 :   WokNode *r;
    1399        55792 :   if (at_word(p, WW_LET)) {
    1400         3210 :     bump(p);
    1401         3210 :     WokNode *bind = parse_bind(p);
    1402              :     // `in` is a continuation lead, so it arrives with no NEWLINE before it:
    1403              :     // after the binding, simply look at the current token.
    1404         3210 :     if (at_word(p, WW_IN)) {
    1405          468 :       bump(p);
    1406          468 :       WokNode *body = parse_body(p);
    1407          468 :       r = mk(p, E_LetIn, start, span_end(p));
    1408          468 :       E_LetIn_set_bind(r, bind);
    1409          468 :       E_LetIn_set_body(r, body);
    1410              :     } else {
    1411         2742 :       r = mk(p, S_Let, start, span_end(p));
    1412         2742 :       S_Let_set_bind(r, bind);
    1413              :     }
    1414        52582 :   } else if (at_word(p, WW_HANDLE)) {
    1415         2935 :     bump(p);
    1416         2935 :     WokSpan label = wok_span(start, 0);
    1417         2935 :     bool has_label = at_handle_label(p);
    1418         2935 :     if (has_label) {
    1419         1437 :       label = tok_span(cur(p));
    1420         1437 :       bump(p);
    1421         1437 :       bump(p);
    1422              :     }
    1423         2935 :     WokNode *handler = parse_expr(p);
    1424         2935 :     if (at_word(p, WW_IN)) {
    1425         1622 :       bump(p);
    1426         1622 :       WokNode *body = parse_body(p);
    1427         1622 :       r = mk(p, E_HandleIn, start, span_end(p));
    1428         1622 :       E_HandleIn_set_label(r, label);
    1429         1622 :       E_HandleIn_set_handler(r, handler);
    1430         1622 :       E_HandleIn_set_body(r, body);
    1431              :     } else {
    1432         1313 :       if (!has_label)
    1433          141 :         perr_form(p, WOK_E_HANDLE_LABEL, lead,
    1434              :                   "a `handle` statement must name the label it binds: write "
    1435              :                   "`handle <label> = <handler>`");
    1436         1313 :       r = mk(p, S_Handle, start, span_end(p));
    1437         1313 :       S_Handle_set_label(r, label);
    1438         1313 :       S_Handle_set_handler(r, handler);
    1439              :     }
    1440        49647 :   } else if (at_word(p, WW_USE)) {
    1441          609 :     bump(p);
    1442          609 :     WokSeq binds = parse_usebinds(p);
    1443          609 :     if (at_word(p, WW_IN)) {
    1444          491 :       bump(p);
    1445          491 :       WokNode *body = parse_body(p);
    1446          491 :       r = mk(p, E_UseIn, start, span_end(p));
    1447          491 :       E_UseIn_set_binds(r, binds);
    1448          491 :       E_UseIn_set_body(r, body);
    1449              :     } else {
    1450          118 :       r = mk(p, S_Use, start, span_end(p));
    1451          118 :       S_Use_set_binds(r, binds);
    1452              :     }
    1453        49255 :   } else if (at(p, WT_UNDERSCORE) && kind_at(p, 1) == WT_EQUALS) {
    1454          217 :     bump(p);
    1455          217 :     bump(p);
    1456          217 :     WokNode *body = parse_body(p);
    1457          217 :     r = mk(p, S_Discard, start, span_end(p));
    1458          217 :     S_Discard_set_body(r, body);
    1459              :   } else {
    1460        48821 :     r = parse_expr(p);
    1461              :   }
    1462        55792 :   leave(p);
    1463        55792 :   return r;
    1464              : }
    1465              : 
    1466         5690 : static WokNode *parse_alt(P *p) {
    1467         5690 :   u32 start = cur(p)->off;
    1468         5690 :   WokNode *pat = parse_pat(p);
    1469         5690 :   expect(p, WT_ARROW, "`->` after the pattern of this alternative");
    1470         5690 :   Block b = block_begin(p);
    1471         5690 :   WokNode *body = parse_block_body(p, &b);
    1472         5690 :   WokSeq wheres = wok_seq_empty();
    1473         5690 :   if (at_word(p, WW_WHERE)) wheres = parse_where(p);
    1474         5690 :   block_end(p, &b);
    1475         5690 :   WokNode *n = mk(p, H_Alt, start, span_end(p));
    1476         5690 :   H_Alt_set_pat(n, pat);
    1477         5690 :   H_Alt_set_body(n, body);
    1478         5690 :   H_Alt_set_wheres(n, wheres);
    1479         5690 :   return n;
    1480              : }
    1481              : 
    1482              : // ------------------------------------------------------------ handler clause
    1483              : //
    1484              : // spec-min section 3, in order, and it reads NOTHING but token kinds and
    1485              : // words -- never a name, a type or a count:
    1486              : //
    1487              : //   1. `var` / `return` / `abort` at the head          -> that kind
    1488              : //   2. `once` at the head                              -> migration diagnostic
    1489              : //   3. a `,` at clause-head depth before the `->`      -> CONTROL, else PLAIN
    1490              : //   4. exactly one bare lowercase varid after the `,`  -> the continuation
    1491              : //
    1492              : // Step 3 needs no lookahead and no depth counter. Clause-head binders are
    1493              : // ATOM patterns, and every atom pattern that can contain a comma is
    1494              : // bracketed, so parse_atompat has already consumed it: a WT_COMMA still
    1495              : // visible here is at head depth by construction.
    1496              : 
    1497              : // The clause-head word set, stated ONCE, directly above the ladder that
    1498              : // dispatches on it. parse_clause's branches and this predicate must agree
    1499              : // forever: a word added to the ladder below without joining this set lets an
    1500              : // effect declare an op spelled like it, and every clause for that op then
    1501              : // silently parses as the new clause KIND instead of a call -- the
    1502              : // parses-silently-and-wrong class. This set churned twice in one week
    1503              : // (`once` demoted, `abort` added); adjacency is the guard.
    1504              : //
    1505              : // Used by parse_opsig for E-RESERVED: a clause naming one of these would be
    1506              : // read as that kind of clause and never as a call. Three are keywords;
    1507              : // `once` is an ordinary name everywhere else (D25) and would otherwise be
    1508              : // accepted as an op name and then be unwritable as a clause.
    1509         6592 : static bool at_reserved_opname(const P *p) {
    1510         6592 :   return at_word(p, WW_ABORT) || at_word(p, WW_RETURN) || at_word(p, WW_VAR) ||
    1511         6589 :          at_word(p, WW_ONCE);
    1512              : }
    1513              : 
    1514         6926 : static WokNode *parse_clause(P *p) {
    1515         6926 :   u32 start = cur(p)->off;
    1516         6926 :   u64 kind = WOK_CLAUSE_PLAIN;
    1517         6926 :   WokSpan name = wok_span(start, 0);
    1518         6926 :   WokSpan k = wok_span(start, 0);
    1519         6926 :   WokNodeBuf pats;
    1520         6926 :   wok_buf_init(&pats, p->a);
    1521         6926 :   WokNode *body = nullptr;
    1522              : 
    1523         6926 :   if (at_word(p, WW_VAR)) {
    1524         1149 :     bump(p);
    1525         1149 :     kind = WOK_CLAUSE_VAR;
    1526         1149 :     name = take_kind(p, WT_VARID, "the baton's name after `var`");
    1527         1149 :     expect(p, WT_EQUALS, "`=` after the baton's name");
    1528         1149 :     body = parse_body(p);
    1529         5777 :   } else if (at_word(p, WW_RETURN)) {
    1530         1279 :     bump(p);
    1531         1279 :     kind = WOK_CLAUSE_RETURN;
    1532         1279 :     wok_buf_push(&pats, parse_pat(p));
    1533         1279 :     expect(p, WT_ARROW, "`->` after the return clause's pattern");
    1534         1279 :     body = parse_body(p);
    1535              :   } else {
    1536              :     // `abort` is the third head keyword (D24). `once` is not a keyword at
    1537              :     // all any more (D25) -- it is a contextual name that means something
    1538              :     // only HERE, and only to say it should not be here.
    1539         4498 :     if (at_word(p, WW_ABORT)) {
    1540          389 :       bump(p);
    1541          389 :       kind = WOK_CLAUSE_ABORT;
    1542         4109 :     } else if (at_word(p, WW_ONCE)) {
    1543           63 :       perr_form(p, WOK_E_MIGRATE, cur(p),
    1544              :                 "v1 clause keyword; drop it -- a control clause is spelled "
    1545              :                 "`op args, k -> body` (D25)");
    1546           63 :       bump(p);
    1547              :     }
    1548         4498 :     name = take_kind(p, WT_VARID, "an operation name");
    1549         7921 :     while (starts_atompat(p) && !p->panic)
    1550         3423 :       wok_buf_push(&pats, parse_atompat(p));
    1551         4498 :     if (at(p, WT_COMMA)) {
    1552              :       // The comma ALONE classifies (D25), so an `abort` head that carries one
    1553              :       // is a contradiction the reader must be told about rather than a fourth
    1554              :       // reading to invent.
    1555         1472 :       if (kind == WOK_CLAUSE_ABORT)
    1556            0 :         perr_form(p, WOK_E_ARITY, cur(p),
    1557              :                   "an `abort` clause never resumes, so it binds no "
    1558              :                   "continuation: drop the `,` and the name after it");
    1559              :       else
    1560              :         kind = WOK_CLAUSE_CONTROL;
    1561         1472 :       bump(p);
    1562         1472 :       if (at(p, WT_VARID)) {
    1563              :         // The ABORT contradiction was reported at the comma; the binder is
    1564              :         // consumed for recovery but NOT stored, so the recovered node keeps
    1565              :         // wok_ast.h's invariant that `k` is empty for every non-control kind.
    1566         1407 :         if (kind != WOK_CLAUSE_ABORT) k = tok_span(cur(p));
    1567         1407 :         bump(p);
    1568              :         // C8: EXACTLY one name. A second binder is an argument written on
    1569              :         // the wrong side of the comma, which is an arity fault about the op,
    1570              :         // not about the continuation.
    1571         1407 :         if (starts_atompat(p) && !p->panic)
    1572           64 :           perr_form(p, WOK_E_ARITY, cur(p),
    1573              :                     "a control clause binds exactly one continuation after "
    1574              :                     "the `,`; the op's arguments go before it");
    1575           65 :       } else if (!p->panic) {
    1576           65 :         perr_form(p, WOK_E_ARITY, cur(p),
    1577              :                   "the name after `,` is the continuation binder and must be "
    1578              :                   "a plain lowercase name, never a pattern");
    1579              :         // The recovered node keeps wok_ast.h's invariant that a CONTROL
    1580              :         // clause has a continuation binder: no binder was stored, so the
    1581              :         // clause is demoted to the plain reading rather than left as a
    1582              :         // CONTROL with an empty `k` that would not re-print.
    1583           65 :         if (kind == WOK_CLAUSE_CONTROL) kind = WOK_CLAUSE_PLAIN;
    1584              :       }
    1585              :       // Whatever followed the comma, the arrow is still where the head ends,
    1586              :       // so a surplus binder is skipped rather than left to derail the body.
    1587         1592 :       while (starts_atompat(p) && !p->panic) (void)parse_atompat(p);
    1588              :     }
    1589         4498 :     expect(p, WT_ARROW, "`->` after the clause's patterns");
    1590         4498 :     body = parse_body(p);
    1591              :   }
    1592              : 
    1593         6926 :   WokNode *n = mk(p, H_Clause, start, span_end(p));
    1594         6926 :   H_Clause_set_kind(n, kind);
    1595         6926 :   H_Clause_set_name(n, name);
    1596         6926 :   H_Clause_set_pats(n, wok_buf_seq(&pats));
    1597         6926 :   H_Clause_set_k(n, k);
    1598         6926 :   H_Clause_set_body(n, body ? body : mk_err(p));
    1599         6926 :   return n;
    1600              : }
    1601              : 
    1602              : // -------------------------------------------------------------- declarations
    1603              : 
    1604         6238 : static WokNode *parse_typaram(P *p) {
    1605         6238 :   u32 start = cur(p)->off;
    1606         6238 :   bool is_row = false;
    1607         6238 :   WokSpan name;
    1608         6238 :   if (at(p, WT_LPAREN)) {
    1609         2038 :     bump(p);
    1610         2038 :     expect_word(p, WW_ROW, "`row` inside this parameter");
    1611         2038 :     name = take_kind(p, WT_VARID, "a row-variable name");
    1612         2038 :     expect(p, WT_RPAREN, "`)` to close this row parameter");
    1613         2038 :     is_row = true;
    1614              :   } else {
    1615         4200 :     name = take_kind(p, WT_VARID, "a type parameter");
    1616              :   }
    1617         6238 :   WokNode *n = mk(p, H_TyParam, start, span_end(p));
    1618         6238 :   H_TyParam_set_name(n, name);
    1619         6238 :   H_TyParam_set_is_row(n, is_row);
    1620         6238 :   return n;
    1621              : }
    1622              : 
    1623        10219 : static WokSeq parse_typarams(P *p) {
    1624        10219 :   WokNodeBuf buf;
    1625        10219 :   wok_buf_init(&buf, p->a);
    1626        16457 :   while (starts_typaram(p) && !p->panic) wok_buf_push(&buf, parse_typaram(p));
    1627        10219 :   return wok_buf_seq(&buf);
    1628              : }
    1629              : 
    1630         3016 : static WokNode *parse_fieldtype(P *p) {
    1631         3016 :   u32 start = cur(p)->off;
    1632         3016 :   WokSpan name = take_kind(p, WT_VARID, "a field name");
    1633         3016 :   expect(p, WT_COLON, "`:` after the field name");
    1634         3016 :   WokNode *type = parse_type(p);
    1635         3016 :   WokNode *n = mk(p, H_FieldType, start, span_end(p));
    1636         3016 :   H_FieldType_set_name(n, name);
    1637         3016 :   H_FieldType_set_type(n, type);
    1638         3016 :   return n;
    1639              : }
    1640              : 
    1641         1643 : static WokSeq parse_fieldtypes(P *p) {
    1642         1643 :   bump(p);  // `{`
    1643         1643 :   WokNodeBuf buf;
    1644         1643 :   wok_buf_init(&buf, p->a);
    1645         1643 :   if (!at(p, WT_RBRACE) && !p->panic) {
    1646         1505 :     wok_buf_push(&buf, parse_fieldtype(p));
    1647         3016 :     while (at(p, WT_COMMA) && !p->panic) {
    1648         1511 :       bump(p);
    1649         1511 :       wok_buf_push(&buf, parse_fieldtype(p));
    1650              :     }
    1651              :   }
    1652         1643 :   expect(p, WT_RBRACE, "`}` to close these fields");
    1653         1643 :   return wok_buf_seq(&buf);
    1654              : }
    1655              : 
    1656         5822 : static WokNode *parse_condef(P *p) {
    1657         5822 :   u32 start = cur(p)->off;
    1658         5822 :   WokSpan name = wok_span(start, 0);
    1659         5822 :   WokSeq args = wok_seq_empty();
    1660         5822 :   WokSeq fields = wok_seq_empty();
    1661         5822 :   bool is_record = false;
    1662         5822 :   if (at(p, WT_LBRACE)) {
    1663              :     // The elided record form: `type Wrapped = { value : U64 }`.
    1664          657 :     fields = parse_fieldtypes(p);
    1665          657 :     is_record = true;
    1666              :   } else {
    1667         5165 :     name = take_kind(p, WT_CONID, "a constructor name");
    1668         5165 :     if (at(p, WT_LBRACE)) {
    1669          986 :       fields = parse_fieldtypes(p);
    1670          986 :       is_record = true;
    1671              :     } else {
    1672         4179 :       WokNodeBuf buf;
    1673         4179 :       wok_buf_init(&buf, p->a);
    1674         6590 :       while (starts_atomtype(p) && !p->panic)
    1675         2411 :         wok_buf_push(&buf, parse_atomtype(p));
    1676         4179 :       args = wok_buf_seq(&buf);
    1677              :     }
    1678              :   }
    1679         5822 :   WokNode *n = mk(p, H_ConDef, start, span_end(p));
    1680         5822 :   H_ConDef_set_name(n, name);
    1681         5822 :   H_ConDef_set_args(n, args);
    1682         5822 :   H_ConDef_set_fields(n, fields);
    1683         5822 :   H_ConDef_set_is_record(n, is_record);
    1684         5822 :   return n;
    1685              : }
    1686              : 
    1687        22436 : static WokNode *parse_signame(P *p) {
    1688        22436 :   u32 start = cur(p)->off;
    1689        22436 :   bool paren = false;
    1690        22436 :   WokSpan name;
    1691        22436 :   if (at(p, WT_LPAREN)) {
    1692         1780 :     bump(p);
    1693         1780 :     name = take_kind(p, WT_VARSYM, "an operator name");
    1694         1780 :     expect(p, WT_RPAREN, "`)` after the operator name");
    1695         1780 :     paren = true;
    1696              :   } else {
    1697        20656 :     name = take_kind(p, WT_VARID, "a name being declared");
    1698              :   }
    1699        22436 :   WokNode *n = mk(p, H_SigName, start, span_end(p));
    1700        22436 :   H_SigName_set_name(n, name);
    1701        22436 :   H_SigName_set_paren(n, paren);
    1702        22436 :   return n;
    1703              : }
    1704              : 
    1705        20526 : static WokNode *parse_sig(P *p, bool is_extern, u32 start) {
    1706        20526 :   WokNodeBuf names;
    1707        20526 :   wok_buf_init(&names, p->a);
    1708        20526 :   wok_buf_push(&names, parse_signame(p));
    1709        22436 :   while (at(p, WT_COMMA) && !p->panic) {
    1710         1910 :     bump(p);
    1711         1910 :     wok_buf_push(&names, parse_signame(p));
    1712              :   }
    1713        20526 :   expect(p, WT_COLON, "`:` between the names and their type");
    1714        20526 :   WokNode *type = parse_type(p);
    1715        20526 :   WokNode *n = mk(p, D_Sig, start, span_end(p));
    1716        20526 :   D_Sig_set_names(n, wok_buf_seq(&names));
    1717        20526 :   D_Sig_set_type(n, type);
    1718        20526 :   D_Sig_set_is_extern(n, is_extern);
    1719        20526 :   return n;
    1720              : }
    1721              : 
    1722        26887 : static WokNode *parse_lhs(P *p) {
    1723        26887 :   u32 start = cur(p)->off;
    1724        26887 :   bool paren = at(p, WT_LPAREN) && kind_at(p, 1) == WT_VARSYM &&
    1725          793 :                kind_at(p, 2) == WT_RPAREN;
    1726        26887 :   bool prefix = at(p, WT_VARID) && kind_at(p, 1) != WT_VARSYM &&
    1727              :                 kind_at(p, 1) != WT_BACKTICK;
    1728        26887 :   if (paren || prefix) {
    1729        19776 :     WokSpan name;
    1730        19776 :     if (paren) {
    1731          634 :       bump(p);
    1732          634 :       name = tok_span(cur(p));
    1733          634 :       bump(p);
    1734          634 :       bump(p);
    1735              :     } else {
    1736        19142 :       name = tok_span(cur(p));
    1737        19142 :       bump(p);
    1738              :     }
    1739        19776 :     WokNodeBuf args;
    1740        19776 :     wok_buf_init(&args, p->a);
    1741        29522 :     while (starts_atompat(p) && !p->panic)
    1742         9746 :       wok_buf_push(&args, parse_atompat(p));
    1743        19776 :     WokNode *n = mk(p, L_Prefix, start, span_end(p));
    1744        19776 :     L_Prefix_set_name(n, name);
    1745        19776 :     L_Prefix_set_paren(n, paren);
    1746        19776 :     L_Prefix_set_args(n, wok_buf_seq(&args));
    1747        19776 :     return n;
    1748              :   }
    1749         7111 :   WokNode *left = parse_atompat(p);
    1750         7111 :   WokSpan op = wok_span(cur(p)->off, 0);
    1751         7111 :   bool backtick = false;
    1752         7111 :   if (at(p, WT_BACKTICK)) {
    1753          743 :     bump(p);
    1754          743 :     op = tok_span(cur(p));
    1755          743 :     backtick = true;
    1756          743 :     bump(p);
    1757          743 :     expect(p, WT_BACKTICK, "a closing backtick");
    1758         6368 :   } else if (at(p, WT_VARSYM)) {
    1759         2639 :     op = tok_span(cur(p));
    1760         2639 :     bump(p);
    1761              :   } else {
    1762         3729 :     perr_expect(p, "an infix operator in this left-hand side");
    1763              :   }
    1764         7111 :   WokNode *right = parse_atompat(p);
    1765         7111 :   WokNode *n = mk(p, L_Infix, start, span_end(p));
    1766         7111 :   L_Infix_set_left(n, left);
    1767         7111 :   L_Infix_set_op(n, op);
    1768         7111 :   L_Infix_set_backtick(n, backtick);
    1769         7111 :   L_Infix_set_right(n, right);
    1770         7111 :   return n;
    1771              : }
    1772              : 
    1773        26887 : static WokNode *parse_equation(P *p) {
    1774        26887 :   u32 start = cur(p)->off;
    1775        26887 :   WokNode *lhs = parse_lhs(p);
    1776        26887 :   expect(p, WT_EQUALS, "`=` after the left-hand side");
    1777        26887 :   Block b = block_begin(p);
    1778        26887 :   WokNode *body = parse_block_body(p, &b);
    1779              :   // `where` is a continuation lead, so it arrives inside the body's block
    1780              :   // region with no separator before it.
    1781        26887 :   WokSeq wheres = wok_seq_empty();
    1782        26887 :   if (at_word(p, WW_WHERE)) wheres = parse_where(p);
    1783        26887 :   block_end(p, &b);
    1784        26887 :   WokNode *n = mk(p, D_Equation, start, span_end(p));
    1785        26887 :   D_Equation_set_lhs(n, lhs);
    1786        26887 :   D_Equation_set_body(n, body);
    1787        26887 :   D_Equation_set_wheres(n, wheres);
    1788        26887 :   return n;
    1789              : }
    1790              : 
    1791              : // `:` before `=` at bracket depth zero is the only thing that separates a
    1792              : // signature from an equation, and forward-only declarations (D23) guarantee
    1793              : // one of the two appears on the item's first logical line.
    1794        46867 : static bool item_is_sig(const P *p) {
    1795        46867 :   int br = 0;
    1796       352553 :   for (usize j = p->i; j < p->n; j++) {
    1797       351620 :     WokKind k = (WokKind)p->tok[j].kind;
    1798       351620 :     if (br == 0) {
    1799       229207 :       if (k == WT_NEWLINE || k == WT_INDENT || k == WT_DEDENT || k == WT_EOF)
    1800              :         return false;
    1801       224354 :       if (k == WT_COLON) return true;
    1802       204374 :       if (k == WT_EQUALS) return false;
    1803              :     }
    1804       305686 :     if (wok_kind_is_open_bracket(k))
    1805        10129 :       br++;
    1806       314164 :     else if (wok_kind_is_close_bracket(k) && br > 0)
    1807         8478 :       br--;
    1808              :   }
    1809              :   return false;
    1810              : }
    1811              : 
    1812        46867 : static WokNode *parse_sig_or_equation(P *p) {
    1813        46867 :   u32 start = cur(p)->off;
    1814        46867 :   if (item_is_sig(p)) return parse_sig(p, false, start);
    1815        26887 :   return parse_equation(p);
    1816              : }
    1817              : 
    1818         6466 : static WokNode *parse_module_decl(P *p) {
    1819         6466 :   u32 start = cur(p)->off;
    1820         6466 :   bump(p);
    1821         6466 :   WokNode *path = parse_modpath(p);
    1822         6466 :   WokNode *n = mk(p, D_Module, start, span_end(p));
    1823         6466 :   D_Module_set_path(n, path);
    1824         6466 :   return n;
    1825              : }
    1826              : 
    1827         5911 : static WokNode *parse_import_decl(P *p) {
    1828         5911 :   u32 start = cur(p)->off;
    1829         5911 :   bump(p);
    1830         5911 :   WokNode *path = parse_modpath(p);
    1831         5911 :   WokNodeBuf names;
    1832         5911 :   wok_buf_init(&names, p->a);
    1833         5911 :   if (at(p, WT_LPAREN)) {
    1834          715 :     bump(p);
    1835          715 :     if (!at(p, WT_RPAREN) && !p->panic) {
    1836          713 :       wok_buf_push(&names, parse_name(p));
    1837         1151 :       while (at(p, WT_COMMA) && !p->panic) {
    1838          438 :         bump(p);
    1839          438 :         wok_buf_push(&names, parse_name(p));
    1840              :       }
    1841              :     }
    1842          715 :     expect(p, WT_RPAREN, "`)` to close the import list");
    1843              :   }
    1844         5911 :   WokNode *alias = nullptr;
    1845         5911 :   if (at_word(p, WW_AS)) {
    1846          689 :     bump(p);
    1847          689 :     alias = parse_name(p);
    1848              :   }
    1849         5911 :   WokNode *n = mk(p, D_Import, start, span_end(p));
    1850         5911 :   D_Import_set_path(n, path);
    1851         5911 :   D_Import_set_names(n, wok_buf_seq(&names));
    1852         5911 :   D_Import_set_alias(n, alias);
    1853         5911 :   return n;
    1854              : }
    1855              : 
    1856         2893 : static WokNode *parse_type_decl(P *p) {
    1857         2893 :   u32 start = cur(p)->off;
    1858         2893 :   bump(p);
    1859         2893 :   WokSpan name = take_kind(p, WT_CONID, "a type name");
    1860         2893 :   WokSeq params = parse_typarams(p);
    1861         2893 :   expect(p, WT_EQUALS, "`=` before the constructors");
    1862         2893 :   WokNodeBuf cons;
    1863         2893 :   wok_buf_init(&cons, p->a);
    1864         2893 :   wok_buf_push(&cons, parse_condef(p));
    1865         5822 :   while (at(p, WT_BAR) && !p->panic) {
    1866         2929 :     bump(p);
    1867         2929 :     wok_buf_push(&cons, parse_condef(p));
    1868              :   }
    1869         2893 :   WokNode *n = mk(p, D_Type, start, span_end(p));
    1870         2893 :   D_Type_set_name(n, name);
    1871         2893 :   D_Type_set_params(n, params);
    1872         2893 :   D_Type_set_cons(n, wok_buf_seq(&cons));
    1873         2893 :   return n;
    1874              : }
    1875              : 
    1876          985 : static WokNode *parse_alias_decl(P *p) {
    1877          985 :   u32 start = cur(p)->off;
    1878          985 :   bump(p);
    1879          985 :   WokSpan name = take_kind(p, WT_CONID, "an alias name");
    1880          985 :   WokSeq params = parse_typarams(p);
    1881          985 :   expect(p, WT_EQUALS, "`=` before the aliased type");
    1882          985 :   WokNode *body = parse_type(p);
    1883          985 :   WokNode *n = mk(p, D_Alias, start, span_end(p));
    1884          985 :   D_Alias_set_name(n, name);
    1885          985 :   D_Alias_set_params(n, params);
    1886          985 :   D_Alias_set_body(n, body);
    1887          985 :   return n;
    1888              : }
    1889              : 
    1890         6592 : static WokNode *parse_opsig(P *p) {
    1891         6592 :   u32 start = cur(p)->off;
    1892         6592 :   if (at_reserved_opname(p)) {
    1893            4 :     const WokToken *t = cur(p);
    1894              :     // perr_form's body, with the word quoted in: naming it is the whole
    1895              :     // message, and quoting text into a diagnostic is the one thing the parser
    1896              :     // may do with a token's bytes.
    1897            4 :     if (!p->panic)
    1898            4 :       wok_diag_add(p->d, WOK_E_RESERVED, t->off, t->len,
    1899              :                    "`%.*s` heads a clause, so an operation cannot be called "
    1900              :                    "that",
    1901            4 :                    (int)t->len, p->src + t->off);
    1902            4 :     bump(p);
    1903            4 :     expect(p, WT_COLON, "`:` after the operation name");
    1904            4 :     WokNode *ty = parse_type(p);
    1905            4 :     WokNode *bad = mk(p, H_OpSig, start, span_end(p));
    1906            4 :     H_OpSig_set_name(bad, tok_span(t));
    1907            4 :     H_OpSig_set_type(bad, ty);
    1908            4 :     return bad;
    1909              :   }
    1910         6588 :   WokSpan name = take_kind(p, WT_VARID, "an operation name");
    1911         6588 :   expect(p, WT_COLON, "`:` after the operation name");
    1912         6588 :   WokNode *type = parse_type(p);
    1913         6588 :   WokNode *n = mk(p, H_OpSig, start, span_end(p));
    1914         6588 :   H_OpSig_set_name(n, name);
    1915         6588 :   H_OpSig_set_type(n, type);
    1916         6588 :   return n;
    1917              : }
    1918              : 
    1919         4557 : static WokNode *parse_effect_decl(P *p) {
    1920         4557 :   u32 start = cur(p)->off;
    1921         4557 :   bump(p);
    1922         4557 :   WokSpan name = take_kind(p, WT_CONID, "an effect name");
    1923         4557 :   WokSeq params = parse_typarams(p);
    1924         4557 :   Block b = block_begin(p);
    1925         4557 :   WokSeq ops = parse_block_list(p, &b, parse_opsig, true);
    1926         4557 :   block_end(p, &b);
    1927         4557 :   WokNode *n = mk(p, D_Effect, start, span_end(p));
    1928         4557 :   D_Effect_set_name(n, name);
    1929         4557 :   D_Effect_set_params(n, params);
    1930         4557 :   D_Effect_set_ops(n, ops);
    1931         4557 :   return n;
    1932              : }
    1933              : 
    1934          900 : static WokNode *parse_class_decl(P *p) {
    1935          900 :   u32 start = cur(p)->off;
    1936          900 :   bump(p);
    1937          900 :   WokSpan name = take_kind(p, WT_CONID, "a class name");
    1938          900 :   WokSeq params = parse_typarams(p);
    1939          900 :   Block b = block_begin(p);
    1940          900 :   WokSeq body = parse_block_list(p, &b, parse_sig_or_equation, true);
    1941          900 :   block_end(p, &b);
    1942          900 :   WokNode *n = mk(p, D_Class, start, span_end(p));
    1943          900 :   D_Class_set_name(n, name);
    1944          900 :   D_Class_set_params(n, params);
    1945          900 :   D_Class_set_body(n, body);
    1946          900 :   return n;
    1947              : }
    1948              : 
    1949          994 : static WokNode *parse_instance_decl(P *p) {
    1950          994 :   u32 start = cur(p)->off;
    1951          994 :   bump(p);
    1952          994 :   WokNode *ctx = nullptr;
    1953          994 :   if (at(p, WT_LPAREN)) {
    1954          593 :     bump(p);
    1955          593 :     ctx = parse_type(p);
    1956          593 :     expect(p, WT_RPAREN, "`)` to close the instance context");
    1957          593 :     expect(p, WT_FATARROW, "`=>` after the instance context");
    1958              :   }
    1959          994 :   WokSpan name = take_kind(p, WT_CONID, "a class name");
    1960          994 :   WokNodeBuf args;
    1961          994 :   wok_buf_init(&args, p->a);
    1962         1978 :   while (starts_atomtype(p) && !p->panic)
    1963          984 :     wok_buf_push(&args, parse_atomtype(p));
    1964          994 :   Block b = block_begin(p);
    1965          994 :   WokSeq body = parse_block_list(p, &b, parse_sig_or_equation, true);
    1966          994 :   block_end(p, &b);
    1967          994 :   WokNode *n = mk(p, D_Instance, start, span_end(p));
    1968          994 :   D_Instance_set_ctx(n, ctx);
    1969          994 :   D_Instance_set_name(n, name);
    1970          994 :   D_Instance_set_args(n, wok_buf_seq(&args));
    1971          994 :   D_Instance_set_body(n, body);
    1972          994 :   return n;
    1973              : }
    1974              : 
    1975         1806 : static WokNode *parse_foreign_member(P *p) {
    1976         1806 :   u32 start = cur(p)->off;
    1977         1806 :   WokSpan name = take_kind(p, WT_VARID, "a foreign member name");
    1978         1806 :   WokSpan symbol = wok_span(start, 0);
    1979         1806 :   if (at(p, WT_STRING)) {
    1980         1139 :     symbol = tok_span(cur(p));
    1981         1139 :     bump(p);
    1982              :   }
    1983         1806 :   expect(p, WT_COLON, "`:` after the foreign member name");
    1984         1806 :   bool saved = p->foreign_sig;
    1985         1806 :   p->foreign_sig = true;
    1986         1806 :   WokNode *type = parse_type(p);
    1987         1806 :   p->foreign_sig = saved;
    1988         1806 :   WokNode *n = mk(p, H_ForeignMember, start, span_end(p));
    1989         1806 :   H_ForeignMember_set_name(n, name);
    1990         1806 :   H_ForeignMember_set_symbol(n, symbol);
    1991         1806 :   H_ForeignMember_set_type(n, type);
    1992         1806 :   return n;
    1993              : }
    1994              : 
    1995          873 : static WokNode *parse_foreign_decl(P *p) {
    1996          873 :   u32 start = cur(p)->off;
    1997          873 :   bump(p);  // `foreign`
    1998          873 :   expect_word(p, WW_MODULE, "`module` after `foreign`");
    1999          873 :   WokSpan name = take_kind(p, WT_CONID, "the foreign module's name");
    2000          873 :   WokSpan lib = take_kind(p, WT_STRING, "the library name as a string");
    2001          873 :   Block b = block_begin(p);
    2002          873 :   WokSeq members = parse_block_list(p, &b, parse_foreign_member, true);
    2003          873 :   block_end(p, &b);
    2004          873 :   WokNode *n = mk(p, D_Foreign, start, span_end(p));
    2005          873 :   D_Foreign_set_name(n, name);
    2006          873 :   D_Foreign_set_lib(n, lib);
    2007          873 :   D_Foreign_set_members(n, members);
    2008          873 :   return n;
    2009              : }
    2010              : 
    2011              : // `fixity + left tighter than *`
    2012              : //
    2013              : // An operator is named by a SYMBOL RUN (`+`) or by a bare identifier meant to
    2014              : // be used infix in backticks (`div`); `alpha` records which, because they are
    2015              : // different names that their text alone does not always tell apart.
    2016              : //
    2017              : // The associativity word is REQUIRED -- there is no `none` tier. An operator
    2018              : // that has been declared at all has an answer for ties against itself, which
    2019              : // is what lets the reassociator resolve a run of one operator without ever
    2020              : // consulting the order.
    2021         4353 : static bool take_fix_name(P *p, WokSpan *out, bool *alpha) {
    2022         4353 :   if (at(p, WT_VARSYM) || at(p, WT_VARID)) {
    2023         4351 :     *alpha = at(p, WT_VARID);
    2024         4351 :     *out = tok_span(cur(p));
    2025         4351 :     bump(p);
    2026         4351 :     return true;
    2027              :   }
    2028            2 :   perr_expect(p, "an operator: a symbol run like `+`, or a name used infix");
    2029            2 :   *out = wok_span(cur(p)->off, 0);
    2030            2 :   *alpha = false;
    2031            2 :   return false;
    2032              : }
    2033              : 
    2034         2094 : static WokNode *parse_fixrel(P *p) {
    2035         2094 :   u32 start = cur(p)->off;
    2036         2094 :   u64 sense = WOK_FIXREL_TIGHTER;
    2037         2094 :   if (at_word(p, WW_LOOSER)) {
    2038         1048 :     sense = WOK_FIXREL_LOOSER;
    2039         1048 :     bump(p);
    2040              :   } else {
    2041         1046 :     expect_word(p, WW_TIGHTER, "`tighter` or `looser`");
    2042              :   }
    2043         2094 :   expect_word(p, WW_THAN, "`than` after `tighter` or `looser`");
    2044         2094 :   WokSpan name = wok_span(cur(p)->off, 0);
    2045         2094 :   bool alpha = false;
    2046         2094 :   (void)take_fix_name(p, &name, &alpha);
    2047         2094 :   WokNode *n = mk(p, H_FixRel, start, span_end(p));
    2048         2094 :   H_FixRel_set_sense(n, sense);
    2049         2094 :   H_FixRel_set_name(n, name);
    2050         2094 :   H_FixRel_set_alpha(n, alpha);
    2051         2094 :   return n;
    2052              : }
    2053              : 
    2054         2259 : static WokNode *parse_fixity_decl(P *p) {
    2055         2259 :   u32 start = cur(p)->off;
    2056         2259 :   bump(p);  // `fixity`
    2057         2259 :   WokSpan name = wok_span(cur(p)->off, 0);
    2058         2259 :   bool alpha = false;
    2059         2259 :   (void)take_fix_name(p, &name, &alpha);
    2060         2259 :   u64 assoc = WOK_ASSOC_LEFT;
    2061         2259 :   if (at_word(p, WW_RIGHT)) {
    2062          894 :     assoc = WOK_ASSOC_RIGHT;
    2063          894 :     bump(p);
    2064              :   } else {
    2065         1365 :     expect_word(p, WW_LEFT, "`left` or `right`, the operator's associativity");
    2066              :   }
    2067         2259 :   WokNodeBuf rels;
    2068         2259 :   wok_buf_init(&rels, p->a);
    2069         4353 :   while ((at_word(p, WW_TIGHTER) || at_word(p, WW_LOOSER)) && !p->panic)
    2070         2094 :     wok_buf_push(&rels, parse_fixrel(p));
    2071         2259 :   WokNode *n = mk(p, D_Fixity, start, span_end(p));
    2072         2259 :   D_Fixity_set_name(n, name);
    2073         2259 :   D_Fixity_set_alpha(n, alpha);
    2074         2259 :   D_Fixity_set_assoc(n, assoc);
    2075         2259 :   D_Fixity_set_rels(n, wok_buf_seq(&rels));
    2076         2259 :   return n;
    2077              : }
    2078              : 
    2079         1430 : static WokNode *parse_extern_decl(P *p) {
    2080         1430 :   u32 start = cur(p)->off;
    2081         1430 :   bump(p);  // `extern`
    2082         1430 :   if (at_word(p, WW_TYPE)) {
    2083          884 :     bump(p);
    2084          884 :     WokSpan name = take_kind(p, WT_CONID, "a type name");
    2085          884 :     WokSeq params = parse_typarams(p);
    2086          884 :     WokNode *n = mk(p, D_ExternType, start, span_end(p));
    2087          884 :     D_ExternType_set_name(n, name);
    2088          884 :     D_ExternType_set_params(n, params);
    2089          884 :     return n;
    2090              :   }
    2091              :   // `extern` marks a compiler hole: analyses trust the MARKER, never a name.
    2092          546 :   return parse_sig(p, true, start);
    2093              : }
    2094              : 
    2095        66513 : static WokNode *parse_decl(P *p) {
    2096        66513 :   if (!enter(p)) return mk_error(p, D_Error, cur(p)->off);
    2097        66513 :   WokNode *r;
    2098        66513 :   if (at(p, WT_KEYWORD)) {
    2099              :     // The `default:` here is a real grammar case, not a silenced enumerator:
    2100              :     // `let`, `then` and `where` are keywords that legitimately reach this and
    2101              :     // legitimately do not begin a declaration. The -Wswitch ban on `default:`
    2102              :     // is about traversals, where it would hide a missing node kind.
    2103        27384 :     switch ((WokWord)cur(p)->word) {
    2104              : #define WOK_X(word, fn) \
    2105              :   case word:            \
    2106              :     r = fn(p);          \
    2107              :     break;
    2108        27268 :       WOK_DECL_WORDS(WOK_X)
    2109              : #undef WOK_X
    2110          116 :       default:
    2111          116 :         perr_expect(p, "a declaration");
    2112          116 :         r = mk_error(p, D_Error, cur(p)->off);
    2113          116 :         break;
    2114              :     }
    2115              :   } else {
    2116        39129 :     r = parse_sig_or_equation(p);
    2117              :   }
    2118        66513 :   leave(p);
    2119        66513 :   return r;
    2120              : }
    2121              : 
    2122              : // ---------------------------------------------------------------- the file
    2123              : 
    2124        20183 : static WokNode *parse_file(P *p) {
    2125        20183 :   WokNodeBuf decls;
    2126        20183 :   wok_buf_init(&decls, p->a);
    2127        86973 :   while (!at(p, WT_EOF)) {
    2128        66790 :     if (at(p, WT_NEWLINE) || at(p, WT_DEDENT)) {
    2129          277 :       bump(p);
    2130          277 :       continue;
    2131              :     }
    2132        66513 :     usize before = p->i;
    2133        66513 :     u32 start = cur(p)->off;
    2134        66513 :     WokNode *decl = parse_decl(p);
    2135        66513 :     if (p->panic) {
    2136         8479 :       resync_decl(p);
    2137         8479 :       decl = mk_error(p, D_Error, start);
    2138         8479 :       p->panic = false;
    2139              :     }
    2140        66513 :     wok_buf_push(&decls, decl);
    2141        66513 :     if (p->sep) {
    2142        16479 :       p->sep = false;
    2143        50034 :     } else if (at(p, WT_NEWLINE)) {
    2144        47845 :       bump(p);
    2145         2189 :     } else if (!at(p, WT_EOF)) {
    2146          917 :       perr_expect(p, "the end of this declaration");
    2147          917 :       resync_decl(p);
    2148          917 :       p->panic = false;
    2149          917 :       p->sep = false;
    2150          917 :       if (at(p, WT_NEWLINE)) bump(p);
    2151              :     }
    2152        66513 :     if (p->i == before && !at(p, WT_EOF)) bump(p);
    2153              :   }
    2154        20183 :   WokNode *n = mk(p, W_File, 0, span_end(p));
    2155        20183 :   W_File_set_decls(n, wok_buf_seq(&decls));
    2156        20183 :   return n;
    2157              : }
    2158              : 
    2159        20183 : WokNode *wok_parse(WokTokens tokens, const char *src, WokArena *arena,
    2160              :                    WokDiagSink *diag) {
    2161        20183 :   P p = {.tok = tokens.tok,
    2162        20183 :          .n = tokens.n,
    2163              :          .i = 0,
    2164              :          .src = src,
    2165              :          .a = arena,
    2166              :          .d = diag};
    2167        20183 :   return parse_file(&p);
    2168              : }
    2169              : 
    2170        20179 : WokNode *wok_parse_source(const char *src, usize src_len, WokArena *arena,
    2171              :                           WokDiagSink *diag) {
    2172        20179 :   WokScanResult scanned = wok_scan(src, src_len, arena, diag);
    2173        20179 :   WokTokens laid_out = wok_layout(scanned.tokens, arena, diag);
    2174        20179 :   WokNode *file = wok_parse(laid_out, src, arena, diag);
    2175              :   // The comments the scanner set aside become trivia here, and nowhere else:
    2176              :   // a tree that never went through this entry point simply has none.
    2177        20179 :   (void)wok_trivia_attach(file, src, src_len, scanned.comments,
    2178              :                           scanned.ncomments, arena);
    2179        20179 :   return file;
    2180              : }
        

Generated by: LCOV version 2.4-1


Machine-readable coverage data

This page is generated. If you are a tool, a script, or an LLM, read the JSON instead of scraping this HTML — it is the same measurement, exact, and it names the individual uncovered lines.

Every artifact here is stamped with the commit it measured and the run that produced it: commit 084deda, dated 2026-08-10T11:39:41Z, built by Github CI run. The totals in coverage.json are checked against lcov’s own summary before publishing, so the JSON and this page cannot disagree.