zig0

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

blob c118700a (86765B) - Raw


      1 #include "common.h"
      2 
      3 #include <assert.h>
      4 #include <stdio.h>
      5 #include <stdlib.h>
      6 #include <string.h>
      7 
      8 #include "ast.h"
      9 #include "parser.h"
     10 
     11 const AstNodeIndex null_node = 0;
     12 const AstTokenIndex null_token = ~(AstTokenIndex)(0);
     13 
     14 // OPT encodes a node index as OptionalIndex: 0 → ~0 (none)
     15 #define OPT(x) ((x) == 0 ? ~(AstNodeIndex)0 : (x))
     16 
     17 typedef struct {
     18     uint32_t len;
     19     AstNodeIndex lhs;
     20     AstNodeIndex rhs;
     21     bool trailing;
     22 } Members;
     23 
     24 static AstNodeIndex parsePrefixExpr(Parser*);
     25 static AstNodeIndex parseTypeExpr(Parser*);
     26 static AstNodeIndex parseBlock(Parser* p);
     27 static AstNodeIndex parseLabeledStatement(Parser*);
     28 static AstNodeIndex parseExpr(Parser*);
     29 static AstNodeIndex expectExpr(Parser*);
     30 static AstNodeIndex expectSemicolon(Parser*);
     31 static AstTokenIndex expectToken(Parser*, TokenizerTag);
     32 static AstNodeIndex parseFnProto(Parser*);
     33 static Members parseContainerMembers(Parser*);
     34 static AstNodeIndex parseInitList(Parser*, AstNodeIndex, AstTokenIndex);
     35 static AstNodeIndex expectBlockExprStatement(Parser*);
     36 
     37 typedef struct {
     38     enum { FIELD_STATE_NONE, FIELD_STATE_SEEN, FIELD_STATE_END } tag;
     39     union {
     40         uint32_t end;
     41     } payload;
     42 } FieldState;
     43 
     44 typedef struct {
     45     enum { SMALL_SPAN_ZERO_OR_ONE, SMALL_SPAN_MULTI } tag;
     46     union {
     47         AstNodeIndex zero_or_one;
     48         AstSubRange multi;
     49     } payload;
     50 } SmallSpan;
     51 
     52 typedef struct {
     53     AstNodeIndexSlice* scratch;
     54     uint32_t old_len;
     55 } CleanupScratch;
     56 
     57 static CleanupScratch initCleanupScratch(Parser* p) {
     58     return (CleanupScratch) {
     59         .scratch = &p->scratch,
     60         .old_len = p->scratch.len,
     61     };
     62 }
     63 
     64 static void cleanupScratch(CleanupScratch* c) { c->scratch->len = c->old_len; }
     65 
     66 static AstSubRange listToSpan(
     67     Parser* p, const AstNodeIndex* list, uint32_t count) {
     68     SLICE_ENSURE_CAPACITY(AstNodeIndex, &p->extra_data, count);
     69     memcpy(p->extra_data.arr + p->extra_data.len, list,
     70         count * sizeof(AstNodeIndex));
     71     p->extra_data.len += count;
     72     return (AstSubRange) {
     73         .start = p->extra_data.len - count,
     74         .end = p->extra_data.len,
     75     };
     76 }
     77 
     78 static AstSubRange membersToSpan(const Members self, Parser* p) {
     79     if (self.len <= 2) {
     80         const AstNodeIndex nodes[] = { self.lhs, self.rhs };
     81         return listToSpan(p, nodes, self.len);
     82     } else {
     83         return (AstSubRange) { .start = self.lhs, .end = self.rhs };
     84     }
     85 }
     86 
     87 static AstTokenIndex nextToken(Parser* p) { return p->tok_i++; }
     88 
     89 static AstTokenIndex eatToken(Parser* p, TokenizerTag tag) {
     90     if (p->token_tags[p->tok_i] == tag) {
     91         return nextToken(p);
     92     } else {
     93         return null_token;
     94     }
     95 }
     96 
     97 static AstTokenIndex assertToken(Parser* p, TokenizerTag tag) {
     98     const AstTokenIndex token = nextToken(p);
     99     assert(p->token_tags[token] == tag);
    100     return token;
    101 }
    102 
    103 static void eatDocComments(Parser* p) {
    104     while (eatToken(p, TOKEN_DOC_COMMENT) != null_token) { }
    105 }
    106 
    107 static AstNodeIndex setNode(Parser* p, uint32_t i, AstNodeItem item) {
    108     p->nodes.tags[i] = item.tag;
    109     p->nodes.main_tokens[i] = item.main_token;
    110     p->nodes.datas[i] = item.data;
    111     return i;
    112 }
    113 
    114 static void astNodeListEnsureCapacity(AstNodeList* list, uint32_t additional) {
    115     const uint32_t new_len = list->len + additional;
    116     if (new_len <= list->cap) {
    117         return;
    118     }
    119 
    120     const uint32_t new_cap = new_len > list->cap * 2 ? new_len : list->cap * 2;
    121     list->tags = realloc(list->tags, new_cap * sizeof(AstNodeTag));
    122     list->main_tokens
    123         = realloc(list->main_tokens, new_cap * sizeof(AstTokenIndex));
    124     list->datas = realloc(list->datas, new_cap * sizeof(AstData));
    125     if (!list->tags || !list->main_tokens || !list->datas)
    126         exit(1);
    127     list->cap = new_cap;
    128 }
    129 
    130 static AstNodeIndex addNode(AstNodeList* nodes, AstNodeItem item) {
    131     astNodeListEnsureCapacity(nodes, 1);
    132     nodes->tags[nodes->len] = item.tag;
    133     nodes->main_tokens[nodes->len] = item.main_token;
    134     nodes->datas[nodes->len] = item.data;
    135     return nodes->len++;
    136 }
    137 
    138 static AstNodeIndex addExtra(
    139     Parser* p, const AstNodeIndex* extra, uint32_t count) {
    140     const AstNodeIndex result = p->extra_data.len;
    141     SLICE_ENSURE_CAPACITY(AstNodeIndex, &p->extra_data, count);
    142     memcpy(p->extra_data.arr + p->extra_data.len, extra,
    143         count * sizeof(AstNodeIndex));
    144     p->extra_data.len += count;
    145     return result;
    146 }
    147 
    148 static AstNodeIndex parseByteAlign(Parser* p) {
    149     if (eatToken(p, TOKEN_KEYWORD_ALIGN) == null_token)
    150         return null_node;
    151     expectToken(p, TOKEN_L_PAREN);
    152     const AstNodeIndex expr = expectExpr(p);
    153     expectToken(p, TOKEN_R_PAREN);
    154     return expr;
    155 }
    156 
    157 static AstNodeIndex parseAddrSpace(Parser* p) {
    158     if (eatToken(p, TOKEN_KEYWORD_ADDRSPACE) == null_token)
    159         return null_node;
    160     expectToken(p, TOKEN_L_PAREN);
    161     const AstNodeIndex expr = expectExpr(p);
    162     expectToken(p, TOKEN_R_PAREN);
    163     return expr;
    164 }
    165 
    166 static AstNodeIndex parseLinkSection(Parser* p) {
    167     if (eatToken(p, TOKEN_KEYWORD_LINKSECTION) == null_token)
    168         return null_node;
    169     expectToken(p, TOKEN_L_PAREN);
    170     const AstNodeIndex expr = expectExpr(p);
    171     expectToken(p, TOKEN_R_PAREN);
    172     return expr;
    173 }
    174 
    175 static AstNodeIndex parseCallconv(Parser* p) {
    176     if (eatToken(p, TOKEN_KEYWORD_CALLCONV) == null_token)
    177         return null_node;
    178     expectToken(p, TOKEN_L_PAREN);
    179     const AstNodeIndex expr = expectExpr(p);
    180     expectToken(p, TOKEN_R_PAREN);
    181     return expr;
    182 }
    183 
    184 typedef struct {
    185     AstNodeIndex align_expr, value_expr;
    186 } NodeContainerField;
    187 
    188 static AstNodeIndex expectContainerField(Parser* p) {
    189     eatToken(p, TOKEN_KEYWORD_COMPTIME);
    190     const AstTokenIndex main_token = p->tok_i;
    191     if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER
    192         && p->token_tags[p->tok_i + 1] == TOKEN_COLON)
    193         p->tok_i += 2;
    194 
    195     const AstNodeIndex type_expr = parseTypeExpr(p);
    196     const AstNodeIndex align_expr = parseByteAlign(p);
    197     const AstNodeIndex value_expr
    198         = eatToken(p, TOKEN_EQUAL) != null_token ? expectExpr(p) : 0;
    199 
    200     if (align_expr == 0) {
    201         return addNode(
    202             &p->nodes,
    203             (AstNodeItem) {
    204                 .tag = AST_NODE_CONTAINER_FIELD_INIT,
    205                 .main_token = main_token,
    206                 .data = {
    207                     .lhs = type_expr,
    208                     .rhs = value_expr,
    209                 },
    210             });
    211     } else if (value_expr == 0) {
    212         return addNode(
    213             &p->nodes,
    214             (AstNodeItem) {
    215                 .tag = AST_NODE_CONTAINER_FIELD_ALIGN,
    216                 .main_token = main_token,
    217                 .data = {
    218                     .lhs = type_expr,
    219                     .rhs = align_expr,
    220                 },
    221             });
    222     } else {
    223         return addNode(
    224             &p->nodes,
    225             (AstNodeItem) {
    226                 .tag = AST_NODE_CONTAINER_FIELD,
    227                 .main_token = main_token,
    228                 .data = {
    229                     .lhs = type_expr,
    230                     .rhs = addExtra(p, (AstNodeIndex[]) { align_expr, value_expr }, 2),
    231                 },
    232             });
    233     }
    234 }
    235 
    236 static AstNodeIndex parseBuiltinCall(Parser* p) {
    237     const AstTokenIndex builtin_token = assertToken(p, TOKEN_BUILTIN);
    238     assertToken(p, TOKEN_L_PAREN);
    239 
    240     CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch)))
    241     = initCleanupScratch(p);
    242 
    243     while (true) {
    244         if (eatToken(p, TOKEN_R_PAREN) != null_token)
    245             break;
    246 
    247         const AstNodeIndex param = expectExpr(p);
    248         SLICE_APPEND(AstNodeIndex, &p->scratch, param);
    249         switch (p->token_tags[p->tok_i]) {
    250         case TOKEN_COMMA:
    251             p->tok_i++;
    252             break;
    253         case TOKEN_R_PAREN:
    254             p->tok_i++;
    255             goto end_loop;
    256         default:
    257             fprintf(stderr, "expected comma after arg\n");
    258             exit(1);
    259         }
    260     }
    261 end_loop:;
    262 
    263     const bool comma = (p->token_tags[p->tok_i - 2] == TOKEN_COMMA);
    264     const uint32_t params_len = p->scratch.len - scratch_top.old_len;
    265     switch (params_len) {
    266     case 0:
    267         return addNode(&p->nodes,
    268             (AstNodeItem) {
    269                 .tag = AST_NODE_BUILTIN_CALL_TWO,
    270                 .main_token = builtin_token,
    271                 .data = {
    272                     .lhs = 0,
    273                     .rhs = 0,
    274                 },
    275             });
    276     case 1:
    277         return addNode(&p->nodes,
    278             (AstNodeItem) {
    279                 .tag = comma ?
    280                     AST_NODE_BUILTIN_CALL_TWO_COMMA :
    281                     AST_NODE_BUILTIN_CALL_TWO,
    282                 .main_token = builtin_token,
    283                 .data = {
    284                     .lhs = p->scratch.arr[scratch_top.old_len],
    285                     .rhs = 0,
    286                 },
    287             });
    288     case 2:
    289         return addNode(&p->nodes,
    290             (AstNodeItem) {
    291                 .tag = comma ?
    292                     AST_NODE_BUILTIN_CALL_TWO_COMMA :
    293                     AST_NODE_BUILTIN_CALL_TWO,
    294                 .main_token = builtin_token,
    295                 .data = {
    296                     .lhs = p->scratch.arr[scratch_top.old_len],
    297                     .rhs = p->scratch.arr[scratch_top.old_len+1],
    298                 },
    299             });
    300     default:;
    301         const AstSubRange span
    302             = listToSpan(p, &p->scratch.arr[scratch_top.old_len], params_len);
    303         return addNode(
    304                 &p->nodes,
    305                 (AstNodeItem) {
    306                     .tag = comma ?
    307                         AST_NODE_BUILTIN_CALL_COMMA :
    308                         AST_NODE_BUILTIN_CALL,
    309                     .main_token = builtin_token,
    310                     .data = {
    311                         .lhs = span.start,
    312                         .rhs = span.end,
    313                     },
    314                 });
    315     }
    316 }
    317 
    318 static AstNodeIndex parseContainerDeclAuto(Parser* p) {
    319     const AstTokenIndex main_token = nextToken(p);
    320     AstNodeIndex arg_expr = null_node;
    321     switch (p->token_tags[main_token]) {
    322     case TOKEN_KEYWORD_OPAQUE:
    323         break;
    324     case TOKEN_KEYWORD_STRUCT:
    325     case TOKEN_KEYWORD_ENUM:
    326         if (eatToken(p, TOKEN_L_PAREN) != null_token) {
    327             arg_expr = expectExpr(p);
    328             expectToken(p, TOKEN_R_PAREN);
    329         }
    330         break;
    331     case TOKEN_KEYWORD_UNION:
    332         if (eatToken(p, TOKEN_L_PAREN) != null_token) {
    333             if (eatToken(p, TOKEN_KEYWORD_ENUM) != null_token) {
    334                 if (eatToken(p, TOKEN_L_PAREN) != null_token) {
    335                     const AstNodeIndex enum_tag_expr = expectExpr(p);
    336                     expectToken(p, TOKEN_R_PAREN);
    337                     expectToken(p, TOKEN_R_PAREN);
    338                     expectToken(p, TOKEN_L_BRACE);
    339                     const Members members = parseContainerMembers(p);
    340                     const AstSubRange members_span = membersToSpan(members, p);
    341                     expectToken(p, TOKEN_R_BRACE);
    342                     return addNode(
    343                         &p->nodes,
    344                         (AstNodeItem) {
    345                             .tag = members.trailing
    346                                 ? AST_NODE_TAGGED_UNION_ENUM_TAG_TRAILING
    347                                 : AST_NODE_TAGGED_UNION_ENUM_TAG,
    348                             .main_token = main_token,
    349                             .data = {
    350                                 .lhs = enum_tag_expr,
    351                                 .rhs = addExtra(p,
    352                                     (AstNodeIndex[]) {
    353                                         members_span.start,
    354                                         members_span.end },
    355                                     2),
    356                             },
    357                         });
    358                 }
    359                 expectToken(p, TOKEN_R_PAREN);
    360                 expectToken(p, TOKEN_L_BRACE);
    361                 const Members members = parseContainerMembers(p);
    362                 expectToken(p, TOKEN_R_BRACE);
    363                 if (members.len <= 2) {
    364                     return addNode(&p->nodes,
    365                         (AstNodeItem) {
    366                             .tag = members.trailing
    367                                 ? AST_NODE_TAGGED_UNION_TWO_TRAILING
    368                                 : AST_NODE_TAGGED_UNION_TWO,
    369                             .main_token = main_token,
    370                             .data = { .lhs = members.lhs, .rhs = members.rhs },
    371                         });
    372                 }
    373                 const AstSubRange span = membersToSpan(members, p);
    374                 return addNode(&p->nodes,
    375                     (AstNodeItem) {
    376                         .tag = members.trailing
    377                             ? AST_NODE_TAGGED_UNION_TRAILING
    378                             : AST_NODE_TAGGED_UNION,
    379                         .main_token = main_token,
    380                         .data = { .lhs = span.start, .rhs = span.end },
    381                     });
    382             }
    383             arg_expr = expectExpr(p);
    384             expectToken(p, TOKEN_R_PAREN);
    385         }
    386         break;
    387     default:
    388         fprintf(stderr, "parseContainerDeclAuto: unexpected token\n");
    389         exit(1);
    390     }
    391 
    392     expectToken(p, TOKEN_L_BRACE);
    393     const Members members = parseContainerMembers(p);
    394     expectToken(p, TOKEN_R_BRACE);
    395 
    396     if (arg_expr == null_node) {
    397         if (members.len <= 2) {
    398             return addNode(&p->nodes,
    399                 (AstNodeItem) {
    400                     .tag = members.trailing
    401                         ? AST_NODE_CONTAINER_DECL_TWO_TRAILING
    402                         : AST_NODE_CONTAINER_DECL_TWO,
    403                     .main_token = main_token,
    404                     .data = { .lhs = members.lhs, .rhs = members.rhs },
    405                 });
    406         }
    407         const AstSubRange span = membersToSpan(members, p);
    408         return addNode(&p->nodes,
    409             (AstNodeItem) {
    410                 .tag = members.trailing ? AST_NODE_CONTAINER_DECL_TRAILING
    411                                         : AST_NODE_CONTAINER_DECL,
    412                 .main_token = main_token,
    413                 .data = { .lhs = span.start, .rhs = span.end },
    414             });
    415     }
    416 
    417     const AstSubRange span = membersToSpan(members, p);
    418     return addNode(
    419         &p->nodes,
    420         (AstNodeItem) {
    421             .tag = members.trailing
    422                 ? AST_NODE_CONTAINER_DECL_ARG_TRAILING
    423                 : AST_NODE_CONTAINER_DECL_ARG,
    424             .main_token = main_token,
    425             .data = {
    426                 .lhs = arg_expr,
    427                 .rhs = addExtra(p,
    428                     (AstNodeIndex[]) { span.start, span.end }, 2),
    429             },
    430         });
    431 }
    432 
    433 static AstNodeIndex parsePrimaryTypeExpr(Parser* p) {
    434     const TokenizerTag tok = p->token_tags[p->tok_i];
    435     switch (tok) {
    436     case TOKEN_CHAR_LITERAL:
    437         return addNode(&p->nodes,
    438             (AstNodeItem) {
    439                 .tag = AST_NODE_CHAR_LITERAL,
    440                 .main_token = nextToken(p),
    441                 .data = {},
    442             });
    443     case TOKEN_NUMBER_LITERAL:
    444         return addNode(&p->nodes,
    445             (AstNodeItem) {
    446                 .tag = AST_NODE_NUMBER_LITERAL,
    447                 .main_token = nextToken(p),
    448                 .data = {},
    449             });
    450     case TOKEN_KEYWORD_UNREACHABLE:
    451         return addNode(&p->nodes,
    452             (AstNodeItem) {
    453                 .tag = AST_NODE_UNREACHABLE_LITERAL,
    454                 .main_token = nextToken(p),
    455                 .data = {},
    456             });
    457     case TOKEN_KEYWORD_ANYFRAME:
    458         fprintf(stderr, "parsePrimaryTypeExpr does not support %s\n",
    459             tokenizerGetTagString(tok));
    460         exit(1);
    461     case TOKEN_STRING_LITERAL:
    462         return addNode(&p->nodes,
    463             (AstNodeItem) {
    464                 .tag = AST_NODE_STRING_LITERAL,
    465                 .main_token = nextToken(p),
    466                 .data = {},
    467             });
    468     case TOKEN_BUILTIN:
    469         return parseBuiltinCall(p);
    470     case TOKEN_KEYWORD_FN:
    471         return parseFnProto(p);
    472     case TOKEN_KEYWORD_IF:
    473     case TOKEN_KEYWORD_SWITCH:
    474         fprintf(stderr, "parsePrimaryTypeExpr does not support %s\n",
    475             tokenizerGetTagString(tok));
    476         exit(1);
    477     case TOKEN_KEYWORD_EXTERN:
    478     case TOKEN_KEYWORD_PACKED:
    479         // extern/packed can precede struct/union/enum
    480         switch (p->token_tags[p->tok_i + 1]) {
    481         case TOKEN_KEYWORD_STRUCT:
    482         case TOKEN_KEYWORD_UNION:
    483         case TOKEN_KEYWORD_ENUM:
    484             p->tok_i++; // consume extern/packed
    485             return parseContainerDeclAuto(p);
    486         default:
    487             fprintf(stderr, "parsePrimaryTypeExpr does not support %s\n",
    488                 tokenizerGetTagString(tok));
    489             exit(1);
    490         }
    491     case TOKEN_KEYWORD_STRUCT:
    492     case TOKEN_KEYWORD_OPAQUE:
    493     case TOKEN_KEYWORD_ENUM:
    494     case TOKEN_KEYWORD_UNION:
    495         return parseContainerDeclAuto(p);
    496     case TOKEN_KEYWORD_COMPTIME:
    497         return addNode(&p->nodes,
    498             (AstNodeItem) {
    499                 .tag = AST_NODE_COMPTIME,
    500                 .main_token = nextToken(p),
    501                 .data = { .lhs = parseTypeExpr(p), .rhs = 0 },
    502             });
    503     case TOKEN_MULTILINE_STRING_LITERAL_LINE: {
    504         const AstTokenIndex first = nextToken(p);
    505         AstTokenIndex last = first;
    506         while (p->token_tags[p->tok_i] == TOKEN_MULTILINE_STRING_LITERAL_LINE)
    507             last = nextToken(p);
    508         return addNode(&p->nodes,
    509             (AstNodeItem) {
    510                 .tag = AST_NODE_MULTILINE_STRING_LITERAL,
    511                 .main_token = first,
    512                 .data = { .lhs = first, .rhs = last },
    513             });
    514     }
    515     case TOKEN_IDENTIFIER:
    516         return addNode(&p->nodes,
    517             (AstNodeItem) {
    518                 .tag = AST_NODE_IDENTIFIER,
    519                 .main_token = nextToken(p),
    520                 .data = {},
    521             });
    522     case TOKEN_KEYWORD_INLINE:
    523     case TOKEN_KEYWORD_FOR:
    524     case TOKEN_KEYWORD_WHILE:
    525     case TOKEN_PERIOD:
    526         switch (p->token_tags[p->tok_i + 1]) {
    527         case TOKEN_IDENTIFIER: {
    528             const AstTokenIndex dot = nextToken(p);
    529             return addNode(&p->nodes,
    530                 (AstNodeItem) {
    531                     .tag = AST_NODE_ENUM_LITERAL,
    532                     .main_token = nextToken(p),
    533                     .data = { .lhs = dot, .rhs = 0 },
    534                 });
    535         }
    536         case TOKEN_L_BRACE: {
    537             // Anonymous struct/array init: .{ ... }
    538             const AstTokenIndex lbrace = p->tok_i + 1;
    539             p->tok_i = lbrace + 1;
    540             return parseInitList(p, null_node, lbrace);
    541         }
    542         default:
    543             fprintf(stderr,
    544                 "parsePrimaryTypeExpr: unsupported period suffix %s\n",
    545                 tokenizerGetTagString(p->token_tags[p->tok_i + 1]));
    546             exit(1);
    547         }
    548         return 0; // tcc
    549     case TOKEN_KEYWORD_ERROR:
    550         fprintf(stderr, "parsePrimaryTypeExpr does not support %s\n",
    551             tokenizerGetTagString(tok));
    552         exit(1);
    553     case TOKEN_L_PAREN: {
    554         const AstTokenIndex lparen = nextToken(p);
    555         const AstNodeIndex inner = expectExpr(p);
    556         const AstTokenIndex rparen = expectToken(p, TOKEN_R_PAREN);
    557         return addNode(&p->nodes,
    558             (AstNodeItem) {
    559                 .tag = AST_NODE_GROUPED_EXPRESSION,
    560                 .main_token = lparen,
    561                 .data = { .lhs = inner, .rhs = rparen },
    562             });
    563     }
    564     default:
    565         return null_node;
    566     }
    567 }
    568 
    569 static AstNodeIndex parseSuffixOp(Parser* p, AstNodeIndex lhs) {
    570     const TokenizerTag tok = p->token_tags[p->tok_i];
    571     switch (tok) {
    572     case TOKEN_L_BRACKET: {
    573         const AstTokenIndex lbracket = nextToken(p);
    574         const AstNodeIndex index_expr = expectExpr(p);
    575         switch (p->token_tags[p->tok_i]) {
    576         case TOKEN_R_BRACKET:
    577             p->tok_i++;
    578             return addNode(&p->nodes,
    579                 (AstNodeItem) {
    580                     .tag = AST_NODE_ARRAY_ACCESS,
    581                     .main_token = lbracket,
    582                     .data = { .lhs = lhs, .rhs = index_expr },
    583                 });
    584         case TOKEN_ELLIPSIS2: {
    585             p->tok_i++; // consume ..
    586             const AstNodeIndex end_expr = parseExpr(p);
    587             if (eatToken(p, TOKEN_COLON) != null_token) {
    588                 const AstNodeIndex sentinel = expectExpr(p);
    589                 expectToken(p, TOKEN_R_BRACKET);
    590                 // end_expr 0 means "no end" — encode as ~0 for
    591                 // OptionalIndex.none
    592                 const AstNodeIndex opt_end
    593                     = end_expr == 0 ? ~(AstNodeIndex)0 : end_expr;
    594                 return addNode(&p->nodes,
    595                     (AstNodeItem) {
    596                         .tag = AST_NODE_SLICE_SENTINEL,
    597                         .main_token = lbracket,
    598                         .data = {
    599                             .lhs = lhs,
    600                             .rhs = addExtra(p,
    601                                 (AstNodeIndex[]) {
    602                                     index_expr, opt_end, sentinel },
    603                                 3),
    604                         },
    605                     });
    606             }
    607             expectToken(p, TOKEN_R_BRACKET);
    608             if (end_expr == 0) {
    609                 return addNode(&p->nodes,
    610                     (AstNodeItem) {
    611                         .tag = AST_NODE_SLICE_OPEN,
    612                         .main_token = lbracket,
    613                         .data = { .lhs = lhs, .rhs = index_expr },
    614                     });
    615             }
    616             return addNode(&p->nodes,
    617                 (AstNodeItem) {
    618                     .tag = AST_NODE_SLICE,
    619                     .main_token = lbracket,
    620                     .data = {
    621                         .lhs = lhs,
    622                         .rhs = addExtra(p,
    623                             (AstNodeIndex[]) { index_expr, end_expr }, 2),
    624                     },
    625                 });
    626         }
    627         default:
    628             fprintf(
    629                 stderr, "parseSuffixOp: expected ] or .. after index expr\n");
    630             exit(1);
    631         }
    632         return 0; // tcc
    633     }
    634     case TOKEN_PERIOD_ASTERISK:
    635     case TOKEN_INVALID_PERIODASTERISKS:
    636         fprintf(stderr, "parseSuffixOp does not support %s\n",
    637             tokenizerGetTagString(tok));
    638         exit(1);
    639     case TOKEN_PERIOD:
    640         if (p->token_tags[p->tok_i + 1] == TOKEN_IDENTIFIER) {
    641             const AstTokenIndex dot = nextToken(p);
    642             return addNode(&p->nodes,
    643                 (AstNodeItem) {
    644                     .tag = AST_NODE_FIELD_ACCESS,
    645                     .main_token = dot,
    646                     .data = { .lhs = lhs, .rhs = nextToken(p) },
    647                 });
    648         }
    649         if (p->token_tags[p->tok_i + 1] == TOKEN_ASTERISK) {
    650             const AstTokenIndex dot = nextToken(p);
    651             nextToken(p); // consume the *
    652             return addNode(&p->nodes,
    653                 (AstNodeItem) {
    654                     .tag = AST_NODE_DEREF,
    655                     .main_token = dot,
    656                     .data = { .lhs = lhs, .rhs = 0 },
    657                 });
    658         }
    659         if (p->token_tags[p->tok_i + 1] == TOKEN_QUESTION_MARK) {
    660             const AstTokenIndex dot = nextToken(p);
    661             return addNode(&p->nodes,
    662                 (AstNodeItem) {
    663                     .tag = AST_NODE_UNWRAP_OPTIONAL,
    664                     .main_token = dot,
    665                     .data = { .lhs = lhs, .rhs = nextToken(p) },
    666                 });
    667         }
    668         fprintf(stderr, "parseSuffixOp: unsupported period suffix\n");
    669         exit(1);
    670         return 0; // tcc
    671     default:
    672         return null_node;
    673     }
    674 }
    675 
    676 static AstNodeIndex parseSuffixExpr(Parser* p) {
    677     if (eatToken(p, TOKEN_KEYWORD_ASYNC) != null_token) {
    678         fprintf(stderr, "async not supported\n");
    679         exit(1);
    680     }
    681 
    682     AstNodeIndex res = parsePrimaryTypeExpr(p);
    683     if (res == 0)
    684         return res;
    685 
    686     while (true) {
    687         const AstNodeIndex suffix_op = parseSuffixOp(p, res);
    688         if (suffix_op != 0) {
    689             res = suffix_op;
    690             continue;
    691         }
    692         const AstTokenIndex lparen = eatToken(p, TOKEN_L_PAREN);
    693         if (lparen == null_token)
    694             return res;
    695 
    696         CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch)))
    697         = initCleanupScratch(p);
    698         while (true) {
    699             if (eatToken(p, TOKEN_R_PAREN) != null_token)
    700                 break;
    701             const AstNodeIndex arg = expectExpr(p);
    702             SLICE_APPEND(AstNodeIndex, &p->scratch, arg);
    703             if (p->token_tags[p->tok_i] == TOKEN_COMMA) {
    704                 p->tok_i++;
    705                 continue;
    706             }
    707             expectToken(p, TOKEN_R_PAREN);
    708             break;
    709         }
    710 
    711         const bool comma = p->token_tags[p->tok_i - 2] == TOKEN_COMMA;
    712         const uint32_t params_len = p->scratch.len - scratch_top.old_len;
    713         switch (params_len) {
    714         case 0:
    715             res = addNode(
    716                 &p->nodes,
    717                 (AstNodeItem) {
    718                     .tag = comma ? AST_NODE_CALL_ONE_COMMA : AST_NODE_CALL_ONE,
    719                     .main_token = lparen,
    720                     .data = {
    721                         .lhs = res,
    722                         .rhs = 0,
    723                     },
    724                 });
    725             break;
    726         case 1:
    727             res = addNode(
    728                 &p->nodes,
    729                 (AstNodeItem) {
    730                     .tag = comma ? AST_NODE_CALL_ONE_COMMA : AST_NODE_CALL_ONE,
    731                     .main_token = lparen,
    732                     .data = {
    733                         .lhs = res,
    734                         .rhs = p->scratch.arr[scratch_top.old_len],
    735                     },
    736                 });
    737             break;
    738         default:;
    739             const AstSubRange span = listToSpan(
    740                 p, &p->scratch.arr[scratch_top.old_len], params_len);
    741             res = addNode(
    742                 &p->nodes,
    743                 (AstNodeItem) {
    744                     .tag = comma ? AST_NODE_CALL_COMMA : AST_NODE_CALL,
    745                     .main_token = lparen,
    746                     .data = {
    747                         .lhs = res,
    748                         .rhs = addExtra(p, (AstNodeIndex[]) {
    749                                 span.start,
    750                                 span.end,
    751                         }, 2),
    752                     },
    753                 });
    754             break;
    755         }
    756     }
    757 }
    758 
    759 static AstTokenIndex expectToken(Parser* p, TokenizerTag tag) {
    760     if (p->token_tags[p->tok_i] == tag) {
    761         return nextToken(p);
    762     } else {
    763         fprintf(stderr, "expected token %s, got %s\n",
    764             tokenizerGetTagString(tag),
    765             tokenizerGetTagString(p->token_tags[p->tok_i]));
    766         exit(1);
    767     }
    768     return 0; // tcc
    769 }
    770 
    771 static AstNodeIndex expectSemicolon(Parser* p) {
    772     return expectToken(p, TOKEN_SEMICOLON);
    773 }
    774 
    775 static AstNodeIndex parseErrorUnionExpr(Parser* p) {
    776     const AstNodeIndex suffix_expr = parseSuffixExpr(p);
    777     if (suffix_expr == 0)
    778         return null_node;
    779 
    780     const AstNodeIndex bang = eatToken(p, TOKEN_BANG);
    781     if (bang == null_token)
    782         return suffix_expr;
    783 
    784     return addNode(
    785         &p->nodes,
    786         (AstNodeItem) {
    787             .tag = AST_NODE_ERROR_UNION,
    788             .main_token = bang,
    789             .data = {
    790                 .lhs = suffix_expr,
    791                 .rhs = parseTypeExpr(p),
    792             },
    793         });
    794 }
    795 
    796 // parsePtrModifiersAndType parses pointer modifiers (allowzero, align,
    797 // addrspace, const, volatile, sentinel) and the child type for a pointer
    798 // started at main_token.
    799 static AstNodeIndex parsePtrModifiersAndType(
    800     Parser* p, AstTokenIndex main_token) {
    801     AstNodeIndex sentinel = 0;
    802     AstNodeIndex align_expr = 0;
    803     AstNodeIndex bit_range_start = 0;
    804     AstNodeIndex bit_range_end = 0;
    805     AstNodeIndex addrspace_expr = 0;
    806 
    807     // sentinel: *:0
    808     if (eatToken(p, TOKEN_COLON) != null_token)
    809         sentinel = expectExpr(p);
    810 
    811     // allowzero, const, volatile (before align)
    812     while (p->token_tags[p->tok_i] == TOKEN_KEYWORD_CONST
    813         || p->token_tags[p->tok_i] == TOKEN_KEYWORD_VOLATILE
    814         || p->token_tags[p->tok_i] == TOKEN_KEYWORD_ALLOWZERO)
    815         p->tok_i++;
    816 
    817     // align(expr) or align(expr:expr:expr)
    818     if (eatToken(p, TOKEN_KEYWORD_ALIGN) != null_token) {
    819         expectToken(p, TOKEN_L_PAREN);
    820         align_expr = expectExpr(p);
    821         if (eatToken(p, TOKEN_COLON) != null_token) {
    822             bit_range_start = expectExpr(p);
    823             expectToken(p, TOKEN_COLON);
    824             bit_range_end = expectExpr(p);
    825         }
    826         expectToken(p, TOKEN_R_PAREN);
    827     }
    828 
    829     // addrspace
    830     addrspace_expr = parseAddrSpace(p);
    831 
    832     // const, volatile, allowzero (after align/addrspace)
    833     while (p->token_tags[p->tok_i] == TOKEN_KEYWORD_CONST
    834         || p->token_tags[p->tok_i] == TOKEN_KEYWORD_VOLATILE
    835         || p->token_tags[p->tok_i] == TOKEN_KEYWORD_ALLOWZERO)
    836         p->tok_i++;
    837 
    838     const AstNodeIndex child_type = parseTypeExpr(p);
    839 
    840     if (bit_range_start != 0) {
    841         return addNode(&p->nodes,
    842             (AstNodeItem) {
    843                 .tag = AST_NODE_PTR_TYPE_BIT_RANGE,
    844                 .main_token = main_token,
    845                 .data = {
    846                     .lhs = addExtra(p,
    847                         (AstNodeIndex[]) { OPT(sentinel), align_expr,
    848                             OPT(addrspace_expr), bit_range_start,
    849                             bit_range_end },
    850                         5),
    851                     .rhs = child_type,
    852                 },
    853             });
    854     }
    855     if (addrspace_expr != 0) {
    856         return addNode(&p->nodes,
    857             (AstNodeItem) {
    858                 .tag = AST_NODE_PTR_TYPE,
    859                 .main_token = main_token,
    860                 .data = {
    861                     .lhs = addExtra(p,
    862                         (AstNodeIndex[]) { OPT(sentinel), OPT(align_expr),
    863                             addrspace_expr },
    864                         3),
    865                     .rhs = child_type,
    866                 },
    867             });
    868     }
    869     if (sentinel != 0) {
    870         return addNode(&p->nodes,
    871             (AstNodeItem) {
    872                 .tag = AST_NODE_PTR_TYPE_SENTINEL,
    873                 .main_token = main_token,
    874                 .data = { .lhs = sentinel, .rhs = child_type },
    875             });
    876     }
    877     return addNode(&p->nodes,
    878         (AstNodeItem) {
    879             .tag = AST_NODE_PTR_TYPE_ALIGNED,
    880             .main_token = main_token,
    881             .data = { .lhs = align_expr, .rhs = child_type },
    882         });
    883 }
    884 
    885 static AstNodeIndex parseTypeExpr(Parser* p) {
    886     const TokenizerTag tok = p->token_tags[p->tok_i];
    887     switch (tok) {
    888     case TOKEN_QUESTION_MARK:
    889         return addNode(&p->nodes,
    890             (AstNodeItem) {
    891                 .tag = AST_NODE_OPTIONAL_TYPE,
    892                 .main_token = nextToken(p),
    893                 .data = { .lhs = parseTypeExpr(p), .rhs = 0 },
    894             });
    895     case TOKEN_KEYWORD_ANYFRAME:
    896         fprintf(stderr, "parseTypeExpr not supported for %s\n",
    897             tokenizerGetTagString(tok));
    898         exit(1);
    899     case TOKEN_ASTERISK: {
    900         const AstTokenIndex asterisk = nextToken(p);
    901         return parsePtrModifiersAndType(p, asterisk);
    902     }
    903     case TOKEN_ASTERISK_ASTERISK: {
    904         // ** is two nested pointer types sharing the same token
    905         const AstTokenIndex asterisk = nextToken(p);
    906         // Inner pointer: parse modifiers and child type
    907         const AstNodeIndex inner_child = parseTypeExpr(p);
    908         const AstNodeIndex inner = addNode(&p->nodes,
    909             (AstNodeItem) {
    910                 .tag = AST_NODE_PTR_TYPE_ALIGNED,
    911                 .main_token = asterisk,
    912                 .data = { .lhs = 0, .rhs = inner_child },
    913             });
    914         // Outer pointer wraps the inner
    915         return addNode(&p->nodes,
    916             (AstNodeItem) {
    917                 .tag = AST_NODE_PTR_TYPE_ALIGNED,
    918                 .main_token = asterisk,
    919                 .data = { .lhs = 0, .rhs = inner },
    920             });
    921     }
    922     case TOKEN_L_BRACKET: {
    923         const AstTokenIndex lbracket = nextToken(p);
    924         if (p->token_tags[p->tok_i] == TOKEN_ASTERISK) {
    925             // [*] many-item pointer, [*c] C pointer, [*:s] sentinel
    926             p->tok_i++; // consume *
    927             AstNodeIndex sentinel = 0;
    928             if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER) {
    929                 // Check for 'c' modifier: [*c]
    930                 // The 'c' is a regular identifier token
    931                 const char c = p->source[p->token_starts[p->tok_i]];
    932                 if (c == 'c'
    933                     && p->token_starts[p->tok_i + 1]
    934                             - p->token_starts[p->tok_i]
    935                         <= 2) {
    936                     p->tok_i++; // consume 'c'
    937                 }
    938             } else if (eatToken(p, TOKEN_COLON) != null_token) {
    939                 sentinel = expectExpr(p);
    940             }
    941             expectToken(p, TOKEN_R_BRACKET);
    942             // Reuse shared pointer modifier + type parsing
    943             // If we captured a sentinel from [*:s], temporarily store it
    944             // and let parsePtrModifiersAndType handle the rest.
    945             // But parsePtrModifiersAndType expects sentinel after main
    946             // token via `:`. Since we already consumed it, we need to
    947             // handle this inline.
    948 
    949             // allowzero, const, volatile (before align)
    950             while (p->token_tags[p->tok_i] == TOKEN_KEYWORD_CONST
    951                 || p->token_tags[p->tok_i] == TOKEN_KEYWORD_VOLATILE
    952                 || p->token_tags[p->tok_i] == TOKEN_KEYWORD_ALLOWZERO)
    953                 p->tok_i++;
    954 
    955             AstNodeIndex align_expr = 0;
    956             AstNodeIndex bit_range_start = 0;
    957             AstNodeIndex bit_range_end = 0;
    958             if (eatToken(p, TOKEN_KEYWORD_ALIGN) != null_token) {
    959                 expectToken(p, TOKEN_L_PAREN);
    960                 align_expr = expectExpr(p);
    961                 if (eatToken(p, TOKEN_COLON) != null_token) {
    962                     bit_range_start = expectExpr(p);
    963                     expectToken(p, TOKEN_COLON);
    964                     bit_range_end = expectExpr(p);
    965                 }
    966                 expectToken(p, TOKEN_R_PAREN);
    967             }
    968             const AstNodeIndex addrspace_expr = parseAddrSpace(p);
    969 
    970             while (p->token_tags[p->tok_i] == TOKEN_KEYWORD_CONST
    971                 || p->token_tags[p->tok_i] == TOKEN_KEYWORD_VOLATILE
    972                 || p->token_tags[p->tok_i] == TOKEN_KEYWORD_ALLOWZERO)
    973                 p->tok_i++;
    974 
    975             const AstNodeIndex elem_type = parseTypeExpr(p);
    976 
    977             if (bit_range_start != 0) {
    978                 return addNode(&p->nodes,
    979                     (AstNodeItem) {
    980                         .tag = AST_NODE_PTR_TYPE_BIT_RANGE,
    981                         .main_token = lbracket,
    982                         .data = {
    983                             .lhs = addExtra(p,
    984                                 (AstNodeIndex[]) { OPT(sentinel),
    985                                     align_expr, OPT(addrspace_expr),
    986                                     bit_range_start, bit_range_end },
    987                                 5),
    988                             .rhs = elem_type,
    989                         },
    990                     });
    991             }
    992             if (addrspace_expr != 0) {
    993                 return addNode(&p->nodes,
    994                     (AstNodeItem) {
    995                         .tag = AST_NODE_PTR_TYPE,
    996                         .main_token = lbracket,
    997                         .data = {
    998                             .lhs = addExtra(p,
    999                                 (AstNodeIndex[]) { OPT(sentinel),
   1000                                     OPT(align_expr), addrspace_expr },
   1001                                 3),
   1002                             .rhs = elem_type,
   1003                         },
   1004                     });
   1005             }
   1006             if (sentinel != 0) {
   1007                 return addNode(&p->nodes,
   1008                     (AstNodeItem) {
   1009                         .tag = AST_NODE_PTR_TYPE_SENTINEL,
   1010                         .main_token = lbracket,
   1011                         .data = { .lhs = sentinel, .rhs = elem_type },
   1012                     });
   1013             }
   1014             return addNode(&p->nodes,
   1015                 (AstNodeItem) {
   1016                     .tag = AST_NODE_PTR_TYPE_ALIGNED,
   1017                     .main_token = lbracket,
   1018                     .data = { .lhs = align_expr, .rhs = elem_type },
   1019                 });
   1020         }
   1021         const AstNodeIndex len_expr = parseExpr(p);
   1022         const AstNodeIndex sentinel
   1023             = eatToken(p, TOKEN_COLON) != null_token ? expectExpr(p) : 0;
   1024         expectToken(p, TOKEN_R_BRACKET);
   1025         if (len_expr == 0) {
   1026             // Slice type: []T or [:s]T — reuse shared modifier parsing
   1027             // allowzero, const, volatile
   1028             while (p->token_tags[p->tok_i] == TOKEN_KEYWORD_CONST
   1029                 || p->token_tags[p->tok_i] == TOKEN_KEYWORD_VOLATILE
   1030                 || p->token_tags[p->tok_i] == TOKEN_KEYWORD_ALLOWZERO)
   1031                 p->tok_i++;
   1032             const AstNodeIndex align_expr = parseByteAlign(p);
   1033             const AstNodeIndex addrspace_expr = parseAddrSpace(p);
   1034             while (p->token_tags[p->tok_i] == TOKEN_KEYWORD_CONST
   1035                 || p->token_tags[p->tok_i] == TOKEN_KEYWORD_VOLATILE
   1036                 || p->token_tags[p->tok_i] == TOKEN_KEYWORD_ALLOWZERO)
   1037                 p->tok_i++;
   1038             const AstNodeIndex elem_type = parseTypeExpr(p);
   1039             if (addrspace_expr != 0) {
   1040                 return addNode(&p->nodes,
   1041                     (AstNodeItem) {
   1042                         .tag = AST_NODE_PTR_TYPE,
   1043                         .main_token = lbracket,
   1044                         .data = {
   1045                             .lhs = addExtra(p,
   1046                                 (AstNodeIndex[]) { OPT(sentinel),
   1047                                     OPT(align_expr), addrspace_expr },
   1048                                 3),
   1049                             .rhs = elem_type,
   1050                         },
   1051                     });
   1052             }
   1053             if (sentinel != 0 && align_expr == 0) {
   1054                 return addNode(&p->nodes,
   1055                     (AstNodeItem) {
   1056                         .tag = AST_NODE_PTR_TYPE_SENTINEL,
   1057                         .main_token = lbracket,
   1058                         .data = { .lhs = sentinel, .rhs = elem_type },
   1059                     });
   1060             }
   1061             return addNode(&p->nodes,
   1062                 (AstNodeItem) {
   1063                     .tag = AST_NODE_PTR_TYPE_ALIGNED,
   1064                     .main_token = lbracket,
   1065                     .data = { .lhs = align_expr, .rhs = elem_type },
   1066                 });
   1067         }
   1068         // Array type: [N]T or [N:s]T
   1069         const AstNodeIndex elem_type = parseTypeExpr(p);
   1070         if (sentinel == 0) {
   1071             return addNode(&p->nodes,
   1072                 (AstNodeItem) {
   1073                     .tag = AST_NODE_ARRAY_TYPE,
   1074                     .main_token = lbracket,
   1075                     .data = { .lhs = len_expr, .rhs = elem_type },
   1076                 });
   1077         }
   1078         return addNode(&p->nodes,
   1079             (AstNodeItem) {
   1080                 .tag = AST_NODE_ARRAY_TYPE_SENTINEL,
   1081                 .main_token = lbracket,
   1082                 .data = {
   1083                     .lhs = len_expr,
   1084                     .rhs = addExtra(p,
   1085                         (AstNodeIndex[]) { sentinel, elem_type }, 2),
   1086                 },
   1087             });
   1088     }
   1089     default:
   1090         return parseErrorUnionExpr(p);
   1091     }
   1092     return 0; // tcc
   1093 }
   1094 
   1095 static SmallSpan parseParamDeclList(Parser* p) {
   1096     expectToken(p, TOKEN_L_PAREN);
   1097 
   1098     CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch)))
   1099     = initCleanupScratch(p);
   1100 
   1101     while (true) {
   1102         if (eatToken(p, TOKEN_R_PAREN) != null_token)
   1103             break;
   1104 
   1105         eatDocComments(p);
   1106 
   1107         // Check for comptime or noalias
   1108         eatToken(p, TOKEN_KEYWORD_COMPTIME);
   1109         eatToken(p, TOKEN_KEYWORD_NOALIAS);
   1110 
   1111         // Check for name: type or just type
   1112         if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER
   1113             && p->token_tags[p->tok_i + 1] == TOKEN_COLON) {
   1114             p->tok_i += 2; // consume name and colon
   1115         } else if (p->token_tags[p->tok_i] == TOKEN_ELLIPSIS3) {
   1116             // varargs (...)
   1117             p->tok_i++;
   1118             if (eatToken(p, TOKEN_R_PAREN) != null_token)
   1119                 break;
   1120             expectToken(p, TOKEN_COMMA);
   1121             continue;
   1122         }
   1123 
   1124         // anytype params are omitted from the AST
   1125         if (eatToken(p, TOKEN_KEYWORD_ANYTYPE) != null_token) {
   1126             if (p->token_tags[p->tok_i] == TOKEN_COMMA) {
   1127                 p->tok_i++;
   1128                 continue;
   1129             }
   1130             expectToken(p, TOKEN_R_PAREN);
   1131             break;
   1132         }
   1133 
   1134         const AstNodeIndex type_expr = parseTypeExpr(p);
   1135         if (type_expr != 0)
   1136             SLICE_APPEND(AstNodeIndex, &p->scratch, type_expr);
   1137 
   1138         if (p->token_tags[p->tok_i] == TOKEN_COMMA) {
   1139             p->tok_i++;
   1140             continue;
   1141         }
   1142         expectToken(p, TOKEN_R_PAREN);
   1143         break;
   1144     }
   1145 
   1146     const uint32_t params_len = p->scratch.len - scratch_top.old_len;
   1147     switch (params_len) {
   1148     case 0:
   1149         return (SmallSpan) {
   1150             .tag = SMALL_SPAN_ZERO_OR_ONE,
   1151             .payload = { .zero_or_one = 0 },
   1152         };
   1153     case 1:
   1154         return (SmallSpan) {
   1155             .tag = SMALL_SPAN_ZERO_OR_ONE,
   1156             .payload = { .zero_or_one = p->scratch.arr[scratch_top.old_len] },
   1157         };
   1158     default:;
   1159         const AstSubRange span
   1160             = listToSpan(p, &p->scratch.arr[scratch_top.old_len], params_len);
   1161         return (SmallSpan) {
   1162             .tag = SMALL_SPAN_MULTI,
   1163             .payload = { .multi = span },
   1164         };
   1165     }
   1166 }
   1167 
   1168 static uint32_t reserveNode(Parser* p, AstNodeTag tag) {
   1169     astNodeListEnsureCapacity(&p->nodes, 1);
   1170     p->nodes.len++;
   1171     p->nodes.tags[p->nodes.len - 1] = tag;
   1172     return p->nodes.len - 1;
   1173 }
   1174 
   1175 static AstNodeIndex parseFnProto(Parser* p) {
   1176     AstTokenIndex fn_token = eatToken(p, TOKEN_KEYWORD_FN);
   1177     if (fn_token == null_token)
   1178         return null_node;
   1179 
   1180     AstNodeIndex fn_proto_index = reserveNode(p, AST_NODE_FN_PROTO);
   1181 
   1182     eatToken(p, TOKEN_IDENTIFIER);
   1183 
   1184     SmallSpan params = parseParamDeclList(p);
   1185     const AstNodeIndex align_expr = parseByteAlign(p);
   1186     const AstNodeIndex addrspace_expr = parseAddrSpace(p);
   1187     const AstNodeIndex section_expr = parseLinkSection(p);
   1188     const AstNodeIndex callconv_expr = parseCallconv(p);
   1189     eatToken(p, TOKEN_BANG);
   1190 
   1191     const AstNodeIndex return_type_expr = parseTypeExpr(p);
   1192 
   1193     if (align_expr == 0 && section_expr == 0 && callconv_expr == 0
   1194         && addrspace_expr == 0) {
   1195         switch (params.tag) {
   1196         case SMALL_SPAN_ZERO_OR_ONE:
   1197             return setNode(p, fn_proto_index,
   1198                 (AstNodeItem) {
   1199                     .tag = AST_NODE_FN_PROTO_SIMPLE,
   1200                     .main_token = fn_token,
   1201                     .data = {
   1202                         .lhs = params.payload.zero_or_one,
   1203                         .rhs = return_type_expr,
   1204                     },
   1205                 });
   1206         case SMALL_SPAN_MULTI:
   1207             return setNode(p, fn_proto_index,
   1208                 (AstNodeItem) {
   1209                     .tag = AST_NODE_FN_PROTO_MULTI,
   1210                     .main_token = fn_token,
   1211                     .data = {
   1212                         .lhs = addExtra(p,
   1213                             (AstNodeIndex[]) {
   1214                                 params.payload.multi.start,
   1215                                 params.payload.multi.end },
   1216                             2),
   1217                         .rhs = return_type_expr,
   1218                     },
   1219                 });
   1220         }
   1221     }
   1222 
   1223     // Complex fn proto with align/section/callconv/addrspace
   1224     switch (params.tag) {
   1225     case SMALL_SPAN_ZERO_OR_ONE:
   1226         return setNode(p, fn_proto_index,
   1227             (AstNodeItem) {
   1228                 .tag = AST_NODE_FN_PROTO_ONE,
   1229                 .main_token = fn_token,
   1230                 .data = {
   1231                     .lhs = addExtra(p,
   1232                         (AstNodeIndex[]) {
   1233                             OPT(params.payload.zero_or_one),
   1234                             OPT(align_expr), OPT(addrspace_expr),
   1235                             OPT(section_expr), OPT(callconv_expr) },
   1236                         5),
   1237                     .rhs = return_type_expr,
   1238                 },
   1239             });
   1240     case SMALL_SPAN_MULTI:
   1241         return setNode(p, fn_proto_index,
   1242             (AstNodeItem) {
   1243                 .tag = AST_NODE_FN_PROTO,
   1244                 .main_token = fn_token,
   1245                 .data = {
   1246                     .lhs = addExtra(p,
   1247                         (AstNodeIndex[]) {
   1248                             params.payload.multi.start,
   1249                             params.payload.multi.end,
   1250                             OPT(align_expr), OPT(addrspace_expr),
   1251                             OPT(section_expr), OPT(callconv_expr) },
   1252                         6),
   1253                     .rhs = return_type_expr,
   1254                 },
   1255             });
   1256     }
   1257     return 0; // tcc
   1258 }
   1259 
   1260 static AstTokenIndex parseBlockLabel(Parser* p) {
   1261     if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER
   1262         && p->token_tags[p->tok_i + 1] == TOKEN_COLON) {
   1263         const AstTokenIndex identifier = p->tok_i;
   1264         p->tok_i += 2;
   1265         return identifier;
   1266     }
   1267     return null_node;
   1268 }
   1269 
   1270 static AstNodeIndex parseForStatement(Parser* p) {
   1271     const AstNodeIndex for_token = eatToken(p, TOKEN_KEYWORD_FOR);
   1272     if (for_token == null_token)
   1273         return null_node;
   1274 
   1275     (void)for_token;
   1276     fprintf(stderr, "parseForStatement cannot parse for statements\n");
   1277     return 0; // tcc
   1278 }
   1279 
   1280 static AstNodeIndex parseWhileStatement(Parser* p) {
   1281     const AstNodeIndex while_token = eatToken(p, TOKEN_KEYWORD_WHILE);
   1282     if (while_token == null_token)
   1283         return null_node;
   1284 
   1285     (void)while_token;
   1286     fprintf(stderr, "parseWhileStatement cannot parse while statements\n");
   1287     return 0; // tcc
   1288 }
   1289 
   1290 static AstNodeIndex parseLoopStatement(Parser* p) {
   1291     const AstTokenIndex inline_token = eatToken(p, TOKEN_KEYWORD_INLINE);
   1292 
   1293     const AstNodeIndex for_statement = parseForStatement(p);
   1294     if (for_statement != 0)
   1295         return for_statement;
   1296 
   1297     const AstNodeIndex while_statement = parseWhileStatement(p);
   1298     if (while_statement != 0)
   1299         return while_statement;
   1300 
   1301     if (inline_token == null_token)
   1302         return null_node;
   1303 
   1304     fprintf(
   1305         stderr, "seen 'inline', there should have been a 'for' or 'while'\n");
   1306     exit(1);
   1307     return 0; // tcc
   1308 }
   1309 
   1310 static AstNodeIndex parseVarDeclProto(Parser* p) {
   1311     AstTokenIndex mut_token;
   1312     if ((mut_token = eatToken(p, TOKEN_KEYWORD_CONST)) == null_token)
   1313         if ((mut_token = eatToken(p, TOKEN_KEYWORD_VAR)) == null_token)
   1314             return null_node;
   1315 
   1316     expectToken(p, TOKEN_IDENTIFIER);
   1317     const AstNodeIndex type_node
   1318         = eatToken(p, TOKEN_COLON) == null_token ? 0 : parseTypeExpr(p);
   1319     const AstNodeIndex align_node = parseByteAlign(p);
   1320     const AstNodeIndex addrspace_node = parseAddrSpace(p);
   1321     const AstNodeIndex section_node = parseLinkSection(p);
   1322 
   1323     if (section_node == 0 && addrspace_node == 0) {
   1324         if (align_node == 0) {
   1325             return addNode(&p->nodes,
   1326                 (AstNodeItem) {
   1327                     .tag = AST_NODE_SIMPLE_VAR_DECL,
   1328                     .main_token = mut_token,
   1329                     .data = { .lhs = type_node, .rhs = 0 },
   1330                 });
   1331         }
   1332         if (type_node == 0) {
   1333             return addNode(&p->nodes,
   1334                 (AstNodeItem) {
   1335                     .tag = AST_NODE_ALIGNED_VAR_DECL,
   1336                     .main_token = mut_token,
   1337                     .data = { .lhs = align_node, .rhs = 0 },
   1338                 });
   1339         }
   1340         return addNode(&p->nodes,
   1341             (AstNodeItem) {
   1342                 .tag = AST_NODE_LOCAL_VAR_DECL,
   1343                 .main_token = mut_token,
   1344                 .data = {
   1345                     .lhs = addExtra(p,
   1346                         (AstNodeIndex[]) { type_node, align_node }, 2),
   1347                     .rhs = 0,
   1348                 },
   1349             });
   1350     }
   1351     return addNode(&p->nodes,
   1352         (AstNodeItem) {
   1353             .tag = AST_NODE_GLOBAL_VAR_DECL,
   1354             .main_token = mut_token,
   1355             .data = {
   1356                 .lhs = addExtra(p,
   1357                     (AstNodeIndex[]) { OPT(type_node), OPT(align_node),
   1358                         OPT(addrspace_node), OPT(section_node) },
   1359                     4),
   1360                 .rhs = 0,
   1361             },
   1362         });
   1363 }
   1364 
   1365 static AstTokenIndex parseBreakLabel(Parser* p) {
   1366     if (eatToken(p, TOKEN_COLON) == null_token)
   1367         return null_token;
   1368     return expectToken(p, TOKEN_IDENTIFIER);
   1369 }
   1370 
   1371 // parseFieldInit tries to parse .field_name = expr; returns 0 if not a
   1372 // field init
   1373 static AstNodeIndex parseFieldInit(Parser* p) {
   1374     if (p->token_tags[p->tok_i] == TOKEN_PERIOD
   1375         && p->token_tags[p->tok_i + 1] == TOKEN_IDENTIFIER
   1376         && p->token_tags[p->tok_i + 2] == TOKEN_EQUAL) {
   1377         p->tok_i += 3;
   1378         return expectExpr(p);
   1379     }
   1380     return null_node;
   1381 }
   1382 
   1383 // parseInitList parses the contents of { ... } for struct/array init.
   1384 // lhs is the type expression (0 for anonymous .{...}).
   1385 // lbrace is the lbrace token index.
   1386 static AstNodeIndex parseInitList(
   1387     Parser* p, AstNodeIndex lhs, AstTokenIndex lbrace) {
   1388     CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch)))
   1389     = initCleanupScratch(p);
   1390 
   1391     const AstNodeIndex field_init = parseFieldInit(p);
   1392     if (field_init != 0) {
   1393         // Struct init
   1394         SLICE_APPEND(AstNodeIndex, &p->scratch, field_init);
   1395         while (true) {
   1396             if (p->token_tags[p->tok_i] == TOKEN_COMMA)
   1397                 p->tok_i++;
   1398             else if (p->token_tags[p->tok_i] == TOKEN_R_BRACE) {
   1399                 p->tok_i++;
   1400                 break;
   1401             } else {
   1402                 fprintf(
   1403                     stderr, "parseInitList: expected , or } in struct init\n");
   1404                 exit(1);
   1405             }
   1406             if (eatToken(p, TOKEN_R_BRACE) != null_token)
   1407                 break;
   1408             const AstNodeIndex next = parseFieldInit(p);
   1409             assert(next != 0);
   1410             SLICE_APPEND(AstNodeIndex, &p->scratch, next);
   1411         }
   1412         const bool comma = p->token_tags[p->tok_i - 2] == TOKEN_COMMA;
   1413         const uint32_t inits_len = p->scratch.len - scratch_top.old_len;
   1414         if (lhs == 0) {
   1415             // Anonymous struct init: .{...}
   1416             switch (inits_len) {
   1417             case 0:
   1418             case 1:
   1419             case 2:
   1420                 return addNode(&p->nodes,
   1421                     (AstNodeItem) {
   1422                         .tag = comma
   1423                             ? AST_NODE_STRUCT_INIT_DOT_TWO_COMMA
   1424                             : AST_NODE_STRUCT_INIT_DOT_TWO,
   1425                         .main_token = lbrace,
   1426                         .data = {
   1427                             .lhs = inits_len >= 1
   1428                                 ? p->scratch.arr[scratch_top.old_len]
   1429                                 : 0,
   1430                             .rhs = inits_len >= 2
   1431                                 ? p->scratch.arr[scratch_top.old_len + 1]
   1432                                 : 0,
   1433                         },
   1434                     });
   1435             default:;
   1436                 const AstSubRange span = listToSpan(
   1437                     p, &p->scratch.arr[scratch_top.old_len], inits_len);
   1438                 return addNode(&p->nodes,
   1439                     (AstNodeItem) {
   1440                         .tag = comma ? AST_NODE_STRUCT_INIT_DOT_COMMA
   1441                                      : AST_NODE_STRUCT_INIT_DOT,
   1442                         .main_token = lbrace,
   1443                         .data = { .lhs = span.start, .rhs = span.end },
   1444                     });
   1445             }
   1446         }
   1447         // Named struct init: X{...}
   1448         switch (inits_len) {
   1449         case 0:
   1450         case 1:
   1451             return addNode(&p->nodes,
   1452                 (AstNodeItem) {
   1453                     .tag = comma ? AST_NODE_STRUCT_INIT_ONE_COMMA
   1454                                  : AST_NODE_STRUCT_INIT_ONE,
   1455                     .main_token = lbrace,
   1456                     .data = {
   1457                         .lhs = lhs,
   1458                         .rhs = inits_len >= 1
   1459                             ? p->scratch.arr[scratch_top.old_len]
   1460                             : 0,
   1461                     },
   1462                 });
   1463         default:;
   1464             const AstSubRange span = listToSpan(
   1465                 p, &p->scratch.arr[scratch_top.old_len], inits_len);
   1466             return addNode(&p->nodes,
   1467                 (AstNodeItem) {
   1468                     .tag = comma ? AST_NODE_STRUCT_INIT_COMMA
   1469                                  : AST_NODE_STRUCT_INIT,
   1470                     .main_token = lbrace,
   1471                     .data = {
   1472                         .lhs = lhs,
   1473                         .rhs = addExtra(p,
   1474                             (AstNodeIndex[]) { span.start, span.end }, 2),
   1475                     },
   1476                 });
   1477         }
   1478     }
   1479 
   1480     // Array init or empty init
   1481     while (true) {
   1482         if (eatToken(p, TOKEN_R_BRACE) != null_token)
   1483             break;
   1484         const AstNodeIndex elem = expectExpr(p);
   1485         SLICE_APPEND(AstNodeIndex, &p->scratch, elem);
   1486         if (p->token_tags[p->tok_i] == TOKEN_COMMA)
   1487             p->tok_i++;
   1488         else if (p->token_tags[p->tok_i] == TOKEN_R_BRACE) {
   1489             p->tok_i++;
   1490             break;
   1491         } else {
   1492             fprintf(stderr, "parseInitList: expected , or } in array init\n");
   1493             exit(1);
   1494         }
   1495     }
   1496 
   1497     const bool comma = p->token_tags[p->tok_i - 2] == TOKEN_COMMA;
   1498     const uint32_t elems_len = p->scratch.len - scratch_top.old_len;
   1499     if (lhs == 0) {
   1500         // Anonymous array init: .{a, b, ...}
   1501         switch (elems_len) {
   1502         case 0:
   1503         case 1:
   1504         case 2:
   1505             return addNode(&p->nodes,
   1506                 (AstNodeItem) {
   1507                     .tag = comma ? AST_NODE_ARRAY_INIT_DOT_TWO_COMMA
   1508                                  : AST_NODE_ARRAY_INIT_DOT_TWO,
   1509                     .main_token = lbrace,
   1510                     .data = {
   1511                         .lhs = elems_len >= 1
   1512                             ? p->scratch.arr[scratch_top.old_len]
   1513                             : 0,
   1514                         .rhs = elems_len >= 2
   1515                             ? p->scratch.arr[scratch_top.old_len + 1]
   1516                             : 0,
   1517                     },
   1518                 });
   1519         default:;
   1520             const AstSubRange span = listToSpan(
   1521                 p, &p->scratch.arr[scratch_top.old_len], elems_len);
   1522             return addNode(&p->nodes,
   1523                 (AstNodeItem) {
   1524                     .tag = comma ? AST_NODE_ARRAY_INIT_DOT_COMMA
   1525                                  : AST_NODE_ARRAY_INIT_DOT,
   1526                     .main_token = lbrace,
   1527                     .data = { .lhs = span.start, .rhs = span.end },
   1528                 });
   1529         }
   1530     }
   1531     // Named init: X{a, b, ...}
   1532     switch (elems_len) {
   1533     case 0:
   1534         // Empty init X{} — treat as struct init
   1535         return addNode(&p->nodes,
   1536             (AstNodeItem) {
   1537                 .tag = AST_NODE_STRUCT_INIT_ONE,
   1538                 .main_token = lbrace,
   1539                 .data = { .lhs = lhs, .rhs = 0 },
   1540             });
   1541     case 1:
   1542         return addNode(&p->nodes,
   1543             (AstNodeItem) {
   1544                 .tag = comma ? AST_NODE_ARRAY_INIT_ONE_COMMA
   1545                              : AST_NODE_ARRAY_INIT_ONE,
   1546                 .main_token = lbrace,
   1547                 .data = {
   1548                     .lhs = lhs,
   1549                     .rhs = p->scratch.arr[scratch_top.old_len],
   1550                 },
   1551             });
   1552     default:;
   1553         const AstSubRange span
   1554             = listToSpan(p, &p->scratch.arr[scratch_top.old_len], elems_len);
   1555         return addNode(&p->nodes,
   1556             (AstNodeItem) {
   1557                 .tag = comma ? AST_NODE_ARRAY_INIT_COMMA
   1558                              : AST_NODE_ARRAY_INIT,
   1559                 .main_token = lbrace,
   1560                 .data = {
   1561                     .lhs = lhs,
   1562                     .rhs = addExtra(p,
   1563                         (AstNodeIndex[]) { span.start, span.end }, 2),
   1564                 },
   1565             });
   1566     }
   1567 }
   1568 
   1569 static AstNodeIndex parseCurlySuffixExpr(Parser* p) {
   1570     const AstNodeIndex lhs = parseTypeExpr(p);
   1571     if (lhs == 0)
   1572         return null_node;
   1573 
   1574     const AstTokenIndex lbrace = eatToken(p, TOKEN_L_BRACE);
   1575     if (lbrace == null_token)
   1576         return lhs;
   1577 
   1578     return parseInitList(p, lhs, lbrace);
   1579 }
   1580 
   1581 typedef struct {
   1582     int8_t prec;
   1583     AstNodeTag tag;
   1584     enum {
   1585         ASSOC_LEFT,
   1586         ASSOC_NONE,
   1587     } assoc;
   1588 } OperInfo;
   1589 
   1590 static OperInfo operTable(TokenizerTag tok_tag) {
   1591     switch (tok_tag) {
   1592     case TOKEN_KEYWORD_OR:
   1593         return (OperInfo) { .prec = 10, .tag = AST_NODE_BOOL_OR };
   1594     case TOKEN_KEYWORD_AND:
   1595         return (OperInfo) { .prec = 20, .tag = AST_NODE_BOOL_AND };
   1596 
   1597     case TOKEN_EQUAL_EQUAL:
   1598         return (OperInfo) {
   1599             .prec = 30, .tag = AST_NODE_EQUAL_EQUAL, .assoc = ASSOC_NONE
   1600         };
   1601     case TOKEN_BANG_EQUAL:
   1602         return (OperInfo) {
   1603             .prec = 30, .tag = AST_NODE_BANG_EQUAL, .assoc = ASSOC_NONE
   1604         };
   1605     case TOKEN_ANGLE_BRACKET_LEFT:
   1606         return (OperInfo) {
   1607             .prec = 30, .tag = AST_NODE_LESS_THAN, .assoc = ASSOC_NONE
   1608         };
   1609     case TOKEN_ANGLE_BRACKET_RIGHT:
   1610         return (OperInfo) {
   1611             .prec = 30, .tag = AST_NODE_GREATER_THAN, .assoc = ASSOC_NONE
   1612         };
   1613     case TOKEN_ANGLE_BRACKET_LEFT_EQUAL:
   1614         return (OperInfo) {
   1615             .prec = 30, .tag = AST_NODE_LESS_OR_EQUAL, .assoc = ASSOC_NONE
   1616         };
   1617     case TOKEN_ANGLE_BRACKET_RIGHT_EQUAL:
   1618         return (OperInfo) {
   1619             .prec = 30, .tag = AST_NODE_GREATER_OR_EQUAL, .assoc = ASSOC_NONE
   1620         };
   1621 
   1622     case TOKEN_AMPERSAND:
   1623         return (OperInfo) { .prec = 40, .tag = AST_NODE_BIT_AND };
   1624     case TOKEN_CARET:
   1625         return (OperInfo) { .prec = 40, .tag = AST_NODE_BIT_XOR };
   1626     case TOKEN_PIPE:
   1627         return (OperInfo) { .prec = 40, .tag = AST_NODE_BIT_OR };
   1628     case TOKEN_KEYWORD_ORELSE:
   1629         return (OperInfo) { .prec = 40, .tag = AST_NODE_ORELSE };
   1630     case TOKEN_KEYWORD_CATCH:
   1631         return (OperInfo) { .prec = 40, .tag = AST_NODE_CATCH };
   1632 
   1633     case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT:
   1634         return (OperInfo) { .prec = 50, .tag = AST_NODE_SHL };
   1635     case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_PIPE:
   1636         return (OperInfo) { .prec = 50, .tag = AST_NODE_SHL_SAT };
   1637     case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_RIGHT:
   1638         return (OperInfo) { .prec = 50, .tag = AST_NODE_SHR };
   1639 
   1640     case TOKEN_PLUS:
   1641         return (OperInfo) { .prec = 60, .tag = AST_NODE_ADD };
   1642     case TOKEN_MINUS:
   1643         return (OperInfo) { .prec = 60, .tag = AST_NODE_SUB };
   1644     case TOKEN_PLUS_PLUS:
   1645         return (OperInfo) { .prec = 60, .tag = AST_NODE_ARRAY_CAT };
   1646     case TOKEN_PLUS_PERCENT:
   1647         return (OperInfo) { .prec = 60, .tag = AST_NODE_ADD_WRAP };
   1648     case TOKEN_MINUS_PERCENT:
   1649         return (OperInfo) { .prec = 60, .tag = AST_NODE_SUB_WRAP };
   1650     case TOKEN_PLUS_PIPE:
   1651         return (OperInfo) { .prec = 60, .tag = AST_NODE_ADD_SAT };
   1652     case TOKEN_MINUS_PIPE:
   1653         return (OperInfo) { .prec = 60, .tag = AST_NODE_SUB_SAT };
   1654 
   1655     case TOKEN_PIPE_PIPE:
   1656         return (OperInfo) { .prec = 70, .tag = AST_NODE_MERGE_ERROR_SETS };
   1657     case TOKEN_ASTERISK:
   1658         return (OperInfo) { .prec = 70, .tag = AST_NODE_MUL };
   1659     case TOKEN_SLASH:
   1660         return (OperInfo) { .prec = 70, .tag = AST_NODE_DIV };
   1661     case TOKEN_PERCENT:
   1662         return (OperInfo) { .prec = 70, .tag = AST_NODE_MOD };
   1663     case TOKEN_ASTERISK_ASTERISK:
   1664         return (OperInfo) { .prec = 70, .tag = AST_NODE_ARRAY_MULT };
   1665     case TOKEN_ASTERISK_PERCENT:
   1666         return (OperInfo) { .prec = 70, .tag = AST_NODE_MUL_WRAP };
   1667     case TOKEN_ASTERISK_PIPE:
   1668         return (OperInfo) { .prec = 70, .tag = AST_NODE_MUL_SAT };
   1669 
   1670     default:
   1671         return (OperInfo) { .prec = -1, .tag = AST_NODE_ROOT };
   1672     }
   1673 }
   1674 
   1675 static AstNodeIndex parseExprPrecedence(Parser* p, int32_t min_prec) {
   1676     assert(min_prec >= 0);
   1677 
   1678     AstNodeIndex node = parsePrefixExpr(p);
   1679     if (node == 0)
   1680         return null_node;
   1681 
   1682     int8_t banned_prec = -1;
   1683 
   1684     while (true) {
   1685         const TokenizerTag tok_tag = p->token_tags[p->tok_i];
   1686         const OperInfo info = operTable(tok_tag);
   1687         if (info.prec < min_prec)
   1688             break;
   1689 
   1690         assert(info.prec != banned_prec);
   1691 
   1692         const AstTokenIndex oper_token = nextToken(p);
   1693         if (tok_tag == TOKEN_KEYWORD_CATCH) {
   1694             fprintf(stderr, "parsePayload not supported\n");
   1695             exit(1);
   1696             return 0; // tcc
   1697         }
   1698         const AstNodeIndex rhs = parseExprPrecedence(p, info.prec + 1);
   1699         assert(rhs != 0);
   1700 
   1701         node = addNode(
   1702                 &p->nodes,
   1703                 (AstNodeItem) {
   1704                     .tag = info.tag,
   1705                     .main_token = oper_token,
   1706                     .data = {
   1707                         .lhs = node,
   1708                         .rhs = rhs,
   1709                     },
   1710                 });
   1711 
   1712         if (info.assoc == ASSOC_NONE)
   1713             banned_prec = info.prec;
   1714     }
   1715 
   1716     return node;
   1717 }
   1718 
   1719 static AstNodeIndex parseExpr(Parser* p) { return parseExprPrecedence(p, 0); }
   1720 
   1721 static AstNodeIndex expectExpr(Parser* p) {
   1722     const AstNodeIndex node = parseExpr(p);
   1723     assert(node != 0);
   1724     return node;
   1725 }
   1726 
   1727 static void parsePtrPayload(Parser* p) {
   1728     if (eatToken(p, TOKEN_PIPE) == null_token)
   1729         return;
   1730     eatToken(p, TOKEN_ASTERISK);
   1731     expectToken(p, TOKEN_IDENTIFIER);
   1732     expectToken(p, TOKEN_PIPE);
   1733 }
   1734 
   1735 static void parsePayload(Parser* p) {
   1736     if (eatToken(p, TOKEN_PIPE) == null_token)
   1737         return;
   1738     expectToken(p, TOKEN_IDENTIFIER);
   1739     expectToken(p, TOKEN_PIPE);
   1740 }
   1741 
   1742 static AstNodeIndex parseIfExpr(Parser* p) {
   1743     const AstTokenIndex if_token = eatToken(p, TOKEN_KEYWORD_IF);
   1744     if (if_token == null_token)
   1745         return null_node;
   1746 
   1747     expectToken(p, TOKEN_L_PAREN);
   1748     const AstNodeIndex condition = expectExpr(p);
   1749     expectToken(p, TOKEN_R_PAREN);
   1750     parsePtrPayload(p);
   1751 
   1752     const AstNodeIndex then_expr = expectExpr(p);
   1753 
   1754     if (eatToken(p, TOKEN_KEYWORD_ELSE) == null_token) {
   1755         return addNode(&p->nodes,
   1756             (AstNodeItem) {
   1757                 .tag = AST_NODE_IF_SIMPLE,
   1758                 .main_token = if_token,
   1759                 .data = { .lhs = condition, .rhs = then_expr },
   1760             });
   1761     }
   1762 
   1763     parsePayload(p);
   1764     const AstNodeIndex else_expr = expectExpr(p);
   1765     return addNode(&p->nodes,
   1766         (AstNodeItem) {
   1767             .tag = AST_NODE_IF,
   1768             .main_token = if_token,
   1769             .data = {
   1770                 .lhs = condition,
   1771                 .rhs = addExtra(p,
   1772                     (AstNodeIndex[]) { then_expr, else_expr }, 2),
   1773             },
   1774         });
   1775 }
   1776 
   1777 static AstNodeIndex parsePrimaryExpr(Parser* p) {
   1778     const char* tok = tokenizerGetTagString(p->token_tags[p->tok_i]);
   1779     switch (p->token_tags[p->tok_i]) {
   1780     case TOKEN_KEYWORD_ASM:
   1781         fprintf(stderr, "parsePrimaryExpr does not implement %s\n", tok);
   1782         exit(1);
   1783         break;
   1784     case TOKEN_KEYWORD_IF:
   1785         return parseIfExpr(p);
   1786     case TOKEN_KEYWORD_BREAK:
   1787         return addNode(
   1788                 &p->nodes,
   1789                 (AstNodeItem) {
   1790                     .tag = AST_NODE_BREAK,
   1791                     .main_token = nextToken(p),
   1792                     .data = {
   1793                         .lhs = parseBreakLabel(p),
   1794                         .rhs = parseExpr(p),
   1795                     },
   1796                 });
   1797     case TOKEN_KEYWORD_CONTINUE:
   1798         return addNode(
   1799                 &p->nodes,
   1800                 (AstNodeItem) {
   1801                     .tag = AST_NODE_CONTINUE,
   1802                     .main_token = nextToken(p),
   1803                     .data = {
   1804                         .lhs = parseBreakLabel(p),
   1805                         .rhs = parseExpr(p),
   1806                     },
   1807                 });
   1808     case TOKEN_KEYWORD_COMPTIME:
   1809         return addNode(&p->nodes,
   1810             (AstNodeItem) {
   1811                 .tag = AST_NODE_COMPTIME,
   1812                 .main_token = nextToken(p),
   1813                 .data = { .lhs = expectExpr(p), .rhs = 0 },
   1814             });
   1815     case TOKEN_KEYWORD_NOSUSPEND:
   1816         return addNode(&p->nodes,
   1817             (AstNodeItem) {
   1818                 .tag = AST_NODE_NOSUSPEND,
   1819                 .main_token = nextToken(p),
   1820                 .data = { .lhs = expectExpr(p), .rhs = 0 },
   1821             });
   1822     case TOKEN_KEYWORD_RESUME:
   1823         return addNode(&p->nodes,
   1824             (AstNodeItem) {
   1825                 .tag = AST_NODE_RESUME,
   1826                 .main_token = nextToken(p),
   1827                 .data = { .lhs = expectExpr(p), .rhs = 0 },
   1828             });
   1829     case TOKEN_KEYWORD_RETURN:
   1830         return addNode(&p->nodes,
   1831             (AstNodeItem) {
   1832                 .tag = AST_NODE_RETURN,
   1833                 .main_token = nextToken(p),
   1834                 .data = { .lhs = parseExpr(p), .rhs = 0 },
   1835             });
   1836     case TOKEN_IDENTIFIER:
   1837         if (p->token_tags[p->tok_i + 1] == TOKEN_COLON) {
   1838             switch (p->token_tags[p->tok_i + 2]) {
   1839             case TOKEN_KEYWORD_INLINE:
   1840             case TOKEN_KEYWORD_FOR:
   1841             case TOKEN_KEYWORD_WHILE:
   1842                 fprintf(stderr, "parsePrimaryExpr NotImplemented\n");
   1843                 exit(1);
   1844                 return 0; // tcc
   1845             case TOKEN_L_BRACE:
   1846                 p->tok_i += 2;
   1847                 return parseBlock(p);
   1848             default:
   1849                 return parseCurlySuffixExpr(p);
   1850             }
   1851         } else {
   1852             return parseCurlySuffixExpr(p);
   1853         }
   1854     case TOKEN_KEYWORD_INLINE:
   1855     case TOKEN_KEYWORD_FOR:
   1856     case TOKEN_KEYWORD_WHILE:
   1857         fprintf(stderr, "parsePrimaryExpr does not implement %s\n", tok);
   1858         exit(1);
   1859         return 0; // tcc
   1860     case TOKEN_L_BRACE:
   1861         return parseBlock(p);
   1862     default:
   1863         return parseCurlySuffixExpr(p);
   1864     }
   1865 
   1866     return 0; // tcc
   1867 }
   1868 
   1869 static AstNodeIndex parsePrefixExpr(Parser* p) {
   1870     AstNodeTag tag;
   1871     switch (p->token_tags[p->tok_i]) {
   1872     case TOKEN_BANG:
   1873         tag = AST_NODE_BOOL_NOT;
   1874         break;
   1875     case TOKEN_MINUS:
   1876         tag = AST_NODE_NEGATION;
   1877         break;
   1878     case TOKEN_TILDE:
   1879         tag = AST_NODE_BIT_NOT;
   1880         break;
   1881     case TOKEN_MINUS_PERCENT:
   1882         tag = AST_NODE_NEGATION_WRAP;
   1883         break;
   1884     case TOKEN_AMPERSAND:
   1885         tag = AST_NODE_ADDRESS_OF;
   1886         break;
   1887     case TOKEN_KEYWORD_TRY:
   1888         tag = AST_NODE_TRY;
   1889         break;
   1890     case TOKEN_KEYWORD_AWAIT:
   1891         tag = AST_NODE_AWAIT;
   1892         break;
   1893     default:
   1894         return parsePrimaryExpr(p);
   1895     }
   1896     return addNode(
   1897         &p->nodes,
   1898         (AstNodeItem) {
   1899             .tag = tag,
   1900             .main_token = nextToken(p),
   1901             .data = {
   1902                 .lhs = parsePrefixExpr(p),
   1903                 .rhs = 0,
   1904             },
   1905         });
   1906 }
   1907 
   1908 static AstNodeTag assignOpTag(TokenizerTag tok) {
   1909     switch (tok) {
   1910     case TOKEN_EQUAL:
   1911         return AST_NODE_ASSIGN;
   1912     case TOKEN_PLUS_EQUAL:
   1913         return AST_NODE_ASSIGN_ADD;
   1914     case TOKEN_MINUS_EQUAL:
   1915         return AST_NODE_ASSIGN_SUB;
   1916     case TOKEN_ASTERISK_EQUAL:
   1917         return AST_NODE_ASSIGN_MUL;
   1918     case TOKEN_SLASH_EQUAL:
   1919         return AST_NODE_ASSIGN_DIV;
   1920     case TOKEN_PERCENT_EQUAL:
   1921         return AST_NODE_ASSIGN_MOD;
   1922     case TOKEN_AMPERSAND_EQUAL:
   1923         return AST_NODE_ASSIGN_BIT_AND;
   1924     case TOKEN_PIPE_EQUAL:
   1925         return AST_NODE_ASSIGN_BIT_OR;
   1926     case TOKEN_CARET_EQUAL:
   1927         return AST_NODE_ASSIGN_BIT_XOR;
   1928     case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_EQUAL:
   1929         return AST_NODE_ASSIGN_SHL;
   1930     case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_RIGHT_EQUAL:
   1931         return AST_NODE_ASSIGN_SHR;
   1932     case TOKEN_PLUS_PERCENT_EQUAL:
   1933         return AST_NODE_ASSIGN_ADD_WRAP;
   1934     case TOKEN_MINUS_PERCENT_EQUAL:
   1935         return AST_NODE_ASSIGN_SUB_WRAP;
   1936     case TOKEN_ASTERISK_PERCENT_EQUAL:
   1937         return AST_NODE_ASSIGN_MUL_WRAP;
   1938     case TOKEN_PLUS_PIPE_EQUAL:
   1939         return AST_NODE_ASSIGN_ADD_SAT;
   1940     case TOKEN_MINUS_PIPE_EQUAL:
   1941         return AST_NODE_ASSIGN_SUB_SAT;
   1942     case TOKEN_ASTERISK_PIPE_EQUAL:
   1943         return AST_NODE_ASSIGN_MUL_SAT;
   1944     case TOKEN_ANGLE_BRACKET_ANGLE_BRACKET_LEFT_PIPE_EQUAL:
   1945         return AST_NODE_ASSIGN_SHL_SAT;
   1946     default:
   1947         return AST_NODE_ROOT; // not an assignment op
   1948     }
   1949 }
   1950 
   1951 static AstNodeIndex parseAssignExpr(Parser* p) {
   1952     const AstNodeIndex expr = parseExpr(p);
   1953     if (expr == 0)
   1954         return null_node;
   1955 
   1956     const AstNodeTag assign_tag = assignOpTag(p->token_tags[p->tok_i]);
   1957     if (assign_tag == AST_NODE_ROOT)
   1958         return expr;
   1959 
   1960     const AstTokenIndex op_token = nextToken(p);
   1961     const AstNodeIndex rhs = expectExpr(p);
   1962     return addNode(&p->nodes,
   1963         (AstNodeItem) {
   1964             .tag = assign_tag,
   1965             .main_token = op_token,
   1966             .data = { .lhs = expr, .rhs = rhs },
   1967         });
   1968 }
   1969 
   1970 static AstNodeIndex expectBlockExprStatement(Parser* p) {
   1971     // Try block first (labeled or unlabeled)
   1972     if (p->token_tags[p->tok_i] == TOKEN_L_BRACE)
   1973         return parseBlock(p);
   1974     if (p->token_tags[p->tok_i] == TOKEN_IDENTIFIER
   1975         && p->token_tags[p->tok_i + 1] == TOKEN_COLON
   1976         && p->token_tags[p->tok_i + 2] == TOKEN_L_BRACE) {
   1977         p->tok_i += 2;
   1978         return parseBlock(p);
   1979     }
   1980     // Assign expr + semicolon
   1981     const AstNodeIndex expr = parseAssignExpr(p);
   1982     if (expr != 0) {
   1983         expectSemicolon(p);
   1984         return expr;
   1985     }
   1986     fprintf(stderr, "expectBlockExprStatement: expected block or expr\n");
   1987     exit(1);
   1988     return 0; // tcc
   1989 }
   1990 
   1991 static AstNodeIndex expectVarDeclExprStatement(Parser* p) {
   1992     CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch)))
   1993     = initCleanupScratch(p);
   1994 
   1995     while (true) {
   1996         const AstNodeIndex var_decl_proto = parseVarDeclProto(p);
   1997         if (var_decl_proto != 0) {
   1998             SLICE_APPEND(AstNodeIndex, &p->scratch, var_decl_proto);
   1999         } else {
   2000             const AstNodeIndex expr = parseExpr(p);
   2001             SLICE_APPEND(AstNodeIndex, &p->scratch, expr);
   2002         }
   2003         if (eatToken(p, TOKEN_COMMA) == null_token)
   2004             break;
   2005     }
   2006 
   2007     const uint32_t lhs_count = p->scratch.len - scratch_top.old_len;
   2008     assert(lhs_count > 0);
   2009 
   2010     if (lhs_count == 1) {
   2011         const AstNodeIndex lhs = p->scratch.arr[scratch_top.old_len];
   2012         switch (p->token_tags[p->tok_i]) {
   2013         case TOKEN_SEMICOLON:
   2014             p->tok_i++;
   2015             return lhs;
   2016         case TOKEN_R_BRACE:
   2017             // Expression that doesn't need semicolon (block-terminated)
   2018             return lhs;
   2019         default: {
   2020             const AstNodeTag assign_tag = assignOpTag(p->token_tags[p->tok_i]);
   2021             if (assign_tag == AST_NODE_ROOT) {
   2022                 fprintf(stderr,
   2023                     "expectVarDeclExprStatement: unexpected token %s\n",
   2024                     tokenizerGetTagString(p->token_tags[p->tok_i]));
   2025                 exit(1);
   2026             }
   2027             if (assign_tag == AST_NODE_ASSIGN) {
   2028                 // Check if lhs is a var decl that needs initialization
   2029                 const AstNodeTag lhs_tag = p->nodes.tags[lhs];
   2030                 if (lhs_tag == AST_NODE_SIMPLE_VAR_DECL
   2031                     || lhs_tag == AST_NODE_ALIGNED_VAR_DECL
   2032                     || lhs_tag == AST_NODE_LOCAL_VAR_DECL
   2033                     || lhs_tag == AST_NODE_GLOBAL_VAR_DECL) {
   2034                     p->tok_i++;
   2035                     p->nodes.datas[lhs].rhs = expectExpr(p);
   2036                     expectSemicolon(p);
   2037                     return lhs;
   2038                 }
   2039             }
   2040             const AstTokenIndex op_token = nextToken(p);
   2041             const AstNodeIndex rhs = expectExpr(p);
   2042             expectSemicolon(p);
   2043             return addNode(&p->nodes,
   2044                 (AstNodeItem) {
   2045                     .tag = assign_tag,
   2046                     .main_token = op_token,
   2047                     .data = { .lhs = lhs, .rhs = rhs },
   2048                 });
   2049         }
   2050         }
   2051     }
   2052 
   2053     fprintf(
   2054         stderr, "expectVarDeclExprStatement: destructuring not implemented\n");
   2055     exit(1);
   2056     return 0; // tcc
   2057 }
   2058 
   2059 static AstNodeIndex expectStatement(Parser* p, bool allow_defer_var) {
   2060     const AstTokenIndex comptime_token = eatToken(p, TOKEN_KEYWORD_COMPTIME);
   2061     if (comptime_token != null_token) {
   2062         // comptime followed by block => comptime block statement
   2063         const AstNodeIndex block = parseBlock(p);
   2064         if (block != 0) {
   2065             return addNode(&p->nodes,
   2066                 (AstNodeItem) {
   2067                     .tag = AST_NODE_COMPTIME,
   2068                     .main_token = comptime_token,
   2069                     .data = { .lhs = block, .rhs = 0 },
   2070                 });
   2071         }
   2072         // comptime var decl or expression
   2073         if (allow_defer_var) {
   2074             return expectVarDeclExprStatement(p);
   2075         }
   2076         fprintf(
   2077             stderr, "expectStatement: comptime keyword not supported here\n");
   2078         exit(1);
   2079     }
   2080 
   2081     const AstNodeIndex tok = p->token_tags[p->tok_i];
   2082     switch (tok) {
   2083     case TOKEN_KEYWORD_DEFER:
   2084         return addNode(&p->nodes,
   2085             (AstNodeItem) {
   2086                 .tag = AST_NODE_DEFER,
   2087                 .main_token = nextToken(p),
   2088                 .data = {
   2089                     .lhs = expectBlockExprStatement(p),
   2090                     .rhs = 0,
   2091                 },
   2092             });
   2093     case TOKEN_KEYWORD_ERRDEFER: {
   2094         const AstTokenIndex errdefer_token = nextToken(p);
   2095         AstTokenIndex payload = null_token;
   2096         if (p->token_tags[p->tok_i] == TOKEN_PIPE) {
   2097             p->tok_i++;
   2098             payload = expectToken(p, TOKEN_IDENTIFIER);
   2099             expectToken(p, TOKEN_PIPE);
   2100         }
   2101         return addNode(&p->nodes,
   2102             (AstNodeItem) {
   2103                 .tag = AST_NODE_ERRDEFER,
   2104                 .main_token = errdefer_token,
   2105                 .data = {
   2106                     .lhs = payload,
   2107                     .rhs = expectBlockExprStatement(p),
   2108                 },
   2109             });
   2110     }
   2111     case TOKEN_KEYWORD_NOSUSPEND:
   2112         return addNode(&p->nodes,
   2113             (AstNodeItem) {
   2114                 .tag = AST_NODE_NOSUSPEND,
   2115                 .main_token = nextToken(p),
   2116                 .data = {
   2117                     .lhs = expectBlockExprStatement(p),
   2118                     .rhs = 0,
   2119                 },
   2120             });
   2121     case TOKEN_KEYWORD_SUSPEND:
   2122     case TOKEN_KEYWORD_ENUM:
   2123     case TOKEN_KEYWORD_STRUCT:
   2124     case TOKEN_KEYWORD_UNION:;
   2125         const char* tok_str = tokenizerGetTagString(tok);
   2126         fprintf(
   2127             stderr, "expectStatement does not support keyword %s\n", tok_str);
   2128         exit(1);
   2129     default:;
   2130     }
   2131 
   2132     const AstNodeIndex labeled_statement = parseLabeledStatement(p);
   2133     if (labeled_statement != 0)
   2134         return labeled_statement;
   2135 
   2136     if (allow_defer_var) {
   2137         return expectVarDeclExprStatement(p);
   2138     } else {
   2139         const AstNodeIndex assign_expr = parseAssignExpr(p);
   2140         expectSemicolon(p);
   2141         return assign_expr;
   2142     }
   2143 }
   2144 
   2145 static AstNodeIndex parseBlock(Parser* p) {
   2146     const AstNodeIndex lbrace = eatToken(p, TOKEN_L_BRACE);
   2147     if (lbrace == null_token)
   2148         return null_node;
   2149 
   2150     CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch)))
   2151     = initCleanupScratch(p);
   2152 
   2153     while (1) {
   2154         if (p->token_tags[p->tok_i] == TOKEN_R_BRACE)
   2155             break;
   2156 
   2157         // "const AstNodeIndex statement" once tinycc supports typeof_unqual
   2158         // (C23)
   2159         AstNodeIndex statement = expectStatement(p, true);
   2160         if (statement == 0)
   2161             break;
   2162         SLICE_APPEND(AstNodeIndex, &p->scratch, statement);
   2163     }
   2164     expectToken(p, TOKEN_R_BRACE);
   2165     const bool semicolon = (p->token_tags[p->tok_i - 2] == TOKEN_SEMICOLON);
   2166 
   2167     const uint32_t statements_len = p->scratch.len - scratch_top.old_len;
   2168     switch (statements_len) {
   2169     case 0:
   2170         return addNode(
   2171             &p->nodes,
   2172             (AstNodeItem) {
   2173                 .tag = AST_NODE_BLOCK_TWO,
   2174                 .main_token = lbrace,
   2175                 .data = {
   2176                     .lhs = 0,
   2177                     .rhs = 0,
   2178                 },
   2179             });
   2180     case 1:
   2181         return addNode(
   2182             &p->nodes,
   2183             (AstNodeItem) {
   2184                 .tag = semicolon ? AST_NODE_BLOCK_TWO_SEMICOLON : AST_NODE_BLOCK_TWO,
   2185                 .main_token = lbrace,
   2186                 .data = {
   2187                     .lhs = p->scratch.arr[scratch_top.old_len],
   2188                     .rhs = 0,
   2189                 },
   2190             });
   2191     case 2:
   2192         return addNode(
   2193             &p->nodes,
   2194             (AstNodeItem) {
   2195                 .tag = semicolon ? AST_NODE_BLOCK_TWO_SEMICOLON : AST_NODE_BLOCK_TWO,
   2196                 .main_token = lbrace,
   2197                 .data = {
   2198                     .lhs = p->scratch.arr[scratch_top.old_len],
   2199                     .rhs = p->scratch.arr[scratch_top.old_len + 1],
   2200                 },
   2201             });
   2202     default:;
   2203         const AstSubRange span = listToSpan(
   2204             p, &p->scratch.arr[scratch_top.old_len], statements_len);
   2205         return addNode(
   2206             &p->nodes,
   2207             (AstNodeItem) {
   2208                 .tag = semicolon ? AST_NODE_BLOCK_SEMICOLON : AST_NODE_BLOCK,
   2209                 .main_token = lbrace,
   2210                 .data = {
   2211                     .lhs = span.start,
   2212                     .rhs = span.end,
   2213                 },
   2214             });
   2215     }
   2216 
   2217     return 0;
   2218 }
   2219 
   2220 static AstNodeIndex parseLabeledStatement(Parser* p) {
   2221     const AstNodeIndex label_token = parseBlockLabel(p);
   2222     const AstNodeIndex block = parseBlock(p);
   2223     if (block != 0)
   2224         return block;
   2225 
   2226     const AstNodeIndex loop_stmt = parseLoopStatement(p);
   2227     if (loop_stmt != 0)
   2228         return loop_stmt;
   2229 
   2230     if (label_token != 0) {
   2231         fprintf(stderr, "parseLabeledStatement does not support labels\n");
   2232         exit(1);
   2233     }
   2234 
   2235     return null_node;
   2236 }
   2237 
   2238 static AstNodeIndex parseGlobalVarDecl(Parser* p) {
   2239     const AstNodeIndex var_decl = parseVarDeclProto(p);
   2240     if (var_decl == 0) {
   2241         return null_node;
   2242     }
   2243 
   2244     if (eatToken(p, TOKEN_EQUAL) != null_token) {
   2245         const AstNodeIndex init_expr = expectExpr(p);
   2246         p->nodes.datas[var_decl].rhs = init_expr;
   2247     }
   2248     expectToken(p, TOKEN_SEMICOLON);
   2249     return var_decl;
   2250 }
   2251 
   2252 static AstNodeIndex expectTopLevelDecl(Parser* p) {
   2253     AstTokenIndex extern_export_inline_token = nextToken(p);
   2254 
   2255     switch (p->token_tags[extern_export_inline_token]) {
   2256     case TOKEN_KEYWORD_EXTERN:
   2257         eatToken(p, TOKEN_STRING_LITERAL);
   2258         break;
   2259     case TOKEN_KEYWORD_EXPORT:
   2260     case TOKEN_KEYWORD_INLINE:
   2261     case TOKEN_KEYWORD_NOINLINE:
   2262         break;
   2263     default:
   2264         p->tok_i--;
   2265     }
   2266 
   2267     AstNodeIndex fn_proto = parseFnProto(p);
   2268     if (fn_proto != 0) {
   2269         switch (p->token_tags[p->tok_i]) {
   2270         case TOKEN_SEMICOLON:
   2271             p->tok_i++;
   2272             return fn_proto;
   2273         case TOKEN_L_BRACE:;
   2274             AstNodeIndex fn_decl_index = reserveNode(p, AST_NODE_FN_DECL);
   2275             AstNodeIndex body_block = parseBlock(p);
   2276             return setNode(p, fn_decl_index,
   2277                 (AstNodeItem) {
   2278                     .tag = AST_NODE_FN_DECL,
   2279                     .main_token = p->nodes.main_tokens[fn_proto],
   2280                     .data = { .lhs = fn_proto, .rhs = body_block },
   2281                 });
   2282         default:
   2283             exit(1); // Expected semicolon or left brace
   2284         }
   2285     }
   2286 
   2287     eatToken(p, TOKEN_KEYWORD_THREADLOCAL);
   2288     AstNodeIndex var_decl = parseGlobalVarDecl(p);
   2289     if (var_decl != 0) {
   2290         return var_decl;
   2291     }
   2292 
   2293     // assuming the program is correct...
   2294     fprintf(stderr,
   2295         "the next token should be usingnamespace, which is not supported\n");
   2296     exit(1);
   2297     return 0; // make tcc happy
   2298 }
   2299 
   2300 static void findNextContainerMember(Parser* p) {
   2301     uint32_t level = 0;
   2302 
   2303     while (true) {
   2304         AstTokenIndex tok = nextToken(p);
   2305 
   2306         switch (p->token_tags[tok]) {
   2307         // Any of these can start a new top level declaration
   2308         case TOKEN_KEYWORD_TEST:
   2309         case TOKEN_KEYWORD_COMPTIME:
   2310         case TOKEN_KEYWORD_PUB:
   2311         case TOKEN_KEYWORD_EXPORT:
   2312         case TOKEN_KEYWORD_EXTERN:
   2313         case TOKEN_KEYWORD_INLINE:
   2314         case TOKEN_KEYWORD_NOINLINE:
   2315         case TOKEN_KEYWORD_USINGNAMESPACE:
   2316         case TOKEN_KEYWORD_THREADLOCAL:
   2317         case TOKEN_KEYWORD_CONST:
   2318         case TOKEN_KEYWORD_VAR:
   2319         case TOKEN_KEYWORD_FN:
   2320             if (level == 0) {
   2321                 p->tok_i--;
   2322                 return;
   2323             }
   2324             break;
   2325         case TOKEN_IDENTIFIER:
   2326             if (p->token_tags[tok + 1] == TOKEN_COMMA && level == 0) {
   2327                 p->tok_i--;
   2328                 return;
   2329             }
   2330             break;
   2331         case TOKEN_COMMA:
   2332         case TOKEN_SEMICOLON:
   2333             // This decl was likely meant to end here
   2334             if (level == 0)
   2335                 return;
   2336             break;
   2337         case TOKEN_L_PAREN:
   2338         case TOKEN_L_BRACKET:
   2339         case TOKEN_L_BRACE:
   2340             level++;
   2341             break;
   2342         case TOKEN_R_PAREN:
   2343         case TOKEN_R_BRACKET:
   2344             if (level != 0)
   2345                 level--;
   2346             break;
   2347         case TOKEN_R_BRACE:
   2348             if (level == 0) {
   2349                 // end of container, exit
   2350                 p->tok_i--;
   2351                 return;
   2352             }
   2353             level--;
   2354             break;
   2355         case TOKEN_EOF:
   2356             p->tok_i--;
   2357             return;
   2358         default:
   2359             break;
   2360         }
   2361     }
   2362 }
   2363 
   2364 static Members parseContainerMembers(Parser* p) {
   2365     CleanupScratch scratch_top __attribute__((__cleanup__(cleanupScratch)))
   2366     = initCleanupScratch(p);
   2367     while (eatToken(p, TOKEN_CONTAINER_DOC_COMMENT) != null_token)
   2368         ;
   2369 
   2370     FieldState field_state = { .tag = FIELD_STATE_NONE };
   2371 
   2372     bool trailing = false;
   2373     while (1) {
   2374         eatDocComments(p);
   2375         switch (p->token_tags[p->tok_i]) {
   2376         case TOKEN_KEYWORD_TEST: {
   2377             const AstTokenIndex test_token = nextToken(p);
   2378             // test name can be a string literal or identifier, or omitted
   2379             const AstTokenIndex test_name
   2380                 = (p->token_tags[p->tok_i] == TOKEN_STRING_LITERAL
   2381                       || p->token_tags[p->tok_i] == TOKEN_IDENTIFIER)
   2382                 ? nextToken(p)
   2383                 : null_token;
   2384             const AstNodeIndex body = parseBlock(p);
   2385             assert(body != 0);
   2386             const AstNodeIndex test_decl = addNode(&p->nodes,
   2387                 (AstNodeItem) {
   2388                     .tag = AST_NODE_TEST_DECL,
   2389                     .main_token = test_token,
   2390                     .data = { .lhs = test_name, .rhs = body },
   2391                 });
   2392             SLICE_APPEND(AstNodeIndex, &p->scratch, test_decl);
   2393             trailing = p->token_tags[p->tok_i - 1] == TOKEN_R_BRACE;
   2394             break;
   2395         }
   2396         case TOKEN_KEYWORD_USINGNAMESPACE:;
   2397             const char* str = tokenizerGetTagString(p->token_tags[p->tok_i]);
   2398             fprintf(
   2399                 stderr, "%s not implemented in parseContainerMembers\n", str);
   2400             exit(1);
   2401         case TOKEN_KEYWORD_COMPTIME:
   2402             // comptime can be a container field modifier or a comptime
   2403             // block/decl. Check if it's followed by a block (comptime { ...
   2404             // }).
   2405             if (p->token_tags[p->tok_i + 1] == TOKEN_L_BRACE) {
   2406                 const AstTokenIndex comptime_token = nextToken(p);
   2407                 const AstNodeIndex block_node = parseBlock(p);
   2408                 SLICE_APPEND(AstNodeIndex, &p->scratch,
   2409                     addNode(&p->nodes,
   2410                         (AstNodeItem) {
   2411                             .tag = AST_NODE_COMPTIME,
   2412                             .main_token = comptime_token,
   2413                             .data = { .lhs = block_node, .rhs = 0 },
   2414                         }));
   2415                 trailing = false;
   2416                 break;
   2417             }
   2418             // Otherwise it's a container field with comptime modifier
   2419             goto container_field;
   2420         case TOKEN_KEYWORD_PUB: {
   2421             p->tok_i++;
   2422             AstNodeIndex top_level_decl = expectTopLevelDecl(p);
   2423             if (top_level_decl != 0) {
   2424                 if (field_state.tag == FIELD_STATE_SEEN) {
   2425                     field_state.tag = FIELD_STATE_END;
   2426                     field_state.payload.end = top_level_decl;
   2427                 }
   2428                 SLICE_APPEND(AstNodeIndex, &p->scratch, top_level_decl);
   2429             }
   2430             trailing = p->token_tags[p->tok_i - 1] == TOKEN_SEMICOLON;
   2431             break;
   2432         }
   2433         case TOKEN_KEYWORD_CONST:
   2434         case TOKEN_KEYWORD_VAR:
   2435         case TOKEN_KEYWORD_THREADLOCAL:
   2436         case TOKEN_KEYWORD_EXPORT:
   2437         case TOKEN_KEYWORD_EXTERN:
   2438         case TOKEN_KEYWORD_INLINE:
   2439         case TOKEN_KEYWORD_NOINLINE:
   2440         case TOKEN_KEYWORD_FN: {
   2441             const AstNodeIndex top_level_decl = expectTopLevelDecl(p);
   2442             if (top_level_decl != 0) {
   2443                 if (field_state.tag == FIELD_STATE_SEEN) {
   2444                     field_state.tag = FIELD_STATE_END;
   2445                     field_state.payload.end = top_level_decl;
   2446                 }
   2447                 SLICE_APPEND(AstNodeIndex, &p->scratch, top_level_decl);
   2448             }
   2449             trailing = (p->token_tags[p->tok_i - 1] == TOKEN_SEMICOLON);
   2450             break;
   2451         }
   2452         case TOKEN_EOF:
   2453         case TOKEN_R_BRACE:
   2454             goto break_loop;
   2455         container_field:
   2456         default:;
   2457             // skip parseCStyleContainer
   2458             const AstNodeIndex field_node = expectContainerField(p);
   2459             switch (field_state.tag) {
   2460             case FIELD_STATE_NONE:
   2461                 field_state.tag = FIELD_STATE_SEEN;
   2462                 break;
   2463             case FIELD_STATE_SEEN:
   2464                 break;
   2465             case FIELD_STATE_END:
   2466                 fprintf(stderr, "parseContainerMembers error condition\n");
   2467                 exit(1);
   2468             }
   2469             SLICE_APPEND(AstNodeIndex, &p->scratch, field_node);
   2470             switch (p->token_tags[p->tok_i]) {
   2471             case TOKEN_COMMA:
   2472                 p->tok_i++;
   2473                 trailing = true;
   2474                 continue;
   2475             case TOKEN_R_BRACE:
   2476             case TOKEN_EOF:
   2477                 trailing = false;
   2478                 goto break_loop;
   2479             default:;
   2480             }
   2481 
   2482             findNextContainerMember(p);
   2483             continue;
   2484         }
   2485     }
   2486 
   2487 break_loop:;
   2488 
   2489     const uint32_t items_len = p->scratch.len - scratch_top.old_len;
   2490     switch (items_len) {
   2491     case 0:
   2492         return (Members) {
   2493             .len = 0,
   2494             .lhs = 0,
   2495             .rhs = 0,
   2496             .trailing = trailing,
   2497         };
   2498     case 1:
   2499         return (Members) {
   2500             .len = 1,
   2501             .lhs = p->scratch.arr[scratch_top.old_len],
   2502             .rhs = 0,
   2503             .trailing = trailing,
   2504         };
   2505     case 2:
   2506         return (Members) {
   2507             .len = 2,
   2508             .lhs = p->scratch.arr[scratch_top.old_len],
   2509             .rhs = p->scratch.arr[scratch_top.old_len + 1],
   2510             .trailing = trailing,
   2511         };
   2512     default:;
   2513         const AstSubRange span
   2514             = listToSpan(p, &p->scratch.arr[scratch_top.old_len], items_len);
   2515         return (Members) {
   2516             .len = items_len,
   2517             .lhs = span.start,
   2518             .rhs = span.end,
   2519             .trailing = trailing,
   2520         };
   2521     }
   2522 }
   2523 
   2524 void parseRoot(Parser* p) {
   2525     addNode(
   2526         &p->nodes, (AstNodeItem) { .tag = AST_NODE_ROOT, .main_token = 0 });
   2527 
   2528     Members root_members = parseContainerMembers(p);
   2529     AstSubRange root_decls = membersToSpan(root_members, p);
   2530 
   2531     p->nodes.datas[0].lhs = root_decls.start;
   2532     p->nodes.datas[0].rhs = root_decls.end;
   2533 }