kit

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

commit 635f2c3aa64e3c29f696dc4bec0e64cfcc7c6158
parent e77764449b12af05837424a0441861d5f29aee6b
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 11 Jun 2026 12:15:29 -0700

refactor(parse): one canonical way to determine keywordness

Keywordness was decided three+ ways: ident_kw_inline (kw_map probe),
is_kw (hand-rolled kw_sym[k] + alias compares), classify_kw (probe +
redundant alias compares), four per-file ident_kw/ident_kw_stmt/
ident_kw_init pass-through wrappers, and two __volatile__ special-cases
that compared sym_volatile_alias directly. The alias set was duplicated
across is_kw and classify_kw and the kw_map.

Collapse to a single classifier with two thin shape-adapters:
  ident_kw_inline(p, Sym) -> CKw   -- THE classifier (kw_map probe)
  classify_kw(p, Tok)     -> CKw   -- = TOK_IDENT ? ident_kw_inline : NONE
  is_kw(p, Tok, k)        -> bool  -- = classify_kw(p,t) == k
The GNU alias spellings (__inline__/__restrict/__asm__/_Alignof/__thread,
and now __volatile__) live in exactly one place — the kw_map population —
so an alias is always classified identically to its canonical keyword.
Removed the duplicated alias compares, the four wrapper functions, and
the two volatile special-cases.

Folding __volatile__ into the map also fixes a latent gap: it is now
recognized as a type qualifier everywhere (e.g. `int __volatile__ x;`),
not just in asm context — so this is intentionally NOT byte-identical for
that GNU spelling. All keyword/alias/asm/qualifier suites pass
(test-parse-ok 3880/0, test-parse-err 128/0, test-cg-api, test-toy,
test-pp); byte-identical to golden on the rest of the gate.

Diffstat:
Mlang/c/parse/parse.c | 7++-----
Mlang/c/parse/parse_expr.c | 16++++++----------
Mlang/c/parse/parse_init.c | 14+++++---------
Mlang/c/parse/parse_priv.h | 50++++++++++++++------------------------------------
Mlang/c/parse/parse_stmt.c | 14+++-----------
Mlang/c/parse/parse_type.c | 24++++++++++--------------
6 files changed, 40 insertions(+), 85 deletions(-)

