kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

parser_core.c (18287B)


      1 #include <stdarg.h>
      2 #include <stdio.h>
      3 #include <string.h>
      4 
      5 #include "internal.h"
      6 
      7 KitCgTypeId toy_builtin_type(ToyParser* p, KitCgBuiltinType ty) {
      8   return kit_cg_type_builtin(p->c, ty);
      9 }
     10 
     11 KitCgTypeId toy_cg_record_type(ToyParser* p, KitSym tag,
     12                                const KitCgFieldDesc* fields, uint32_t nfields,
     13                                int is_union, uint32_t align_override) {
     14   KitCgRecordDesc desc;
     15   memset(&desc, 0, sizeof desc);
     16   desc.tag = tag;
     17   desc.fields = fields;
     18   desc.nfields = nfields;
     19   desc.is_union = is_union;
     20   desc.align_override = align_override;
     21   return kit_cg_type_record(p->c, &desc);
     22 }
     23 
     24 int toy_cg_record_complete(ToyParser* p, KitCgTypeId record, KitSym tag,
     25                            const KitCgFieldDesc* fields, uint32_t nfields,
     26                            int is_union, uint32_t align_override) {
     27   KitCgRecordDesc desc;
     28   memset(&desc, 0, sizeof desc);
     29   desc.tag = tag;
     30   desc.fields = fields;
     31   desc.nfields = nfields;
     32   desc.is_union = is_union;
     33   desc.align_override = align_override;
     34   return kit_cg_type_record_complete(p->c, record, &desc) == KIT_OK;
     35 }
     36 
     37 KitCgTypeId toy_cg_func_ret(ToyParser* p, KitCgTypeId fn_ty) {
     38   KitCgTypeId rt = kit_cg_type_func_result(p->c, fn_ty).type;
     39   return rt ? rt : toy_builtin_type(p, KIT_CG_BUILTIN_VOID);
     40 }
     41 
     42 void* toy_parser_zalloc(ToyParser* p, size_t count, size_t elem_size,
     43                         const char* what) {
     44   KitHeap* h = kit_compiler_context(p->c)->heap;
     45   size_t size = count * elem_size;
     46   void* items;
     47   if (count != 0 && size / count != elem_size) {
     48     toy_error(p, p->cur.loc, "out of memory growing %.*s",
     49               KIT_SLICE_ARG(kit_slice_cstr(what)));
     50     return NULL;
     51   }
     52   items = h->alloc(h, size ? size : 1u, 1);
     53   if (!items) {
     54     toy_error(p, p->cur.loc, "out of memory growing %.*s",
     55               KIT_SLICE_ARG(kit_slice_cstr(what)));
     56     return NULL;
     57   }
     58   memset(items, 0, size ? size : 1u);
     59   return items;
     60 }
     61 
     62 void toy_parser_free_mem(ToyParser* p, void* items, size_t size) {
     63   KitHeap* h;
     64   if (!items) return;
     65   h = kit_compiler_context(p->c)->heap;
     66   h->free(h, items, size ? size : 1u);
     67 }
     68 
     69 static uint32_t toy_source_file_id(KitCompiler* c, const char* name) {
     70   uint32_t file_id = 0;
     71   if (name && *name)
     72     (void)kit_source_add_memory(c, kit_slice_cstr(name), &file_id);
     73   return file_id;
     74 }
     75 
     76 void toy_parser_init(ToyParser* p, KitCompiler* c, KitCg* cg, ToyModule* module,
     77                      const uint8_t* data, size_t len, const char* input_name) {
     78   memset(p, 0, sizeof *p);
     79   p->module = module;
     80   p->file_id = toy_source_file_id(c, input_name);
     81   p->input_name = input_name;
     82   toy_lexer_init(&p->lex, data, len, p->file_id);
     83   p->cur = toy_lexer_next(&p->lex);
     84   p->c = c;
     85   p->cg = cg;
     86   p->target = kit_compiler_target_spec(c);
     87   p->int_type = toy_builtin_type(p, KIT_CG_BUILTIN_I64);
     88   p->size_type = toy_builtin_type(
     89       p, p->target.ptr_size <= 4u ? KIT_CG_BUILTIN_I32 : KIT_CG_BUILTIN_I64);
     90   p->int_ptr_type = kit_cg_type_ptr(c, p->int_type, 0);
     91   p->va_list_type = toy_builtin_type(p, KIT_CG_BUILTIN_VARARG_STATE);
     92   p->nvars = 0;
     93   p->cap_vars = 0;
     94   p->vars = NULL;
     95   /* The durable module is zero-initialized by the frontend; register the
     96    * builtin types into it exactly once, on this first compile. */
     97   toy_type_register_builtins(p);
     98   p->nscopes = 0;
     99   p->cap_scopes = 0;
    100   p->scopes = NULL;
    101   p->nlabels = 0;
    102   p->cap_labels = 0;
    103   p->labels = NULL;
    104   p->goto_targets = NULL;
    105   p->cap_goto_targets = 0;
    106   p->cur_fn_ret = toy_builtin_type(p, KIT_CG_BUILTIN_VOID);
    107   p->cur_fn_ret_toy = toy_type_from_cg(p, p->cur_fn_ret);
    108   p->diag = kit_compiler_context(c)->diag;
    109   p->input_name = input_name;
    110   p->has_error = 0;
    111   p->expr_island_mask = 0;
    112   p->last_type = TOY_TYPE_NONE;
    113   p->allow_tail_call_expr = 0;
    114   p->tail_call_expr = 0;
    115   p->tail_call_ret_toy = TOY_TYPE_NONE;
    116   p->input_kind = KIT_FRONTEND_INPUT_TRANSLATION_UNIT;
    117 }
    118 
    119 void toy_parser_reinit(ToyParser* p, KitCompiler* c, KitCg* cg,
    120                        const uint8_t* data, size_t len, const char* input_name,
    121                        KitFrontendInputKind input_kind) {
    122   p->file_id = toy_source_file_id(c, input_name);
    123   p->input_name = input_name;
    124   toy_lexer_init(&p->lex, data, len, p->file_id);
    125   p->cur = toy_lexer_next(&p->lex);
    126   p->c = c;
    127   p->cg = cg;
    128   p->target = kit_compiler_target_spec(c);
    129   p->int_type = toy_builtin_type(p, KIT_CG_BUILTIN_I64);
    130   p->size_type = toy_builtin_type(
    131       p, p->target.ptr_size <= 4u ? KIT_CG_BUILTIN_I32 : KIT_CG_BUILTIN_I64);
    132   p->int_ptr_type = kit_cg_type_ptr(c, p->int_type, 0);
    133   p->va_list_type = toy_builtin_type(p, KIT_CG_BUILTIN_VARARG_STATE);
    134   p->nvars = 0;
    135   p->nscopes = 0;
    136   p->nlabels = 0;
    137   p->cur_fn_ret = toy_builtin_type(p, KIT_CG_BUILTIN_VOID);
    138   p->cur_fn_ret_toy = toy_type_from_cg(p, p->cur_fn_ret);
    139   p->diag = kit_compiler_context(c)->diag;
    140   p->input_name = input_name;
    141   p->has_error = 0;
    142   p->expr_island_mask = 0;
    143   p->last_type = TOY_TYPE_NONE;
    144   p->allow_tail_call_expr = 0;
    145   p->tail_call_expr = 0;
    146   p->tail_call_ret_toy = TOY_TYPE_NONE;
    147   p->input_kind = input_kind;
    148 }
    149 
    150 /* Frees one heap block sized for the module owner. Mirrors
    151  * toy_parser_free_mem but does not need a live parser, so the durable module
    152  * can be torn down independently of any compile. */
    153 static void toy_mem_free(KitCompiler* c, void* items, size_t size) {
    154   KitHeap* h;
    155   if (!items) return;
    156   h = kit_compiler_context(c)->heap;
    157   h->free(h, items, size ? size : 1u);
    158 }
    159 
    160 void toy_parser_dispose(ToyParser* p) {
    161   /* Per-compile scratch only; the durable arrays belong to the module and
    162    * are freed by toy_module_dispose. */
    163   toy_parser_free_mem(p, p->vars, p->cap_vars * sizeof *p->vars);
    164   toy_parser_free_mem(p, p->scopes, p->cap_scopes * sizeof *p->scopes);
    165   toy_parser_free_mem(p, p->labels, p->cap_labels * sizeof *p->labels);
    166   toy_parser_free_mem(p, p->goto_targets,
    167                       p->cap_goto_targets * sizeof *p->goto_targets);
    168   toy_parser_free_mem(p, p->fn_syms, p->cap_fn_syms * sizeof *p->fn_syms);
    169   toy_parser_free_mem(p, p->global_syms,
    170                       p->cap_global_syms * sizeof *p->global_syms);
    171   toy_parser_free_mem(p, p->txn.undo, p->txn.cap_undo * sizeof *p->txn.undo);
    172   p->txn.undo = NULL;
    173   p->txn.cap_undo = 0;
    174   p->txn.nundo = 0;
    175   p->txn.open = 0;
    176   p->vars = NULL;
    177   p->scopes = NULL;
    178   p->labels = NULL;
    179   p->goto_targets = NULL;
    180   p->fn_syms = NULL;
    181   p->global_syms = NULL;
    182   p->cap_fn_syms = 0;
    183   p->cap_global_syms = 0;
    184   p->nvars = 0;
    185   p->nscopes = p->nlabels = 0;
    186   p->cap_vars = 0;
    187   p->cap_scopes = p->cap_labels = 0;
    188   p->cap_goto_targets = 0;
    189   p->last_type = TOY_TYPE_NONE;
    190 }
    191 
    192 void toy_module_dispose(ToyModule* m, KitCompiler* c) {
    193   size_t i;
    194   ToyTypeTable* tt = &m->type_table;
    195   /* Only free per-element pointer arrays when their companion count is
    196    * non-zero — allocation only happens in that case, so a zero count means
    197    * the pointer field was never written by an allocation. This guards
    198    * against rare paths that leave a pointer slot uninitialized while
    199    * leaving the count at 0; without it, freeing a garbage pointer aborts
    200    * libc malloc with "pointer being freed was not allocated". */
    201   for (i = 0; i < m->nfns; ++i) {
    202     if (m->fns[i].nparams) {
    203       toy_mem_free(c, m->fns[i].params,
    204                    m->fns[i].nparams * sizeof *m->fns[i].params);
    205       toy_mem_free(c, m->fns[i].toy_params,
    206                    m->fns[i].nparams * sizeof *m->fns[i].toy_params);
    207     }
    208   }
    209   toy_mem_free(c, m->fns, m->cap_fns * sizeof *m->fns);
    210   toy_mem_free(c, m->globals, m->cap_globals * sizeof *m->globals);
    211   for (i = 0; i < tt->ntypes; ++i) {
    212     if (tt->types[i].nparams) {
    213       toy_mem_free(c, tt->types[i].params,
    214                    tt->types[i].nparams * sizeof *tt->types[i].params);
    215     }
    216   }
    217   toy_mem_free(c, tt->types, tt->cap_types * sizeof *tt->types);
    218   for (i = 0; i < tt->count; ++i) {
    219     if (tt->named[i].cap_enum_values) {
    220       toy_mem_free(
    221           c, tt->named[i].enum_values,
    222           tt->named[i].cap_enum_values * sizeof *tt->named[i].enum_values);
    223     }
    224     if (tt->named[i].cap_fields) {
    225       toy_mem_free(c, tt->named[i].fields,
    226                    tt->named[i].cap_fields * sizeof *tt->named[i].fields);
    227     }
    228   }
    229   toy_mem_free(c, tt->named, tt->cap * sizeof *tt->named);
    230   m->fns = NULL;
    231   m->globals = NULL;
    232   tt->types = NULL;
    233   tt->named = NULL;
    234   m->nfns = m->nglobals = 0;
    235   tt->count = tt->ntypes = 0;
    236   m->cap_fns = m->cap_globals = 0;
    237   tt->cap = tt->cap_types = 0;
    238 }
    239 
    240 void toy_txn_begin(ToyParser* p) {
    241   ToyModule* m = p->module;
    242   p->txn.open = 1;
    243   p->txn.fns = m->nfns;
    244   p->txn.globals = m->nglobals;
    245   p->txn.types = m->type_table.ntypes;
    246   p->txn.named = m->type_table.count;
    247   p->txn.nundo = 0; /* reuse the journal buffer; cap_undo persists */
    248 }
    249 
    250 void toy_txn_commit(ToyParser* p) {
    251   if (!p->txn.open) return;
    252   /* Staged appends are already in the module; just drop the journal. */
    253   p->txn.open = 0;
    254   p->txn.nundo = 0;
    255 }
    256 
    257 int toy_txn_record_named(ToyParser* p, size_t index) {
    258   size_t i;
    259   if (!p->txn.open || index >= p->txn.named) return 1; /* staged or no txn */
    260   for (i = 0; i < p->txn.nundo; ++i) {
    261     if (p->txn.undo[i].kind == TOY_UNDO_NAMED && p->txn.undo[i].index == index)
    262       return 1; /* keep the earliest before-image */
    263   }
    264   if (!toy_parser_reserve(p, (void**)&p->txn.undo, &p->txn.cap_undo,
    265                           p->txn.nundo + 1u, sizeof *p->txn.undo,
    266                           "undo journal")) {
    267     return 0;
    268   }
    269   p->txn.undo[p->txn.nundo].kind = TOY_UNDO_NAMED;
    270   p->txn.undo[p->txn.nundo].index = index;
    271   p->txn.undo[p->txn.nundo].saved.named = p->module->type_table.named[index];
    272   p->txn.nundo++;
    273   return 1;
    274 }
    275 
    276 int toy_txn_record_type(ToyParser* p, size_t index) {
    277   size_t i;
    278   if (!p->txn.open || index >= p->txn.types) return 1; /* staged or no txn */
    279   for (i = 0; i < p->txn.nundo; ++i) {
    280     if (p->txn.undo[i].kind == TOY_UNDO_TYPE && p->txn.undo[i].index == index)
    281       return 1;
    282   }
    283   if (!toy_parser_reserve(p, (void**)&p->txn.undo, &p->txn.cap_undo,
    284                           p->txn.nundo + 1u, sizeof *p->txn.undo,
    285                           "undo journal")) {
    286     return 0;
    287   }
    288   p->txn.undo[p->txn.nundo].kind = TOY_UNDO_TYPE;
    289   p->txn.undo[p->txn.nundo].index = index;
    290   p->txn.undo[p->txn.nundo].saved.type = p->module->type_table.types[index];
    291   p->txn.nundo++;
    292   return 1;
    293 }
    294 
    295 void toy_txn_abort(ToyParser* p) {
    296   ToyModule* m = p->module;
    297   ToyTypeTable* tt = &m->type_table;
    298   size_t i;
    299   if (!p->txn.open) return;
    300   p->txn.open = 0;
    301   /* 1. Restore committed entries that were mutated in place, newest first.
    302    * The current entry may have allocated a fields/enum/params array since the
    303    * snapshot; free it before restoring the (typically NULL) saved pointer. A
    304    * committed entry is only mutated when completing a forward declaration, so
    305    * the saved array is NULL and there is no realloc-of-saved hazard. */
    306   while (p->txn.nundo > 0) {
    307     ToyUndo* u = &p->txn.undo[--p->txn.nundo];
    308     if (u->kind == TOY_UNDO_NAMED) {
    309       ToyNamedType* cur = &tt->named[u->index];
    310       if (cur->fields != u->saved.named.fields) {
    311         toy_mem_free(p->c, cur->fields, cur->cap_fields * sizeof *cur->fields);
    312       }
    313       if (cur->enum_values != u->saved.named.enum_values) {
    314         toy_mem_free(p->c, cur->enum_values,
    315                      cur->cap_enum_values * sizeof *cur->enum_values);
    316       }
    317       *cur = u->saved.named;
    318     } else {
    319       ToyType* cur = &tt->types[u->index];
    320       if (cur->params != u->saved.type.params) {
    321         toy_mem_free(p->c, cur->params, cur->nparams * sizeof *cur->params);
    322       }
    323       *cur = u->saved.type;
    324     }
    325   }
    326   /* 2. Truncate staged appends, freeing each dropped entry's sub-arrays. */
    327   for (i = p->txn.fns; i < m->nfns; ++i) {
    328     if (m->fns[i].nparams) {
    329       toy_mem_free(p->c, m->fns[i].params,
    330                    m->fns[i].nparams * sizeof *m->fns[i].params);
    331       toy_mem_free(p->c, m->fns[i].toy_params,
    332                    m->fns[i].nparams * sizeof *m->fns[i].toy_params);
    333     }
    334   }
    335   m->nfns = p->txn.fns;
    336   m->nglobals = p->txn.globals; /* globals own no sub-arrays */
    337   for (i = p->txn.types; i < tt->ntypes; ++i) {
    338     if (tt->types[i].nparams) {
    339       toy_mem_free(p->c, tt->types[i].params,
    340                    tt->types[i].nparams * sizeof *tt->types[i].params);
    341     }
    342   }
    343   tt->ntypes = p->txn.types;
    344   for (i = p->txn.named; i < tt->count; ++i) {
    345     if (tt->named[i].cap_fields) {
    346       toy_mem_free(p->c, tt->named[i].fields,
    347                    tt->named[i].cap_fields * sizeof *tt->named[i].fields);
    348     }
    349     if (tt->named[i].cap_enum_values) {
    350       toy_mem_free(
    351           p->c, tt->named[i].enum_values,
    352           tt->named[i].cap_enum_values * sizeof *tt->named[i].enum_values);
    353     }
    354   }
    355   tt->count = p->txn.named;
    356 }
    357 
    358 int toy_parser_reserve(ToyParser* p, void** items, size_t* cap, size_t want,
    359                        size_t elem_size, const char* what) {
    360   KitHeap* h;
    361   size_t new_cap;
    362   size_t old_size;
    363   size_t new_size;
    364   size_t old_cap;
    365   void* new_items;
    366   if (want <= *cap) return 1;
    367   old_cap = *cap;
    368   new_cap = old_cap ? old_cap * 2u : 8u;
    369   while (new_cap < want) new_cap *= 2u;
    370   old_size = old_cap * elem_size;
    371   new_size = new_cap * elem_size;
    372   if (new_cap != 0 && new_size / new_cap != elem_size) {
    373     toy_error(p, p->cur.loc, "out of memory growing %.*s",
    374               KIT_SLICE_ARG(kit_slice_cstr(what)));
    375     return 0;
    376   }
    377   h = kit_compiler_context(p->c)->heap;
    378   /* Allocate fresh and copy old contents over (rather than realloc'ing in
    379    * place and only zeroing the tail). Zeroing the whole buffer guarantees
    380    * any slot the writer doesn't fully initialize has NULL pointers, so the
    381    * dispose path never sees stale bytes — a previous realloc-in-place
    382    * variant left under-initialized slots holding the realloc'd pages'
    383    * prior contents, which manifested as a "pointer being freed was not
    384    * allocated" abort in lib c malloc when the dispose loop walked the
    385    * type table. */
    386   new_items = h->alloc(h, new_size ? new_size : 1u, 1);
    387   if (!new_items) {
    388     toy_error(p, p->cur.loc, "out of memory growing %.*s",
    389               KIT_SLICE_ARG(kit_slice_cstr(what)));
    390     return 0;
    391   }
    392   memset(new_items, 0, new_size ? new_size : 1u);
    393   if (*items && old_size) memcpy(new_items, *items, old_size);
    394   if (*items) h->free(h, *items, old_size ? old_size : 1u);
    395   *items = new_items;
    396   *cap = new_cap;
    397   return 1;
    398 }
    399 
    400 void toy_parser_advance(ToyParser* p) { p->cur = toy_lexer_next(&p->lex); }
    401 
    402 int toy_parser_match(ToyParser* p, ToyTokenKind kind) {
    403   if (p->cur.kind == kind) {
    404     toy_parser_advance(p);
    405     return 1;
    406   }
    407   return 0;
    408 }
    409 
    410 int toy_parser_expect(ToyParser* p, ToyTokenKind kind) {
    411   if (p->cur.kind == kind) {
    412     toy_parser_advance(p);
    413     return 1;
    414   }
    415   return 0;
    416 }
    417 
    418 void toy_error(ToyParser* p, KitSrcLoc loc, const char* fmt, ...) {
    419   va_list ap;
    420   p->has_error = 1;
    421   if (!p->diag) return;
    422   va_start(ap, fmt);
    423   p->diag->emit(p->diag, KIT_DIAG_ERROR, loc, fmt, ap);
    424   va_end(ap);
    425 }
    426 
    427 void toy_set_loc(ToyParser* p) {
    428   if (p->cg) kit_cg_set_loc(p->cg, p->cur.loc);
    429 }
    430 
    431 KitSym toy_tok_sym(ToyParser* p, ToyToken tok) {
    432   char buf[64];
    433   if (tok.text_len >= sizeof(buf)) {
    434     toy_error(p, tok.loc, "identifier too long");
    435     return 0;
    436   }
    437   memcpy(buf, tok.text, tok.text_len);
    438   buf[tok.text_len] = '\0';
    439   return kit_sym_intern(p->c, (KitSlice){.s = buf, .len = tok.text_len});
    440 }
    441 
    442 int toy_sym_is(ToyParser* p, KitSym sym, const char* name) {
    443   return sym == kit_sym_intern(p->c, kit_slice_cstr(name));
    444 }
    445 
    446 int toy_lookup_const(ToyParser* p, KitSym tok, const ToyConstRow* rows,
    447                      size_t n, const char* what, uint64_t* out) {
    448   size_t i;
    449   for (i = 0; i < n; ++i) {
    450     if (tok == kit_sym_intern(p->c, kit_slice_cstr(rows[i].name))) {
    451       *out = rows[i].value;
    452       return 1;
    453     }
    454   }
    455   toy_error(p, p->cur.loc, "unknown %s", what);
    456   return 0;
    457 }
    458 
    459 int toy_parse_dot_const(ToyParser* p, const ToyConstRow* rows, size_t n,
    460                         int strict_ident, const char* expected,
    461                         const char* unknown, uint64_t* out) {
    462   KitSym tok;
    463   if (strict_ident) {
    464     if (!toy_parser_expect(p, TOK_DOT) || p->cur.kind != TOK_IDENT) {
    465       toy_error(p, p->cur.loc, "expected %s", expected);
    466       return 0;
    467     }
    468     tok = toy_tok_sym(p, p->cur);
    469     toy_parser_advance(p);
    470   } else {
    471     (void)expected;
    472     if (!toy_parse_attr_dot_name(p, &tok)) return 0;
    473   }
    474   return toy_lookup_const(p, tok, rows, n, unknown, out);
    475 }
    476 
    477 int toy_parse_flag_set(ToyParser* p, const ToyConstRow* rows, size_t n,
    478                        const char* unknown, int comma_prefixed,
    479                        uint32_t* mask) {
    480   for (;;) {
    481     KitSym name;
    482     uint64_t value;
    483     if (comma_prefixed) {
    484       if (!toy_parser_match(p, TOK_COMMA)) break;
    485     } else if (p->cur.kind == TOK_RPAREN || p->cur.kind == TOK_EOF) {
    486       break;
    487     }
    488     if (!toy_parse_attr_dot_name(p, &name)) return 0;
    489     if (!toy_lookup_const(p, name, rows, n, unknown, &value)) return 0;
    490     *mask |= (uint32_t)value;
    491     if (!comma_prefixed && !toy_parser_match(p, TOK_COMMA)) break;
    492   }
    493   return 1;
    494 }
    495 
    496 int toy_skip_attr_list_ex(ToyParser* p, int* has_static) {
    497   int bracket_depth = 0;
    498   int paren_depth = 0;
    499   if (has_static) *has_static = 0;
    500   if (!toy_parser_match(p, TOK_AT)) return 1;
    501   if (!toy_parser_expect(p, TOK_LBRACKET)) {
    502     toy_error(p, p->cur.loc, "expected '[' after '@'");
    503     return 0;
    504   }
    505   bracket_depth = 1;
    506   while (p->cur.kind != TOK_EOF && bracket_depth > 0) {
    507     if (p->cur.kind == TOK_DOT) {
    508       toy_parser_advance(p);
    509       if (p->cur.kind == TOK_IDENT && has_static && p->cur.text_len == 6 &&
    510           memcmp(p->cur.text, "static", 6) == 0) {
    511         *has_static = 1;
    512       }
    513       continue;
    514     }
    515     if (p->cur.kind == TOK_LBRACKET && paren_depth == 0)
    516       bracket_depth++;
    517     else if (p->cur.kind == TOK_RBRACKET && paren_depth == 0)
    518       bracket_depth--;
    519     else if (p->cur.kind == TOK_LPAREN)
    520       paren_depth++;
    521     else if (p->cur.kind == TOK_RPAREN && paren_depth > 0)
    522       paren_depth--;
    523     toy_parser_advance(p);
    524   }
    525   if (bracket_depth != 0) {
    526     toy_error(p, p->cur.loc, "unterminated attribute list");
    527     return 0;
    528   }
    529   return 1;
    530 }
    531 
    532 int toy_skip_attr_list(ToyParser* p) { return toy_skip_attr_list_ex(p, NULL); }