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

            Line data    Source code
       1              : // wok_token -- the token rosters and the scanner.
       2              : //
       3              : // The division of labour with the parser is deliberate: THE SCANNER RESOLVES
       4              : // EVERYTHING THAT DEPENDS ON THE SOURCE TEXT; the parser only ever looks at
       5              : // fields of a token. No production reads a token's bytes to make a decision --
       6              : // text is carried into the AST or quoted in a diagnostic, never compared.
       7              : 
       8              : #pragma once
       9              : 
      10              : // The prelude comes FIRST: it carries the POSIX feature-test macros, which
      11              : // have no effect once a system header has been read. wok_base.h hard-errors
      12              : // if it is reached too late.
      13              : #include "wok_base.h"
      14              : 
      15              : #include <stdbool.h>
      16              : #include <stddef.h>
      17              : #include <stdint.h>
      18              : 
      19              : #include "wok_arena.h"
      20              : #include "wok_diag.h"
      21              : 
      22              : // ---------------------------------------------------------------- kinds
      23              : //
      24              : // One roster. Every table keyed by a kind carries a static_assert against
      25              : // WOK_KIND_COUNT, and no switch over a kind may carry a `default:` label --
      26              : // that is what turns a missing enumerator into -Werror=switch.
      27              : 
      28              : #define WOK_KINDS(X)                                                      \
      29              :   X(WT_EOF)                                                               \
      30              :   /* layout tokens: produced by stage 2, never by the scanner */          \
      31              :   X(WT_NEWLINE) X(WT_INDENT) X(WT_DEDENT)                                 \
      32              :   /* open lexical classes */                                              \
      33              :   X(WT_VARID) X(WT_CONID) X(WT_VARSYM) X(WT_KEYWORD)                      \
      34              :   X(WT_INT) X(WT_STRING) X(WT_CHAR)                                       \
      35              :   /* brackets */                                                          \
      36              :   X(WT_LPAREN) X(WT_RPAREN) X(WT_LBRACKET) X(WT_RBRACKET)                 \
      37              :   X(WT_LBRACE) X(WT_RBRACE)                                               \
      38              :   /* fixed punctuation */                                                 \
      39              :   X(WT_COMMA) X(WT_DOT) X(WT_DOTDOT) X(WT_BACKTICK)                       \
      40              :   X(WT_UNDERSCORE) X(WT_LAMBDA)                                           \
      41              :   /* reserved operator runs, carved out of WT_VARSYM by table lookup */   \
      42              :   X(WT_ARROW) X(WT_FATARROW) X(WT_EQUALS) X(WT_COLON)                     \
      43              :   X(WT_COLONCOLON) X(WT_ASSIGN) X(WT_BAR)                                 \
      44              :   /* v2 has no statement separator; scanned only to diagnose it (D22) */  \
      45              :   X(WT_SEMI)                                                              \
      46              :   X(WT_BAD)
      47              : 
      48              : typedef enum wok_kind : unsigned char {
      49              : #define WOK_X(name) name,
      50              :   WOK_KINDS(WOK_X)
      51              : #undef WOK_X
      52              :       WOK_KIND_COUNT
      53              : } WokKind;
      54              : 
      55              : static_assert(WOK_KIND_COUNT <= 255, "kind must fit in one byte");
      56              : 
      57              : WOK_READONLY const char *wok_kind_name(WokKind);
      58              : 
      59              : // ---------------------------------------------------------------- words
      60              : //
      61              : // The interned identity of a NAMED SPELLING. Three registers stay apart
      62              : // without a second roster because each records the kind it is emitted as:
      63              : //
      64              : //   WT_KEYWORD -- reserved; never a variable.
      65              : //   WT_VARID   -- CONTEXTUAL; an ordinary identifier that carries its tag, so
      66              : //                 the one production that wants it matches an integer and
      67              : //                 every other production sees a plain name. `own`/`lend`/
      68              : //                 `copy` must be contextual: spec.md section 2 writes
      69              : //                 `Bytes.copy` itself.
      70              : //   WT_VARSYM  -- a user operator that one production happens to name; `+`
      71              : //                 stays freely redefinable.
      72              : //
      73              : // Matching a word is an integer compare, and `wok_word_is(t, WW_HANLDE)`
      74              : // fails to COMPILE where a string comparison would compile, run, and simply
      75              : // never be true.
      76              : 
      77              : #define WOK_WORDS(X)                                                      \
      78              :   /* --- ML skeleton: the ordinary word on familiar ground (D10) --- */   \
      79              :   X(WW_MODULE, "module", WT_KEYWORD)                                      \
      80              :   X(WW_IMPORT, "import", WT_KEYWORD)                                      \
      81              :   X(WW_AS, "as", WT_KEYWORD)                                              \
      82              :   X(WW_TYPE, "type", WT_KEYWORD)                                          \
      83              :   X(WW_ALIAS, "alias", WT_KEYWORD)                                        \
      84              :   X(WW_CLASS, "class", WT_KEYWORD)                                        \
      85              :   X(WW_INSTANCE, "instance", WT_KEYWORD)                                  \
      86              :   X(WW_LET, "let", WT_KEYWORD)                                            \
      87              :   X(WW_IN, "in", WT_KEYWORD)                                              \
      88              :   X(WW_CASE, "case", WT_KEYWORD)                                          \
      89              :   X(WW_OF, "of", WT_KEYWORD)                                              \
      90              :   X(WW_IF, "if", WT_KEYWORD)                                              \
      91              :   X(WW_THEN, "then", WT_KEYWORD)                                          \
      92              :   X(WW_ELSE, "else", WT_KEYWORD)                                          \
      93              :   X(WW_WHERE, "where", WT_KEYWORD)                                        \
      94              :   /* --- law words: the keyword states the promise (D10) --- */           \
      95              :   X(WW_EFFECT, "effect", WT_KEYWORD)                                      \
      96              :   X(WW_HANDLER, "handler", WT_KEYWORD)                                    \
      97              :   X(WW_HANDLE, "handle", WT_KEYWORD)                                      \
      98              :   X(WW_USE, "use", WT_KEYWORD)                                            \
      99              :   X(WW_ABORT, "abort", WT_KEYWORD)                                        \
     100              :   X(WW_RETURN, "return", WT_KEYWORD)                                      \
     101              :   X(WW_VAR, "var", WT_KEYWORD)                                            \
     102              :   X(WW_WITH, "with", WT_KEYWORD)                                          \
     103              :   X(WW_FOREIGN, "foreign", WT_KEYWORD)                                    \
     104              :   X(WW_EXTERN, "extern", WT_KEYWORD)                                      \
     105              :   X(WW_FIXITY, "fixity", WT_KEYWORD)                                      \
     106              :   /* --- contextual: tagged, but still ordinary identifiers --- */        \
     107              :   /* `once` is CUT (D25): reserved at CLAUSE-HEAD position only, where */  \
     108              :   /* parse_clause reads the tag to emit the v1-migration diagnostic. */    \
     109              :   X(WW_ONCE, "once", WT_VARID)                                            \
     110              :   X(WW_OWN, "own", WT_VARID)                                              \
     111              :   X(WW_LEND, "lend", WT_VARID)                                            \
     112              :   X(WW_COPY, "copy", WT_VARID)                                            \
     113              :   /* the five words of a `fixity` line. Contextual for the same reason */ \
     114              :   /* `own` is: `left`, `right` and `than` are ordinary names anywhere */  \
     115              :   /* else, and reserving them would cost every program that has one. */   \
     116              :   X(WW_LEFT, "left", WT_VARID)                                            \
     117              :   X(WW_RIGHT, "right", WT_VARID)                                          \
     118              :   X(WW_TIGHTER, "tighter", WT_VARID)                                      \
     119              :   X(WW_LOOSER, "looser", WT_VARID)                                        \
     120              :   X(WW_THAN, "than", WT_VARID)                                            \
     121              :   X(WW_ROW, "row", WT_VARID)                                              \
     122              :   X(WW_EFF, "eff", WT_VARID)                                              \
     123              :   /* --- an operator run one production reads by name --- */              \
     124              :   X(WW_PLUS, "+", WT_VARSYM)
     125              : 
     126              : typedef enum wok_word : unsigned char {
     127              :   WW_NONE = 0,
     128              : #define WOK_X(name, text, kind) name,
     129              :   WOK_WORDS(WOK_X)
     130              : #undef WOK_X
     131              :       WOK_WORD_COUNT
     132              : } WokWord;
     133              : 
     134              : static_assert(WOK_WORD_COUNT <= 255, "word must fit in one byte");
     135              : 
     136              : WOK_READONLY const char *wok_word_text(WokWord);
     137              : WOK_READONLY WokKind wok_word_kind(WokWord);
     138              : 
     139              : // ---------------------------------------------------------------- token
     140              : 
     141              : enum {
     142              :   WOK_TF_FIRST_ON_LINE = 1u << 0,  // this token opens its physical line
     143              :   WOK_TF_HAS_ESCAPE = 1u << 1,     // literal text differs from source bytes
     144              : };
     145              : 
     146              : typedef struct {
     147              :   u32 off;  // byte offset into the source; text is never copied
     148              :   u32 len;
     149              :   u32 col;  // 1-based; tabs are rejected in indentation, so this is exact
     150              :   u8 kind;  // WokKind
     151              :   u8 word;  // WokWord, or WW_NONE
     152              :   u16 flags;
     153              : } WokToken;
     154              : 
     155              : static_assert(sizeof(WokToken) == 16, "token is the hot array; keep it 16 bytes");
     156              : 
     157              : typedef struct {
     158              :   WokToken *tok;
     159              :   usize n;  // always >= 1; the last token is WT_EOF and is never consumed past
     160              : } WokTokens;
     161              : 
     162              : // Comments are collected into a side list rather than discarded, so attaching
     163              : // them as trivia does not have to re-open the scanner. wok_trivia_attach
     164              : // consumes this list; the tokens themselves never mention a comment.
     165              : typedef struct {
     166              :   u32 off, len;
     167              :   bool block;  // {- -} rather than --
     168              : } WokComment;
     169              : 
     170              : typedef struct {
     171              :   WokTokens tokens;
     172              :   WokComment *comments;
     173              :   usize ncomments;
     174              : } WokScanResult;
     175              : 
     176              : WokScanResult wok_scan(const char *src, usize src_len, WokArena *,
     177              :                        WokDiagSink *);
     178              : 
     179              : // ------------------------------------------------- lexical classification
     180              : 
     181              : // Static inline, not out-of-line: these are asked once per token, and
     182              : // profiling showed the calls themselves on the clock (~5% of a parse).
     183      6789841 : WOK_PURE static inline bool wok_kind_is_open_bracket(WokKind k) {
     184      6789841 :   return k == WT_LPAREN || k == WT_LBRACKET || k == WT_LBRACE;
     185              : }
     186      6430039 : WOK_PURE static inline bool wok_kind_is_close_bracket(WokKind k) {
     187      6430039 :   return k == WT_RPAREN || k == WT_RBRACKET || k == WT_RBRACE;
     188              : }
     189              : 
     190              : // THE predicate stage 2 is built on: a token that can never BEGIN a block
     191              : // item, and therefore marks its line as a CONTINUATION of the previous one.
     192              : // It lives here, not in wok_layout.c, because it is a lexical fact -- the
     193              : // layout filter must not be able to name a keyword.
     194              : //
     195              : // Two things hold this down, and a third does not.
     196              : //
     197              : //   - -Wswitch. The switch below carries no `default:`, over the kinds OR the
     198              : //     words, so a token added to either roster does not COMPILE until someone
     199              : //     classifies it here. That is the load-bearing half, and it is the reason
     200              : //     no roster test is needed to say every token has an answer.
     201              : //   - test_fill pins the PRINTER's own copy of this question against this one
     202              : //     for every lead a fill break can produce, so the two cannot drift into a
     203              : //     second opinion about the lexis.
     204              : //
     205              : // What is NOT checked is that each answer is RIGHT -- that a token called a
     206              : // lead cannot in fact begin an item. A wrong answer there compiles, and under
     207              : // an offside rule it is silent: the line joins the previous one instead of
     208              : // starting an item, and only the tree shows it. Checking it needs the FIRST
     209              : // set of the grammar, and the only honest source for that is a generator that
     210              : // builds items rather than a second list typed out beside this one -- so if it
     211              : // is ever written, it belongs in test_generative, which already builds items
     212              : // across 80 of the schema's 82 tags.
     213       765787 : WOK_READONLY static inline bool wok_token_is_continuation_lead(
     214              :     const WokToken *t) {
     215       765787 :   switch ((WokKind)t->kind) {
     216              :     // Every operator lead. No prefix operator exists except `-`, and a line
     217              :     // beginning with `-` is subtraction continuing the previous line -- the
     218              :     // same call v2 already made when it decided `1-2` is subtraction.
     219              :     case WT_VARSYM:
     220              :     case WT_ARROW:
     221              :     case WT_FATARROW:
     222              :     case WT_EQUALS:
     223              :     case WT_COLON:
     224              :     case WT_COLONCOLON:
     225              :     case WT_ASSIGN:
     226              :     case WT_BAR:
     227              :     case WT_COMMA:
     228              :     case WT_DOT:
     229              :     case WT_DOTDOT:
     230              :     case WT_BACKTICK:
     231              :     case WT_RPAREN:
     232              :     case WT_RBRACKET:
     233              :     case WT_RBRACE:
     234              :       return true;
     235              :     // Words that can never begin a block item.
     236       186462 :     case WT_KEYWORD:
     237       186462 :       switch ((WokWord)t->word) {
     238              :         case WW_WHERE:
     239              :         case WW_IN:
     240              :         case WW_THEN:
     241              :         case WW_ELSE:
     242              :         case WW_OF:
     243              :         case WW_AS:
     244              :         case WW_WITH:
     245              :           return true;
     246       175072 :         case WW_NONE:
     247              :         case WW_MODULE:
     248              :         case WW_IMPORT:
     249              :         case WW_TYPE:
     250              :         case WW_ALIAS:
     251              :         case WW_CLASS:
     252              :         case WW_INSTANCE:
     253              :         case WW_LET:
     254              :         case WW_CASE:
     255              :         case WW_IF:
     256              :         case WW_EFFECT:
     257              :         case WW_HANDLER:
     258              :         case WW_HANDLE:
     259              :         case WW_USE:
     260              :         case WW_ABORT:
     261              :         case WW_ONCE:
     262              :         case WW_RETURN:
     263              :         case WW_VAR:
     264              :         case WW_FOREIGN:
     265              :         case WW_EXTERN:
     266              :         case WW_FIXITY:
     267              :         case WW_OWN:
     268              :         case WW_LEND:
     269              :         case WW_COPY:
     270              :         case WW_ROW:
     271              :         case WW_EFF:
     272              :         case WW_LEFT:
     273              :         case WW_RIGHT:
     274              :         case WW_TIGHTER:
     275              :         case WW_LOOSER:
     276              :         case WW_THAN:
     277              :         case WW_PLUS:
     278              :         case WOK_WORD_COUNT:
     279       175072 :           return false;
     280              :       }
     281            0 :       WOK_UNREACHABLE();
     282       474967 :     case WT_EOF:
     283              :     case WT_NEWLINE:
     284              :     case WT_INDENT:
     285              :     case WT_DEDENT:
     286              :     case WT_VARID:
     287              :     case WT_CONID:
     288              :     case WT_INT:
     289              :     case WT_STRING:
     290              :     case WT_CHAR:
     291              :     case WT_LPAREN:
     292              :     case WT_LBRACKET:
     293              :     case WT_LBRACE:
     294              :     case WT_UNDERSCORE:
     295              :     case WT_LAMBDA:
     296              :     case WT_SEMI:
     297              :     case WT_BAD:
     298              :     case WOK_KIND_COUNT:
     299       474967 :       return false;
     300              :   }
     301            0 :   WOK_UNREACHABLE();
     302              : }
     303              : 
     304              : // Decodes an integer literal, reporting E-LEX-INT-RANGE on overflow past U64.
     305              : // Uses ckd_mul/ckd_add where available: literal conversion is where parsers
     306              : // get CVEs, and this is a defect-class elimination rather than a style choice.
     307              : bool wok_token_int_value(const char *src, const WokToken *, WokDiagSink *,
     308              :                          u64 *out);
     309              : 
     310              : static inline const char *wok_token_text(const char *src, const WokToken *t) {
     311              :   return src + t->off;
     312              : }
     313              : 
     314              : // Test hooks. The symbol charset is a bitmap for speed; test_roster.c checks
     315              : // it against the readable definition for all 256 byte values, which is what
     316              : // makes a hand-written bitmap safe. wok_print.c also reads two of them, so the
     317              : // filler's lead test cannot grow a second opinion about the charset.
     318              : bool wok_test_is_sym(unsigned char);
     319              : bool wok_test_is_ident_start(unsigned char);
     320              : bool wok_test_is_ident_cont(unsigned char);
     321              : 
        

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.