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:
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);