selfhost: mirror enum tokens + AST + parsetype branch

Parses byte-identical to the C frontend on enum sources (verified
via `diff` of wwdump vs wwdump_ww -a on an enum-using fixture).
The selfhost side reserves the slot in the AST and TY_* enums so
later check.ww and cgen mirror work doesn't shift numeric IDs.

Codegen-side enum support (member-value folding in cgdot, enum↔int
pass-through in cgtypeassert) is deferred — current selfhost sources
don't use enum, so 990_selfhost / 995_self_rebuild stay green.

main.combined.ww in wwdump/ and w6c/ regenerated by `ww build` as
a side effect of `make wwstage`.
This commit is contained in:
2026-05-12 04:20:59 +09:00
parent 34817eedcd
commit 5bf30f209c
7 changed files with 1161 additions and 6 deletions

View File

@@ -91,8 +91,10 @@ def N_TYPEASSERT: i32 = 61;
def N_VOIDLIT: i32 = 62;
def N_TBANG: i32 = 63;
def N_YIELD: i32 = 64;
def N_TENUM: i32 = 65;
def N_TENUMMEMBER: i32 = 66;
def N_LAST: i32 = 65;
def N_LAST: i32 = 67;
// ---- Node -------------------------------------------------------------
@@ -196,6 +198,8 @@ fn nkname(k: i32) str = {
if (k == N_VOIDLIT) { return "voidlit"; };
if (k == N_TBANG) { return "tbang"; };
if (k == N_YIELD) { return "yield"; };
if (k == N_TENUM) { return "tenum"; };
if (k == N_TENUMMEMBER) { return "tenummember"; };
if (k == N_LAST) { return "last"; };
return "?";
};
@@ -281,6 +285,7 @@ fn pr(fd: i32, n: *node, d: i32) void = {
n.kind == N_LET ||
n.kind == N_TNAME ||
n.kind == N_TFIELD ||
n.kind == N_TENUMMEMBER ||
n.kind == N_FIELD ||
n.kind == N_ATTR
) {

1014
lib/ww/lex/lex.combined.ww Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -111,8 +111,9 @@ def TK_FATARROW: i32 = 81;
def TK_IS: i32 = 82;
def TK_VOID: i32 = 83;
def TK_YIELD: i32 = 84;
def TK_ENUM: i32 = 85;
def TK_LAST: i32 = 85;
def TK_LAST: i32 = 86;
// ---- Pos / Tok --------------------------------------------------------
//
@@ -166,6 +167,7 @@ export fn kwlookup(p: *u8, n: i32) i32 = {
if (streqn(p, "def", n)) { return TK_DEF; };
if (streqn(p, "defer", n)) { return TK_DEFER; };
if (streqn(p, "else", n)) { return TK_ELSE; };
if (streqn(p, "enum", n)) { return TK_ENUM; };
if (streqn(p, "export", n)) { return TK_EXPORT; };
if (streqn(p, "false", n)) { return TK_FALSE; };
if (streqn(p, "fn", n)) { return TK_FN; };
@@ -232,6 +234,7 @@ export fn tokname(k: i32) str = {
if (k == TK_MATCH) { return "match"; };
if (k == TK_CONST) { return "const"; };
if (k == TK_UNDER) { return "_"; };
if (k == TK_ENUM) { return "enum"; };
if (k == TK_LPAREN) { return "("; };
if (k == TK_RPAREN) { return ")"; };

View File

@@ -192,6 +192,40 @@ fn parsetype(p: *parser) *node = {
return n;
};
if (p.curkind == TK_ENUM) {
// `enum [storage] { NAME [= expr], ... }`
// Storage defaults to i32 (lhs == nil). Each member is an
// N_TENUMMEMBER with str=name and lhs = value expr or nil
// (auto-increment when omitted).
advance(p);
let n: *node = newnode(p.a, N_TENUM, pf, pl, pc);
if (p.curkind != TK_LBRACE) {
n.lhs = parsetype(p);
};
expecttok(p, TK_LBRACE, "expected '{' after enum");
let mhead: *node = nil;
let mtail: *node = nil;
for (p.curkind != TK_RBRACE) {
if (p.curkind == TK_EOF) { break; };
let mpf: str = p.curfile;
let mpl: i32 = p.curline;
let mpc: i32 = p.curcol;
let m: *node = newnode(p.a, N_TENUMMEMBER, mpf, mpl, mpc);
let mid: str;
expectident(p, &mid);
m.str = mid;
if (accepttok(p, TK_ASSIGN)) {
m.lhs = parseexpr(p);
};
if (mhead == nil) { mhead = m; mtail = m; }
else { mtail.next = m; mtail = m; };
if (!accepttok(p, TK_COMMA)) { break; };
};
expecttok(p, TK_RBRACE, "expected '}' after enum members");
n.list = mhead;
return n;
};
if (p.curkind == TK_VOID) {
// `void` keyword in type-expr context — emit as N_TNAME so
// resolution treats it like any other primitive name.

View File

@@ -49,6 +49,10 @@ def TY_UNTYPED_STR: i32 = 31;
def TY_UNTYPED_RUNE: i32 = 32;
def TY_UNTYPED_BOOL: i32 = 33;
def TY_UNTYPED_NIL: i32 = 34;
// Appended at the tail to keep prior TY_* values stable — they're
// mirrored in cmd/wcc/ww.h and the selfhost selfcheck depends on
// matching numeric layout.
def TY_ENUM: i32 = 35;
// ---- tinfo / tfield / tparam -----------------------------------------
@@ -223,6 +227,7 @@ export fn typeisint(t: *tinfo) bool = {
if (k == TY_RUNE){ return true; };
if (k == TY_UNTYPED_INT) { return true; };
if (k == TY_UNTYPED_RUNE) { return true; };
if (k == TY_ENUM) { return typeisint(t.sub); };
if (k == TY_NAMED) { return typeisint(t.under); };
return false;
};

View File

@@ -546,8 +546,9 @@ def TK_FATARROW: i32 = 81;
def TK_IS: i32 = 82;
def TK_VOID: i32 = 83;
def TK_YIELD: i32 = 84;
def TK_ENUM: i32 = 85;
def TK_LAST: i32 = 85;
def TK_LAST: i32 = 86;
// ---- Pos / Tok --------------------------------------------------------
//
@@ -601,6 +602,7 @@ export fn kwlookup(p: *u8, n: i32) i32 = {
if (streqn(p, "def", n)) { return TK_DEF; };
if (streqn(p, "defer", n)) { return TK_DEFER; };
if (streqn(p, "else", n)) { return TK_ELSE; };
if (streqn(p, "enum", n)) { return TK_ENUM; };
if (streqn(p, "export", n)) { return TK_EXPORT; };
if (streqn(p, "false", n)) { return TK_FALSE; };
if (streqn(p, "fn", n)) { return TK_FN; };
@@ -667,6 +669,7 @@ export fn tokname(k: i32) str = {
if (k == TK_MATCH) { return "match"; };
if (k == TK_CONST) { return "const"; };
if (k == TK_UNDER) { return "_"; };
if (k == TK_ENUM) { return "enum"; };
if (k == TK_LPAREN) { return "("; };
if (k == TK_RPAREN) { return ")"; };
@@ -1736,8 +1739,10 @@ def N_TYPEASSERT: i32 = 61;
def N_VOIDLIT: i32 = 62;
def N_TBANG: i32 = 63;
def N_YIELD: i32 = 64;
def N_TENUM: i32 = 65;
def N_TENUMMEMBER: i32 = 66;
def N_LAST: i32 = 65;
def N_LAST: i32 = 67;
// ---- Node -------------------------------------------------------------
@@ -1841,6 +1846,8 @@ fn nkname(k: i32) str = {
if (k == N_VOIDLIT) { return "voidlit"; };
if (k == N_TBANG) { return "tbang"; };
if (k == N_YIELD) { return "yield"; };
if (k == N_TENUM) { return "tenum"; };
if (k == N_TENUMMEMBER) { return "tenummember"; };
if (k == N_LAST) { return "last"; };
return "?";
};
@@ -1926,6 +1933,7 @@ fn pr(fd: i32, n: *node, d: i32) void = {
n.kind == N_LET ||
n.kind == N_TNAME ||
n.kind == N_TFIELD ||
n.kind == N_TENUMMEMBER ||
n.kind == N_FIELD ||
n.kind == N_ATTR
) {
@@ -3074,6 +3082,40 @@ fn parsetype(p: *parser) *node = {
return n;
};
if (p.curkind == TK_ENUM) {
// `enum [storage] { NAME [= expr], ... }`
// Storage defaults to i32 (lhs == nil). Each member is an
// N_TENUMMEMBER with str=name and lhs = value expr or nil
// (auto-increment when omitted).
advance(p);
let n: *node = newnode(p.a, N_TENUM, pf, pl, pc);
if (p.curkind != TK_LBRACE) {
n.lhs = parsetype(p);
};
expecttok(p, TK_LBRACE, "expected '{' after enum");
let mhead: *node = nil;
let mtail: *node = nil;
for (p.curkind != TK_RBRACE) {
if (p.curkind == TK_EOF) { break; };
let mpf: str = p.curfile;
let mpl: i32 = p.curline;
let mpc: i32 = p.curcol;
let m: *node = newnode(p.a, N_TENUMMEMBER, mpf, mpl, mpc);
let mid: str;
expectident(p, &mid);
m.str = mid;
if (accepttok(p, TK_ASSIGN)) {
m.lhs = parseexpr(p);
};
if (mhead == nil) { mhead = m; mtail = m; }
else { mtail.next = m; mtail = m; };
if (!accepttok(p, TK_COMMA)) { break; };
};
expecttok(p, TK_RBRACE, "expected '}' after enum members");
n.list = mhead;
return n;
};
if (p.curkind == TK_VOID) {
// `void` keyword in type-expr context — emit as N_TNAME so
// resolution treats it like any other primitive name.
@@ -3316,6 +3358,10 @@ def TY_UNTYPED_STR: i32 = 31;
def TY_UNTYPED_RUNE: i32 = 32;
def TY_UNTYPED_BOOL: i32 = 33;
def TY_UNTYPED_NIL: i32 = 34;
// Appended at the tail to keep prior TY_* values stable — they're
// mirrored in cmd/wcc/ww.h and the selfhost selfcheck depends on
// matching numeric layout.
def TY_ENUM: i32 = 35;
// ---- tinfo / tfield / tparam -----------------------------------------
@@ -3490,6 +3536,7 @@ export fn typeisint(t: *tinfo) bool = {
if (k == TY_RUNE){ return true; };
if (k == TY_UNTYPED_INT) { return true; };
if (k == TY_UNTYPED_RUNE) { return true; };
if (k == TY_ENUM) { return typeisint(t.sub); };
if (k == TY_NAMED) { return typeisint(t.under); };
return false;
};

View File

@@ -546,8 +546,9 @@ def TK_FATARROW: i32 = 81;
def TK_IS: i32 = 82;
def TK_VOID: i32 = 83;
def TK_YIELD: i32 = 84;
def TK_ENUM: i32 = 85;
def TK_LAST: i32 = 85;
def TK_LAST: i32 = 86;
// ---- Pos / Tok --------------------------------------------------------
//
@@ -601,6 +602,7 @@ export fn kwlookup(p: *u8, n: i32) i32 = {
if (streqn(p, "def", n)) { return TK_DEF; };
if (streqn(p, "defer", n)) { return TK_DEFER; };
if (streqn(p, "else", n)) { return TK_ELSE; };
if (streqn(p, "enum", n)) { return TK_ENUM; };
if (streqn(p, "export", n)) { return TK_EXPORT; };
if (streqn(p, "false", n)) { return TK_FALSE; };
if (streqn(p, "fn", n)) { return TK_FN; };
@@ -667,6 +669,7 @@ export fn tokname(k: i32) str = {
if (k == TK_MATCH) { return "match"; };
if (k == TK_CONST) { return "const"; };
if (k == TK_UNDER) { return "_"; };
if (k == TK_ENUM) { return "enum"; };
if (k == TK_LPAREN) { return "("; };
if (k == TK_RPAREN) { return ")"; };
@@ -1736,8 +1739,10 @@ def N_TYPEASSERT: i32 = 61;
def N_VOIDLIT: i32 = 62;
def N_TBANG: i32 = 63;
def N_YIELD: i32 = 64;
def N_TENUM: i32 = 65;
def N_TENUMMEMBER: i32 = 66;
def N_LAST: i32 = 65;
def N_LAST: i32 = 67;
// ---- Node -------------------------------------------------------------
@@ -1841,6 +1846,8 @@ fn nkname(k: i32) str = {
if (k == N_VOIDLIT) { return "voidlit"; };
if (k == N_TBANG) { return "tbang"; };
if (k == N_YIELD) { return "yield"; };
if (k == N_TENUM) { return "tenum"; };
if (k == N_TENUMMEMBER) { return "tenummember"; };
if (k == N_LAST) { return "last"; };
return "?";
};
@@ -1926,6 +1933,7 @@ fn pr(fd: i32, n: *node, d: i32) void = {
n.kind == N_LET ||
n.kind == N_TNAME ||
n.kind == N_TFIELD ||
n.kind == N_TENUMMEMBER ||
n.kind == N_FIELD ||
n.kind == N_ATTR
) {
@@ -3074,6 +3082,40 @@ fn parsetype(p: *parser) *node = {
return n;
};
if (p.curkind == TK_ENUM) {
// `enum [storage] { NAME [= expr], ... }`
// Storage defaults to i32 (lhs == nil). Each member is an
// N_TENUMMEMBER with str=name and lhs = value expr or nil
// (auto-increment when omitted).
advance(p);
let n: *node = newnode(p.a, N_TENUM, pf, pl, pc);
if (p.curkind != TK_LBRACE) {
n.lhs = parsetype(p);
};
expecttok(p, TK_LBRACE, "expected '{' after enum");
let mhead: *node = nil;
let mtail: *node = nil;
for (p.curkind != TK_RBRACE) {
if (p.curkind == TK_EOF) { break; };
let mpf: str = p.curfile;
let mpl: i32 = p.curline;
let mpc: i32 = p.curcol;
let m: *node = newnode(p.a, N_TENUMMEMBER, mpf, mpl, mpc);
let mid: str;
expectident(p, &mid);
m.str = mid;
if (accepttok(p, TK_ASSIGN)) {
m.lhs = parseexpr(p);
};
if (mhead == nil) { mhead = m; mtail = m; }
else { mtail.next = m; mtail = m; };
if (!accepttok(p, TK_COMMA)) { break; };
};
expecttok(p, TK_RBRACE, "expected '}' after enum members");
n.list = mhead;
return n;
};
if (p.curkind == TK_VOID) {
// `void` keyword in type-expr context — emit as N_TNAME so
// resolution treats it like any other primitive name.
@@ -3316,6 +3358,10 @@ def TY_UNTYPED_STR: i32 = 31;
def TY_UNTYPED_RUNE: i32 = 32;
def TY_UNTYPED_BOOL: i32 = 33;
def TY_UNTYPED_NIL: i32 = 34;
// Appended at the tail to keep prior TY_* values stable — they're
// mirrored in cmd/wcc/ww.h and the selfhost selfcheck depends on
// matching numeric layout.
def TY_ENUM: i32 = 35;
// ---- tinfo / tfield / tparam -----------------------------------------
@@ -3490,6 +3536,7 @@ export fn typeisint(t: *tinfo) bool = {
if (k == TY_RUNE){ return true; };
if (k == TY_UNTYPED_INT) { return true; };
if (k == TY_UNTYPED_RUNE) { return true; };
if (k == TY_ENUM) { return typeisint(t.sub); };
if (k == TY_NAMED) { return typeisint(t.under); };
return false;
};