zig0

my attempts at zig bootstrapping in C
Log | Files | Refs | README | LICENSE

parser.c (117904B) - Raw


      1 
      2 #include "common.h"
      3 
      4 #include <assert.h>
      5 #include <ctype.h>
      6 #include <stdlib.h>
      7 #include <string.h>
      8 
      9 #include "ast.h"
     10 #include "parser.h"
     11 
     12 const AstNodeIndex null_node = 0;
     13 const AstTokenIndex null_token = ~(AstTokenIndex)(0);
     14 
     15 // OPT encodes a node index as OptionalIndex: 0 → ~0 (none)
     16 #define OPT(x) ((x) == 0 ? ~(AstNodeIndex)0 : (x))
     17 
     18 typedef struct {
     19     uint32_t len;
     20     AstNodeIndex lhs;
     21     AstNodeIndex rhs;
     22     bool trailing;
     23 } Members;
     24 
     25 typedef struct {
     26     enum { FIELD_STATE_NONE, FIELD_STATE_SEEN, FIELD_STATE_END } tag;
     27     union {
     28         uint32_t end;
     29     } payload;
     30 } FieldState;
     31 
     32 typedef struct {
     33     enum { SMALL_SPAN_ZERO_OR_ONE, SMALL_SPAN_MULTI } tag;
     34     union {
     35         AstNodeIndex zero_or_one;
     36         AstSubRange multi;
     37     } payload;
     38 } SmallSpan;
     39 
     40 typedef struct {
     41     AstNodeIndex align_node;
     42     AstNodeIndex addrspace_node;
     43     AstNodeIndex bit_range_start;
     44     AstNodeIndex bit_range_end;
     45 } PtrModifiers;
     46 
     47 typedef struct {
     48     int8_t prec;
     49     AstNodeTag tag;
     50     enum {
     51         ASSOC_LEFT,
     52         ASSOC_NONE,
     53     } assoc;
     54 } OperInfo;
     55 
     56 typedef struct {
     57     AstNodeIndex align_expr, value_expr;
     58 } NodeContainerField;
     59 
     60 static AstNodeIndex addExtra(Parser*, const AstNodeIndex*, uint32_t);
     61 static AstNodeIndex addNode(AstNodeList*, AstNodeItem);
     62 static AstNodeTag assignOpNode(TokenizerTag);
     63 static AstTokenIndex assertToken(Parser*, TokenizerTag);
     64 static void astNodeListEnsureCapacity(AstNodeList*, uint32_t);
     65 static AstTokenIndex eatDocComments(Parser*);
     66 static AstTokenIndex eatToken(Parser*, TokenizerTag);
     67 static AstNodeIndex expectBlockExprStatement(Parser*);
     68 static AstNodeIndex expectContainerField(Parser*);
     69 static AstNodeIndex expectExpr(Parser*);
     70 static AstNodeIndex expectIfStatement(Parser*);
     71 static AstNodeIndex expectParamDecl(Parser*);
     72 static AstNodeIndex expectSemicolon(Parser*);
     73 static AstNodeIndex expectStatement(Parser*, bool);
     74 static AstNodeIndex expectTestDecl(Parser*);
     75 static AstTokenIndex expectToken(Parser*, TokenizerTag);
     76 static AstNodeIndex expectTopLevelDecl(Parser*);
     77 static AstNodeIndex expectVarDeclExprStatement(Parser*, AstTokenIndex);
     78 static void findNextContainerMember(Parser*);
     79 static AstNodeIndex finishAssignExpr(Parser*, AstNodeIndex);
     80 static uint32_t forPrefix(Parser*);
     81 static AstSubRange listToSpan(Parser*, const AstNodeIndex*, uint32_t);
     82 static AstNodeIndex makePtrTypeNode(
     83     Parser*, AstTokenIndex, AstNodeIndex, PtrModifiers, AstNodeIndex);
     84 static AstSubRange membersToSpan(const Members, Parser*);
     85 static AstTokenIndex nextToken(Parser*);
     86 static OperInfo operTable(TokenizerTag);
     87 static AstNodeIndex parseAddrSpace(Parser*);
     88 static AstNodeIndex parseAsmExpr(Parser*);
     89 static AstNodeIndex parseAsmInputItem(Parser*);
     90 static AstNodeIndex parseAsmOutputItem(Parser*);
     91 static AstNodeIndex parseAssignExpr(Parser*);
     92 static AstNodeIndex parseBlock(Parser*);
     93 static AstNodeIndex parseBlockExpr(Parser*);
     94 static AstTokenIndex parseBlockLabel(Parser*);
     95 static AstTokenIndex parseBreakLabel(Parser*);
     96 static AstNodeIndex parseBuiltinCall(Parser*);
     97 static AstNodeIndex parseByteAlign(Parser*);
     98 static AstNodeIndex parseCallconv(Parser*);
     99 static AstNodeIndex parseContainerDeclAuto(Parser*);
    100 static Members parseContainerMembers(Parser*);
    101 static AstNodeIndex parseCurlySuffixExpr(Parser*);
    102 static AstNodeIndex parseErrorUnionExpr(Parser*);
    103 static AstNodeIndex parseExpr(Parser*);
    104 static AstNodeIndex parseExprPrecedence(Parser*, int32_t);
    105 static AstNodeIndex parseFieldInit(Parser*);
    106 static AstNodeIndex parseFnProto(Parser*);
    107 static AstNodeIndex parseForExpr(Parser*);
    108 static AstNodeIndex parseForStatement(Parser*);
    109 static AstNodeIndex parseGlobalVarDecl(Parser*);
    110 static AstNodeIndex parseIfExpr(Parser*);
    111 static AstNodeIndex parseInitList(Parser*, AstNodeIndex, AstTokenIndex);
    112 static AstNodeIndex parseLabeledStatement(Parser*);
    113 static AstNodeIndex parseLinkSection(Parser*);
    114 static AstNodeIndex parseLoopStatement(Parser*);
    115 static SmallSpan parseParamDeclList(Parser*);
    116 static void parsePayload(Parser*);
    117 static AstNodeIndex parsePrefixExpr(Parser*);
    118 static AstNodeIndex parsePrimaryExpr(Parser*);
    119 static AstNodeIndex parsePrimaryTypeExpr(Parser*);
    120 static PtrModifiers parsePtrModifiers(Parser*);
    121 static void parsePtrPayload(Parser*);
    122 static AstNodeIndex parseSingleAssignExpr(Parser*);
    123 static AstNodeIndex parseSuffixExpr(Parser*);
    124 static AstNodeIndex parseSuffixOp(Parser*, AstNodeIndex);
    125 static AstNodeIndex parseSwitchExpr(Parser*);
    126 static AstNodeIndex parseSwitchItem(Parser*);
    127 static AstNodeIndex parseSwitchProng(Parser*);
    128 static AstSubRange parseSwitchProngList(Parser*);
    129 static AstNodeIndex parseTypeExpr(Parser*);
    130 static AstNodeIndex parseVarDeclProto(Parser*);
    131 static AstNodeIndex parseWhileContinueExpr(Parser*);
    132 static AstNodeIndex parseWhileExpr(Parser*);
    133 static AstNodeIndex parseWhileStatement(Parser*);
    134 static uint32_t reserveNode(Parser*, AstNodeTag);
    135 static AstNodeIndex setNode(Parser*, uint32_t, AstNodeItem);
    136 static uint32_t tokenTagLexemeLen(TokenizerTag);
    137 static bool tokensOnSameLine(Parser*, AstTokenIndex, AstTokenIndex);
    138 
    139 static AstSubRange membersToSpan(const Members self, Parser* p) {
    140     if (self.len <= 2) {
    141         const AstNodeIndex nodes[] = { self.lhs, self.rhs };
    142         return listToSpan(p, nodes, self.len);
    143     } else {
    144         return (AstSubRange) { .start = self.lhs, .end = self.rhs };
    145     }
    146 }
    147 
    148 static AstSubRange listToSpan(
    149     Parser* p, const AstNodeIndex* list, uint32_t count) {
    150     SLICE_ENSURE_CAPACITY(AstNodeIndex, &p->extra_data, count);
    151     memcpy(p->extra_data.arr + p->extra_data.len, list,
    152         count * sizeof(AstNodeIndex));
    153     p->extra_data.len += count;
    154     return (AstSubRange) {
    155         .start = p->extra_data.len - count,
    156         .end = p->extra_data.len,
    157     };
    158 }
    159 
    160 static AstNodeIndex addNode(AstNodeList* nodes, AstNodeItem item) {
    161     astNodeListEnsureCapacity(nodes, 1);
    162     nodes->tags[nodes->len] = item.tag;
    163     nodes->main_tokens[nodes->len] = item.main_token;
    164     nodes->datas[nodes->len] = item.data;
    165     return nodes->len++;
    166 }
    167 
    168 static AstNodeIndex setNode(Parser* p, uint32_t i, AstNodeItem item) {
    169     p->nodes.tags[i] = item.tag;
    170     p->nodes.main_tokens[i] = item.main_token;
    171     p->nodes.datas[i] = item.data;
    172     return i;
    173 }
    174 
    175 static uint32_t reserveNode(Parser* p, AstNodeTag tag) {
    176     astNodeListEnsureCapacity(&p->nodes, 1);
    177     p->nodes.len++;
    178     p->nodes.tags[p->nodes.len - 1] = tag;
    179     return p->nodes.len - 1;
    180 }
    181 
    182 static AstNodeIndex addExtra(
    183     Parser* p, const AstNodeIndex* extra, uint32_t count) {
    184     const AstNodeIndex result = p->extra_data.len;
    185     SLICE_ENSURE_CAPACITY(AstNodeIndex, &p->extra_data, count);
    186     memcpy(p->extra_data.arr + p->extra_data.len, extra,
    187         count * sizeof(AstNodeIndex));
    188     p->extra_data.len += count;
    189     return result;
    190 }
    191 
    192 static void astNodeListEnsureCapacity(AstNodeList* list, uint32_t additional) {
    193     const uint32_t new_len = list->len + additional;
    194     if (new_len <= list->cap) {
    195         return;
    196     }
    197 
    198     const uint32_t new_cap = new_len > list->cap * 2 ? new_len : list->cap * 2;
    199     list->tags = realloc(list->tags, new_cap * sizeof(AstNodeTag));
    200     list->main_tokens
    201         = realloc(list->main_tokens, new_cap * sizeof(AstTokenIndex));
    202     list->datas = realloc(list->datas, new_cap * sizeof(AstData));
    203     if (!list->tags || !list->main_tokens || !list->datas)
    204         exit(1);
    205     list->cap = new_cap;
    206 }
    207 
    208 void parseRoot(Parser* p) {
    209     addNode(
    210         &p->nodes, (AstNodeItem) { .tag = AST_NODE_ROOT, .main_token = 0 });
    211 
    212     Members root_members = parseContainerMembers(p);
    213     AstSubRange root_decls = membersToSpan(root_members, p);
    214 
    215     if (p->token_tags[p->tok_i] != TOKEN_EOF) {
    216         fail(p, "expected EOF");
    217     }
    218 
    219     p->nodes.datas[0].lhs = root_decls.start;
    220     p->nodes.datas[0].rhs = root_decls.end;
    221 }
    222 
    223 static Members parseContainerMembers(Parser* p) {
    224     const uint32_t scratch_top = p->scratch.len;
    225     while (eatToken(p, TOKEN_CONTAINER_DOC_COMMENT) != null_token)
    226         ;
    227 
    228     FieldState field_state = { .tag = FIELD_STATE_NONE };
    229 
    230     bool trailing = false;
    231     while (1) {
    232         const AstTokenIndex doc_comment = eatDocComments(p);
    233         switch (p->token_tags[p->tok_i]) {
    234         case TOKEN_KEYWORD_TEST: {
    235             if (doc_comment != null_token)
    236                 fail(p, "test_doc_comment");
    237             const AstNodeIndex test_decl = expectTestDecl(p);
    238             if (field_state.tag == FIELD_STATE_SEEN) {
    239                 field_state.tag = FIELD_STATE_END;
    240                 field_state.payload.end = test_decl;
    241             }
    242             SLICE_APPEND(AstNodeIndex, &p->scratch, test_decl);
    243             trailing = false;
    244             break;
    245         }
    246         case TOKEN_KEYWORD_COMPTIME:
    247             // comptime can be a container field modifier or a comptime
    248             // block/decl. Check if it's followed by a block (comptime { ...
    249             // }).
    250             if (p->token_tags[p->tok_i + 1] == TOKEN_L_BRACE) {
    251                 if (doc_comment != null_token) {
    252                     fail(p, "comptime_doc_comment");
    253                 }
    254                 const AstTokenIndex comptime_token = nextToken(p);
    255                 const AstNodeIndex block_node = parseBlock(p);
    256                 SLICE_APPEND(AstNodeIndex, &p->scratch,
    257                     addNode(&p->nodes,
    258                         (AstNodeItem) {
    259                             .tag = AST_NODE_COMPTIME,
    260                             .main_token = comptime_token,
    261                             .data = { .lhs = block_node, .rhs = 0 },
    262                         }));
    263                 trailing = false;
    264                 break;
    265             }
    266             // Otherwise it's a container field with comptime modifier
    267             goto container_field;
    268         case TOKEN_KEYWORD_PUB: {
    269             p->tok_i++;
    270             AstNodeIndex top_level_decl = expectTopLevelDecl(p);
    271             if (top_level_decl != 0) {
    272                 if (field_state.tag == FIELD_STATE_SEEN) {
    273                     field_state.tag = FIELD_STATE_END;
    274                     field_state.payload.end = top_level_decl;
    275                 }
    276                 SLICE_APPEND(AstNodeIndex, &p->scratch, top_level_decl);
    277             }
    278             trailing = p->token_tags[p->tok_i - 1] == TOKEN_SEMICOLON;
    279             break;
    280         }
    281         case TOKEN_KEYWORD_CONST:
    282         case TOKEN_KEYWORD_VAR:
    283         case TOKEN_KEYWORD_THREADLOCAL:
    284         case TOKEN_KEYWORD_EXPORT:
    285         case TOKEN_KEYWORD_EXTERN:
    286         case TOKEN_KEYWORD_INLINE:
    287         case TOKEN_KEYWORD_NOINLINE:
    288         case TOKEN_KEYWORD_FN: {
    289             const AstNodeIndex top_level_decl = expectTopLevelDecl(p);
    290             if (top_level_decl != 0) {
    291                 if (field_state.tag == FIELD_STATE_SEEN) {
    292                     field_state.tag = FIELD_STATE_END;
    293                     field_state.payload.end = top_level_decl;
    294                 }
    295                 SLICE_APPEND(AstNodeIndex, &p->scratch, top_level_decl);
    296             }
    297             trailing = (p->token_tags[p->tok_i - 1] == TOKEN_SEMICOLON);
    298             break;
    299         }
    300         case TOKEN_EOF:
    301         case TOKEN_R_BRACE:
    302             goto break_loop;
    303         container_field:
    304         default:;
    305             // skip parseCStyleContainer
    306             const AstNodeIndex field_node = expectContainerField(p);
    307             switch (field_state.tag) {
    308             case FIELD_STATE_NONE:
    309                 field_state.tag = FIELD_STATE_SEEN;
    310                 break;
    311             case FIELD_STATE_SEEN:
    312                 break;
    313             case FIELD_STATE_END:
    314                 fail(p, "parseContainerMembers error condition");
    315             }
    316             SLICE_APPEND(AstNodeIndex, &p->scratch, field_node);
    317             switch (p->token_tags[p->tok_i]) {
    318             case TOKEN_COMMA:
    319                 p->tok_i++;
    320                 trailing = true;
    321                 continue;
    322             case TOKEN_R_BRACE:
    323             case TOKEN_EOF:
    324                 trailing = false;
    325                 goto break_loop;
    326             default:
    327                 fail(p, "expected comma after field");
    328             }
    329         }
    330     }
    331 
    332 break_loop:;
    333 
    334     const uint32_t items_len = p->scratch.len - scratch_top;
    335     p->scratch.len = scratch_top;
    336     switch (items_len) {
    337     case 0:
    338         return (Members) {
    339             .len = 0,
    340             .lhs = 0,
    341             .rhs = 0,
    342             .trailing = trailing,
    343         };
    344     case 1:
    345         return (Members) {
    346             .len = 1,
    347             .lhs = p->scratch.arr[scratch_top],
    348             .rhs = 0,
    349             .trailing = trailing,
    350         };
    351     case 2:
    352         return (Members) {
    353             .len = 2,
    354             .lhs = p->scratch.arr[scratch_top],
    355             .rhs = p->scratch.arr[scratch_top + 1],
    356             .trailing = trailing,
    357         };
    358     default:;
    359         const AstSubRange span
    360             = listToSpan(p, &p->scratch.arr[scratch_top], items_len);
    361         return (Members) {
    362             .len = items_len,
    363             .lhs = span.start,
    364             .rhs = span.end,
    365             .trailing = trailing,
    366         };
    367     }
    368 }
    369 
    370 static void findNextContainerMember(Parser* p) {
    371     uint32_t level = 0;
    372 
    373     while (true) {
    374         AstTokenIndex tok = nextToken(p);
    375 
    376         switch (p->token_tags[tok]) {
    377         // Any of these can start a new top level declaration
    378         case TOKEN_KEYWORD_TEST:
    379         case TOKEN_KEYWORD_COMPTIME:
    380         case TOKEN_KEYWORD_PUB:
    381         case TOKEN_KEYWORD_EXPORT:
    382         case TOKEN_KEYWORD_EXTERN:
    383         case TOKEN_KEYWORD_INLINE:
    384         case TOKEN_KEYWORD_NOINLINE:
    385         case TOKEN_KEYWORD_THREADLOCAL:
    386         case TOKEN_KEYWORD_CONST:
    387         case TOKEN_KEYWORD_VAR:
    388         case TOKEN_KEYWORD_FN:
    389             if (level == 0) {
    390                 p->tok_i--;
    391                 return;
    392             }
    393             break;
    394         case TOKEN_IDENTIFIER:
    395             if (p->token_tags[tok + 1] == TOKEN_COMMA && level == 0) {
    396                 p->tok_i--;
    397                 return;
    398             }
    399             break;
    400         case TOKEN_COMMA:
    401         case TOKEN_SEMICOLON:
    402             // This decl was likely meant to end here
    403             if (level == 0)
    404                 return;
    405             break;
    406         case TOKEN_L_PAREN:
    407         case TOKEN_L_BRACKET:
    408         case TOKEN_L_BRACE:
    409             level++;
    410             break;
    411         case TOKEN_R_PAREN:
    412         case TOKEN_R_BRACKET:
    413             if (level != 0)
    414                 level--;
    415             break;
    416         case TOKEN_R_BRACE:
    417             if (level == 0) {
    418                 // end of container, exit
    419                 p->tok_i--;
    420                 return;
    421             }
    422             level--;
    423             break;
    424         case TOKEN_EOF:
    425             p->tok_i--;
    426             return;
    427         default:
    428             break;
    429         }
    430     }
    431 }
    432 
    433 static AstNodeIndex expectTestDecl(Parser* p) {
    434     const AstTokenIndex test_token = assertToken(p, TOKEN_KEYWORD_TEST);
    435     const AstTokenIndex test_name
    436         = (p->token_tags[p->tok_i] == TOKEN_STRING_LITERAL
    437               || p->token_tags[p->tok_i] == TOKEN_IDENTIFIER)
    438         ? nextToken(p)
    439         : null_token;
    440     const AstNodeIndex body = parseBlock(p);
    441     if (body == 0)
    442         fail(p, "expected block after test");
    443     return addNode(&p->nodes,
    444         (AstNodeItem) {
    445             .tag = AST_NODE_TEST_DECL,
    446             .main_token = test_token,
    447             .data = { .lhs = test_name, .rhs = body },
    448         });
    449 }
    450 
    451 static AstNodeIndex expectTopLevelDecl(Parser* p) {
    452     AstTokenIndex extern_export_inline_token = nextToken(p);
    453     bool is_extern = false;
    454 
    455     switch (p->token_tags[extern_export_inline_token]) {
    456     case TOKEN_KEYWORD_EXTERN:
    457         eatToken(p, TOKEN_STRING_LITERAL);
    458         is_extern = true;
    459         break;
    460     case TOKEN_KEYWORD_EXPORT:
    461     case TOKEN_KEYWORD_INLINE:
    462     case TOKEN_KEYWORD_NOINLINE:
    463         break;
    464     default:
    465         p->tok_i--;
    466     }
    467 
    468     AstNodeIndex fn_proto = parseFnProto(p);
    469     if (fn_proto != 0) {
    470         switch (p->token_tags[p->tok_i]) {
    471         case TOKEN_SEMICOLON:
    472             p->tok_i++;
    473             return fn_proto;
    474         case TOKEN_L_BRACE:;
    475             if (is_extern) {
    476                 fail(p, "extern_fn_body");
    477             }
    478             AstNodeIndex fn_decl_index = reserveNode(p, AST_NODE_FN_DECL);
    479             AstNodeIndex body_block = parseBlock(p);
    480             return setNode(p, fn_decl_index,
    481                 (AstNodeItem) {
    482                     .tag = AST_NODE_FN_DECL,
    483                     .main_token = p->nodes.main_tokens[fn_proto],
    484                     .data = { .lhs = fn_proto, .rhs = body_block },
    485                 });
    486         default:
    487             fail(p, "expected semicolon or lbrace");
    488         }
    489     }
    490 
    491     eatToken(p, TOKEN_KEYWORD_THREADLOCAL);
    492     AstNodeIndex var_decl = parseGlobalVarDecl(p);
    493     if (var_decl != 0) {
    494         return var_decl;
    495     }
    496 
    497     // assuming the program is correct...
    498     fail(p, "the next token should be usingnamespace, which is not supported");
    499     return 0; // make tcc happy
    500 }
    501 
    502 static AstNodeIndex parseFnProto(Parser* p) {
    503     AstTokenIndex fn_token = eatToken(p, TOKEN_KEYWORD_FN);
    504     if (fn_token == null_token)
    505         return null_node;
    506 
    507     AstNodeIndex fn_proto_index = reserveNode(p, AST_NODE_FN_PROTO);
    508 
    509     eatToken(p, TOKEN_IDENTIFIER);
    510 
    511     SmallSpan params = parseParamDeclList(p);
    512     const AstNodeIndex align_expr = parseByteAlign(p);
    513     const AstNodeIndex addrspace_expr = parseAddrSpace(p);
    514     const AstNodeIndex section_expr = parseLinkSection(p);
    515     const AstNodeIndex callconv_expr = parseCallconv(p);
    516     eatToken(p, TOKEN_BANG);
    517 
    518     const AstNodeIndex return_type_expr = parseTypeExpr(p);
    519     if (return_type_expr == 0) {
    520         fail(p, "expected_return_type");
    521     }
    522 
    523     if (align_expr == 0 && section_expr == 0 && callconv_expr == 0
    524         && addrspace_expr == 0) {
    525         switch (params.tag) {
    526         case SMALL_SPAN_ZERO_OR_ONE:
    527             return setNode(p, fn_proto_index,
    528                 (AstNodeItem) {
    529                     .tag = AST_NODE_FN_PROTO_SIMPLE,
    530                     .main_token = fn_token,
    531                     .data = {
    532                         .lhs = params.payload.zero_or_one,
    533                         .rhs = return_type_expr,
    534                     },
    535                 });
    536         case SMALL_SPAN_MULTI:
    537             return setNode(p, fn_proto_index,
    538                 (AstNodeItem) {
    539                     .tag = AST_NODE_FN_PROTO_MULTI,
    540                     .main_token = fn_token,
    541                     .data = {
    542                         .lhs = addExtra(p,
    543                             (AstNodeIndex[]) {
    544                                 params.payload.multi.start,
    545                                 params.payload.multi.end },
    546                             2),
    547                         .rhs = return_type_expr,
    548                     },
    549                 });
    550         }
    551     }
    552 
    553     // Complex fn proto with align/section/callconv/addrspace
    554     switch (params.tag) {
    555     case SMALL_SPAN_ZERO_OR_ONE:
    556         return setNode(p, fn_proto_index,
    557             (AstNodeItem) {
    558                 .tag = AST_NODE_FN_PROTO_ONE,
    559                 .main_token = fn_token,
    560                 .data = {
    561                     .lhs = addExtra(p,
    562                         (AstNodeIndex[]) {
    563                             OPT(params.payload.zero_or_one),
    564                             OPT(align_expr), OPT(addrspace_expr),
    565                             OPT(section_expr), OPT(callconv_expr) },
    566                         5),
    567                     .rhs = return_type_expr,
    568                 },
    569             });
    570     case SMALL_SPAN_MULTI:
    571         return setNode(p, fn_proto_index,
    572             (AstNodeItem) {
    573                 .tag = AST_NODE_FN_PROTO,
    574                 .main_token = fn_token,
    575                 .data = {
    576                     .lhs = addExtra(p,
    577                         (AstNodeIndex[]) {
    578                             params.payload.multi.start,
    579                             params.payload.multi.end,
    580                             OPT(align_expr), OPT(addrspace_expr),
    581                             OPT(section_expr), OPT(callconv_expr) },
    582                         6),
    583                     .rhs = return_type_expr,
    584                 },
    585             });
    586     }
    587     return 0; // tcc
    588 }
    589 
    590 static AstNodeIndex parseVarDeclProto(Parser* p) {
    591     AstTokenIndex mut_token;
    592     if ((mut_token = eatToken(p, TOKEN_KEYWORD_CONST)) == null_token)
    593         if ((mut_token = eatToken(p, TOKEN_KEYWORD_VAR)) == null_token)
    594             return null_node;
    595 
    596     expectToken(p, TOKEN_IDENTIFIER);
    597     const AstNodeIndex type_node
    598         = eatToken(p, TOKEN_COLON) == null_token ? 0 : parseTypeExpr(p);
    599     const AstNodeIndex align_node = parseByteAlign(p);
    600     const AstNodeIndex addrspace_node = parseAddrSpace(p);
    601     const AstNodeIndex section_node = parseLinkSection(p);
    602 
    603     if (section_node == 0 && addrspace_node == 0) {
    604         if (align_node == 0) {
    605             return addNode(&p->nodes,
    606                 (AstNodeItem) {
    607                     .tag = AST_NODE_SIMPLE_VAR_DECL,
    608                     .main_token = mut_token,
    609                     .data = { .lhs = type_node, .rhs = 0 },
    610                 });
    611         }
    612         if (type_node == 0) {
    613             return addNode(&p->nodes,
    614                 (AstNodeItem) {
    615                     .tag = AST_NODE_ALIGNED_VAR_DECL,
    616                     .main_token = mut_token,
    617                     .data = { .lhs = align_node, .rhs = 0 },
    618                 });
    619         }
    620         return addNode(&p->nodes,
    621             (AstNodeItem) {
    622                 .tag = AST_NODE_LOCAL_VAR_DECL,
    623                 .main_token = mut_token,
    624                 .data = {
    625                     .lhs = addExtra(p,
    626                         (AstNodeIndex[]) { type_node, align_node }, 2),
    627                     .rhs = 0,
    628                 },
    629             });
    630     }
    631     return addNode(&p->nodes,
    632         (AstNodeItem) {
    633             .tag = AST_NODE_GLOBAL_VAR_DECL,
    634             .main_token = mut_token,
    635             .data = {
    636                 .lhs = addExtra(p,
    637                     (AstNodeIndex[]) { OPT(type_node), OPT(align_node),
    638                         OPT(addrspace_node), OPT(section_node) },
    639                     4),
    640                 .rhs = 0,
    641             },
    642         });
    643 }
    644 
    645 static AstNodeIndex parseGlobalVarDecl(Parser* p) {
    646     const AstNodeIndex var_decl = parseVarDeclProto(p);
    647     if (var_decl == 0) {
    648         return null_node;
    649     }
    650 
    651     if (eatToken(p, TOKEN_EQUAL) != null_token) {
    652         const AstNodeIndex init_expr = expectExpr(p);
    653         p->nodes.datas[var_decl].rhs = init_expr;
    654     }
    655     expectToken(p, TOKEN_SEMICOLON);
    656     return var_decl;
    657 }
    658 
    659 static AstNodeIndex expectContainerField(Parser* p) {
    660     eatToken(p, TOKEN_KEYWORD_COMPTIME);
    661     const AstTokenIndex main_token = p->tok_i;
    662     if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER
    663         && p->token_tags[p->tok_i + 1] == TOKEN_COLON)
    664         p->tok_i += 2;
    665 
    666     const AstNodeIndex type_expr = parseTypeExpr(p);
    667     if (type_expr == 0) {
    668         fail(p, "expected type expression");
    669     }
    670     const AstNodeIndex align_expr = parseByteAlign(p);
    671     const AstNodeIndex value_expr
    672         = eatToken(p, TOKEN_EQUAL) != null_token ? expectExpr(p) : 0;
    673 
    674     if (align_expr == 0) {
    675         return addNode(
    676             &p->nodes,
    677             (AstNodeItem) {
    678                 .tag = AST_NODE_CONTAINER_FIELD_INIT,
    679                 .main_token = main_token,
    680                 .data = {
    681                     .lhs = type_expr,
    682                     .rhs = value_expr,
    683                 },
    684             });
    685     } else if (value_expr == 0) {
    686         return addNode(
    687             &p->nodes,
    688             (AstNodeItem) {
    689                 .tag = AST_NODE_CONTAINER_FIELD_ALIGN,
    690                 .main_token = main_token,
    691                 .data = {
    692                     .lhs = type_expr,
    693                     .rhs = align_expr,
    694                 },
    695             });
    696     } else {
    697         return addNode(
    698             &p->nodes,
    699             (AstNodeItem) {
    700                 .tag = AST_NODE_CONTAINER_FIELD,
    701                 .main_token = main_token,
    702                 .data = {
    703                     .lhs = type_expr,
    704                     .rhs = addExtra(p, (AstNodeIndex[]) { align_expr, value_expr }, 2),
    705                 },
    706             });
    707     }
    708 }
    709 
    710 static AstNodeIndex expectStatement(Parser* p, bool allow_defer_var) {
    711     const AstTokenIndex comptime_token = eatToken(p, TOKEN_KEYWORD_COMPTIME);
    712     if (comptime_token != null_token) {
    713         // comptime followed by block => comptime block statement
    714         const AstNodeIndex block = parseBlockExpr(p);
    715         if (block != 0) {
    716             return addNode(&p->nodes,
    717                 (AstNodeItem) {
    718                     .tag = AST_NODE_COMPTIME,
    719                     .main_token = comptime_token,
    720                     .data = { .lhs = block, .rhs = 0 },
    721                 });
    722         }
    723         // comptime var decl or expression
    724         if (allow_defer_var)
    725             return expectVarDeclExprStatement(p, comptime_token);
    726         {
    727             const AstNodeIndex assign = parseAssignExpr(p);
    728             if (assign == 0) {
    729                 fail(p, "expected expression");
    730             }
    731             expectSemicolon(p);
    732             return addNode(&p->nodes,
    733                 (AstNodeItem) {
    734                     .tag = AST_NODE_COMPTIME,
    735                     .main_token = comptime_token,
    736                     .data = { .lhs = assign, .rhs = 0 },
    737                 });
    738         }
    739     }
    740 
    741     const AstNodeIndex tok = p->token_tags[p->tok_i];
    742     switch (tok) {
    743     case TOKEN_KEYWORD_DEFER:
    744         if (allow_defer_var)
    745             return addNode(&p->nodes,
    746                 (AstNodeItem) {
    747                     .tag = AST_NODE_DEFER,
    748                     .main_token = nextToken(p),
    749                     .data = {
    750                         .lhs = expectBlockExprStatement(p),
    751                         .rhs = 0,
    752                     },
    753                 });
    754         break;
    755     case TOKEN_KEYWORD_ERRDEFER:
    756         if (allow_defer_var) {
    757             const AstTokenIndex errdefer_token = nextToken(p);
    758             AstTokenIndex payload = null_token;
    759             if (p->token_tags[p->tok_i] == TOKEN_PIPE) {
    760                 p->tok_i++;
    761                 payload = expectToken(p, TOKEN_IDENTIFIER);
    762                 expectToken(p, TOKEN_PIPE);
    763             }
    764             return addNode(&p->nodes,
    765                 (AstNodeItem) {
    766                     .tag = AST_NODE_ERRDEFER,
    767                     .main_token = errdefer_token,
    768                     .data = {
    769                         .lhs = payload,
    770                         .rhs = expectBlockExprStatement(p),
    771                     },
    772                 });
    773         }
    774         break;
    775     case TOKEN_KEYWORD_NOSUSPEND:
    776         return addNode(&p->nodes,
    777             (AstNodeItem) {
    778                 .tag = AST_NODE_NOSUSPEND,
    779                 .main_token = nextToken(p),
    780                 .data = {
    781                     .lhs = expectBlockExprStatement(p),
    782                     .rhs = 0,
    783                 },
    784             });
    785     case TOKEN_KEYWORD_SUSPEND:
    786         return addNode(&p->nodes,
    787             (AstNodeItem) {
    788                 .tag = AST_NODE_SUSPEND,
    789                 .main_token = nextToken(p),
    790                 .data = {
    791                     .lhs = expectBlockExprStatement(p),
    792                     .rhs = 0,
    793                 },
    794             });
    795     case TOKEN_KEYWORD_IF:
    796         return expectIfStatement(p);
    797     case TOKEN_KEYWORD_ENUM:
    798     case TOKEN_KEYWORD_STRUCT:
    799     case TOKEN_KEYWORD_UNION:;
    800         fail(p, "unsupported statement keyword");
    801     default:;
    802     }
    803 
    804     const AstNodeIndex labeled_statement = parseLabeledStatement(p);
    805     if (labeled_statement != 0)
    806         return labeled_statement;
    807 
    808     if (allow_defer_var) {
    809         return expectVarDeclExprStatement(p, null_token);
    810     } else {
    811         const AstNodeIndex assign_expr = parseAssignExpr(p);
    812         expectSemicolon(p);
    813         return assign_expr;
    814     }
    815 }
    816 
    817 static AstNodeIndex expectVarDeclExprStatement(
    818     Parser* p, AstTokenIndex comptime_token) {
    819     const uint32_t scratch_top = p->scratch.len;
    820 
    821     while (true) {
    822         const AstNodeIndex var_decl_proto = parseVarDeclProto(p);
    823         if (var_decl_proto != 0) {
    824             SLICE_APPEND(AstNodeIndex, &p->scratch, var_decl_proto);
    825         } else {
    826             const AstNodeIndex expr = parseExpr(p);
    827             SLICE_APPEND(AstNodeIndex, &p->scratch, expr);
    828         }
    829         if (eatToken(p, TOKEN_COMMA) == null_token)
    830             break;
    831     }
    832 
    833     const uint32_t lhs_count = p->scratch.len - scratch_top;
    834     assert(lhs_count > 0);
    835 
    836     // Try to eat '=' for assignment/initialization
    837     // (matches upstream: `const equal_token = p.eatToken(.equal) orelse eql:`)
    838     AstTokenIndex equal_token = eatToken(p, TOKEN_EQUAL);
    839     if (equal_token == null_token) {
    840         if (lhs_count > 1) {
    841             // Destructure requires '='
    842             fail(p, "expected '='");
    843         }
    844         const AstNodeIndex lhs = p->scratch.arr[scratch_top];
    845         p->scratch.len = scratch_top;
    846         const AstNodeTag lhs_tag = p->nodes.tags[lhs];
    847         if (lhs_tag == AST_NODE_SIMPLE_VAR_DECL
    848             || lhs_tag == AST_NODE_ALIGNED_VAR_DECL
    849             || lhs_tag == AST_NODE_LOCAL_VAR_DECL
    850             || lhs_tag == AST_NODE_GLOBAL_VAR_DECL) {
    851             // var decl without init requires '='
    852             fail(p, "expected '='");
    853         }
    854         // Expression statement: finish with assignment operators or semicolon
    855         const AstNodeIndex expr = finishAssignExpr(p, lhs);
    856         // Semicolon is optional for block-terminated expressions
    857         eatToken(p, TOKEN_SEMICOLON);
    858         if (comptime_token != null_token) {
    859             return addNode(&p->nodes,
    860                 (AstNodeItem) {
    861                     .tag = AST_NODE_COMPTIME,
    862                     .main_token = comptime_token,
    863                     .data = { .lhs = expr, .rhs = 0 },
    864                 });
    865         }
    866         return expr;
    867     }
    868 
    869     // Have '=', parse RHS and semicolon
    870     const AstNodeIndex rhs = expectExpr(p);
    871     expectSemicolon(p);
    872 
    873     if (lhs_count == 1) {
    874         const AstNodeIndex lhs = p->scratch.arr[scratch_top];
    875         p->scratch.len = scratch_top;
    876         const AstNodeTag lhs_tag = p->nodes.tags[lhs];
    877         if (lhs_tag == AST_NODE_SIMPLE_VAR_DECL
    878             || lhs_tag == AST_NODE_ALIGNED_VAR_DECL
    879             || lhs_tag == AST_NODE_LOCAL_VAR_DECL
    880             || lhs_tag == AST_NODE_GLOBAL_VAR_DECL) {
    881             // var decl initialization: const x = val;
    882             p->nodes.datas[lhs].rhs = rhs;
    883             return lhs;
    884         }
    885         // Simple assignment: x = val;
    886         const AstNodeIndex assign = addNode(&p->nodes,
    887             (AstNodeItem) {
    888                 .tag = AST_NODE_ASSIGN,
    889                 .main_token = equal_token,
    890                 .data = { .lhs = lhs, .rhs = rhs },
    891             });
    892         if (comptime_token != null_token) {
    893             return addNode(&p->nodes,
    894                 (AstNodeItem) {
    895                     .tag = AST_NODE_COMPTIME,
    896                     .main_token = comptime_token,
    897                     .data = { .lhs = assign, .rhs = 0 },
    898                 });
    899         }
    900         return assign;
    901     }
    902 
    903     // Destructure: a, b, c = rhs
    904     // rhs and semicolon already parsed above
    905 
    906     // Store count + lhs nodes in extra_data
    907     const AstNodeIndex extra_start = p->extra_data.len;
    908     SLICE_ENSURE_CAPACITY(AstNodeIndex, &p->extra_data, lhs_count + 1);
    909     p->extra_data.arr[p->extra_data.len++] = lhs_count;
    910     memcpy(p->extra_data.arr + p->extra_data.len, &p->scratch.arr[scratch_top],
    911         lhs_count * sizeof(AstNodeIndex));
    912     p->extra_data.len += lhs_count;
    913     p->scratch.len = scratch_top;
    914 
    915     return addNode(&p->nodes,
    916         (AstNodeItem) {
    917             .tag = AST_NODE_ASSIGN_DESTRUCTURE,
    918             .main_token = equal_token,
    919             .data = { .lhs = extra_start, .rhs = rhs },
    920         });
    921 }
    922 
    923 static AstNodeIndex expectIfStatement(Parser* p) {
    924     const AstTokenIndex if_token = assertToken(p, TOKEN_KEYWORD_IF);
    925     expectToken(p, TOKEN_L_PAREN);
    926     const AstNodeIndex condition = expectExpr(p);
    927     expectToken(p, TOKEN_R_PAREN);
    928     parsePtrPayload(p);
    929     bool else_required = false;
    930     AstNodeIndex then_body;
    931     const AstNodeIndex block2 = parseBlockExpr(p);
    932     if (block2 != 0) {
    933         then_body = block2;
    934     } else {
    935         then_body = parseAssignExpr(p);
    936         if (then_body == 0)
    937             fail(p, "expected block or assignment");
    938         if (eatToken(p, TOKEN_SEMICOLON) != null_token)
    939             return addNode(&p->nodes,
    940                 (AstNodeItem) {
    941                     .tag = AST_NODE_IF_SIMPLE,
    942                     .main_token = if_token,
    943                     .data = { .lhs = condition, .rhs = then_body },
    944                 });
    945         else_required = true;
    946     }
    947     if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) {
    948         if (else_required)
    949             fail(p, "expected_semi_or_else");
    950         return addNode(&p->nodes,
    951             (AstNodeItem) {
    952                 .tag = AST_NODE_IF_SIMPLE,
    953                 .main_token = if_token,
    954                 .data = { .lhs = condition, .rhs = then_body },
    955             });
    956     }
    957     parsePayload(p);
    958     const AstNodeIndex else_body = expectStatement(p, false);
    959     return addNode(&p->nodes,
    960         (AstNodeItem) {
    961             .tag = AST_NODE_IF,
    962             .main_token = if_token,
    963             .data = {
    964                 .lhs = condition,
    965                 .rhs = addExtra(p,
    966                     (AstNodeIndex[]) { then_body, else_body }, 2),
    967             },
    968         });
    969 }
    970 
    971 static AstNodeIndex parseLabeledStatement(Parser* p) {
    972     const AstNodeIndex label_token = parseBlockLabel(p);
    973     const AstNodeIndex block = parseBlock(p);
    974     if (block != 0)
    975         return block;
    976 
    977     const AstNodeIndex loop_stmt = parseLoopStatement(p);
    978     if (loop_stmt != 0)
    979         return loop_stmt;
    980 
    981     const AstNodeIndex switch_expr = parseSwitchExpr(p);
    982     if (switch_expr != 0)
    983         return switch_expr;
    984 
    985     if (label_token != 0) {
    986         fail(p, "expected_labelable");
    987     }
    988 
    989     return null_node;
    990 }
    991 
    992 static AstNodeIndex parseLoopStatement(Parser* p) {
    993     const AstTokenIndex inline_token = eatToken(p, TOKEN_KEYWORD_INLINE);
    994 
    995     const AstNodeIndex for_statement = parseForStatement(p);
    996     if (for_statement != 0)
    997         return for_statement;
    998 
    999     const AstNodeIndex while_statement = parseWhileStatement(p);
   1000     if (while_statement != 0)
   1001         return while_statement;
   1002 
   1003     if (inline_token == null_token)
   1004         return null_node;
   1005 
   1006     fail(p, "seen 'inline', there should have been a 'for' or 'while'");
   1007     return 0; // tcc
   1008 }
   1009 
   1010 static AstNodeIndex parseForStatement(Parser* p) {
   1011     const AstTokenIndex for_token = eatToken(p, TOKEN_KEYWORD_FOR);
   1012     if (for_token == null_token)
   1013         return null_node;
   1014 
   1015     const uint32_t scratch_top = p->scratch.len;
   1016     const uint32_t inputs = forPrefix(p);
   1017 
   1018     // Statement body: block or assign expr
   1019     bool else_required = false;
   1020     bool seen_semicolon = false;
   1021     AstNodeIndex then_body;
   1022     const AstNodeIndex block = parseBlock(p);
   1023     if (block != 0) {
   1024         then_body = block;
   1025     } else {
   1026         then_body = parseAssignExpr(p);
   1027         if (then_body == 0) {
   1028             fail(p, "expected_block_or_assignment");
   1029         }
   1030         if (eatToken(p, TOKEN_SEMICOLON) != null_token) {
   1031             seen_semicolon = true;
   1032         } else {
   1033             else_required = true;
   1034         }
   1035     }
   1036 
   1037     bool has_else = false;
   1038     if (!seen_semicolon && eatToken(p, TOKEN_KEYWORD_ELSE) != null_token) {
   1039         parsePayload(p);
   1040         SLICE_APPEND(AstNodeIndex, &p->scratch, then_body);
   1041         const AstNodeIndex else_body = expectStatement(p, false);
   1042         SLICE_APPEND(AstNodeIndex, &p->scratch, else_body);
   1043         has_else = true;
   1044     } else if (inputs == 1) {
   1045         if (else_required)
   1046             fail(p, "expected_semi_or_else");
   1047         p->scratch.len = scratch_top;
   1048         return addNode(&p->nodes,
   1049             (AstNodeItem) {
   1050                 .tag = AST_NODE_FOR_SIMPLE,
   1051                 .main_token = for_token,
   1052                 .data = {
   1053                     .lhs = p->scratch.arr[scratch_top],
   1054                     .rhs = then_body,
   1055                 },
   1056             });
   1057     } else {
   1058         if (else_required)
   1059             fail(p, "expected_semi_or_else");
   1060         SLICE_APPEND(AstNodeIndex, &p->scratch, then_body);
   1061     }
   1062 
   1063     const uint32_t total = p->scratch.len - scratch_top;
   1064     const AstSubRange span
   1065         = listToSpan(p, &p->scratch.arr[scratch_top], total);
   1066     p->scratch.len = scratch_top;
   1067     return addNode(&p->nodes,
   1068         (AstNodeItem) {
   1069             .tag = AST_NODE_FOR,
   1070             .main_token = for_token,
   1071             .data = {
   1072                 .lhs = span.start,
   1073                 .rhs = ((uint32_t)inputs & 0x7FFFFFFF)
   1074                     | (has_else ? (1u << 31) : 0),
   1075             },
   1076         });
   1077 }
   1078 
   1079 static AstNodeIndex parseForExpr(Parser* p) {
   1080     const AstTokenIndex for_token = eatToken(p, TOKEN_KEYWORD_FOR);
   1081     if (for_token == null_token)
   1082         return null_node;
   1083 
   1084     const uint32_t scratch_top = p->scratch.len;
   1085     const uint32_t inputs = forPrefix(p);
   1086 
   1087     const AstNodeIndex then_expr = expectExpr(p);
   1088 
   1089     if (eatToken(p, TOKEN_KEYWORD_ELSE) != null_token) {
   1090         parsePayload(p);
   1091         SLICE_APPEND(AstNodeIndex, &p->scratch, then_expr);
   1092         const AstNodeIndex else_expr = expectExpr(p);
   1093         SLICE_APPEND(AstNodeIndex, &p->scratch, else_expr);
   1094         const uint32_t total = p->scratch.len - scratch_top;
   1095         const AstSubRange span
   1096             = listToSpan(p, &p->scratch.arr[scratch_top], total);
   1097         p->scratch.len = scratch_top;
   1098         return addNode(&p->nodes,
   1099             (AstNodeItem) {
   1100                 .tag = AST_NODE_FOR,
   1101                 .main_token = for_token,
   1102                 .data = {
   1103                     .lhs = span.start,
   1104                     .rhs = ((uint32_t)inputs & 0x7FFFFFFF) | (1u << 31),
   1105                 },
   1106             });
   1107     }
   1108 
   1109     if (inputs == 1) {
   1110         const AstNodeIndex input = p->scratch.arr[scratch_top];
   1111         p->scratch.len = scratch_top;
   1112         return addNode(&p->nodes,
   1113             (AstNodeItem) {
   1114                 .tag = AST_NODE_FOR_SIMPLE,
   1115                 .main_token = for_token,
   1116                 .data = { .lhs = input, .rhs = then_expr },
   1117             });
   1118     }
   1119 
   1120     SLICE_APPEND(AstNodeIndex, &p->scratch, then_expr);
   1121     const uint32_t total = p->scratch.len - scratch_top;
   1122     const AstSubRange span
   1123         = listToSpan(p, &p->scratch.arr[scratch_top], total);
   1124     p->scratch.len = scratch_top;
   1125     return addNode(&p->nodes,
   1126         (AstNodeItem) {
   1127             .tag = AST_NODE_FOR,
   1128             .main_token = for_token,
   1129             .data = {
   1130                 .lhs = span.start,
   1131                 .rhs = (uint32_t)inputs & 0x7FFFFFFF,
   1132             },
   1133         });
   1134 }
   1135 
   1136 static AstNodeIndex parseWhileStatement(Parser* p) {
   1137     const AstTokenIndex while_token = eatToken(p, TOKEN_KEYWORD_WHILE);
   1138     if (while_token == null_token)
   1139         return null_node;
   1140 
   1141     expectToken(p, TOKEN_L_PAREN);
   1142     const AstNodeIndex condition = expectExpr(p);
   1143     expectToken(p, TOKEN_R_PAREN);
   1144     parsePtrPayload(p);
   1145 
   1146     const AstNodeIndex cont_expr = parseWhileContinueExpr(p);
   1147 
   1148     // Statement body: block, or assign expr
   1149     bool else_required = false;
   1150     AstNodeIndex body;
   1151     const AstNodeIndex block = parseBlock(p);
   1152     if (block != 0) {
   1153         body = block;
   1154     } else {
   1155         body = parseAssignExpr(p);
   1156         if (body == 0) {
   1157             fail(p, "expected_block_or_assignment");
   1158         }
   1159         if (eatToken(p, TOKEN_SEMICOLON) != null_token) {
   1160             if (cont_expr != 0) {
   1161                 return addNode(&p->nodes,
   1162                     (AstNodeItem) {
   1163                         .tag = AST_NODE_WHILE_CONT,
   1164                         .main_token = while_token,
   1165                         .data = {
   1166                             .lhs = condition,
   1167                             .rhs = addExtra(p,
   1168                                 (AstNodeIndex[]) { cont_expr, body }, 2),
   1169                         },
   1170                     });
   1171             }
   1172             return addNode(&p->nodes,
   1173                 (AstNodeItem) {
   1174                     .tag = AST_NODE_WHILE_SIMPLE,
   1175                     .main_token = while_token,
   1176                     .data = { .lhs = condition, .rhs = body },
   1177                 });
   1178         }
   1179         else_required = true;
   1180     }
   1181 
   1182     if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) {
   1183         if (else_required)
   1184             fail(p, "expected_semi_or_else");
   1185         if (cont_expr != 0) {
   1186             return addNode(&p->nodes,
   1187                 (AstNodeItem) {
   1188                     .tag = AST_NODE_WHILE_CONT,
   1189                     .main_token = while_token,
   1190                     .data = {
   1191                         .lhs = condition,
   1192                         .rhs = addExtra(p,
   1193                             (AstNodeIndex[]) { cont_expr, body }, 2),
   1194                     },
   1195                 });
   1196         }
   1197         return addNode(&p->nodes,
   1198             (AstNodeItem) {
   1199                 .tag = AST_NODE_WHILE_SIMPLE,
   1200                 .main_token = while_token,
   1201                 .data = { .lhs = condition, .rhs = body },
   1202             });
   1203     }
   1204 
   1205     parsePayload(p);
   1206     const AstNodeIndex else_body = expectStatement(p, false);
   1207     return addNode(&p->nodes,
   1208         (AstNodeItem) {
   1209             .tag = AST_NODE_WHILE,
   1210             .main_token = while_token,
   1211             .data = {
   1212                 .lhs = condition,
   1213                 .rhs = addExtra(p,
   1214                     (AstNodeIndex[]) { OPT(cont_expr), body, else_body },
   1215                     3),
   1216             },
   1217         });
   1218 }
   1219 
   1220 static AstNodeIndex expectBlockExprStatement(Parser* p) {
   1221     const AstNodeIndex block_expr = parseBlockExpr(p);
   1222     if (block_expr != 0)
   1223         return block_expr;
   1224     // Assign expr + semicolon
   1225     const AstNodeIndex expr = parseAssignExpr(p);
   1226     if (expr != 0) {
   1227         expectSemicolon(p);
   1228         return expr;
   1229     }
   1230     fail(p, "expectBlockExprStatement: expected block or expr");
   1231     return 0; // tcc
   1232 }
   1233 
   1234 static AstNodeIndex parseBlockExpr(Parser* p) {
   1235     if (p->token_tags[p->tok_i] == TOKEN_L_BRACE)
   1236         return parseBlock(p);
   1237     if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER
   1238         && p->token_tags[p->tok_i + 1] == TOKEN_COLON
   1239         && p->token_tags[p->tok_i + 2] == TOKEN_L_BRACE) {
   1240         p->tok_i += 2;
   1241         return parseBlock(p);
   1242     }
   1243     return null_node;
   1244 }
   1245 
   1246 static AstNodeIndex parseAssignExpr(Parser* p) {
   1247     const AstNodeIndex expr = parseExpr(p);
   1248     if (expr == 0)
   1249         return null_node;
   1250     return finishAssignExpr(p, expr);
   1251 }
   1252 
   1253 static AstNodeIndex parseSingleAssignExpr(Parser* p) {
   1254     const AstNodeIndex expr = parseExpr(p);
   1255     if (expr == 0)
   1256         return null_node;
   1257     const AstNodeTag tag = assignOpNode(p->token_tags[p->tok_i]);
   1258     if (tag == AST_NODE_ROOT)
   1259         return expr;
   1260     const AstTokenIndex op_token = nextToken(p);
   1261     const AstNodeIndex rhs = expectExpr(p);
   1262     return addNode(&p->nodes,
   1263         (AstNodeItem) {
   1264             .tag = tag,
   1265             .main_token = op_token,
   1266             .data = { .lhs = expr, .rhs = rhs },
   1267         });
   1268 }
   1269 
   1270 static AstNodeIndex finishAssignExpr(Parser* p, AstNodeIndex lhs) {
   1271     const AstNodeTag assign_tag = assignOpNode(p->token_tags[p->tok_i]);
   1272     if (assign_tag == AST_NODE_ROOT)
   1273         return lhs;
   1274 
   1275     const AstTokenIndex op_token = nextToken(p);
   1276     const AstNodeIndex rhs = expectExpr(p);
   1277     return addNode(&p->nodes,
   1278         (AstNodeItem) {
   1279             .tag = assign_tag,
   1280             .main_token = op_token,
   1281             .data = { .lhs = lhs, .rhs = rhs },
   1282         });
   1283 }
   1284 
   1285 static AstNodeTag assignOpNode(TokenizerTag tok) {
   1286     switch (tok) {
   1287     case TOKEN_EQUAL:
   1288         return AST_NODE_ASSIGN;
   1289     case TOKEN_PLUS_EQUAL:
   1290         return AST_NODE_ASSIGN_ADD;
   1291     case TOKEN_MINUS_EQUAL:
   1292         return AST_NODE_ASSIGN_SUB;
   1293     case TOKEN_ASTERISK_EQUAL:
   1294         return AST_NODE_ASSIGN_MUL;
   1295     case TOKEN_SLASH_EQUAL:
   1296         return AST_NODE_ASSIGN_DIV;
   1297     case TOKEN_PERCENT_EQUAL:
   1298         return AST_NODE_ASSIGN_MOD;
   1299     case TOKEN_AMPERSAND_EQUAL:
   1300         return AST_NODE_ASSIGN_BIT_AND;
   1301     case TOKEN_PIPE_EQUAL:
   1302         return AST_NODE_ASSIGN_BIT_OR;
   1303     case TOKEN_CARET_EQUAL:
   1304         return AST_NODE_ASSIGN_BIT_XOR;
   1305     case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_EQUAL:
   1306         return AST_NODE_ASSIGN_SHL;
   1307     case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_RIGHT_EQUAL:
   1308         return AST_NODE_ASSIGN_SHR;
   1309     case TOKEN_PLUS_PERCENT_EQUAL:
   1310         return AST_NODE_ASSIGN_ADD_WRAP;
   1311     case TOKEN_MINUS_PERCENT_EQUAL:
   1312         return AST_NODE_ASSIGN_SUB_WRAP;
   1313     case TOKEN_ASTERISK_PERCENT_EQUAL:
   1314         return AST_NODE_ASSIGN_MUL_WRAP;
   1315     case TOKEN_PLUS_PIPE_EQUAL:
   1316         return AST_NODE_ASSIGN_ADD_SAT;
   1317     case TOKEN_MINUS_PIPE_EQUAL:
   1318         return AST_NODE_ASSIGN_SUB_SAT;
   1319     case TOKEN_ASTERISK_PIPE_EQUAL:
   1320         return AST_NODE_ASSIGN_MUL_SAT;
   1321     case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_PIPE_EQUAL:
   1322         return AST_NODE_ASSIGN_SHL_SAT;
   1323     default:
   1324         return AST_NODE_ROOT; // not an assignment op
   1325     }
   1326 }
   1327 
   1328 static AstNodeIndex parseExpr(Parser* p) { return parseExprPrecedence(p, 0); }
   1329 
   1330 static AstNodeIndex expectExpr(Parser* p) {
   1331     const AstNodeIndex node = parseExpr(p);
   1332     if (node == 0) {
   1333         fail(p, "expected expression");
   1334     }
   1335     return node;
   1336 }
   1337 
   1338 static AstNodeIndex parseExprPrecedence(Parser* p, int32_t min_prec) {
   1339     assert(min_prec >= 0);
   1340 
   1341     AstNodeIndex node = parsePrefixExpr(p);
   1342     if (node == 0)
   1343         return null_node;
   1344 
   1345     int8_t banned_prec = -1;
   1346 
   1347     while (true) {
   1348         const TokenizerTag tok_tag = p->token_tags[p->tok_i];
   1349         const OperInfo info = operTable(tok_tag);
   1350         if (info.prec < min_prec)
   1351             break;
   1352 
   1353         if (info.prec == banned_prec) {
   1354             fail(p, "chained comparison operators");
   1355         }
   1356 
   1357         const AstTokenIndex oper_token = nextToken(p);
   1358         if (tok_tag == TOKEN_KEYWORD_CATCH)
   1359             parsePayload(p);
   1360         const AstNodeIndex rhs = parseExprPrecedence(p, info.prec + 1);
   1361         if (rhs == 0) {
   1362             fail(p, "expected expression");
   1363         }
   1364 
   1365         {
   1366             const uint32_t tok_len = tokenTagLexemeLen(tok_tag);
   1367             if (tok_len > 0) {
   1368                 const uint32_t tok_start = p->token_starts[oper_token];
   1369                 const char char_before = p->source[tok_start - 1];
   1370                 const char char_after = p->source[tok_start + tok_len];
   1371                 if (tok_tag == TOKEN_AMPERSAND && char_after == '&') {
   1372                     fail(p, "invalid ampersand ampersand");
   1373                 } else if (isspace((unsigned char)char_before)
   1374                     != isspace((unsigned char)char_after)) {
   1375                     fail(p, "mismatched binary op whitespace");
   1376                 }
   1377             }
   1378         }
   1379 
   1380         node = addNode(
   1381                 &p->nodes,
   1382                 (AstNodeItem) {
   1383                     .tag = info.tag,
   1384                     .main_token = oper_token,
   1385                     .data = {
   1386                         .lhs = node,
   1387                         .rhs = rhs,
   1388                     },
   1389                 });
   1390 
   1391         if (info.assoc == ASSOC_NONE)
   1392             banned_prec = info.prec;
   1393     }
   1394 
   1395     return node;
   1396 }
   1397 
   1398 static uint32_t tokenTagLexemeLen(TokenizerTag tag) {
   1399     switch (tag) {
   1400     case TOKEN_PLUS:
   1401     case TOKEN_MINUS:
   1402     case TOKEN_ASTERISK:
   1403     case TOKEN_SLASH:
   1404     case TOKEN_PERCENT:
   1405     case TOKEN_AMPERSAND:
   1406     case TOKEN_CARET:
   1407     case TOKEN_PIPE:
   1408     case TOKEN_ANGLE_BRACKET_LEFT:
   1409     case TOKEN_ANGLE_BRACKET_RIGHT:
   1410         return 1;
   1411     case TOKEN_PLUS_PLUS:
   1412     case TOKEN_MINUS_PERCENT:
   1413     case TOKEN_PLUS_PERCENT:
   1414     case TOKEN_MINUS_PIPE:
   1415     case TOKEN_PLUS_PIPE:
   1416     case TOKEN_ASTERISK_ASTERISK:
   1417     case TOKEN_ASTERISK_PERCENT:
   1418     case TOKEN_ASTERISK_PIPE:
   1419     case TOKEN_PIPE_PIPE:
   1420     case TOKEN_EQUAL_EQUAL:
   1421     case TOKEN_BANG_EQUAL:
   1422     case TOKEN_ANGLE_BRACKET_LEFT_EQUAL:
   1423     case TOKEN_ANGLE_BRACKET_RIGHT_EQUAL:
   1424     case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT:
   1425     case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_RIGHT:
   1426         return 2;
   1427     case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_PIPE:
   1428         return 3;
   1429     case TOKEN_KEYWORD_OR:
   1430         return 2;
   1431     case TOKEN_KEYWORD_AND:
   1432         return 3;
   1433     case TOKEN_KEYWORD_ORELSE:
   1434         return 6;
   1435     case TOKEN_KEYWORD_CATCH:
   1436         return 5;
   1437     default:
   1438         return 0;
   1439     }
   1440 }
   1441 
   1442 static OperInfo operTable(TokenizerTag tok_tag) {
   1443     switch (tok_tag) {
   1444     case TOKEN_KEYWORD_OR:
   1445         return (OperInfo) { .prec = 10, .tag = AST_NODE_BOOL_OR };
   1446     case TOKEN_KEYWORD_AND:
   1447         return (OperInfo) { .prec = 20, .tag = AST_NODE_BOOL_AND };
   1448 
   1449     case TOKEN_EQUAL_EQUAL:
   1450         return (OperInfo) {
   1451             .prec = 30, .tag = AST_NODE_EQUAL_EQUAL, .assoc = ASSOC_NONE
   1452         };
   1453     case TOKEN_BANG_EQUAL:
   1454         return (OperInfo) {
   1455             .prec = 30, .tag = AST_NODE_BANG_EQUAL, .assoc = ASSOC_NONE
   1456         };
   1457     case TOKEN_ANGLE_BRACKET_LEFT:
   1458         return (OperInfo) {
   1459             .prec = 30, .tag = AST_NODE_LESS_THAN, .assoc = ASSOC_NONE
   1460         };
   1461     case TOKEN_ANGLE_BRACKET_RIGHT:
   1462         return (OperInfo) {
   1463             .prec = 30, .tag = AST_NODE_GREATER_THAN, .assoc = ASSOC_NONE
   1464         };
   1465     case TOKEN_ANGLE_BRACKET_LEFT_EQUAL:
   1466         return (OperInfo) {
   1467             .prec = 30, .tag = AST_NODE_LESS_OR_EQUAL, .assoc = ASSOC_NONE
   1468         };
   1469     case TOKEN_ANGLE_BRACKET_RIGHT_EQUAL:
   1470         return (OperInfo) {
   1471             .prec = 30, .tag = AST_NODE_GREATER_OR_EQUAL, .assoc = ASSOC_NONE
   1472         };
   1473 
   1474     case TOKEN_AMPERSAND:
   1475         return (OperInfo) { .prec = 40, .tag = AST_NODE_BIT_AND };
   1476     case TOKEN_CARET:
   1477         return (OperInfo) { .prec = 40, .tag = AST_NODE_BIT_XOR };
   1478     case TOKEN_PIPE:
   1479         return (OperInfo) { .prec = 40, .tag = AST_NODE_BIT_OR };
   1480     case TOKEN_KEYWORD_ORELSE:
   1481         return (OperInfo) { .prec = 40, .tag = AST_NODE_ORELSE };
   1482     case TOKEN_KEYWORD_CATCH:
   1483         return (OperInfo) { .prec = 40, .tag = AST_NODE_CATCH };
   1484 
   1485     case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT:
   1486         return (OperInfo) { .prec = 50, .tag = AST_NODE_SHL };
   1487     case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_PIPE:
   1488         return (OperInfo) { .prec = 50, .tag = AST_NODE_SHL_SAT };
   1489     case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_RIGHT:
   1490         return (OperInfo) { .prec = 50, .tag = AST_NODE_SHR };
   1491 
   1492     case TOKEN_PLUS:
   1493         return (OperInfo) { .prec = 60, .tag = AST_NODE_ADD };
   1494     case TOKEN_MINUS:
   1495         return (OperInfo) { .prec = 60, .tag = AST_NODE_SUB };
   1496     case TOKEN_PLUS_PLUS:
   1497         return (OperInfo) { .prec = 60, .tag = AST_NODE_ARRAY_CAT };
   1498     case TOKEN_PLUS_PERCENT:
   1499         return (OperInfo) { .prec = 60, .tag = AST_NODE_ADD_WRAP };
   1500     case TOKEN_MINUS_PERCENT:
   1501         return (OperInfo) { .prec = 60, .tag = AST_NODE_SUB_WRAP };
   1502     case TOKEN_PLUS_PIPE:
   1503         return (OperInfo) { .prec = 60, .tag = AST_NODE_ADD_SAT };
   1504     case TOKEN_MINUS_PIPE:
   1505         return (OperInfo) { .prec = 60, .tag = AST_NODE_SUB_SAT };
   1506 
   1507     case TOKEN_PIPE_PIPE:
   1508         return (OperInfo) { .prec = 70, .tag = AST_NODE_MERGE_ERROR_SETS };
   1509     case TOKEN_ASTERISK:
   1510         return (OperInfo) { .prec = 70, .tag = AST_NODE_MUL };
   1511     case TOKEN_SLASH:
   1512         return (OperInfo) { .prec = 70, .tag = AST_NODE_DIV };
   1513     case TOKEN_PERCENT:
   1514         return (OperInfo) { .prec = 70, .tag = AST_NODE_MOD };
   1515     case TOKEN_ASTERISK_ASTERISK:
   1516         return (OperInfo) { .prec = 70, .tag = AST_NODE_ARRAY_MULT };
   1517     case TOKEN_ASTERISK_PERCENT:
   1518         return (OperInfo) { .prec = 70, .tag = AST_NODE_MUL_WRAP };
   1519     case TOKEN_ASTERISK_PIPE:
   1520         return (OperInfo) { .prec = 70, .tag = AST_NODE_MUL_SAT };
   1521 
   1522     default:
   1523         return (OperInfo) { .prec = -1, .tag = AST_NODE_ROOT };
   1524     }
   1525 }
   1526 
   1527 static AstNodeIndex parsePrefixExpr(Parser* p) {
   1528     AstNodeTag tag;
   1529     switch (p->token_tags[p->tok_i]) {
   1530     case TOKEN_BANG:
   1531         tag = AST_NODE_BOOL_NOT;
   1532         break;
   1533     case TOKEN_MINUS:
   1534         tag = AST_NODE_NEGATION;
   1535         break;
   1536     case TOKEN_TILDE:
   1537         tag = AST_NODE_BIT_NOT;
   1538         break;
   1539     case TOKEN_MINUS_PERCENT:
   1540         tag = AST_NODE_NEGATION_WRAP;
   1541         break;
   1542     case TOKEN_AMPERSAND:
   1543         tag = AST_NODE_ADDRESS_OF;
   1544         break;
   1545     case TOKEN_KEYWORD_TRY:
   1546         tag = AST_NODE_TRY;
   1547         break;
   1548     default:
   1549         return parsePrimaryExpr(p);
   1550     }
   1551     return addNode(
   1552         &p->nodes,
   1553         (AstNodeItem) {
   1554             .tag = tag,
   1555             .main_token = nextToken(p),
   1556             .data = {
   1557                 .lhs = parsePrefixExpr(p),
   1558                 .rhs = 0,
   1559             },
   1560         });
   1561 }
   1562 
   1563 static AstNodeIndex parseTypeExpr(Parser* p) {
   1564     const TokenizerTag tok = p->token_tags[p->tok_i];
   1565     switch (tok) {
   1566     case TOKEN_QUESTION_MARK:
   1567         return addNode(&p->nodes,
   1568             (AstNodeItem) {
   1569                 .tag = AST_NODE_OPTIONAL_TYPE,
   1570                 .main_token = nextToken(p),
   1571                 .data = { .lhs = parseTypeExpr(p), .rhs = 0 },
   1572             });
   1573     case TOKEN_KEYWORD_ANYFRAME:
   1574         fail(p, "unsupported type expression");
   1575     case TOKEN_ASTERISK: {
   1576         const AstTokenIndex asterisk = nextToken(p);
   1577         const PtrModifiers mods = parsePtrModifiers(p);
   1578         const AstNodeIndex elem_type = parseTypeExpr(p);
   1579         return makePtrTypeNode(p, asterisk, 0, mods, elem_type);
   1580     }
   1581     case TOKEN_ASTERISK_ASTERISK: {
   1582         const AstTokenIndex asterisk = nextToken(p);
   1583         const PtrModifiers mods = parsePtrModifiers(p);
   1584         const AstNodeIndex elem_type = parseTypeExpr(p);
   1585         if (elem_type == 0) {
   1586             fail(p, "expected type expression");
   1587         }
   1588         const AstNodeIndex inner
   1589             = makePtrTypeNode(p, asterisk, 0, mods, elem_type);
   1590         return addNode(&p->nodes,
   1591             (AstNodeItem) {
   1592                 .tag = AST_NODE_PTR_TYPE_ALIGNED,
   1593                 .main_token = asterisk,
   1594                 .data = { .lhs = 0, .rhs = inner },
   1595             });
   1596     }
   1597     case TOKEN_L_BRACKET: {
   1598         const AstTokenIndex lbracket = nextToken(p);
   1599         if (p->token_tags[p->tok_i] == TOKEN_ASTERISK) {
   1600             // [*] many-item pointer, [*c] C pointer, [*:s] sentinel
   1601             p->tok_i++; // consume *
   1602             AstNodeIndex sentinel = 0;
   1603             if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER) {
   1604                 // Check for 'c' modifier: [*c]
   1605                 const char c = p->source[p->token_starts[p->tok_i]];
   1606                 if (c == 'c'
   1607                     && p->token_starts[p->tok_i + 1]
   1608                             - p->token_starts[p->tok_i]
   1609                         <= 2) {
   1610                     p->tok_i++; // consume 'c'
   1611                 }
   1612             } else if (eatToken(p, TOKEN_COLON) != null_token) {
   1613                 sentinel = expectExpr(p);
   1614             }
   1615             expectToken(p, TOKEN_R_BRACKET);
   1616             const PtrModifiers mods = parsePtrModifiers(p);
   1617             const AstNodeIndex elem_type = parseTypeExpr(p);
   1618             return makePtrTypeNode(p, lbracket, sentinel, mods, elem_type);
   1619         }
   1620         const AstNodeIndex len_expr = parseExpr(p);
   1621         const AstNodeIndex sentinel
   1622             = eatToken(p, TOKEN_COLON) != null_token ? expectExpr(p) : 0;
   1623         expectToken(p, TOKEN_R_BRACKET);
   1624         if (len_expr == 0) {
   1625             // Slice type: []T or [:s]T
   1626             const PtrModifiers mods = parsePtrModifiers(p);
   1627             const AstNodeIndex elem_type = parseTypeExpr(p);
   1628             if (mods.bit_range_start != 0) {
   1629                 fail(p, "invalid_bit_range");
   1630             }
   1631             return makePtrTypeNode(p, lbracket, sentinel, mods, elem_type);
   1632         }
   1633         // Array type: [N]T or [N:s]T
   1634         const AstNodeIndex elem_type = parseTypeExpr(p);
   1635         if (sentinel == 0) {
   1636             return addNode(&p->nodes,
   1637                 (AstNodeItem) {
   1638                     .tag = AST_NODE_ARRAY_TYPE,
   1639                     .main_token = lbracket,
   1640                     .data = { .lhs = len_expr, .rhs = elem_type },
   1641                 });
   1642         }
   1643         return addNode(&p->nodes,
   1644             (AstNodeItem) {
   1645                 .tag = AST_NODE_ARRAY_TYPE_SENTINEL,
   1646                 .main_token = lbracket,
   1647                 .data = {
   1648                     .lhs = len_expr,
   1649                     .rhs = addExtra(p,
   1650                         (AstNodeIndex[]) { sentinel, elem_type }, 2),
   1651                 },
   1652             });
   1653     }
   1654     case TOKEN_KEYWORD_IF: {
   1655         // if-type-expr: uses parseTypeExpr for branches instead of parseExpr
   1656         const AstTokenIndex if_token = nextToken(p);
   1657         expectToken(p, TOKEN_L_PAREN);
   1658         const AstNodeIndex condition = expectExpr(p);
   1659         expectToken(p, TOKEN_R_PAREN);
   1660         parsePtrPayload(p);
   1661         const AstNodeIndex then_expr = parseTypeExpr(p);
   1662         if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token)
   1663             return addNode(&p->nodes,
   1664                 (AstNodeItem) {
   1665                     .tag = AST_NODE_IF_SIMPLE,
   1666                     .main_token = if_token,
   1667                     .data = { .lhs = condition, .rhs = then_expr },
   1668                 });
   1669         parsePayload(p);
   1670         const AstNodeIndex else_expr = parseTypeExpr(p);
   1671         return addNode(&p->nodes,
   1672             (AstNodeItem) {
   1673                 .tag = AST_NODE_IF,
   1674                 .main_token = if_token,
   1675                 .data = {
   1676                     .lhs = condition,
   1677                     .rhs = addExtra(p,
   1678                         (AstNodeIndex[]) { then_expr, else_expr }, 2),
   1679                 },
   1680             });
   1681     }
   1682     case TOKEN_KEYWORD_FOR: {
   1683         // for-type-expr: uses parseTypeExpr for body instead of parseExpr
   1684         const AstTokenIndex for_token = nextToken(p);
   1685         const uint32_t scratch_top2 = p->scratch.len;
   1686         const uint32_t inputs = forPrefix(p);
   1687         const AstNodeIndex body = parseTypeExpr(p);
   1688         bool has_else = false;
   1689         if (eatToken(p, TOKEN_KEYWORD_ELSE) != null_token) {
   1690             parsePayload(p);
   1691             SLICE_APPEND(AstNodeIndex, &p->scratch, body);
   1692             const AstNodeIndex else_expr = parseTypeExpr(p);
   1693             SLICE_APPEND(AstNodeIndex, &p->scratch, else_expr);
   1694             has_else = true;
   1695         } else if (inputs == 1) {
   1696             p->scratch.len = scratch_top2;
   1697             return addNode(&p->nodes,
   1698                 (AstNodeItem) {
   1699                     .tag = AST_NODE_FOR_SIMPLE,
   1700                     .main_token = for_token,
   1701                     .data = {
   1702                         .lhs = p->scratch.arr[scratch_top2],
   1703                         .rhs = body,
   1704                     },
   1705                 });
   1706         } else {
   1707             SLICE_APPEND(AstNodeIndex, &p->scratch, body);
   1708         }
   1709         const uint32_t total = p->scratch.len - scratch_top2;
   1710         const AstSubRange span
   1711             = listToSpan(p, &p->scratch.arr[scratch_top2], total);
   1712         p->scratch.len = scratch_top2;
   1713         return addNode(&p->nodes,
   1714             (AstNodeItem) {
   1715                 .tag = AST_NODE_FOR,
   1716                 .main_token = for_token,
   1717                 .data = {
   1718                     .lhs = span.start,
   1719                     .rhs = ((uint32_t)inputs & 0x7FFFFFFF)
   1720                         | (has_else ? (1u << 31) : 0),
   1721                 },
   1722             });
   1723     }
   1724     case TOKEN_KEYWORD_WHILE: {
   1725         // while-type-expr: uses parseTypeExpr for body instead of parseExpr
   1726         const AstTokenIndex while_token = nextToken(p);
   1727         expectToken(p, TOKEN_L_PAREN);
   1728         const AstNodeIndex condition = expectExpr(p);
   1729         expectToken(p, TOKEN_R_PAREN);
   1730         parsePtrPayload(p);
   1731         const AstNodeIndex cont_expr = parseWhileContinueExpr(p);
   1732         const AstNodeIndex body = parseTypeExpr(p);
   1733         if (eatToken(p, TOKEN_KEYWORD_ELSE) != null_token) {
   1734             parsePayload(p);
   1735             const AstNodeIndex else_expr = parseTypeExpr(p);
   1736             return addNode(&p->nodes,
   1737                 (AstNodeItem) {
   1738                     .tag = AST_NODE_WHILE,
   1739                     .main_token = while_token,
   1740                     .data = {
   1741                         .lhs = condition,
   1742                         .rhs = addExtra(p,
   1743                             (AstNodeIndex[]) { cont_expr, body, else_expr }, 3),
   1744                     },
   1745                 });
   1746         }
   1747         if (cont_expr != 0)
   1748             return addNode(&p->nodes,
   1749                 (AstNodeItem) {
   1750                     .tag = AST_NODE_WHILE_CONT,
   1751                     .main_token = while_token,
   1752                     .data = {
   1753                         .lhs = condition,
   1754                         .rhs = addExtra(p,
   1755                             (AstNodeIndex[]) { cont_expr, body }, 2),
   1756                     },
   1757                 });
   1758         return addNode(&p->nodes,
   1759             (AstNodeItem) {
   1760                 .tag = AST_NODE_WHILE_SIMPLE,
   1761                 .main_token = while_token,
   1762                 .data = { .lhs = condition, .rhs = body },
   1763             });
   1764     }
   1765     default:
   1766         return parseErrorUnionExpr(p);
   1767     }
   1768     return 0; // tcc
   1769 }
   1770 
   1771 static AstNodeIndex makePtrTypeNode(Parser* p, AstTokenIndex main_token,
   1772     AstNodeIndex sentinel, PtrModifiers mods, AstNodeIndex elem_type) {
   1773     if (mods.bit_range_start != 0) {
   1774         return addNode(&p->nodes,
   1775             (AstNodeItem) {
   1776                 .tag = AST_NODE_PTR_TYPE_BIT_RANGE,
   1777                 .main_token = main_token,
   1778                 .data = {
   1779                     .lhs = addExtra(p,
   1780                         (AstNodeIndex[]) { OPT(sentinel), mods.align_node,
   1781                             OPT(mods.addrspace_node), mods.bit_range_start,
   1782                             mods.bit_range_end },
   1783                         5),
   1784                     .rhs = elem_type,
   1785                 },
   1786             });
   1787     }
   1788     if (mods.addrspace_node != 0 || (sentinel != 0 && mods.align_node != 0)) {
   1789         return addNode(&p->nodes,
   1790             (AstNodeItem) {
   1791                 .tag = AST_NODE_PTR_TYPE,
   1792                 .main_token = main_token,
   1793                 .data = {
   1794                     .lhs = addExtra(p,
   1795                         (AstNodeIndex[]) { OPT(sentinel),
   1796                             OPT(mods.align_node),
   1797                             OPT(mods.addrspace_node) },
   1798                         3),
   1799                     .rhs = elem_type,
   1800                 },
   1801             });
   1802     }
   1803     if (sentinel != 0) {
   1804         return addNode(&p->nodes,
   1805             (AstNodeItem) {
   1806                 .tag = AST_NODE_PTR_TYPE_SENTINEL,
   1807                 .main_token = main_token,
   1808                 .data = { .lhs = sentinel, .rhs = elem_type },
   1809             });
   1810     }
   1811     return addNode(&p->nodes,
   1812         (AstNodeItem) {
   1813             .tag = AST_NODE_PTR_TYPE_ALIGNED,
   1814             .main_token = main_token,
   1815             .data = { .lhs = mods.align_node, .rhs = elem_type },
   1816         });
   1817 }
   1818 
   1819 static AstNodeIndex parsePrimaryExpr(Parser* p) {
   1820     switch (p->token_tags[p->tok_i]) {
   1821     case TOKEN_KEYWORD_ASM:
   1822         return parseAsmExpr(p);
   1823     case TOKEN_KEYWORD_IF:
   1824         return parseIfExpr(p);
   1825     case TOKEN_KEYWORD_BREAK:
   1826         return addNode(
   1827                 &p->nodes,
   1828                 (AstNodeItem) {
   1829                     .tag = AST_NODE_BREAK,
   1830                     .main_token = nextToken(p),
   1831                     .data = {
   1832                         .lhs = parseBreakLabel(p),
   1833                         .rhs = parseExpr(p),
   1834                     },
   1835                 });
   1836     case TOKEN_KEYWORD_CONTINUE:
   1837         return addNode(
   1838                 &p->nodes,
   1839                 (AstNodeItem) {
   1840                     .tag = AST_NODE_CONTINUE,
   1841                     .main_token = nextToken(p),
   1842                     .data = {
   1843                         .lhs = parseBreakLabel(p),
   1844                         .rhs = parseExpr(p),
   1845                     },
   1846                 });
   1847     case TOKEN_KEYWORD_COMPTIME:
   1848         return addNode(&p->nodes,
   1849             (AstNodeItem) {
   1850                 .tag = AST_NODE_COMPTIME,
   1851                 .main_token = nextToken(p),
   1852                 .data = { .lhs = expectExpr(p), .rhs = 0 },
   1853             });
   1854     case TOKEN_KEYWORD_NOSUSPEND:
   1855         return addNode(&p->nodes,
   1856             (AstNodeItem) {
   1857                 .tag = AST_NODE_NOSUSPEND,
   1858                 .main_token = nextToken(p),
   1859                 .data = { .lhs = expectExpr(p), .rhs = 0 },
   1860             });
   1861     case TOKEN_KEYWORD_RESUME:
   1862         return addNode(&p->nodes,
   1863             (AstNodeItem) {
   1864                 .tag = AST_NODE_RESUME,
   1865                 .main_token = nextToken(p),
   1866                 .data = { .lhs = expectExpr(p), .rhs = 0 },
   1867             });
   1868     case TOKEN_KEYWORD_RETURN:
   1869         return addNode(&p->nodes,
   1870             (AstNodeItem) {
   1871                 .tag = AST_NODE_RETURN,
   1872                 .main_token = nextToken(p),
   1873                 .data = { .lhs = parseExpr(p), .rhs = 0 },
   1874             });
   1875     case TOKEN_IDENTIFIER:
   1876         if (p->token_tags[p->tok_i + 1] == TOKEN_COLON) {
   1877             switch (p->token_tags[p->tok_i + 2]) {
   1878             case TOKEN_KEYWORD_INLINE:
   1879                 p->tok_i += 3;
   1880                 if (p->token_tags[p->tok_i] == TOKEN_KEYWORD_FOR)
   1881                     return parseForExpr(p);
   1882                 if (p->token_tags[p->tok_i] == TOKEN_KEYWORD_WHILE)
   1883                     return parseWhileExpr(p);
   1884                 fail(p, "expected for or while after inline");
   1885                 return 0; // tcc
   1886             case TOKEN_KEYWORD_FOR:
   1887                 p->tok_i += 2;
   1888                 return parseForExpr(p);
   1889             case TOKEN_KEYWORD_WHILE:
   1890                 p->tok_i += 2;
   1891                 return parseWhileExpr(p);
   1892             case TOKEN_L_BRACE:
   1893                 p->tok_i += 2;
   1894                 return parseBlock(p);
   1895             default:
   1896                 return parseCurlySuffixExpr(p);
   1897             }
   1898         } else {
   1899             return parseCurlySuffixExpr(p);
   1900         }
   1901     case TOKEN_KEYWORD_WHILE:
   1902         return parseWhileExpr(p);
   1903     case TOKEN_KEYWORD_FOR:
   1904         return parseForExpr(p);
   1905     case TOKEN_KEYWORD_INLINE:
   1906         p->tok_i++;
   1907         if (p->token_tags[p->tok_i] == TOKEN_KEYWORD_FOR)
   1908             return parseForExpr(p);
   1909         if (p->token_tags[p->tok_i] == TOKEN_KEYWORD_WHILE)
   1910             return parseWhileExpr(p);
   1911         fail(p, "parsePrimaryExpr: inline without for/while");
   1912         return 0; // tcc
   1913     case TOKEN_L_BRACE:
   1914         return parseBlock(p);
   1915     default:
   1916         return parseCurlySuffixExpr(p);
   1917     }
   1918 
   1919     return 0; // tcc
   1920 }
   1921 
   1922 static AstNodeIndex parseIfExpr(Parser* p) {
   1923     const AstTokenIndex if_token = eatToken(p, TOKEN_KEYWORD_IF);
   1924     if (if_token == null_token)
   1925         return null_node;
   1926 
   1927     expectToken(p, TOKEN_L_PAREN);
   1928     const AstNodeIndex condition = expectExpr(p);
   1929     expectToken(p, TOKEN_R_PAREN);
   1930     parsePtrPayload(p);
   1931 
   1932     const AstNodeIndex then_expr = expectExpr(p);
   1933 
   1934     if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) {
   1935         return addNode(&p->nodes,
   1936             (AstNodeItem) {
   1937                 .tag = AST_NODE_IF_SIMPLE,
   1938                 .main_token = if_token,
   1939                 .data = { .lhs = condition, .rhs = then_expr },
   1940             });
   1941     }
   1942 
   1943     parsePayload(p);
   1944     const AstNodeIndex else_expr = expectExpr(p);
   1945     return addNode(&p->nodes,
   1946         (AstNodeItem) {
   1947             .tag = AST_NODE_IF,
   1948             .main_token = if_token,
   1949             .data = {
   1950                 .lhs = condition,
   1951                 .rhs = addExtra(p,
   1952                     (AstNodeIndex[]) { then_expr, else_expr }, 2),
   1953             },
   1954         });
   1955 }
   1956 
   1957 static AstNodeIndex parseBlock(Parser* p) {
   1958     const AstNodeIndex lbrace = eatToken(p, TOKEN_L_BRACE);
   1959     if (lbrace == null_token)
   1960         return null_node;
   1961 
   1962     const uint32_t scratch_top = p->scratch.len;
   1963 
   1964     while (1) {
   1965         if (p->token_tags[p->tok_i] == TOKEN_R_BRACE)
   1966             break;
   1967 
   1968         // "const AstNodeIndex statement" once tinycc supports typeof_unqual
   1969         // (C23)
   1970         AstNodeIndex statement = expectStatement(p, true);
   1971         if (statement == 0)
   1972             break;
   1973         SLICE_APPEND(AstNodeIndex, &p->scratch, statement);
   1974     }
   1975     expectToken(p, TOKEN_R_BRACE);
   1976     const uint32_t statements_len = p->scratch.len - scratch_top;
   1977     p->scratch.len = scratch_top;
   1978     const bool semicolon = statements_len != 0
   1979         && (p->token_tags[p->tok_i - 2] == TOKEN_SEMICOLON);
   1980     switch (statements_len) {
   1981     case 0:
   1982         return addNode(
   1983             &p->nodes,
   1984             (AstNodeItem) {
   1985                 .tag = AST_NODE_BLOCK_TWO,
   1986                 .main_token = lbrace,
   1987                 .data = {
   1988                     .lhs = 0,
   1989                     .rhs = 0,
   1990                 },
   1991             });
   1992     case 1:
   1993         return addNode(
   1994             &p->nodes,
   1995             (AstNodeItem) {
   1996                 .tag = semicolon ? AST_NODE_BLOCK_TWO_SEMICOLON : AST_NODE_BLOCK_TWO,
   1997                 .main_token = lbrace,
   1998                 .data = {
   1999                     .lhs = p->scratch.arr[scratch_top],
   2000                     .rhs = 0,
   2001                 },
   2002             });
   2003     case 2:
   2004         return addNode(
   2005             &p->nodes,
   2006             (AstNodeItem) {
   2007                 .tag = semicolon ? AST_NODE_BLOCK_TWO_SEMICOLON : AST_NODE_BLOCK_TWO,
   2008                 .main_token = lbrace,
   2009                 .data = {
   2010                     .lhs = p->scratch.arr[scratch_top],
   2011                     .rhs = p->scratch.arr[scratch_top + 1],
   2012                 },
   2013             });
   2014     default:;
   2015         const AstSubRange span
   2016             = listToSpan(p, &p->scratch.arr[scratch_top], statements_len);
   2017         return addNode(
   2018             &p->nodes,
   2019             (AstNodeItem) {
   2020                 .tag = semicolon ? AST_NODE_BLOCK_SEMICOLON : AST_NODE_BLOCK,
   2021                 .main_token = lbrace,
   2022                 .data = {
   2023                     .lhs = span.start,
   2024                     .rhs = span.end,
   2025                 },
   2026             });
   2027     }
   2028 
   2029     return 0;
   2030 }
   2031 
   2032 // forPrefix parses the for prefix: (expr, expr, ...) |captures|.
   2033 // Returns the number of input expressions. The inputs are appended
   2034 // to the scratch buffer.
   2035 static uint32_t forPrefix(Parser* p) {
   2036     const uint32_t start = p->scratch.len;
   2037     expectToken(p, TOKEN_L_PAREN);
   2038 
   2039     while (true) {
   2040         AstNodeIndex input = expectExpr(p);
   2041         if (eatToken(p, TOKEN_ELLIPSIS2) != null_token) {
   2042             const AstTokenIndex ellipsis = p->tok_i - 1;
   2043             const AstNodeIndex end = parseExpr(p);
   2044             input = addNode(&p->nodes,
   2045                 (AstNodeItem) {
   2046                     .tag = AST_NODE_FOR_RANGE,
   2047                     .main_token = ellipsis,
   2048                     .data = { .lhs = input, .rhs = end },
   2049                 });
   2050         }
   2051         SLICE_APPEND(AstNodeIndex, &p->scratch, input);
   2052         if (p->token_tags[p->tok_i] == TOKEN_COMMA) {
   2053             p->tok_i++;
   2054             if (eatToken(p, TOKEN_R_PAREN) != null_token)
   2055                 break;
   2056             continue;
   2057         }
   2058         expectToken(p, TOKEN_R_PAREN);
   2059         break;
   2060     }
   2061     const uint32_t inputs = p->scratch.len - start;
   2062 
   2063     // Parse payload |a, *b, c|
   2064     if (eatToken(p, TOKEN_PIPE) == null_token) {
   2065         fail(p, "expected loop payload");
   2066     }
   2067     {
   2068         while (true) {
   2069             eatToken(p, TOKEN_ASTERISK);
   2070             expectToken(p, TOKEN_IDENTIFIER);
   2071             if (p->token_tags[p->tok_i] == TOKEN_COMMA) {
   2072                 p->tok_i++;
   2073                 if (eatToken(p, TOKEN_PIPE) != null_token)
   2074                     break;
   2075                 continue;
   2076             }
   2077             expectToken(p, TOKEN_PIPE);
   2078             break;
   2079         }
   2080     }
   2081     return inputs;
   2082 }
   2083 
   2084 static AstNodeIndex parseWhileExpr(Parser* p) {
   2085     const AstTokenIndex while_token = eatToken(p, TOKEN_KEYWORD_WHILE);
   2086     if (while_token == null_token)
   2087         return null_node;
   2088 
   2089     expectToken(p, TOKEN_L_PAREN);
   2090     const AstNodeIndex condition = expectExpr(p);
   2091     expectToken(p, TOKEN_R_PAREN);
   2092     parsePtrPayload(p);
   2093 
   2094     const AstNodeIndex cont_expr = parseWhileContinueExpr(p);
   2095 
   2096     const AstNodeIndex body = expectExpr(p);
   2097 
   2098     if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) {
   2099         if (cont_expr != 0) {
   2100             return addNode(&p->nodes,
   2101                 (AstNodeItem) {
   2102                     .tag = AST_NODE_WHILE_CONT,
   2103                     .main_token = while_token,
   2104                     .data = {
   2105                         .lhs = condition,
   2106                         .rhs = addExtra(p,
   2107                             (AstNodeIndex[]) { cont_expr, body }, 2),
   2108                     },
   2109                 });
   2110         }
   2111         return addNode(&p->nodes,
   2112             (AstNodeItem) {
   2113                 .tag = AST_NODE_WHILE_SIMPLE,
   2114                 .main_token = while_token,
   2115                 .data = { .lhs = condition, .rhs = body },
   2116             });
   2117     }
   2118 
   2119     parsePayload(p);
   2120     const AstNodeIndex else_expr = expectExpr(p);
   2121     return addNode(&p->nodes,
   2122         (AstNodeItem) {
   2123             .tag = AST_NODE_WHILE,
   2124             .main_token = while_token,
   2125             .data = {
   2126                 .lhs = condition,
   2127                 .rhs = addExtra(p,
   2128                     (AstNodeIndex[]) { OPT(cont_expr), body, else_expr },
   2129                     3),
   2130             },
   2131         });
   2132 }
   2133 
   2134 static AstNodeIndex parseWhileContinueExpr(Parser* p) {
   2135     if (eatToken(p, TOKEN_COLON) == null_token)
   2136         return null_node;
   2137     expectToken(p, TOKEN_L_PAREN);
   2138     const AstNodeIndex expr = parseAssignExpr(p);
   2139     expectToken(p, TOKEN_R_PAREN);
   2140     return expr;
   2141 }
   2142 
   2143 static AstNodeIndex parseCurlySuffixExpr(Parser* p) {
   2144     const AstNodeIndex lhs = parseTypeExpr(p);
   2145     if (lhs == 0)
   2146         return null_node;
   2147 
   2148     const AstTokenIndex lbrace = eatToken(p, TOKEN_L_BRACE);
   2149     if (lbrace == null_token)
   2150         return lhs;
   2151 
   2152     return parseInitList(p, lhs, lbrace);
   2153 }
   2154 
   2155 // parseInitList parses the contents of { ... } for struct/array init.
   2156 // lhs is the type expression (0 for anonymous .{...}).
   2157 // lbrace is the lbrace token index.
   2158 static AstNodeIndex parseInitList(
   2159     Parser* p, AstNodeIndex lhs, AstTokenIndex lbrace) {
   2160     const uint32_t scratch_top = p->scratch.len;
   2161 
   2162     const AstNodeIndex field_init = parseFieldInit(p);
   2163     if (field_init != 0) {
   2164         // Struct init
   2165         SLICE_APPEND(AstNodeIndex, &p->scratch, field_init);
   2166         while (true) {
   2167             if (p->token_tags[p->tok_i] == TOKEN_COMMA)
   2168                 p->tok_i++;
   2169             else if (p->token_tags[p->tok_i] == TOKEN_R_BRACE) {
   2170                 p->tok_i++;
   2171                 break;
   2172             } else {
   2173                 fail(p, "parseInitList: expected , or } in struct init");
   2174             }
   2175             if (eatToken(p, TOKEN_R_BRACE) != null_token)
   2176                 break;
   2177             const AstNodeIndex next = parseFieldInit(p);
   2178             assert(next != 0);
   2179             SLICE_APPEND(AstNodeIndex, &p->scratch, next);
   2180         }
   2181         const bool comma = p->token_tags[p->tok_i - 2] == TOKEN_COMMA;
   2182         const uint32_t inits_len = p->scratch.len - scratch_top;
   2183         p->scratch.len = scratch_top;
   2184         if (lhs == 0) {
   2185             // Anonymous struct init: .{...}
   2186             switch (inits_len) {
   2187             case 0:
   2188             case 1:
   2189             case 2:
   2190                 return addNode(&p->nodes,
   2191                     (AstNodeItem) {
   2192                         .tag = comma
   2193                             ? AST_NODE_STRUCT_INIT_DOT_TWO_COMMA
   2194                             : AST_NODE_STRUCT_INIT_DOT_TWO,
   2195                         .main_token = lbrace,
   2196                         .data = {
   2197                             .lhs = inits_len >= 1
   2198                                 ? p->scratch.arr[scratch_top]
   2199                                 : 0,
   2200                             .rhs = inits_len >= 2
   2201                                 ? p->scratch.arr[scratch_top + 1]
   2202                                 : 0,
   2203                         },
   2204                     });
   2205             default:;
   2206                 const AstSubRange span
   2207                     = listToSpan(p, &p->scratch.arr[scratch_top], inits_len);
   2208                 return addNode(&p->nodes,
   2209                     (AstNodeItem) {
   2210                         .tag = comma ? AST_NODE_STRUCT_INIT_DOT_COMMA
   2211                                      : AST_NODE_STRUCT_INIT_DOT,
   2212                         .main_token = lbrace,
   2213                         .data = { .lhs = span.start, .rhs = span.end },
   2214                     });
   2215             }
   2216         }
   2217         // Named struct init: X{...}
   2218         switch (inits_len) {
   2219         case 0:
   2220         case 1:
   2221             return addNode(&p->nodes,
   2222                 (AstNodeItem) {
   2223                     .tag = comma ? AST_NODE_STRUCT_INIT_ONE_COMMA
   2224                                  : AST_NODE_STRUCT_INIT_ONE,
   2225                     .main_token = lbrace,
   2226                     .data = {
   2227                         .lhs = lhs,
   2228                         .rhs = inits_len >= 1
   2229                             ? p->scratch.arr[scratch_top]
   2230                             : 0,
   2231                     },
   2232                 });
   2233         default:;
   2234             const AstSubRange span
   2235                 = listToSpan(p, &p->scratch.arr[scratch_top], inits_len);
   2236             return addNode(&p->nodes,
   2237                 (AstNodeItem) {
   2238                     .tag = comma ? AST_NODE_STRUCT_INIT_COMMA
   2239                                  : AST_NODE_STRUCT_INIT,
   2240                     .main_token = lbrace,
   2241                     .data = {
   2242                         .lhs = lhs,
   2243                         .rhs = addExtra(p,
   2244                             (AstNodeIndex[]) { span.start, span.end }, 2),
   2245                     },
   2246                 });
   2247         }
   2248     }
   2249 
   2250     // Array init or empty init
   2251     while (true) {
   2252         if (eatToken(p, TOKEN_R_BRACE) != null_token)
   2253             break;
   2254         const AstNodeIndex elem = expectExpr(p);
   2255         SLICE_APPEND(AstNodeIndex, &p->scratch, elem);
   2256         if (p->token_tags[p->tok_i] == TOKEN_COMMA)
   2257             p->tok_i++;
   2258         else if (p->token_tags[p->tok_i] == TOKEN_R_BRACE) {
   2259             p->tok_i++;
   2260             break;
   2261         } else {
   2262             fail(p, "parseInitList: expected , or } in array init");
   2263         }
   2264     }
   2265 
   2266     const bool comma = p->token_tags[p->tok_i - 2] == TOKEN_COMMA;
   2267     const uint32_t elems_len = p->scratch.len - scratch_top;
   2268     p->scratch.len = scratch_top;
   2269     if (lhs == 0) {
   2270         // Anonymous array init: .{a, b, ...}
   2271         switch (elems_len) {
   2272         case 0:
   2273         case 1:
   2274         case 2:
   2275             return addNode(&p->nodes,
   2276                 (AstNodeItem) {
   2277                     .tag = (elems_len == 0)
   2278                         ? AST_NODE_STRUCT_INIT_DOT_TWO
   2279                         : (comma ? AST_NODE_ARRAY_INIT_DOT_TWO_COMMA
   2280                                  : AST_NODE_ARRAY_INIT_DOT_TWO),
   2281                     .main_token = lbrace,
   2282                     .data = {
   2283                         .lhs = elems_len >= 1
   2284                             ? p->scratch.arr[scratch_top]
   2285                             : 0,
   2286                         .rhs = elems_len >= 2
   2287                             ? p->scratch.arr[scratch_top + 1]
   2288                             : 0,
   2289                     },
   2290                 });
   2291         default:;
   2292             const AstSubRange span
   2293                 = listToSpan(p, &p->scratch.arr[scratch_top], elems_len);
   2294             return addNode(&p->nodes,
   2295                 (AstNodeItem) {
   2296                     .tag = comma ? AST_NODE_ARRAY_INIT_DOT_COMMA
   2297                                  : AST_NODE_ARRAY_INIT_DOT,
   2298                     .main_token = lbrace,
   2299                     .data = { .lhs = span.start, .rhs = span.end },
   2300                 });
   2301         }
   2302     }
   2303     // Named init: X{a, b, ...}
   2304     switch (elems_len) {
   2305     case 0:
   2306         // Empty init X{} — treat as struct init
   2307         return addNode(&p->nodes,
   2308             (AstNodeItem) {
   2309                 .tag = AST_NODE_STRUCT_INIT_ONE,
   2310                 .main_token = lbrace,
   2311                 .data = { .lhs = lhs, .rhs = 0 },
   2312             });
   2313     case 1:
   2314         return addNode(&p->nodes,
   2315             (AstNodeItem) {
   2316                 .tag = comma ? AST_NODE_ARRAY_INIT_ONE_COMMA
   2317                              : AST_NODE_ARRAY_INIT_ONE,
   2318                 .main_token = lbrace,
   2319                 .data = {
   2320                     .lhs = lhs,
   2321                     .rhs = p->scratch.arr[scratch_top],
   2322                 },
   2323             });
   2324     default:;
   2325         const AstSubRange span
   2326             = listToSpan(p, &p->scratch.arr[scratch_top], elems_len);
   2327         return addNode(&p->nodes,
   2328             (AstNodeItem) {
   2329                 .tag = comma ? AST_NODE_ARRAY_INIT_COMMA
   2330                              : AST_NODE_ARRAY_INIT,
   2331                 .main_token = lbrace,
   2332                 .data = {
   2333                     .lhs = lhs,
   2334                     .rhs = addExtra(p,
   2335                         (AstNodeIndex[]) { span.start, span.end }, 2),
   2336                 },
   2337             });
   2338     }
   2339 }
   2340 
   2341 static AstNodeIndex parseErrorUnionExpr(Parser* p) {
   2342     const AstNodeIndex suffix_expr = parseSuffixExpr(p);
   2343     if (suffix_expr == 0)
   2344         return null_node;
   2345 
   2346     const AstNodeIndex bang = eatToken(p, TOKEN_BANG);
   2347     if (bang == null_token)
   2348         return suffix_expr;
   2349 
   2350     return addNode(
   2351         &p->nodes,
   2352         (AstNodeItem) {
   2353             .tag = AST_NODE_ERROR_UNION,
   2354             .main_token = bang,
   2355             .data = {
   2356                 .lhs = suffix_expr,
   2357                 .rhs = parseTypeExpr(p),
   2358             },
   2359         });
   2360 }
   2361 
   2362 static AstNodeIndex parseSuffixExpr(Parser* p) {
   2363     AstNodeIndex res = parsePrimaryTypeExpr(p);
   2364     if (res == 0)
   2365         return res;
   2366 
   2367     while (true) {
   2368         const AstNodeIndex suffix_op = parseSuffixOp(p, res);
   2369         if (suffix_op != 0) {
   2370             res = suffix_op;
   2371             continue;
   2372         }
   2373         const AstTokenIndex lparen = eatToken(p, TOKEN_L_PAREN);
   2374         if (lparen == null_token)
   2375             return res;
   2376 
   2377         const uint32_t scratch_top = p->scratch.len;
   2378         while (true) {
   2379             if (eatToken(p, TOKEN_R_PAREN) != null_token)
   2380                 break;
   2381             const AstNodeIndex arg = expectExpr(p);
   2382             SLICE_APPEND(AstNodeIndex, &p->scratch, arg);
   2383             if (p->token_tags[p->tok_i] == TOKEN_COMMA) {
   2384                 p->tok_i++;
   2385                 continue;
   2386             }
   2387             expectToken(p, TOKEN_R_PAREN);
   2388             break;
   2389         }
   2390 
   2391         const bool comma = p->token_tags[p->tok_i - 2] == TOKEN_COMMA;
   2392         const uint32_t params_len = p->scratch.len - scratch_top;
   2393         p->scratch.len = scratch_top;
   2394         switch (params_len) {
   2395         case 0:
   2396             res = addNode(
   2397                 &p->nodes,
   2398                 (AstNodeItem) {
   2399                     .tag = comma ? AST_NODE_CALL_ONE_COMMA : AST_NODE_CALL_ONE,
   2400                     .main_token = lparen,
   2401                     .data = {
   2402                         .lhs = res,
   2403                         .rhs = 0,
   2404                     },
   2405                 });
   2406             break;
   2407         case 1:
   2408             res = addNode(
   2409                 &p->nodes,
   2410                 (AstNodeItem) {
   2411                     .tag = comma ? AST_NODE_CALL_ONE_COMMA : AST_NODE_CALL_ONE,
   2412                     .main_token = lparen,
   2413                     .data = {
   2414                         .lhs = res,
   2415                         .rhs = p->scratch.arr[scratch_top],
   2416                     },
   2417                 });
   2418             break;
   2419         default:;
   2420             const AstSubRange span
   2421                 = listToSpan(p, &p->scratch.arr[scratch_top], params_len);
   2422             res = addNode(
   2423                 &p->nodes,
   2424                 (AstNodeItem) {
   2425                     .tag = comma ? AST_NODE_CALL_COMMA : AST_NODE_CALL,
   2426                     .main_token = lparen,
   2427                     .data = {
   2428                         .lhs = res,
   2429                         .rhs = addExtra(p, (AstNodeIndex[]) {
   2430                                 span.start,
   2431                                 span.end,
   2432                         }, 2),
   2433                     },
   2434                 });
   2435             break;
   2436         }
   2437     }
   2438 }
   2439 
   2440 static AstNodeIndex parsePrimaryTypeExpr(Parser* p) {
   2441     const TokenizerTag tok = p->token_tags[p->tok_i];
   2442     switch (tok) {
   2443     case TOKEN_CHAR_LITERAL:
   2444         return addNode(&p->nodes,
   2445             (AstNodeItem) {
   2446                 .tag = AST_NODE_CHAR_LITERAL,
   2447                 .main_token = nextToken(p),
   2448                 .data = {},
   2449             });
   2450     case TOKEN_NUMBER_LITERAL:
   2451         return addNode(&p->nodes,
   2452             (AstNodeItem) {
   2453                 .tag = AST_NODE_NUMBER_LITERAL,
   2454                 .main_token = nextToken(p),
   2455                 .data = {},
   2456             });
   2457     case TOKEN_KEYWORD_UNREACHABLE:
   2458         return addNode(&p->nodes,
   2459             (AstNodeItem) {
   2460                 .tag = AST_NODE_UNREACHABLE_LITERAL,
   2461                 .main_token = nextToken(p),
   2462                 .data = {},
   2463             });
   2464     case TOKEN_KEYWORD_ANYFRAME:
   2465         fail(p, "unsupported primary type expression");
   2466     case TOKEN_STRING_LITERAL:
   2467         return addNode(&p->nodes,
   2468             (AstNodeItem) {
   2469                 .tag = AST_NODE_STRING_LITERAL,
   2470                 .main_token = nextToken(p),
   2471                 .data = {},
   2472             });
   2473     case TOKEN_BUILTIN:
   2474         return parseBuiltinCall(p);
   2475     case TOKEN_KEYWORD_FN:
   2476         return parseFnProto(p);
   2477     case TOKEN_KEYWORD_IF:
   2478         return parseIfExpr(p);
   2479     case TOKEN_KEYWORD_SWITCH:
   2480         return parseSwitchExpr(p);
   2481     case TOKEN_KEYWORD_EXTERN:
   2482     case TOKEN_KEYWORD_PACKED:
   2483         // extern/packed can precede struct/union/enum
   2484         switch (p->token_tags[p->tok_i + 1]) {
   2485         case TOKEN_KEYWORD_STRUCT:
   2486         case TOKEN_KEYWORD_UNION:
   2487         case TOKEN_KEYWORD_ENUM:
   2488             p->tok_i++; // consume extern/packed
   2489             return parseContainerDeclAuto(p);
   2490         default:
   2491             fail(p, "unsupported primary type expression");
   2492         }
   2493     case TOKEN_KEYWORD_STRUCT:
   2494     case TOKEN_KEYWORD_OPAQUE:
   2495     case TOKEN_KEYWORD_ENUM:
   2496     case TOKEN_KEYWORD_UNION:
   2497         return parseContainerDeclAuto(p);
   2498     case TOKEN_KEYWORD_COMPTIME:
   2499         return addNode(&p->nodes,
   2500             (AstNodeItem) {
   2501                 .tag = AST_NODE_COMPTIME,
   2502                 .main_token = nextToken(p),
   2503                 .data = { .lhs = parseTypeExpr(p), .rhs = 0 },
   2504             });
   2505     case TOKEN_MULTILINE_STRING_LITERAL_LINE: {
   2506         const AstTokenIndex first = nextToken(p);
   2507         AstTokenIndex last = first;
   2508         while (p->token_tags[p->tok_i] == TOKEN_MULTILINE_STRING_LITERAL_LINE)
   2509             last = nextToken(p);
   2510         return addNode(&p->nodes,
   2511             (AstNodeItem) {
   2512                 .tag = AST_NODE_MULTILINE_STRING_LITERAL,
   2513                 .main_token = first,
   2514                 .data = { .lhs = first, .rhs = last },
   2515             });
   2516     }
   2517     case TOKEN_IDENTIFIER:
   2518         if (p->token_tags[p->tok_i + 1] == TOKEN_COLON) {
   2519             switch (p->token_tags[p->tok_i + 2]) {
   2520             case TOKEN_L_BRACE: {
   2521                 // Labeled block: label: { ... }
   2522                 nextToken(p); // consume label
   2523                 nextToken(p); // consume ':'
   2524                 return parseBlock(p);
   2525             }
   2526             case TOKEN_KEYWORD_WHILE:
   2527                 return parseLabeledStatement(p);
   2528             case TOKEN_KEYWORD_FOR:
   2529                 return parseLabeledStatement(p);
   2530             default:
   2531                 break;
   2532             }
   2533         }
   2534         return addNode(&p->nodes,
   2535             (AstNodeItem) {
   2536                 .tag = AST_NODE_IDENTIFIER,
   2537                 .main_token = nextToken(p),
   2538                 .data = {},
   2539             });
   2540     case TOKEN_KEYWORD_FOR:
   2541         return parseForExpr(p);
   2542     case TOKEN_KEYWORD_WHILE:
   2543         return parseWhileExpr(p);
   2544     case TOKEN_KEYWORD_INLINE:
   2545     case TOKEN_PERIOD:
   2546         switch (p->token_tags[p->tok_i + 1]) {
   2547         case TOKEN_IDENTIFIER: {
   2548             const AstTokenIndex dot = nextToken(p);
   2549             return addNode(&p->nodes,
   2550                 (AstNodeItem) {
   2551                     .tag = AST_NODE_ENUM_LITERAL,
   2552                     .main_token = nextToken(p),
   2553                     .data = { .lhs = dot, .rhs = 0 },
   2554                 });
   2555         }
   2556         case TOKEN_L_BRACE: {
   2557             // Anonymous struct/array init: .{ ... }
   2558             const AstTokenIndex lbrace = p->tok_i + 1;
   2559             p->tok_i = lbrace + 1;
   2560             return parseInitList(p, null_node, lbrace);
   2561         }
   2562         default:
   2563             fail(p, "unsupported period suffix");
   2564         }
   2565         return 0; // tcc
   2566     case TOKEN_KEYWORD_ERROR:
   2567         switch (p->token_tags[p->tok_i + 1]) {
   2568         case TOKEN_PERIOD: {
   2569             const AstTokenIndex error_token = nextToken(p);
   2570             const AstTokenIndex dot = nextToken(p);
   2571             const AstTokenIndex value = expectToken(p, TOKEN_IDENTIFIER);
   2572             return addNode(&p->nodes,
   2573                 (AstNodeItem) {
   2574                     .tag = AST_NODE_ERROR_VALUE,
   2575                     .main_token = error_token,
   2576                     .data = { .lhs = dot, .rhs = value },
   2577                 });
   2578         }
   2579         case TOKEN_L_BRACE: {
   2580             const AstTokenIndex error_token = nextToken(p);
   2581             const AstTokenIndex lbrace = nextToken(p);
   2582             while (p->token_tags[p->tok_i] != TOKEN_R_BRACE)
   2583                 p->tok_i++;
   2584             const AstTokenIndex rbrace = nextToken(p);
   2585             return addNode(&p->nodes,
   2586                 (AstNodeItem) {
   2587                     .tag = AST_NODE_ERROR_SET_DECL,
   2588                     .main_token = error_token,
   2589                     .data = { .lhs = lbrace, .rhs = rbrace },
   2590                 });
   2591         }
   2592         default: {
   2593             const AstTokenIndex main_token = nextToken(p);
   2594             const AstTokenIndex period = eatToken(p, TOKEN_PERIOD);
   2595             if (period == null_token) {
   2596                 fail(p, "expected '.'");
   2597             }
   2598             const AstTokenIndex identifier = eatToken(p, TOKEN_IDENTIFIER);
   2599             if (identifier == null_token) {
   2600                 fail(p, "expected identifier");
   2601             }
   2602             return addNode(&p->nodes,
   2603                 (AstNodeItem) {
   2604                     .tag = AST_NODE_ERROR_VALUE,
   2605                     .main_token = main_token,
   2606                     .data = { .lhs = period, .rhs = identifier },
   2607                 });
   2608         }
   2609         }
   2610     case TOKEN_L_PAREN: {
   2611         const AstTokenIndex lparen = nextToken(p);
   2612         const AstNodeIndex inner = expectExpr(p);
   2613         const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN);
   2614         return addNode(&p->nodes,
   2615             (AstNodeItem) {
   2616                 .tag = AST_NODE_GROUPED_EXPRESSION,
   2617                 .main_token = lparen,
   2618                 .data = { .lhs = inner, .rhs = rparen },
   2619             });
   2620     }
   2621     default:
   2622         return null_node;
   2623     }
   2624 }
   2625 
   2626 static AstNodeIndex parseSwitchExpr(Parser* p) {
   2627     const AstTokenIndex switch_token = eatToken(p, TOKEN_KEYWORD_SWITCH);
   2628     if (switch_token == null_token)
   2629         return null_node;
   2630 
   2631     expectToken(p, TOKEN_L_PAREN);
   2632     const AstNodeIndex operand = expectExpr(p);
   2633     expectToken(p, TOKEN_R_PAREN);
   2634     expectToken(p, TOKEN_L_BRACE);
   2635 
   2636     const AstSubRange span = parseSwitchProngList(p);
   2637     const bool comma = p->token_tags[p->tok_i - 2] == TOKEN_COMMA;
   2638     return addNode(&p->nodes,
   2639         (AstNodeItem) {
   2640             .tag = comma ? AST_NODE_SWITCH_COMMA : AST_NODE_SWITCH,
   2641             .main_token = switch_token,
   2642             .data = {
   2643                 .lhs = operand,
   2644                 .rhs = addExtra(p,
   2645                     (AstNodeIndex[]) { span.start, span.end }, 2),
   2646             },
   2647         });
   2648 }
   2649 
   2650 static AstNodeIndex parseAsmExpr(Parser* p) {
   2651     const AstTokenIndex asm_token = nextToken(p);
   2652     assert(p->token_tags[asm_token] == TOKEN_KEYWORD_ASM);
   2653     eatToken(p, TOKEN_KEYWORD_VOLATILE);
   2654     expectToken(p, TOKEN_L_PAREN);
   2655     const AstNodeIndex template = expectExpr(p);
   2656 
   2657     // Simple asm: asm("...")
   2658     if (eatToken(p, TOKEN_R_PAREN) != null_token) {
   2659         return addNode(&p->nodes,
   2660             (AstNodeItem) {
   2661                 .tag = AST_NODE_ASM_SIMPLE,
   2662                 .main_token = asm_token,
   2663                 .data = { .lhs = template, .rhs = p->tok_i - 1 },
   2664             });
   2665     }
   2666 
   2667     // Complex asm with outputs, inputs, clobbers
   2668     expectToken(p, TOKEN_COLON);
   2669 
   2670     const uint32_t scratch_top = p->scratch.len;
   2671 
   2672     // Parse outputs
   2673     while (true) {
   2674         const AstNodeIndex output = parseAsmOutputItem(p);
   2675         if (output == 0)
   2676             break;
   2677         SLICE_APPEND(AstNodeIndex, &p->scratch, output);
   2678         if (eatToken(p, TOKEN_COMMA) == null_token)
   2679             break;
   2680     }
   2681 
   2682     // Parse inputs (after second colon)
   2683     if (eatToken(p, TOKEN_COLON) != null_token) {
   2684         while (true) {
   2685             const AstNodeIndex input = parseAsmInputItem(p);
   2686             if (input == 0)
   2687                 break;
   2688             SLICE_APPEND(AstNodeIndex, &p->scratch, input);
   2689             if (eatToken(p, TOKEN_COMMA) == null_token)
   2690                 break;
   2691         }
   2692     }
   2693 
   2694     // Parse clobbers (after third colon)
   2695     if (eatToken(p, TOKEN_COLON) != null_token) {
   2696         if (p->token_tags[p->tok_i] == TOKEN_STRING_LITERAL) {
   2697             // Legacy clobber format: "str1", "str2", ...
   2698             // Produces asm_legacy node
   2699             while (p->token_tags[p->tok_i] == TOKEN_STRING_LITERAL) {
   2700                 p->tok_i++;
   2701                 if (eatToken(p, TOKEN_COMMA) == null_token)
   2702                     break;
   2703             }
   2704             const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN);
   2705             const uint32_t items_len = p->scratch.len - scratch_top;
   2706             const AstSubRange items_span
   2707                 = listToSpan(p, &p->scratch.arr[scratch_top], items_len);
   2708             p->scratch.len = scratch_top;
   2709             return addNode(&p->nodes,
   2710                 (AstNodeItem) {
   2711                     .tag = AST_NODE_ASM_LEGACY,
   2712                     .main_token = asm_token,
   2713                     .data = {
   2714                         .lhs = template,
   2715                         .rhs = addExtra(p,
   2716                             (AstNodeIndex[]) { items_span.start,
   2717                                 items_span.end, rparen },
   2718                             3),
   2719                     },
   2720                 });
   2721         }
   2722         // New clobber format: expression (e.g. .{ .clobber = true })
   2723         AstNodeIndex clobbers = 0;
   2724         if (p->token_tags[p->tok_i] != TOKEN_R_PAREN)
   2725             clobbers = expectExpr(p);
   2726 
   2727         const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN);
   2728         const uint32_t items_len = p->scratch.len - scratch_top;
   2729         const AstSubRange items_span
   2730             = listToSpan(p, &p->scratch.arr[scratch_top], items_len);
   2731         p->scratch.len = scratch_top;
   2732         return addNode(&p->nodes,
   2733             (AstNodeItem) {
   2734                 .tag = AST_NODE_ASM,
   2735                 .main_token = asm_token,
   2736                 .data = {
   2737                     .lhs = template,
   2738                     .rhs = addExtra(p,
   2739                         (AstNodeIndex[]) { items_span.start,
   2740                             items_span.end, OPT(clobbers), rparen },
   2741                         4),
   2742                 },
   2743             });
   2744     }
   2745 
   2746     // No clobbers
   2747     const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN);
   2748     const uint32_t items_len = p->scratch.len - scratch_top;
   2749     const AstSubRange items_span
   2750         = listToSpan(p, &p->scratch.arr[scratch_top], items_len);
   2751     p->scratch.len = scratch_top;
   2752     return addNode(&p->nodes,
   2753         (AstNodeItem) {
   2754             .tag = AST_NODE_ASM,
   2755             .main_token = asm_token,
   2756             .data = {
   2757                 .lhs = template,
   2758                 .rhs = addExtra(p,
   2759                     (AstNodeIndex[]) { items_span.start, items_span.end,
   2760                         OPT((AstNodeIndex)0), rparen },
   2761                     4),
   2762             },
   2763         });
   2764 }
   2765 
   2766 static AstNodeIndex parseAsmOutputItem(Parser* p) {
   2767     if (p->token_tags[p->tok_i] == TOKEN_L_BRACKET) {
   2768         p->tok_i++; // [
   2769         const AstTokenIndex ident = expectToken(p, TOKEN_IDENTIFIER);
   2770         expectToken(p, TOKEN_R_BRACKET);
   2771         expectToken(p, TOKEN_STRING_LITERAL);
   2772         expectToken(p, TOKEN_L_PAREN);
   2773         AstNodeIndex type_expr = 0;
   2774         if (eatToken(p, TOKEN_ARROW) != null_token) {
   2775             type_expr = parseTypeExpr(p);
   2776         } else {
   2777             expectToken(p, TOKEN_IDENTIFIER);
   2778         }
   2779         const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN);
   2780         return addNode(&p->nodes,
   2781             (AstNodeItem) {
   2782                 .tag = AST_NODE_ASM_OUTPUT,
   2783                 .main_token = ident,
   2784                 .data = { .lhs = type_expr, .rhs = rparen },
   2785             });
   2786     }
   2787     return null_node;
   2788 }
   2789 
   2790 static AstNodeIndex parseAsmInputItem(Parser* p) {
   2791     if (p->token_tags[p->tok_i] == TOKEN_L_BRACKET) {
   2792         p->tok_i++; // [
   2793         const AstTokenIndex ident = expectToken(p, TOKEN_IDENTIFIER);
   2794         expectToken(p, TOKEN_R_BRACKET);
   2795         expectToken(p, TOKEN_STRING_LITERAL);
   2796         expectToken(p, TOKEN_L_PAREN);
   2797         const AstNodeIndex operand = expectExpr(p);
   2798         const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN);
   2799         return addNode(&p->nodes,
   2800             (AstNodeItem) {
   2801                 .tag = AST_NODE_ASM_INPUT,
   2802                 .main_token = ident,
   2803                 .data = { .lhs = operand, .rhs = rparen },
   2804             });
   2805     }
   2806     return null_node;
   2807 }
   2808 
   2809 static AstTokenIndex parseBreakLabel(Parser* p) {
   2810     if (eatToken(p, TOKEN_COLON) == null_token)
   2811         return null_token;
   2812     return expectToken(p, TOKEN_IDENTIFIER);
   2813 }
   2814 
   2815 static AstTokenIndex parseBlockLabel(Parser* p) {
   2816     if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER
   2817         && p->token_tags[p->tok_i + 1] == TOKEN_COLON) {
   2818         const AstTokenIndex identifier = p->tok_i;
   2819         p->tok_i += 2;
   2820         return identifier;
   2821     }
   2822     return null_node;
   2823 }
   2824 
   2825 // parseFieldInit tries to parse .field_name = expr; returns 0 if not a
   2826 // field init
   2827 static AstNodeIndex parseFieldInit(Parser* p) {
   2828     if (p->token_tags[p->tok_i] == TOKEN_PERIOD
   2829         && p->token_tags[p->tok_i + 1] == TOKEN_IDENTIFIER
   2830         && p->token_tags[p->tok_i + 2] == TOKEN_EQUAL) {
   2831         p->tok_i += 3;
   2832         return expectExpr(p);
   2833     }
   2834     return null_node;
   2835 }
   2836 
   2837 static AstNodeIndex parseLinkSection(Parser* p) {
   2838     if (eatToken(p, TOKEN_KEYWORD_LINKSECTION) == null_token)
   2839         return null_node;
   2840     expectToken(p, TOKEN_L_PAREN);
   2841     const AstNodeIndex expr = expectExpr(p);
   2842     expectToken(p, TOKEN_R_PAREN);
   2843     return expr;
   2844 }
   2845 
   2846 static AstNodeIndex parseCallconv(Parser* p) {
   2847     if (eatToken(p, TOKEN_KEYWORD_CALLCONV) == null_token)
   2848         return null_node;
   2849     expectToken(p, TOKEN_L_PAREN);
   2850     const AstNodeIndex expr = expectExpr(p);
   2851     expectToken(p, TOKEN_R_PAREN);
   2852     return expr;
   2853 }
   2854 
   2855 static AstNodeIndex parseAddrSpace(Parser* p) {
   2856     if (eatToken(p, TOKEN_KEYWORD_ADDRSPACE) == null_token)
   2857         return null_node;
   2858     expectToken(p, TOKEN_L_PAREN);
   2859     const AstNodeIndex expr = expectExpr(p);
   2860     expectToken(p, TOKEN_R_PAREN);
   2861     return expr;
   2862 }
   2863 
   2864 static AstNodeIndex expectParamDecl(Parser* p) {
   2865     eatDocComments(p);
   2866     eatToken(p, TOKEN_KEYWORD_COMPTIME);
   2867     eatToken(p, TOKEN_KEYWORD_NOALIAS);
   2868     if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER
   2869         && p->token_tags[p->tok_i + 1] == TOKEN_COLON)
   2870         p->tok_i += 2;
   2871     if (eatToken(p, TOKEN_KEYWORD_ANYTYPE) != null_token)
   2872         return 0;
   2873     return parseTypeExpr(p);
   2874 }
   2875 
   2876 static void parsePayload(Parser* p) {
   2877     if (eatToken(p, TOKEN_PIPE) == null_token)
   2878         return;
   2879     expectToken(p, TOKEN_IDENTIFIER);
   2880     expectToken(p, TOKEN_PIPE);
   2881 }
   2882 
   2883 static void parsePtrPayload(Parser* p) {
   2884     if (eatToken(p, TOKEN_PIPE) == null_token)
   2885         return;
   2886     while (true) {
   2887         eatToken(p, TOKEN_ASTERISK);
   2888         expectToken(p, TOKEN_IDENTIFIER);
   2889         if (p->token_tags[p->tok_i] == TOKEN_COMMA) {
   2890             p->tok_i++;
   2891             continue;
   2892         }
   2893         break;
   2894     }
   2895     expectToken(p, TOKEN_PIPE);
   2896 }
   2897 
   2898 static AstNodeIndex parseSwitchProng(Parser* p) {
   2899     const uint32_t items_old_len = p->scratch.len;
   2900 
   2901     if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) {
   2902         while (true) {
   2903             const AstNodeIndex item = parseSwitchItem(p);
   2904             if (item == 0)
   2905                 break;
   2906             SLICE_APPEND(AstNodeIndex, &p->scratch, item);
   2907             if (eatToken(p, TOKEN_COMMA) == null_token)
   2908                 break;
   2909         }
   2910         if (p->scratch.len == items_old_len)
   2911             return null_node;
   2912     }
   2913 
   2914     const AstTokenIndex arrow
   2915         = expectToken(p, TOKEN_EQUAL_ANGLE_BRACKET_RIGHT);
   2916     parsePtrPayload(p);
   2917     const AstNodeIndex case_body = parseAssignExpr(p);
   2918     if (case_body == 0) {
   2919         fail(p, "expected expression");
   2920     }
   2921 
   2922     const uint32_t items_len = p->scratch.len - items_old_len;
   2923     AstNodeIndex case_node;
   2924     switch (items_len) {
   2925     case 0:
   2926     case 1:
   2927         case_node = addNode(&p->nodes,
   2928             (AstNodeItem) {
   2929                 .tag = AST_NODE_SWITCH_CASE_ONE,
   2930                 .main_token = arrow,
   2931                 .data = {
   2932                     .lhs
   2933                     = items_len >= 1 ? p->scratch.arr[items_old_len] : 0,
   2934                     .rhs = case_body,
   2935                 },
   2936             });
   2937         break;
   2938     default: {
   2939         const AstSubRange span
   2940             = listToSpan(p, &p->scratch.arr[items_old_len], items_len);
   2941         case_node = addNode(&p->nodes,
   2942             (AstNodeItem) {
   2943                 .tag = AST_NODE_SWITCH_CASE,
   2944                 .main_token = arrow,
   2945                 .data = {
   2946                     .lhs = addExtra(p,
   2947                         (AstNodeIndex[]) { span.start, span.end }, 2),
   2948                     .rhs = case_body,
   2949                 },
   2950             });
   2951     } break;
   2952     }
   2953 
   2954     p->scratch.len = items_old_len;
   2955     return case_node;
   2956 }
   2957 
   2958 static AstNodeIndex parseSwitchItem(Parser* p) {
   2959     const AstNodeIndex expr = parseExpr(p);
   2960     if (expr == 0)
   2961         return null_node;
   2962     if (p->token_tags[p->tok_i] == TOKEN_ELLIPSIS3) {
   2963         const AstTokenIndex range_tok = nextToken(p);
   2964         const AstNodeIndex range_end = expectExpr(p);
   2965         return addNode(&p->nodes,
   2966             (AstNodeItem) {
   2967                 .tag = AST_NODE_SWITCH_RANGE,
   2968                 .main_token = range_tok,
   2969                 .data = { .lhs = expr, .rhs = range_end },
   2970             });
   2971     }
   2972     return expr;
   2973 }
   2974 
   2975 static PtrModifiers parsePtrModifiers(Parser* p) {
   2976     PtrModifiers mods = {};
   2977 
   2978     while (true) {
   2979         switch (p->token_tags[p->tok_i]) {
   2980         case TOKEN_KEYWORD_CONST:
   2981         case TOKEN_KEYWORD_VOLATILE:
   2982         case TOKEN_KEYWORD_ALLOWZERO:
   2983             p->tok_i++;
   2984             continue;
   2985         case TOKEN_KEYWORD_ALIGN:
   2986             p->tok_i++;
   2987             expectToken(p, TOKEN_L_PAREN);
   2988             mods.align_node = expectExpr(p);
   2989             if (eatToken(p, TOKEN_COLON) != null_token) {
   2990                 mods.bit_range_start = expectExpr(p);
   2991                 expectToken(p, TOKEN_COLON);
   2992                 mods.bit_range_end = expectExpr(p);
   2993             }
   2994             expectToken(p, TOKEN_R_PAREN);
   2995             continue;
   2996         case TOKEN_KEYWORD_ADDRSPACE:
   2997             p->tok_i++;
   2998             expectToken(p, TOKEN_L_PAREN);
   2999             mods.addrspace_node = expectExpr(p);
   3000             expectToken(p, TOKEN_R_PAREN);
   3001             continue;
   3002         default:
   3003             return mods;
   3004         }
   3005     }
   3006 }
   3007 
   3008 static AstNodeIndex parseSuffixOp(Parser* p, AstNodeIndex lhs) {
   3009     const TokenizerTag tok = p->token_tags[p->tok_i];
   3010     switch (tok) {
   3011     case TOKEN_L_BRACKET: {
   3012         const AstTokenIndex lbracket = nextToken(p);
   3013         const AstNodeIndex index_expr = expectExpr(p);
   3014         switch (p->token_tags[p->tok_i]) {
   3015         case TOKEN_R_BRACKET:
   3016             p->tok_i++;
   3017             return addNode(&p->nodes,
   3018                 (AstNodeItem) {
   3019                     .tag = AST_NODE_ARRAY_ACCESS,
   3020                     .main_token = lbracket,
   3021                     .data = { .lhs = lhs, .rhs = index_expr },
   3022                 });
   3023         case TOKEN_ELLIPSIS2: {
   3024             p->tok_i++; // consume ..
   3025             const AstNodeIndex end_expr = parseExpr(p);
   3026             if (eatToken(p, TOKEN_COLON) != null_token) {
   3027                 const AstNodeIndex sentinel = expectExpr(p);
   3028                 expectToken(p, TOKEN_R_BRACKET);
   3029                 // end_expr 0 means "no end" — encode as ~0 for
   3030                 // OptionalIndex.none
   3031                 const AstNodeIndex opt_end
   3032                     = end_expr == 0 ? ~(AstNodeIndex)0 : end_expr;
   3033                 return addNode(&p->nodes,
   3034                     (AstNodeItem) {
   3035                         .tag = AST_NODE_SLICE_SENTINEL,
   3036                         .main_token = lbracket,
   3037                         .data = {
   3038                             .lhs = lhs,
   3039                             .rhs = addExtra(p,
   3040                                 (AstNodeIndex[]) {
   3041                                     index_expr, opt_end, sentinel },
   3042                                 3),
   3043                         },
   3044                     });
   3045             }
   3046             expectToken(p, TOKEN_R_BRACKET);
   3047             if (end_expr == 0) {
   3048                 return addNode(&p->nodes,
   3049                     (AstNodeItem) {
   3050                         .tag = AST_NODE_SLICE_OPEN,
   3051                         .main_token = lbracket,
   3052                         .data = { .lhs = lhs, .rhs = index_expr },
   3053                     });
   3054             }
   3055             return addNode(&p->nodes,
   3056                 (AstNodeItem) {
   3057                     .tag = AST_NODE_SLICE,
   3058                     .main_token = lbracket,
   3059                     .data = {
   3060                         .lhs = lhs,
   3061                         .rhs = addExtra(p,
   3062                             (AstNodeIndex[]) { index_expr, end_expr }, 2),
   3063                     },
   3064                 });
   3065         }
   3066         default:
   3067             fail(p, "parseSuffixOp: expected ] or .. after index expr");
   3068         }
   3069         return 0; // tcc
   3070     }
   3071     case TOKEN_PERIOD_ASTERISK:
   3072         return addNode(&p->nodes,
   3073             (AstNodeItem) {
   3074                 .tag = AST_NODE_DEREF,
   3075                 .main_token = nextToken(p),
   3076                 .data = { .lhs = lhs, .rhs = 0 },
   3077             });
   3078     case TOKEN_INVALID_PERIODASTERISKS:
   3079         fail(p, "unsupported suffix op");
   3080     case TOKEN_PERIOD:
   3081         if (p->token_tags[p->tok_i + 1] == TOKEN_IDENTIFIER) {
   3082             const AstTokenIndex dot = nextToken(p);
   3083             return addNode(&p->nodes,
   3084                 (AstNodeItem) {
   3085                     .tag = AST_NODE_FIELD_ACCESS,
   3086                     .main_token = dot,
   3087                     .data = { .lhs = lhs, .rhs = nextToken(p) },
   3088                 });
   3089         }
   3090         if (p->token_tags[p->tok_i + 1] == TOKEN_ASTERISK) {
   3091             const AstTokenIndex dot = nextToken(p);
   3092             nextToken(p); // consume the *
   3093             return addNode(&p->nodes,
   3094                 (AstNodeItem) {
   3095                     .tag = AST_NODE_DEREF,
   3096                     .main_token = dot,
   3097                     .data = { .lhs = lhs, .rhs = 0 },
   3098                 });
   3099         }
   3100         if (p->token_tags[p->tok_i + 1] == TOKEN_QUESTION_MARK) {
   3101             const AstTokenIndex dot = nextToken(p);
   3102             return addNode(&p->nodes,
   3103                 (AstNodeItem) {
   3104                     .tag = AST_NODE_UNWRAP_OPTIONAL,
   3105                     .main_token = dot,
   3106                     .data = { .lhs = lhs, .rhs = nextToken(p) },
   3107                 });
   3108         }
   3109         fail(p, "parseSuffixOp: unsupported period suffix");
   3110         return 0; // tcc
   3111     default:
   3112         return null_node;
   3113     }
   3114 }
   3115 
   3116 static AstNodeIndex parseContainerDeclAuto(Parser* p) {
   3117     const AstTokenIndex main_token = nextToken(p);
   3118     AstNodeIndex arg_expr = null_node;
   3119     switch (p->token_tags[main_token]) {
   3120     case TOKEN_KEYWORD_OPAQUE:
   3121         break;
   3122     case TOKEN_KEYWORD_STRUCT:
   3123     case TOKEN_KEYWORD_ENUM:
   3124         if (eatToken(p, TOKEN_L_PAREN) != null_token) {
   3125             arg_expr = expectExpr(p);
   3126             expectToken(p, TOKEN_R_PAREN);
   3127         }
   3128         break;
   3129     case TOKEN_KEYWORD_UNION:
   3130         if (eatToken(p, TOKEN_L_PAREN) != null_token) {
   3131             if (eatToken(p, TOKEN_KEYWORD_ENUM) != null_token) {
   3132                 if (eatToken(p, TOKEN_L_PAREN) != null_token) {
   3133                     const AstNodeIndex enum_tag_expr = expectExpr(p);
   3134                     expectToken(p, TOKEN_R_PAREN);
   3135                     expectToken(p, TOKEN_R_PAREN);
   3136                     expectToken(p, TOKEN_L_BRACE);
   3137                     const Members members = parseContainerMembers(p);
   3138                     const AstSubRange members_span = membersToSpan(members, p);
   3139                     expectToken(p, TOKEN_R_BRACE);
   3140                     return addNode(
   3141                         &p->nodes,
   3142                         (AstNodeItem) {
   3143                             .tag = members.trailing
   3144                                 ? AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING
   3145                                 : AST_NODE_TAGGED_UNION_ENUM_TAG,
   3146                             .main_token = main_token,
   3147                             .data = {
   3148                                 .lhs = enum_tag_expr,
   3149                                 .rhs = addExtra(p,
   3150                                     (AstNodeIndex[]) {
   3151                                         members_span.start,
   3152                                         members_span.end },
   3153                                     2),
   3154                             },
   3155                         });
   3156                 }
   3157                 expectToken(p, TOKEN_R_PAREN);
   3158                 expectToken(p, TOKEN_L_BRACE);
   3159                 const Members members = parseContainerMembers(p);
   3160                 expectToken(p, TOKEN_R_BRACE);
   3161                 if (members.len <= 2) {
   3162                     return addNode(&p->nodes,
   3163                         (AstNodeItem) {
   3164                             .tag = members.trailing
   3165                                 ? AST_NODE_TAGGED_UNION_TWO_TRAILING
   3166                                 : AST_NODE_TAGGED_UNION_TWO,
   3167                             .main_token = main_token,
   3168                             .data = { .lhs = members.lhs, .rhs = members.rhs },
   3169                         });
   3170                 }
   3171                 const AstSubRange span = membersToSpan(members, p);
   3172                 return addNode(&p->nodes,
   3173                     (AstNodeItem) {
   3174                         .tag = members.trailing
   3175                             ? AST_NODE_TAGGED_UNION_TRAILING
   3176                             : AST_NODE_TAGGED_UNION,
   3177                         .main_token = main_token,
   3178                         .data = { .lhs = span.start, .rhs = span.end },
   3179                     });
   3180             }
   3181             arg_expr = expectExpr(p);
   3182             expectToken(p, TOKEN_R_PAREN);
   3183         }
   3184         break;
   3185     default:
   3186         fail(p, "parseContainerDeclAuto: unexpected token");
   3187     }
   3188 
   3189     expectToken(p, TOKEN_L_BRACE);
   3190     const Members members = parseContainerMembers(p);
   3191     expectToken(p, TOKEN_R_BRACE);
   3192 
   3193     if (arg_expr == null_node) {
   3194         if (members.len <= 2) {
   3195             return addNode(&p->nodes,
   3196                 (AstNodeItem) {
   3197                     .tag = members.trailing
   3198                         ? AST_NODE_CONTAINER_DECL_TWO_TRAILING
   3199                         : AST_NODE_CONTAINER_DECL_TWO,
   3200                     .main_token = main_token,
   3201                     .data = { .lhs = members.lhs, .rhs = members.rhs },
   3202                 });
   3203         }
   3204         const AstSubRange span = membersToSpan(members, p);
   3205         return addNode(&p->nodes,
   3206             (AstNodeItem) {
   3207                 .tag = members.trailing ? AST_NODE_CONTAINER_DECL_TRAILING
   3208                                         : AST_NODE_CONTAINER_DECL,
   3209                 .main_token = main_token,
   3210                 .data = { .lhs = span.start, .rhs = span.end },
   3211             });
   3212     }
   3213 
   3214     const AstSubRange span = membersToSpan(members, p);
   3215     return addNode(
   3216         &p->nodes,
   3217         (AstNodeItem) {
   3218             .tag = members.trailing
   3219                 ? AST_NODE_CONTAINER_DECL_ARG_TRAILING
   3220                 : AST_NODE_CONTAINER_DECL_ARG,
   3221             .main_token = main_token,
   3222             .data = {
   3223                 .lhs = arg_expr,
   3224                 .rhs = addExtra(p,
   3225                     (AstNodeIndex[]) { span.start, span.end }, 2),
   3226             },
   3227         });
   3228 }
   3229 
   3230 static AstNodeIndex parseByteAlign(Parser* p) {
   3231     if (eatToken(p, TOKEN_KEYWORD_ALIGN) == null_token)
   3232         return null_node;
   3233     expectToken(p, TOKEN_L_PAREN);
   3234     const AstNodeIndex expr = expectExpr(p);
   3235     expectToken(p, TOKEN_R_PAREN);
   3236     return expr;
   3237 }
   3238 
   3239 static AstSubRange parseSwitchProngList(Parser* p) {
   3240     const uint32_t scratch_top = p->scratch.len;
   3241     while (true) {
   3242         if (eatToken(p, TOKEN_R_BRACE) != null_token)
   3243             break;
   3244         eatDocComments(p);
   3245         const AstNodeIndex case_node = parseSwitchProng(p);
   3246         if (case_node == 0)
   3247             break;
   3248         SLICE_APPEND(AstNodeIndex, &p->scratch, case_node);
   3249         if (p->token_tags[p->tok_i] == TOKEN_COMMA)
   3250             p->tok_i++;
   3251     }
   3252     const uint32_t cases_len = p->scratch.len - scratch_top;
   3253     const AstSubRange span
   3254         = listToSpan(p, &p->scratch.arr[scratch_top], cases_len);
   3255     p->scratch.len = scratch_top;
   3256     return span;
   3257 }
   3258 
   3259 static SmallSpan parseParamDeclList(Parser* p) {
   3260     expectToken(p, TOKEN_L_PAREN);
   3261 
   3262     const uint32_t scratch_top = p->scratch.len;
   3263 
   3264     // 0 = none, 1 = seen, 2 = nonfinal
   3265     int varargs = 0;
   3266 
   3267     while (true) {
   3268         if (eatToken(p, TOKEN_R_PAREN) != null_token)
   3269             break;
   3270         if (varargs == 1)
   3271             varargs = 2;
   3272 
   3273         if (p->token_tags[p->tok_i] == TOKEN_ELLIPSIS3) {
   3274             p->tok_i++;
   3275             if (varargs == 0)
   3276                 varargs = 1;
   3277             if (eatToken(p, TOKEN_R_PAREN) != null_token)
   3278                 break;
   3279             expectToken(p, TOKEN_COMMA);
   3280             continue;
   3281         }
   3282 
   3283         const AstNodeIndex type_expr = expectParamDecl(p);
   3284         if (type_expr != 0)
   3285             SLICE_APPEND(AstNodeIndex, &p->scratch, type_expr);
   3286 
   3287         if (p->token_tags[p->tok_i] == TOKEN_COMMA) {
   3288             p->tok_i++;
   3289             continue;
   3290         }
   3291         expectToken(p, TOKEN_R_PAREN);
   3292         break;
   3293     }
   3294 
   3295     if (varargs == 2) {
   3296         fail(p, "varargs_nonfinal");
   3297     }
   3298 
   3299     const uint32_t params_len = p->scratch.len - scratch_top;
   3300     p->scratch.len = scratch_top;
   3301     switch (params_len) {
   3302     case 0:
   3303         return (SmallSpan) {
   3304             .tag = SMALL_SPAN_ZERO_OR_ONE,
   3305             .payload = { .zero_or_one = 0 },
   3306         };
   3307     case 1:
   3308         return (SmallSpan) {
   3309             .tag = SMALL_SPAN_ZERO_OR_ONE,
   3310             .payload = { .zero_or_one = p->scratch.arr[scratch_top] },
   3311         };
   3312     default:;
   3313         const AstSubRange span
   3314             = listToSpan(p, &p->scratch.arr[scratch_top], params_len);
   3315         return (SmallSpan) {
   3316             .tag = SMALL_SPAN_MULTI,
   3317             .payload = { .multi = span },
   3318         };
   3319     }
   3320 }
   3321 
   3322 static AstNodeIndex parseBuiltinCall(Parser* p) {
   3323     const AstTokenIndex builtin_token = assertToken(p, TOKEN_BUILTIN);
   3324     assertToken(p, TOKEN_L_PAREN);
   3325 
   3326     const uint32_t scratch_top = p->scratch.len;
   3327 
   3328     while (true) {
   3329         if (eatToken(p, TOKEN_R_PAREN) != null_token)
   3330             break;
   3331 
   3332         const AstNodeIndex param = expectExpr(p);
   3333         SLICE_APPEND(AstNodeIndex, &p->scratch, param);
   3334         switch (p->token_tags[p->tok_i]) {
   3335         case TOKEN_COMMA:
   3336             p->tok_i++;
   3337             break;
   3338         case TOKEN_R_PAREN:
   3339             p->tok_i++;
   3340             goto end_loop;
   3341         default:
   3342             fail(p, "expected comma after arg");
   3343         }
   3344     }
   3345 end_loop:;
   3346 
   3347     const bool comma = (p->token_tags[p->tok_i - 2] == TOKEN_COMMA);
   3348     const uint32_t params_len = p->scratch.len - scratch_top;
   3349     p->scratch.len = scratch_top;
   3350     switch (params_len) {
   3351     case 0:
   3352         return addNode(&p->nodes,
   3353             (AstNodeItem) {
   3354                 .tag = AST_NODE_BUILTIN_CALL_TWO,
   3355                 .main_token = builtin_token,
   3356                 .data = {
   3357                     .lhs = 0,
   3358                     .rhs = 0,
   3359                 },
   3360             });
   3361     case 1:
   3362         return addNode(&p->nodes,
   3363             (AstNodeItem) {
   3364                 .tag = comma ?
   3365                     AST_NODE_BUILTIN_CALL_TWO_COMMA :
   3366                     AST_NODE_BUILTIN_CALL_TWO,
   3367                 .main_token = builtin_token,
   3368                 .data = {
   3369                     .lhs = p->scratch.arr[scratch_top],
   3370                     .rhs = 0,
   3371                 },
   3372             });
   3373     case 2:
   3374         return addNode(&p->nodes,
   3375             (AstNodeItem) {
   3376                 .tag = comma ?
   3377                     AST_NODE_BUILTIN_CALL_TWO_COMMA :
   3378                     AST_NODE_BUILTIN_CALL_TWO,
   3379                 .main_token = builtin_token,
   3380                 .data = {
   3381                     .lhs = p->scratch.arr[scratch_top],
   3382                     .rhs = p->scratch.arr[scratch_top+1],
   3383                 },
   3384             });
   3385     default:;
   3386         const AstSubRange span
   3387             = listToSpan(p, &p->scratch.arr[scratch_top], params_len);
   3388         return addNode(
   3389                 &p->nodes,
   3390                 (AstNodeItem) {
   3391                     .tag = comma ?
   3392                         AST_NODE_BUILTIN_CALL_COMMA :
   3393                         AST_NODE_BUILTIN_CALL,
   3394                     .main_token = builtin_token,
   3395                     .data = {
   3396                         .lhs = span.start,
   3397                         .rhs = span.end,
   3398                     },
   3399                 });
   3400     }
   3401 }
   3402 
   3403 static AstTokenIndex eatDocComments(Parser* p) {
   3404     AstTokenIndex first = null_token;
   3405     AstTokenIndex tok;
   3406     while ((tok = eatToken(p, TOKEN_DOC_COMMENT)) != null_token) {
   3407         if (first == null_token) {
   3408             if (tok > 0 && tokensOnSameLine(p, tok - 1, tok)) {
   3409                 fail(p, "same_line_doc_comment");
   3410             }
   3411             first = tok;
   3412         }
   3413     }
   3414     return first;
   3415 }
   3416 
   3417 static bool tokensOnSameLine(
   3418     Parser* p, AstTokenIndex tok1, AstTokenIndex tok2) {
   3419     const uint32_t start1 = p->token_starts[tok1];
   3420     const uint32_t start2 = p->token_starts[tok2];
   3421     for (uint32_t i = start1; i < start2; i++) {
   3422         if (p->source[i] == '\n')
   3423             return false;
   3424     }
   3425     return true;
   3426 }
   3427 
   3428 static AstTokenIndex eatToken(Parser* p, TokenizerTag tag) {
   3429     if (p->token_tags[p->tok_i] == tag) {
   3430         return nextToken(p);
   3431     } else {
   3432         return null_token;
   3433     }
   3434 }
   3435 
   3436 static AstTokenIndex assertToken(Parser* p, TokenizerTag tag) {
   3437     const AstTokenIndex token = nextToken(p);
   3438     if (p->token_tags[token] != tag) {
   3439         fail(p, "unexpected token");
   3440     }
   3441     return token;
   3442 }
   3443 
   3444 static AstTokenIndex expectToken(Parser* p, TokenizerTag tag) {
   3445     if (p->token_tags[p->tok_i] == tag) {
   3446         return nextToken(p);
   3447     } else {
   3448         fail(p, "unexpected token");
   3449     }
   3450     return 0; // tcc
   3451 }
   3452 
   3453 static AstNodeIndex expectSemicolon(Parser* p) {
   3454     return expectToken(p, TOKEN_SEMICOLON);
   3455 }
   3456 
   3457 static AstTokenIndex nextToken(Parser* p) { return p->tok_i++; }