WokML compiler code coverage report (LCOV)
Current view: top level - c - wok_ast.h (source / functions) Coverage Total Hit
Test: 084deda Lines: 100.0 % 17 17
Test Date: 2026-08-10 11:39:41 Functions: 99.4 % 323 321
Legend: Lines:     hit not hit

            Line data    Source code
       1              : // wok_ast -- THE SCHEMA. Every node's fields are written exactly once, here.
       2              : //
       3              : // From this one list the build derives: the tag enum, the per-node slot
       4              : // indices, typed accessors and setters, and a descriptor table that the dump,
       5              : // the reader and the coverage bitmap all walk generically. Adding a node means
       6              : // adding one line; forgetting to teach the dump about it is impossible,
       7              : // because the dump does not know about nodes individually.
       8              : //
       9              : // Each line says TWO things: the family the node belongs to, and -- per field --
      10              : // the family that field demands. The second used to live only in wok_parse.c,
      11              : // which meant the reader could not tell an expression from a type and a
      12              : // corrupted dump read back as a different program. Now it can. A node added
      13              : // here without a family does not compile; a family that disagrees with what the
      14              : // parser builds fails in test_generative at startup, by name.
      15              : //
      16              : // Nodes are UNIFORM: a header plus a flexible array of slots. That trades a
      17              : // little static typing for a schema a machine can walk, and two things buy
      18              : // some of it back: an accessor's C type comes from the field's CLASS, so a
      19              : // NODE slot cannot be read as a span; and every accessor asserts the node's
      20              : // TAG in a debug build, so E_App_fn(someTypeNode) fires rather than returning
      21              : // a misread word. Neither checks the FAMILY -- that is the reader's job, in
      22              : // wok_sexpr.c, on the way in.
      23              : 
      24              : #pragma once
      25              : 
      26              : // The prelude comes FIRST: it carries the POSIX feature-test macros, which
      27              : // have no effect once a system header has been read. wok_base.h hard-errors
      28              : // if it is reached too late.
      29              : #include "wok_base.h"
      30              : 
      31              : #include <assert.h>
      32              : #include <stdbool.h>
      33              : #include <stddef.h>
      34              : #include <stdint.h>
      35              : 
      36              : #include "wok_arena.h"
      37              : 
      38              : typedef struct WokNode WokNode;
      39              : 
      40              : typedef struct {
      41              :   u32 off, len;
      42              : } WokSpan;
      43              : 
      44              : typedef struct {
      45              :   WokNode **items;
      46              :   u32 n;
      47              : } WokSeq;
      48              : 
      49              : // A sequence slot stores ONLY the items pointer; its length lives inline, in
      50              : // the word immediately before items. Without that, WokSeq's {ptr, uint32}
      51              : // would drag the whole union to 16 bytes and every NODE, INT, NAME and FLAG
      52              : // slot would pay 8 bytes of padding it never uses -- and 77% of nodes have
      53              : // just one or two slots.
      54              : typedef union {
      55              :   WokNode *node;
      56              :   WokNode **seq;
      57              :   WokSpan span;
      58              :   u64 num;
      59              :   bool flag;
      60              : } WokSlot;
      61              : 
      62              : static_assert(sizeof(WokSlot) == 8, "a slot is one word; see the note above");
      63              : 
      64              : static inline WokSeq wok_seq_empty(void) {
      65              :   return (WokSeq){.items = nullptr, .n = 0};
      66              : }
      67              : 
      68              : // Rebuilds the fat {items, n} view from a stored pointer. The count sits at
      69              : // items[-1]; an empty sequence is a null pointer.
      70      1069210 : static inline WokSeq wok_seq_unpack(WokNode **items) {
      71       754488 :   if (items == nullptr) return wok_seq_empty();
      72       820597 :   return (WokSeq){.items = items, .n = (u32)(uptr)items[-1]};
      73              : }
      74              : 
      75              : struct WokNode {
      76              :   u32 off, len;  // source span, for diagnostics only
      77              :   // WHICH node this is: a WokTag, and the index every generic walk takes into
      78              :   // wok_node_desc[] to find the field list it then walks -- the dump, the
      79              :   // reader, the coverage bitmap, the printer's overflow report. It is the one
      80              :   // field that turns a uniform header-plus-slots block back into a typed node.
      81              :   //
      82              :   // Spelled u16 rather than WokTag only because the enum is DERIVED from
      83              :   // WOK_NODES further down this file, so the name does not exist yet here. The
      84              :   // enum is already `: u16`, so nothing is narrowed by saying so.
      85              :   u16 tag;
      86              :   // The arity its tag declares, copied here by wok_node so a walk never has to
      87              :   // reach for the schema just to bound a loop.
      88              :   u16 nslots;
      89              :   // The four bytes of padding the slot array's 8-byte alignment already
      90              :   // forced. 0 means no comments; otherwise this indexes a WokTrivia table
      91              :   // (wok_trivia.h). It is NOT a slot: comments are not part of a program's
      92              :   // identity, so nothing derived from the schema -- dump, reader, coverage --
      93              :   // can see this field.
      94              :   u32 trivia;
      95              :   WokSlot slot[];
      96              : };
      97              : 
      98              : // The trivia index must stay free. If either of these fires the header grew,
      99              : // and every node in every parse just paid for it.
     100              : static_assert(sizeof(WokNode) == 16, "the node header is 16 bytes");
     101              : static_assert(offsetof(WokNode, slot) == 16, "slots follow the header");
     102              : 
     103              : // Field classes. NODE is required, OPT may be null, SEQ is a slice into the
     104              : // arena, NAME and TEXT are views into the source, INT is a decoded literal or
     105              : // a schema constant, FLAG is a decided bit.
     106              : #define WOK_FIELD_CLASSES(X) \
     107              :   X(NODE) X(OPT) X(SEQ) X(NAME) X(TEXT) X(INT) X(FLAG)
     108              : 
     109              : typedef enum : unsigned char {
     110              : #define WOK_X(c) WFC_##c,
     111              :   WOK_FIELD_CLASSES(WOK_X)
     112              : #undef WOK_X
     113              :       WOK_FIELD_CLASS_COUNT
     114              : } WokFieldClass;
     115              : 
     116              : // Field FAMILIES. The class says a field holds a child; the family says WHICH
     117              : // KIND of child, and that is the half the schema used to leave to wok_parse.c.
     118              : // Every node declares the family it BELONGS to, every NODE/OPT/SEQ field the
     119              : // family it DEMANDS, and the s-expression reader compares the two -- so a dump
     120              : // with a type where an expression belongs is rejected rather than silently
     121              : // read back as a different program.
     122              : //
     123              : // NONE is for NAME, TEXT, INT and FLAG fields, which hold no child at all.
     124              : // ERROR is the damage marker, and it is a WILDCARD: mk_err plants an error
     125              : // node wherever a production gave up -- in a type, a pattern, an alternative,
     126              : // a clause -- so a family that refused it would make a damaged parse's dump
     127              : // unreadable, which is exactly when reading one back is worth something.
     128              : #define WOK_FAMILIES(X)                                                     \
     129              :   X(NONE) X(ERROR)                                                          \
     130              :   /* the big four, plus the file and the block item */                      \
     131              :   X(FILE) X(DECL) X(TYPE) X(PAT) X(EXPR) X(STMT)                            \
     132              :   /* names, and a dotted path of them */                                    \
     133              :   X(NAME) X(PATH)                                                           \
     134              :   /* every helper is its own family: each is demanded by ONE kind of slot */ \
     135              :   X(LHS) X(BINDLHS) X(SIGNAME) X(TYPARAM) X(CONDEF) X(FIELDTYPE) X(OPSIG)   \
     136              :   X(FOREIGNMEM) X(ROWENTRY) X(FIELDPAT) X(CHAINOP) X(BIND) X(USEBIND)       \
     137              :   X(ALT) X(CLAUSE) X(FIELD) X(FIXREL)
     138              : 
     139              : typedef enum : unsigned char {
     140              : #define WOK_X(f) WFAM_##f,
     141              :   WOK_FAMILIES(WOK_X)
     142              : #undef WOK_X
     143              :       WOK_FAMILY_COUNT
     144              : } WokFamily;
     145              : 
     146              : // The three places a family is WIDER than one node kind. Each is a fact about
     147              : // the grammar, not a loosening: parse_stmt's last arm is a bare expression, and
     148              : // parse_bind takes either a prefix head or a pattern. Nothing else subsumes.
     149       239598 : WOK_PURE static inline bool wok_family_accepts(WokFamily want, WokFamily got) {
     150       239598 :   if (want == got) return true;
     151         2992 :   if (got == WFAM_ERROR) return true;  // damage stands anywhere; see above
     152         2991 :   if (want == WFAM_STMT && got == WFAM_EXPR) return true;
     153         1220 :   if (want == WFAM_BINDLHS && (got == WFAM_LHS || got == WFAM_PAT)) return true;
     154              :   return false;
     155              : }
     156              : 
     157              : // --------------------------------------------------------------- constants
     158              : 
     159              : // H_Clause kinds. Four are keyword-headed; the fifth is spelled by a COMMA
     160              : // and nothing else (D25 cut `once`), which is why classification can consult
     161              : // no name, type or count -- see parse_clause.
     162              : enum {
     163              :   WOK_CLAUSE_PLAIN = 0,    // op p1 p2 -> e      auto-resume, tail-resumptive
     164              :   WOK_CLAUSE_CONTROL = 1,  // op p1, k -> e      control clause; k after the `,`
     165              :   WOK_CLAUSE_RETURN = 2,   // return p -> e      the value clause
     166              :   WOK_CLAUSE_VAR = 3,      // var cur = e        a frame baton
     167              :   WOK_CLAUSE_ABORT = 4,    // abort op p1 -> e   never resumes; binds no k
     168              : };
     169              : 
     170              : // H_RowEntry kinds (spec 1.3 / D20).
     171              : enum {
     172              :   WOK_ROW_SLOT = 0,    // `State U64`          designation-slot obligation (P2)
     173              :   WOK_ROW_ROLE = 1,    // `(from : State U64)` role obligation, parenthesized
     174              :   WOK_ROW_VAR = 2,     // `eff e`              row variable
     175              : };
     176              : 
     177              : // D_Fixity associativity. There is no `none`: `fixity` requires one of the
     178              : // two words, so an operator that was declared at all has an answer for its
     179              : // own ties.
     180              : enum {
     181              :   WOK_ASSOC_LEFT = 0,
     182              :   WOK_ASSOC_RIGHT = 1,
     183              : };
     184              : 
     185              : // H_FixRel senses. `a tighter than b` and `b looser than a` state the SAME
     186              : // edge from opposite ends, and both spellings are kept because which one
     187              : // reads better depends on which operator you are declaring.
     188              : enum {
     189              :   WOK_FIXREL_TIGHTER = 0,
     190              :   WOK_FIXREL_LOOSER = 1,
     191              : };
     192              : 
     193              : // T_Transfer modes (surface.md section 3: the FFI transfer law).
     194              : enum {
     195              :   WOK_TRANSFER_OWN = 0,   // consumed by C
     196              :   WOK_TRANSFER_LEND = 1,  // read-only view
     197              :   WOK_TRANSFER_COPY = 2,  // duplicated across the boundary
     198              : };
     199              : 
     200              : // ------------------------------------------------------------- the schema
     201              : //
     202              : // One line per node: X(tag, FAMILY). The FIELDS macro takes the field-emitting
     203              : // macro F and the tag T, so the generated names can be prefixed per node, and
     204              : // each field reads F(T, CLASS, name, FAMILY) -- the class it stores, and the
     205              : // family it demands of whatever fills it.
     206              : 
     207              : #define WOK_NODES(X)                                                       \
     208              :   /* names and paths */                                                    \
     209              :   X(N_Name, NAME) X(N_ModPath, PATH)                                       \
     210              :   /* declarations */                                                       \
     211              :   X(D_Module, DECL) X(D_Import, DECL) X(D_Type, DECL) X(D_Alias, DECL)     \
     212              :   X(D_Effect, DECL) X(D_Class, DECL) X(D_Instance, DECL)                   \
     213              :   X(D_Foreign, DECL) X(D_ExternType, DECL) X(D_Sig, DECL)                  \
     214              :   X(D_Fixity, DECL) X(D_Equation, DECL) X(D_Error, ERROR)                  \
     215              :   /* declaration helpers */                                                \
     216              :   X(H_TyParam, TYPARAM) X(H_ConDef, CONDEF) X(H_FieldType, FIELDTYPE)      \
     217              :   X(H_OpSig, OPSIG) X(H_ForeignMember, FOREIGNMEM) X(H_SigName, SIGNAME)   \
     218              :   X(H_FixRel, FIXREL)                                                      \
     219              :   X(L_Prefix, LHS) X(L_Infix, LHS)                                         \
     220              :   /* types */                                                              \
     221              :   X(T_Var, TYPE) X(T_Con, TYPE) X(T_App, TYPE) X(T_Fun, TYPE)              \
     222              :   X(T_Qual, TYPE) X(T_With, TYPE) X(T_List, TYPE) X(T_Tuple, TYPE)         \
     223              :   X(T_Unit, TYPE) X(T_RowArg, TYPE) X(T_Transfer, TYPE)                    \
     224              :   X(H_RowEntry, ROWENTRY)                                                  \
     225              :   /* patterns */                                                           \
     226              :   X(P_Var, PAT) X(P_Wild, PAT) X(P_Int, PAT) X(P_Str, PAT) X(P_Char, PAT)  \
     227              :   X(P_Con, PAT) X(P_Cons, PAT) X(P_Tuple, PAT) X(P_List, PAT)              \
     228              :   X(P_Unit, PAT) X(P_As, PAT) X(P_Record, PAT) X(H_FieldPat, FIELDPAT)     \
     229              :   /* expressions */                                                        \
     230              :   X(E_Var, EXPR) X(E_Con, EXPR) X(E_Int, EXPR) X(E_Str, EXPR)              \
     231              :   X(E_Char, EXPR) X(E_Unit, EXPR) X(E_OpRef, EXPR) X(E_App, EXPR)          \
     232              :   X(E_Chain, EXPR) X(E_Dot, EXPR) X(E_Neg, EXPR) X(E_List, EXPR)           \
     233              :   X(E_Tuple, EXPR) X(E_Lambda, EXPR) X(E_LetIn, EXPR) X(E_HandleIn, EXPR)  \
     234              :   X(E_UseIn, EXPR) X(E_If, EXPR) X(E_Case, EXPR) X(E_Handler, EXPR)        \
     235              :   X(E_Assign, EXPR) X(E_Record, EXPR) X(E_Block, EXPR)          \
     236              :   X(E_Error, ERROR)    \
     237              :   /* statements (D11: an indented block is a SEQUENCE) */                  \
     238              :   X(S_Let, STMT) X(S_Handle, STMT) X(S_Use, STMT) X(S_Discard, STMT)       \
     239              :   /* expression helpers */                                                 \
     240              :   X(H_ChainOp, CHAINOP) X(H_Bind, BIND) X(H_UseBind, USEBIND)              \
     241              :   X(H_Alt, ALT) X(H_Clause, CLAUSE) X(H_Field, FIELD)                      \
     242              :   /* the file */                                                           \
     243              :   X(W_File, FILE)
     244              : 
     245              : // names ---------------------------------------------------------------------
     246              : #define N_Name_FIELDS(F, T) F(T, NAME, text, NONE) F(T, FLAG, upper, NONE)
     247              : #define N_ModPath_FIELDS(F, T) F(T, SEQ, parts, NAME)
     248              : 
     249              : // declarations --------------------------------------------------------------
     250              : #define D_Module_FIELDS(F, T) F(T, NODE, path, PATH)
     251              : // names is empty for a plain import; alias is null unless `as A` was written.
     252              : #define D_Import_FIELDS(F, T) \
     253              :   F(T, NODE, path, PATH) F(T, SEQ, names, NAME) F(T, OPT, alias, NAME)
     254              : #define D_Type_FIELDS(F, T) \
     255              :   F(T, NAME, name, NONE) F(T, SEQ, params, TYPARAM) F(T, SEQ, cons, CONDEF)
     256              : #define D_Alias_FIELDS(F, T) \
     257              :   F(T, NAME, name, NONE) F(T, SEQ, params, TYPARAM) F(T, NODE, body, TYPE)
     258              : #define D_Effect_FIELDS(F, T) \
     259              :   F(T, NAME, name, NONE) F(T, SEQ, params, TYPARAM) F(T, SEQ, ops, OPSIG)
     260              : #define D_Class_FIELDS(F, T) \
     261              :   F(T, NAME, name, NONE) F(T, SEQ, params, TYPARAM) F(T, SEQ, body, DECL)
     262              : #define D_Instance_FIELDS(F, T) \
     263              :   F(T, OPT, ctx, TYPE) F(T, NAME, name, NONE) F(T, SEQ, args, TYPE) \
     264              :   F(T, SEQ, body, DECL)
     265              : #define D_Foreign_FIELDS(F, T) \
     266              :   F(T, NAME, name, NONE) F(T, TEXT, lib, NONE) F(T, SEQ, members, FOREIGNMEM)
     267              : #define D_ExternType_FIELDS(F, T) \
     268              :   F(T, NAME, name, NONE) F(T, SEQ, params, TYPARAM)
     269              : // `extern` marks a compiler hole: analyses trust the MARKER, never a name.
     270              : #define D_Sig_FIELDS(F, T) \
     271              :   F(T, SEQ, names, SIGNAME) F(T, NODE, type, TYPE) F(T, FLAG, is_extern, NONE)
     272              : #define D_Equation_FIELDS(F, T) \
     273              :   F(T, NODE, lhs, LHS) F(T, NODE, body, EXPR) F(T, SEQ, wheres, DECL)
     274              : #define D_Error_FIELDS(F, T) F(T, TEXT, text, NONE)
     275              : 
     276              : #define H_TyParam_FIELDS(F, T) F(T, NAME, name, NONE) F(T, FLAG, is_row, NONE)
     277              : // name is empty for the elided record form `{ x : U64 }`.
     278              : #define H_ConDef_FIELDS(F, T) \
     279              :   F(T, NAME, name, NONE) F(T, SEQ, args, TYPE) F(T, SEQ, fields, FIELDTYPE) \
     280              :   F(T, FLAG, is_record, NONE)
     281              : #define H_FieldType_FIELDS(F, T) F(T, NAME, name, NONE) F(T, NODE, type, TYPE)
     282              : #define H_OpSig_FIELDS(F, T) F(T, NAME, name, NONE) F(T, NODE, type, TYPE)
     283              : // `fixity + left tighter than *`. `alpha` records which SPELLING the operator
     284              : // was named by -- a symbol run (`+`) or a bare identifier used infix in
     285              : // backticks (`div`) -- because the two are different names that may not be
     286              : // told apart by their text alone downstream.
     287              : #define D_Fixity_FIELDS(F, T)                                        \
     288              :   F(T, NAME, name, NONE) F(T, FLAG, alpha, NONE) F(T, INT, assoc, NONE) \
     289              :   F(T, SEQ, rels, FIXREL)
     290              : #define H_FixRel_FIELDS(F, T) \
     291              :   F(T, INT, sense, NONE) F(T, NAME, name, NONE) F(T, FLAG, alpha, NONE)
     292              : #define H_ForeignMember_FIELDS(F, T) \
     293              :   F(T, NAME, name, NONE) F(T, TEXT, symbol, NONE) F(T, NODE, type, TYPE)
     294              : // paren records `(+)` so the printer re-derives the brackets from the fact.
     295              : #define H_SigName_FIELDS(F, T) F(T, NAME, name, NONE) F(T, FLAG, paren, NONE)
     296              : 
     297              : #define L_Prefix_FIELDS(F, T) \
     298              :   F(T, NAME, name, NONE) F(T, FLAG, paren, NONE) F(T, SEQ, args, PAT)
     299              : #define L_Infix_FIELDS(F, T) \
     300              :   F(T, NODE, left, PAT) F(T, NAME, op, NONE) F(T, FLAG, backtick, NONE) \
     301              :   F(T, NODE, right, PAT)
     302              : 
     303              : // types ---------------------------------------------------------------------
     304              : #define T_Var_FIELDS(F, T) F(T, NAME, name, NONE)
     305              : #define T_Con_FIELDS(F, T) F(T, NODE, path, PATH)
     306              : #define T_App_FIELDS(F, T) F(T, NODE, fn, TYPE) F(T, NODE, arg, TYPE)
     307              : #define T_Fun_FIELDS(F, T) F(T, NODE, from, TYPE) F(T, NODE, to, TYPE)
     308              : #define T_Qual_FIELDS(F, T) F(T, NODE, ctx, TYPE) F(T, NODE, body, TYPE)
     309              : #define T_With_FIELDS(F, T) F(T, NODE, body, TYPE) F(T, SEQ, row, ROWENTRY)
     310              : #define T_List_FIELDS(F, T) F(T, NODE, elem, TYPE)
     311              : #define T_Tuple_FIELDS(F, T) F(T, SEQ, items, TYPE)
     312              : #define T_Unit_FIELDS(F, T)
     313              : #define T_RowArg_FIELDS(F, T) F(T, NAME, name, NONE)
     314              : #define T_Transfer_FIELDS(F, T) F(T, INT, mode, NONE) F(T, NODE, body, TYPE)
     315              : // label is empty for a slot obligation; kind is one of WOK_ROW_*.
     316              : #define H_RowEntry_FIELDS(F, T) \
     317              :   F(T, INT, kind, NONE) F(T, NAME, label, NONE) F(T, OPT, type, TYPE)
     318              : 
     319              : // patterns ------------------------------------------------------------------
     320              : #define P_Var_FIELDS(F, T) F(T, NAME, name, NONE)
     321              : #define P_Wild_FIELDS(F, T)
     322              : #define P_Int_FIELDS(F, T) F(T, INT, value, NONE) F(T, FLAG, negative, NONE)
     323              : #define P_Str_FIELDS(F, T) F(T, TEXT, text, NONE)
     324              : #define P_Char_FIELDS(F, T) F(T, TEXT, text, NONE)
     325              : #define P_Con_FIELDS(F, T) F(T, NODE, path, PATH) F(T, SEQ, args, PAT)
     326              : #define P_Cons_FIELDS(F, T) F(T, NODE, head, PAT) F(T, NODE, tail, PAT)
     327              : #define P_Tuple_FIELDS(F, T) F(T, SEQ, items, PAT)
     328              : #define P_List_FIELDS(F, T) F(T, SEQ, items, PAT)
     329              : #define P_Unit_FIELDS(F, T)
     330              : #define P_As_FIELDS(F, T) F(T, NODE, pat, PAT) F(T, NAME, name, NONE)
     331              : // rest is empty unless `..name` was written; open records also set is_open.
     332              : #define P_Record_FIELDS(F, T) \
     333              :   F(T, NODE, path, PATH) F(T, SEQ, fields, FIELDPAT) F(T, FLAG, is_open, NONE) \
     334              :   F(T, NAME, rest, NONE)
     335              : #define H_FieldPat_FIELDS(F, T) F(T, NAME, name, NONE) F(T, NODE, pat, PAT)
     336              : 
     337              : // expressions ---------------------------------------------------------------
     338              : #define E_Var_FIELDS(F, T) F(T, NAME, name, NONE)
     339              : #define E_Con_FIELDS(F, T) F(T, NAME, name, NONE)
     340              : #define E_Int_FIELDS(F, T) F(T, INT, value, NONE)
     341              : #define E_Str_FIELDS(F, T) F(T, TEXT, text, NONE)
     342              : #define E_Char_FIELDS(F, T) F(T, TEXT, text, NONE)
     343              : #define E_Unit_FIELDS(F, T)
     344              : #define E_OpRef_FIELDS(F, T) F(T, NAME, name, NONE)
     345              : #define E_App_FIELDS(F, T) F(T, NODE, fn, EXPR) F(T, NODE, arg, EXPR)
     346              : // FLAT: precedence and associativity are a later pass's job, exactly as
     347              : // src/Wok/Reordering.hs already expects. No fixity table lives in the parser.
     348              : #define E_Chain_FIELDS(F, T) F(T, NODE, head, EXPR) F(T, SEQ, ops, CHAINOP)
     349              : #define H_ChainOp_FIELDS(F, T) \
     350              :   F(T, NAME, op, NONE) F(T, FLAG, backtick, NONE) F(T, NODE, rhs, EXPR)
     351              : // ONE Dot node for `M.f`, `st.get` and `p.x`: spec 1.5 resolves qualifier /
     352              : // label / projection later, and its collision rule needs them undistinguished
     353              : // at parse time.
     354              : #define E_Dot_FIELDS(F, T) \
     355              :   F(T, NODE, recv, EXPR) F(T, NAME, name, NONE) F(T, FLAG, upper, NONE)
     356              : #define E_Neg_FIELDS(F, T) F(T, NODE, body, EXPR)
     357              : #define E_List_FIELDS(F, T) F(T, SEQ, items, EXPR)
     358              : #define E_Tuple_FIELDS(F, T) F(T, SEQ, items, EXPR)
     359              : #define E_Lambda_FIELDS(F, T) F(T, SEQ, params, PAT) F(T, NODE, body, EXPR)
     360              : #define E_LetIn_FIELDS(F, T) F(T, NODE, bind, BIND) F(T, NODE, body, EXPR)
     361              : // label is empty when elided -- legal only in this delimited inline form
     362              : // (D13 two-tier); the statement form S_Handle always writes one.
     363              : #define E_HandleIn_FIELDS(F, T) \
     364              :   F(T, NAME, label, NONE) F(T, NODE, handler, EXPR) F(T, NODE, body, EXPR)
     365              : #define E_UseIn_FIELDS(F, T) F(T, SEQ, binds, USEBIND) F(T, NODE, body, EXPR)
     366              : #define E_If_FIELDS(F, T) \
     367              :   F(T, NODE, cond, EXPR) F(T, NODE, then_, EXPR) F(T, NODE, else_, EXPR)
     368              : #define E_Case_FIELDS(F, T) F(T, NODE, scrut, EXPR) F(T, SEQ, alts, ALT)
     369              : // `handler E` names its effect MANDATORILY (C3).
     370              : #define E_Handler_FIELDS(F, T) \
     371              :   F(T, NAME, effect, NONE) F(T, SEQ, clauses, CLAUSE)
     372              : #define E_Assign_FIELDS(F, T) F(T, NODE, target, EXPR) F(T, NODE, value, EXPR)
     373              : #define E_Record_FIELDS(F, T) \
     374              :   F(T, NODE, path, EXPR) F(T, OPT, spread, EXPR) F(T, SEQ, fields, FIELD)
     375              : #define E_Block_FIELDS(F, T) F(T, SEQ, stmts, STMT)
     376              : #define E_Error_FIELDS(F, T) F(T, TEXT, text, NONE)
     377              : 
     378              : #define S_Let_FIELDS(F, T) F(T, NODE, bind, BIND)
     379              : #define S_Handle_FIELDS(F, T) F(T, NAME, label, NONE) F(T, NODE, handler, EXPR)
     380              : #define S_Use_FIELDS(F, T) F(T, SEQ, binds, USEBIND)
     381              : #define S_Discard_FIELDS(F, T) F(T, NODE, body, EXPR)
     382              : 
     383              : #define H_Bind_FIELDS(F, T) F(T, NODE, lhs, BINDLHS) F(T, NODE, body, EXPR)
     384              : #define H_UseBind_FIELDS(F, T) F(T, NAME, from, NONE) F(T, NAME, to, NONE)
     385              : #define H_Alt_FIELDS(F, T) \
     386              :   F(T, NODE, pat, PAT) F(T, NODE, body, EXPR) F(T, SEQ, wheres, DECL)
     387              : // A control clause's continuation is held apart from `pats`, so E-ARITY can
     388              : // compare pattern count against op arity directly for every kind (D14). The
     389              : // comma is what delimits it: left of a comma, binder count = op arity (C8,
     390              : // amended). `k` is empty for every other kind, `abort` included -- an abort
     391              : // clause has no continuation to name (D24).
     392              : #define H_Clause_FIELDS(F, T) \
     393              :   F(T, INT, kind, NONE) F(T, NAME, name, NONE) F(T, SEQ, pats, PAT) \
     394              :   F(T, NAME, k, NONE) F(T, NODE, body, EXPR)
     395              : #define H_Field_FIELDS(F, T) F(T, NAME, name, NONE) F(T, NODE, value, EXPR)
     396              : 
     397              : #define W_File_FIELDS(F, T) F(T, SEQ, decls, DECL)
     398              : 
     399              : // ------------------------------------------------------------ derived: tags
     400              : 
     401              : typedef enum : u16 {
     402              : #define WOK_X(tag, fam) tag,
     403              :   WOK_NODES(WOK_X)
     404              : #undef WOK_X
     405              :       WOK_TAG_COUNT
     406              : } WokTag;
     407              : 
     408              : // ------------------------------------------------- derived: slot indices
     409              : 
     410              : #define WOK_SLOT_INDEX(T, cls, name, fam) T##__##name,
     411              : #define WOK_DECLARE_SLOTS(T, fam) \
     412              :   enum { T##_FIELDS(WOK_SLOT_INDEX, T) T##__NSLOTS };
     413              : WOK_NODES(WOK_DECLARE_SLOTS)
     414              : #undef WOK_DECLARE_SLOTS
     415              : 
     416              : // ------------------------------------------- derived: accessors and setters
     417              : 
     418              : #define WOK_CT_NODE WokNode *
     419              : #define WOK_CT_OPT WokNode *
     420              : #define WOK_CT_SEQ WokSeq
     421              : #define WOK_CT_NAME WokSpan
     422              : #define WOK_CT_TEXT WokSpan
     423              : #define WOK_CT_INT u64
     424              : #define WOK_CT_FLAG bool
     425              : 
     426              : #define WOK_GET_NODE(s) ((s).node)
     427              : #define WOK_GET_OPT(s) ((s).node)
     428              : #define WOK_GET_SEQ(s) wok_seq_unpack((s).seq)
     429              : #define WOK_GET_NAME(s) ((s).span)
     430              : #define WOK_GET_TEXT(s) ((s).span)
     431              : #define WOK_GET_INT(s) ((s).num)
     432              : #define WOK_GET_FLAG(s) ((s).flag)
     433              : 
     434              : #define WOK_MK_NODE(v) ((WokSlot){.node = (v)})
     435              : #define WOK_MK_OPT(v) ((WokSlot){.node = (v)})
     436              : #define WOK_MK_SEQ(v) ((WokSlot){.seq = (v).items})
     437              : #define WOK_MK_NAME(v) ((WokSlot){.span = (v)})
     438              : #define WOK_MK_TEXT(v) ((WokSlot){.span = (v)})
     439              : #define WOK_MK_INT(v) ((WokSlot){.num = (v)})
     440              : #define WOK_MK_FLAG(v) ((WokSlot){.flag = (v)})
     441              : 
     442              : #define WOK_SLOT_ACCESS(T, cls, name, fam)                                 \
     443              :   static inline WOK_CT_##cls T##_##name(const WokNode *n) {           \
     444              :     assert(n->tag == T);                                              \
     445              :     return WOK_GET_##cls(n->slot[T##__##name]);                       \
     446              :   }                                                                   \
     447              :   static inline void T##_set_##name(WokNode *n, WOK_CT_##cls v) {     \
     448              :     assert(n->tag == T);                                              \
     449              :     n->slot[T##__##name] = WOK_MK_##cls(v);                           \
     450              :   }
     451              : #define WOK_DECLARE_ACCESS(T, fam) T##_FIELDS(WOK_SLOT_ACCESS, T)
     452      2864499 : WOK_NODES(WOK_DECLARE_ACCESS)
     453              : #undef WOK_DECLARE_ACCESS
     454              : 
     455              : // --------------------------------------------------- derived: descriptors
     456              : 
     457              : typedef struct {
     458              :   const char *name;
     459              :   WokFieldClass cls;
     460              :   WokFamily family;  // WFAM_NONE unless the field holds a child
     461              : } WokFieldDesc;
     462              : 
     463              : typedef struct {
     464              :   const char *tag;
     465              :   const WokFieldDesc *fields;
     466              :   u16 nfields;
     467              :   WokFamily family;  // the family this node BELONGS to
     468              : } WokNodeDesc;
     469              : 
     470              : extern const WokNodeDesc wok_node_desc[WOK_TAG_COUNT];
     471              : 
     472              : WOK_READONLY const char *wok_family_name(WokFamily);
     473              : 
     474              : // ------------------------------------------------------------ construction
     475              : 
     476              : // Allocates a node with the slot count its tag declares, zeroed. The parser
     477              : // never states an arity: the schema does.
     478              : WokNode *wok_node(WokArena *, WokTag, u32 off, u32 len);
     479              : WokSeq wok_seq(WokArena *, WokNode *const *restrict items, u32 n);
     480              : 
     481              : 
     482       352054 : static inline WokSpan wok_span(u32 off, u32 len) {
     483        76405 :   return (WokSpan){.off = off, .len = len};
     484              : }
     485        27472 : static inline bool wok_span_empty(WokSpan s) { return s.len == 0; }
     486              : 
     487              : // A growable node list for the parser, backed by the arena.
     488              : // The fields are named to be awkward on purpose: index 0 is RESERVED for the
     489              : // inline count so wok_buf_seq can hand out the buffer without copying, which
     490              : // means `raw`/`raw_n` do not mean what `items`/`n` used to. Reading them
     491              : // directly is how a tree gets silently corrupted -- use the accessors.
     492              : typedef struct {
     493              :   WokNode **raw;
     494              :   u32 raw_n, cap;
     495              :   WokArena *arena;
     496              : } WokNodeBuf;
     497              : 
     498       383135 : static inline u32 wok_buf_count(const WokNodeBuf *b) {
     499       383135 :   return b->raw_n > 0 ? b->raw_n - 1u : 0u;
     500              : }
     501         9022 : static inline WokNode *wok_buf_at(const WokNodeBuf *b, u32 i) {
     502         9022 :   return b->raw[i + 1u];
     503              : }
     504              : 
     505              : void wok_buf_init(WokNodeBuf *, WokArena *);
     506              : void wok_buf_push(WokNodeBuf *, WokNode *);
     507              : WokSeq wok_buf_seq(WokNodeBuf *);
     508              : 
     509              : // --------------------------------------------------------------- coverage
     510              : //
     511              : // Production coverage, not line coverage. Every parse* function marks its tag
     512              : // on entry; the corpus must reach 100%, and a form no file reaches names
     513              : // itself rather than sitting silently uncovered.
     514              : 
     515              : // The bitmap is extern and the mark inline: it runs once per node built, and
     516              : // a store does not deserve a call. Single-threaded by design (a test
     517              : // instrument); the readers stay out of line.
     518              : extern bool wok_cover_bits[];
     519       646530 : static inline void wok_cover_mark(WokTag t) { wok_cover_bits[t] = true; }
     520              : void wok_cover_reset(void);
     521              : bool wok_cover_seen(WokTag);
     522              : usize wok_cover_missing(const WokTag **out);
     523              : 
        

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.