diff --git a/lang/c/parse/parse.c b/lang/c/parse/parse.c @@ -1552,11 +1552,7 @@ static void parse_file_scope_asm(Parser* p) { size_t nbytes; advance(p); /* asm / __asm__ */ for (;;) { - if (is_kw(p, &p->cur, KW_VOLATILE)) { - advance(p); - continue; - } - if (p->cur.kind == TOK_IDENT && p->cur.v.ident == p->sym_volatile_alias) { + if (is_kw(p, &p->cur, KW_VOLATILE)) { /* matches `volatile` and `__volatile__` */ advance(p); continue; } @@ -1733,6 +1729,7 @@ void parse_c(Compiler* c, Pool* pool, Pp* pp, DeclTable* decls, CG* cg, (void)KwMap_set(&p.kw_map, p.sym_restrict_alias, (u8)KW_RESTRICT); (void)KwMap_set(&p.kw_map, p.sym_restrict_alias2, (u8)KW_RESTRICT); (void)KwMap_set(&p.kw_map, p.sym_thread_alias, (u8)KW_THREAD_LOCAL); + (void)KwMap_set(&p.kw_map, p.sym_volatile_alias, (u8)KW_VOLATILE); p.sym_int128 = kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__int128")); p.sym_int128_t = kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__int128_t")); p.sym_uint128_t = kit_sym_intern(p.pool->c, KIT_SLICE_LIT("__uint128_t")); diff --git a/lang/c/parse/parse_expr.c b/lang/c/parse/parse_expr.c @@ -10,10 +10,6 @@ static const Type* ty_size_t(Parser* p) { } static int type_is_fp(const Type* t); -static CKw ident_kw(const Parser* p, Sym name) { - return ident_kw_inline(p, name); -} - static int accept_kw(Parser* p, CKw k) { if (is_kw(p, &p->cur, k)) { advance(p); @@ -1299,7 +1295,7 @@ static int offsetof_find_member(Parser* p, const Type* rec_ty, Sym mname, static const Type* offsetof_designator(Parser* p, const Type* base, u32* off) { const Type* cur = base; - if (p->cur.kind != TOK_IDENT || ident_kw(p, p->cur.v.ident) != KW_NONE) { + if (p->cur.kind != TOK_IDENT || ident_kw_inline(p, p->cur.v.ident) != KW_NONE) { perr(p, "expected member name in __builtin_offsetof"); } for (;;) { @@ -1319,7 +1315,7 @@ static const Type* offsetof_designator(Parser* p, const Type* base, u32* off) { } if (is_punct(&p->cur, '.')) { advance(p); - if (p->cur.kind != TOK_IDENT || ident_kw(p, p->cur.v.ident) != KW_NONE) { + if (p->cur.kind != TOK_IDENT || ident_kw_inline(p, p->cur.v.ident) != KW_NONE) { perr(p, "expected member name after '.'"); } continue; @@ -2092,7 +2088,7 @@ static void parse_primary(Parser* p) { } if (t.kind == TOK_IDENT) { SymEntry* e; - if (ident_kw(p, t.v.ident) != KW_NONE) { + if (ident_kw_inline(p, t.v.ident) != KW_NONE) { perr(p, "unexpected keyword in expression"); } { @@ -2375,7 +2371,7 @@ static void parse_postfix(Parser* p) { perr(p, "request for member in something that is not a struct or union"); } - if (p->cur.kind != TOK_IDENT || ident_kw(p, p->cur.v.ident) != KW_NONE) { + if (p->cur.kind != TOK_IDENT || ident_kw_inline(p, p->cur.v.ident) != KW_NONE) { perr(p, "expected member name after '.'"); } mname = p->cur.v.ident; @@ -2405,7 +2401,7 @@ static void parse_postfix(Parser* p) { if (!rec_ty || (rec_ty->kind != TY_STRUCT && rec_ty->kind != TY_UNION)) { perr(p, "'->' on pointer to non-struct/union"); } - if (p->cur.kind != TOK_IDENT || ident_kw(p, p->cur.v.ident) != KW_NONE) { + if (p->cur.kind != TOK_IDENT || ident_kw_inline(p, p->cur.v.ident) != KW_NONE) { perr(p, "expected member name after '->'"); } mname = p->cur.v.ident; @@ -2507,7 +2503,7 @@ void parse_unary(Parser* p) { Sym name; SrcLoc loc; advance(p); /* '&&' */ - if (p->cur.kind != TOK_IDENT || ident_kw(p, p->cur.v.ident) != KW_NONE) { + if (p->cur.kind != TOK_IDENT || ident_kw_inline(p, p->cur.v.ident) != KW_NONE) { perr(p, "expected label name after '&&'"); } name = p->cur.v.ident; diff --git a/lang/c/parse/parse_init.c b/lang/c/parse/parse_init.c @@ -18,10 +18,6 @@ static SrcLoc tok_loc_init(const Tok* t) { return t->loc; } -static CKw ident_kw_init(const Parser* p, Sym name) { - return ident_kw_inline(p, name); -} - static const Type* init_field_type_at(const Type* ty, u16 i) { const Field* f = &ty->rec.fields[i]; return f->type; @@ -427,7 +423,7 @@ static void parse_designator_chain(Parser* p, const Type* outer_ty, u32 parent_off = cur_off; advance(p); if (p->cur.kind != TOK_IDENT || - ident_kw_init(p, p->cur.v.ident) != KW_NONE) { + ident_kw_inline(p, p->cur.v.ident) != KW_NONE) { perr(p, "expected field name after '.'"); } fname = p->cur.v.ident; @@ -1203,12 +1199,12 @@ static int try_parse_static_address_const(Parser* p, CStaticConst* out) { } } if (p->cur.kind != TOK_IDENT || - ident_kw_init(p, p->cur.v.ident) != KW_NONE) { + ident_kw_inline(p, p->cur.v.ident) != KW_NONE) { perr(p, "expected identifier after '&' in static initializer"); } name = p->cur.v.ident; advance(p); - } else if (t.kind == TOK_IDENT && ident_kw_init(p, t.v.ident) == KW_NONE) { + } else if (t.kind == TOK_IDENT && ident_kw_inline(p, t.v.ident) == KW_NONE) { name = t.v.ident; advance(p); } else { @@ -1269,7 +1265,7 @@ static CStaticConst parse_static_const(Parser* p, const Type* ty, SrcLoc loc) { SrcLoc lloc; advance(p); /* '&&' */ if (p->cur.kind != TOK_IDENT || - ident_kw_init(p, p->cur.v.ident) != KW_NONE) { + ident_kw_inline(p, p->cur.v.ident) != KW_NONE) { perr(p, "expected label name after '&&' in static initializer"); } lname = p->cur.v.ident; @@ -1318,7 +1314,7 @@ static CStaticConst parse_static_const(Parser* p, const Type* ty, SrcLoc loc) { if (cast_unqual->kind == TY_PTR && (p->cur.kind == TOK_STR || is_punct(&p->cur, '&') || (p->cur.kind == TOK_IDENT && - ident_kw_init(p, p->cur.v.ident) == KW_NONE)) && + ident_kw_inline(p, p->cur.v.ident) == KW_NONE)) && try_parse_static_address_const(p, &r)) { return r; } diff --git a/lang/c/parse/parse_priv.h b/lang/c/parse/parse_priv.h @@ -441,49 +441,27 @@ static inline int is_punct(const Tok* t, u32 punct) { static inline int is_pp_hash(const Tok* t) { return t->kind == TOK_PP_HASH; } +/* THE keyword classifier — the single canonical way keywordness is decided. + * Interned Sym -> CKw via kw_map; KW_NONE if the Sym is not a keyword. The map + * holds the canonical keyword spellings AND the GNU alias spellings (`__inline__` + * etc.), each mapping to its CKw, registered once in the kw_map population in + * parse_c — so aliases are classified identically to their canonical keyword, + * with no separate "alias-aware" path. Everything below (and the per-token + * classify_kw / is_kw adapters) routes through this. */ static inline CKw ident_kw_inline(const Parser* p, Sym name) { - /* O(1) classification via kw_map (canonical keywords + GNU alias spellings, - * populated once in parse_c with canonical-first ordering so a real keyword - * is never overwritten by an alias). Equivalent to the old linear scan of - * kw_sym[] followed by the explicit alias compares. */ const u8* v = name ? KwMap_get(&p->kw_map, name) : NULL; return v ? (CKw)*v : KW_NONE; } -static inline int is_kw(const Parser* p, const Tok* t, CKw k) { - if (t->kind != TOK_IDENT) return 0; - if (t->v.ident == p->kw_sym[k]) return 1; - if (k == KW_ALIGNOF && t->v.ident == p->sym_alignof_alias) return 1; - if (k == KW_BUILTIN_ASM && t->v.ident == p->sym_asm_alias) return 1; - if (k == KW_INLINE && - (t->v.ident == p->sym_inline_alias || t->v.ident == p->sym_inline_alias2)) - return 1; - if (k == KW_RESTRICT && (t->v.ident == p->sym_restrict_alias || - t->v.ident == p->sym_restrict_alias2)) - return 1; - if (k == KW_THREAD_LOCAL && t->v.ident == p->sym_thread_alias) return 1; - return 0; +/* A token's keyword identity (KW_NONE for a non-identifier or non-keyword). The + * one place to classify a Tok: classify once, then compare CKw. */ +static inline CKw classify_kw(const Parser* p, const Tok* t) { + return t->kind == TOK_IDENT ? ident_kw_inline(p, t->v.ident) : KW_NONE; } -/* Classify a token's keyword identity ONCE: the canonical keyword (via kw_map) - * or, for the GNU alias spellings, the keyword they alias to. Returns the same - * CKw that `is_kw(p, t, k)` matches on — i.e. `classify_kw(p, t) == k` is exactly - * `is_kw(p, t, k)` for every k — so a caller that tests a token against many - * keywords can classify it once and compare, instead of re-deciding keyword-ness - * per candidate. MUST stay in lockstep with is_kw's alias set above. */ -static inline CKw classify_kw(const Parser* p, const Tok* t) { - Sym s; - CKw k; - if (t->kind != TOK_IDENT) return KW_NONE; - s = t->v.ident; - k = ident_kw_inline(p, s); /* canonical spellings (kw_map) */ - if (k != KW_NONE) return k; - if (s == p->sym_alignof_alias) return KW_ALIGNOF; - if (s == p->sym_asm_alias) return KW_BUILTIN_ASM; - if (s == p->sym_inline_alias || s == p->sym_inline_alias2) return KW_INLINE; - if (s == p->sym_restrict_alias || s == p->sym_restrict_alias2) return KW_RESTRICT; - if (s == p->sym_thread_alias) return KW_THREAD_LOCAL; - return KW_NONE; +/* Is token t the keyword k? Thin boolean shape-adapter over classify_kw. */ +static inline int is_kw(const Parser* p, const Tok* t, CKw k) { + return classify_kw(p, t) == k; } static inline int c_type_is_scalar(const Type* ty) { diff --git a/lang/c/parse/parse_stmt.c b/lang/c/parse/parse_stmt.c @@ -11,10 +11,6 @@ * File-local helpers * ============================================================ */ -static CKw ident_kw_stmt(const Parser* p, Sym name) { - return ident_kw_inline(p, name); -} - static SrcLoc tok_loc_stmt(const Tok* t) { return t->loc; } static int accept_kw_stmt(Parser* p, CKw k) { @@ -319,7 +315,7 @@ static void parse_goto_stmt(Parser* p) { parse_computed_goto(p); return; } - if (p->cur.kind != TOK_IDENT || ident_kw_stmt(p, p->cur.v.ident) != KW_NONE) { + if (p->cur.kind != TOK_IDENT || ident_kw_inline(p, p->cur.v.ident) != KW_NONE) { perr(p, "expected label name after 'goto'"); } name = p->cur.v.ident; @@ -646,11 +642,7 @@ static void parse_asm_stmt(Parser* p) { SrcLoc loc = tok_loc_stmt(&p->cur); for (;;) { - if (accept_kw_stmt(p, KW_VOLATILE)) continue; - if (p->cur.kind == TOK_IDENT && p->cur.v.ident == p->sym_volatile_alias) { - advance(p); - continue; - } + if (accept_kw_stmt(p, KW_VOLATILE)) continue; /* `volatile` or `__volatile__` */ break; } if (accept_kw_stmt(p, KW_GOTO)) saw_goto = 1; @@ -880,7 +872,7 @@ void parse_compound_stmt(Parser* p) { void parse_stmt(Parser* p) { pcg_set_loc(p, tok_loc_stmt(&p->cur)); - if (p->cur.kind == TOK_IDENT && ident_kw_stmt(p, p->cur.v.ident) == KW_NONE) { + if (p->cur.kind == TOK_IDENT && ident_kw_inline(p, p->cur.v.ident) == KW_NONE) { Tok n = peek1(p); if (is_punct(&n, ':')) { parse_label_stmt(p); diff --git a/lang/c/parse/parse_type.c b/lang/c/parse/parse_type.c @@ -109,10 +109,6 @@ static const Type* attrs_apply_type_mode(Parser* p, const Type* base, return base; } -static CKw ident_kw(const Parser* p, Sym name) { - return ident_kw_inline(p, name); -} - int starts_attr(const Parser* p) { return p->cur.kind == TOK_IDENT && p->cur.v.ident == p->sym_attribute; } @@ -824,7 +820,7 @@ int parse_decl_specs(Parser* p, DeclSpecs* out) { seen = 1; } else if (!acc.saw_explicit_type && !tagged_ty && t.kind == TOK_IDENT && tkw == KW_NONE) { - /* tkw == KW_NONE here is exactly ident_kw(t)==KW_NONE: this is the + /* tkw == KW_NONE here is exactly ident_kw_inline(t)==KW_NONE: this is the * terminal else-if, so every keyword/alias branch above already failed. */ if (t.v.ident == p->sym_b_va_list) { if (!p->type_b_va_list) @@ -1049,7 +1045,7 @@ const Type* parse_struct_or_union(Parser* p, TypeKind kind, Attr* rec_attrs = NULL; parse_attrs_into(p, &rec_attrs); tag_loc = tok_loc(&p->cur); - if (p->cur.kind == TOK_IDENT && ident_kw(p, p->cur.v.ident) == KW_NONE) { + if (p->cur.kind == TOK_IDENT && ident_kw_inline(p, p->cur.v.ident) == KW_NONE) { tag_name = p->cur.v.ident; advance(p); } @@ -1143,7 +1139,7 @@ const Type* parse_enum(Parser* p, Attr** anon_attrs_out) { Attr* rec_attrs = NULL; parse_attrs_into(p, &rec_attrs); tag_loc = tok_loc(&p->cur); - if (p->cur.kind == TOK_IDENT && ident_kw(p, p->cur.v.ident) == KW_NONE) { + if (p->cur.kind == TOK_IDENT && ident_kw_inline(p, p->cur.v.ident) == KW_NONE) { tag_name = p->cur.v.ident; advance(p); } @@ -1170,7 +1166,7 @@ const Type* parse_enum(Parser* p, Attr** anon_attrs_out) { Sym name; SrcLoc nloc = tok_loc(&p->cur); SymEntry* e; - if (p->cur.kind != TOK_IDENT || ident_kw(p, p->cur.v.ident) != KW_NONE) { + if (p->cur.kind != TOK_IDENT || ident_kw_inline(p, p->cur.v.ident) != KW_NONE) { perr(p, "expected enumerator name"); } name = p->cur.v.ident; @@ -1218,7 +1214,7 @@ const Type* parse_enum(Parser* p, Attr** anon_attrs_out) { int starts_type_name(const Parser* p, const Tok* t) { if (t->kind != TOK_IDENT) return 0; - CKw k = ident_kw(p, t->v.ident); + CKw k = ident_kw_inline(p, t->v.ident); switch (k) { case KW_VOID: case KW_CHAR: @@ -1408,7 +1404,7 @@ int parse_decl_suffix(Parser* p, DeclSuffix* out) { SymEntry* e = scope_lookup(p, t.v.ident); if (e && e->kind == SEK_ENUM_CST) is_const_start = 1; if (!is_const_start) { - CKw k = ident_kw(p, t.v.ident); + CKw k = ident_kw_inline(p, t.v.ident); if (k == KW_SIZEOF || k == KW_ALIGNOF) is_const_start = 1; } } @@ -1529,7 +1525,7 @@ const Type* parse_declarator_full_info(Parser* p, const Type* base, int is_inner = 0; if (is_punct(&n, '*')) { is_inner = 1; - } else if (n.kind == TOK_IDENT && ident_kw(p, n.v.ident) == KW_NONE) { + } else if (n.kind == TOK_IDENT && ident_kw_inline(p, n.v.ident) == KW_NONE) { SymEntry* e = scope_lookup(p, n.v.ident); if (!(e && e->kind == SEK_TYPEDEF)) is_inner = 1; } @@ -1564,7 +1560,7 @@ const Type* parse_declarator_full_info(Parser* p, const Type* base, } inner_quals[nptrs_inner++] = q; } - if (p->cur.kind == TOK_IDENT && ident_kw(p, p->cur.v.ident) == KW_NONE) { + if (p->cur.kind == TOK_IDENT && ident_kw_inline(p, p->cur.v.ident) == KW_NONE) { name = p->cur.v.ident; nloc = tok_loc(&p->cur); advance(p); @@ -1604,7 +1600,7 @@ const Type* parse_declarator_full_info(Parser* p, const Type* base, nested_quals[nptrs_nested++] = q; } if (p->cur.kind == TOK_IDENT && - ident_kw(p, p->cur.v.ident) == KW_NONE) { + ident_kw_inline(p, p->cur.v.ident) == KW_NONE) { name = p->cur.v.ident; nloc = tok_loc(&p->cur); advance(p); @@ -1628,7 +1624,7 @@ const Type* parse_declarator_full_info(Parser* p, const Type* base, } if (!has_inner_parens) { - if (p->cur.kind == TOK_IDENT && ident_kw(p, p->cur.v.ident) == KW_NONE) { + if (p->cur.kind == TOK_IDENT && ident_kw_inline(p, p->cur.v.ident) == KW_NONE) { name = p->cur.v.ident; nloc = tok_loc(&p->cur); advance(p);