diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 945cc0ea..04da3fb2 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -2175,6 +2175,16 @@ cgexpr(Cg *c, Node *n, Local *locals) Type *st = s ? s->type : NULL; Type *u = (st && st->kind == TY_NAMED) ? st->under : st; Type *vt = n->type; + /* Enum ↔ integer: reinterpret-only. The value already lives + * in AX after evaluating the LHS; no tag/unwrap needed. */ + { + Type *vu = (vt && vt->kind == TY_NAMED) ? vt->under : vt; + if ((u && u->kind == TY_ENUM) || + (vu && vu->kind == TY_ENUM)) { + cgexpr(c, s, locals); + break; + } + } int slot_size = (u && u->kind == TY_TAGGED) ? (int)u->size : 16; int sl_off = 0; if (s && s->kind == N_IDENT && s->str) { diff --git a/cmd/wcc/ast.c b/cmd/wcc/ast.c index dd569c14..6358cec0 100644 --- a/cmd/wcc/ast.c +++ b/cmd/wcc/ast.c @@ -86,6 +86,8 @@ nkname(Nkind k) case N_VOIDLIT: return "voidlit"; case N_TBANG: return "tbang"; case N_YIELD: return "yield"; + case N_TENUM: return "tenum"; + case N_TENUMMEMBER: return "tenummember"; case N_LAST: return "last"; } return "?"; @@ -158,6 +160,7 @@ pr(FILE *f, Node *n, int d) case N_LET: case N_TNAME: case N_TFIELD: + case N_TENUMMEMBER: case N_FIELD: case N_ATTR: if (n->str) { fputc(' ', f); printq(f, n->str); } diff --git a/cmd/wcc/check.c b/cmd/wcc/check.c index 86926ddc..990b625e 100644 --- a/cmd/wcc/check.c +++ b/cmd/wcc/check.c @@ -164,6 +164,85 @@ tagged_success_type(Type *u) return u->params ? u->params->type : NULL; } +/* eval_enum_value — fold an enum member-value expression to a u64 + * constant. Sees prior siblings via the `prev` Tfield list (each + * carries the member's name and resolved value in .offset). Returns + * 1 on success; on failure emits the error and returns 0. The op set + * is the constant subset typical of Hare-style flag enums: + * literal, sibling-ident, + - * / % & | ^ << >>, unary - and ~. */ +static int +eval_enum_value(Checker *c, Node *n, Tfield *prev, u64 *out) +{ + if (n == NULL) return 0; + switch (n->kind) { + case N_INTLIT: + case N_RUNELIT: + *out = n->uval; + return 1; + case N_TRUE: *out = 1; return 1; + case N_FALSE: *out = 0; return 1; + case N_IDENT: { + for (Tfield *f = prev; f; f = f->next) { + if (f->name && n->str && + strcmp(f->name, n->str) == 0) { + *out = f->offset; + return 1; + } + } + err(c, n->pos, "enum value: unknown identifier '%s'", + n->str ? n->str : "?"); + return 0; + } + case N_BIN: { + u64 a, b; + if (!eval_enum_value(c, n->lhs, prev, &a) || + !eval_enum_value(c, n->rhs, prev, &b)) + return 0; + switch (n->op) { + case TK_PLUS: *out = a + b; return 1; + case TK_MINUS: *out = a - b; return 1; + case TK_STAR: *out = a * b; return 1; + case TK_SLASH: + if (b == 0) goto divzero; + *out = a / b; return 1; + case TK_PERCENT: + if (b == 0) goto divzero; + *out = a % b; return 1; + case TK_AMP: *out = a & b; return 1; + case TK_PIPE: *out = a | b; return 1; + case TK_CARET: *out = a ^ b; return 1; + case TK_LSHIFT: *out = a << b; return 1; + case TK_RSHIFT: *out = a >> b; return 1; + default: + err(c, n->pos, "enum value: unsupported binary op %s", + tokname(n->op)); + return 0; + } + divzero: + err(c, n->pos, "enum value: division by zero"); + return 0; + } + case N_UN: { + u64 v; + if (!eval_enum_value(c, n->lhs, prev, &v)) + return 0; + switch (n->op) { + case TK_MINUS: *out = (u64)(-(i64)v); return 1; + case TK_TILDE: *out = ~v; return 1; + case TK_PLUS: *out = v; return 1; + default: + err(c, n->pos, "enum value: unsupported unary op %s", + tokname(n->op)); + return 0; + } + } + default: + err(c, n->pos, + "enum value must be a constant integer expression"); + return 0; + } +} + static Type * resolve_type(Checker *c, Node *n) { @@ -387,6 +466,50 @@ resolve_type(Checker *c, Node *n) t->size = (off + maxalign - 1) & ~(maxalign - 1); return t; } + case N_TENUM: { + Type *t = newtype(c->a, TY_ENUM); + Type *storage = ty_i32; /* default storage */ + if (n->lhs) { + Type *s = resolve_type(c, n->lhs); + if (s == ty_err || !type_isint(s)) + err(c, n->lhs->pos, + "enum storage type must be integer"); + else + storage = s; + } + t->sub = storage; + t->size = storage->size; + t->align = storage->align; + Tfield *head = NULL, *tail = NULL; + u64 prev = (u64)-1; /* so first omitted → 0 */ + for (Node *m = n->list; m; m = m->next) { + u64 val; + if (m->lhs == NULL) { + val = prev + 1; + } else if (!eval_enum_value(c, m->lhs, head, &val)) { + val = prev + 1; + } + prev = val; + for (Tfield *e = head; e; e = e->next) { + if (e->name && m->str && + strcmp(e->name, m->str) == 0) { + err(c, m->pos, + "duplicate enum member '%s'", + m->str); + break; + } + } + Tfield *tf = amalloc(c->a, sizeof *tf); + tf->name = m->str; + tf->type = NULL; + tf->offset = val; + if (head == NULL) head = tf; + else tail->next = tf; + tail = tf; + } + t->fields = head; + return t; + } default: return err(c, n->pos, "expected type expression"); } @@ -555,6 +678,34 @@ cexpr(Checker *c, Node *n) * missing. */ return n->type = ty_err; } + /* enum member access: TypeName.MEMBER → fold to + * the member's integer literal value. Type is the + * (named) enum type itself, so bitwise ops between + * members yield the same enum type via type_eq. */ + if (ms && ms->kind == SK_TYPE && ms->type) { + Type *u = (ms->type->kind == TY_NAMED) + ? ms->type->under : ms->type; + if (u && u->kind == TY_ENUM) { + for (Tfield *f = u->fields; f; f = f->next) { + if (f->name && n->str && + strcmp(f->name, n->str) == 0) { + n->kind = N_INTLIT; + n->uval = f->offset; + n->str = aprintf(c->a, "%llu", + (unsigned long long)f->offset); + n->strlen = strlen(n->str); + n->lhs = NULL; + n->rhs = NULL; + n->tsuffix = NULL; + return n->type = ms->type; + } + } + return n->type = err(c, n->pos, + "no enum member '%s' in %s", + n->str ? n->str : "?", + ms->name); + } + } } Type *base = cexpr(c, n->lhs); if (base == NULL || base == ty_err) return n->type = ty_err; @@ -991,6 +1142,18 @@ cexpr(Checker *c, Node *n) * itself (as). */ if (n->rhs) n->rhs->type = vt; Type *u = (t && t->kind == TY_NAMED) ? t->under : t; + /* Enum ↔ integer cast: `enumval as intT` or `int as enumT`. + * Reinterpret-only — the storage shape is already integer, so + * cgen treats the cast as a no-op (the value lives in the same + * register). The `is` form is rejected; enums aren't sums. */ + Type *uu = u; + Type *vu = (vt && vt->kind == TY_NAMED) ? vt->under : vt; + int lhs_enum = uu && uu->kind == TY_ENUM; + int rhs_enum = vu && vu->kind == TY_ENUM; + if (n->kind == N_TYPEASSERT && (lhs_enum || rhs_enum) && + type_isint(t) && type_isint(vt)) { + return n->type = vt; + } if (u == NULL || u->kind != TY_TAGGED) { const char *op = (n->kind == N_TYPETEST) ? "is" : "as"; return n->type = err(c, n->pos, diff --git a/cmd/wcc/parse.c b/cmd/wcc/parse.c index 28eaf4b8..5e75d06d 100644 --- a/cmd/wcc/parse.c +++ b/cmd/wcc/parse.c @@ -232,6 +232,34 @@ parsetype(Parser *p) n->lhs = parsetype(p); return n; } + case TK_ENUM: { + /* `enum [storage] { NAME [= expr] [, ...] }` + * storage defaults to i32 (lhs == NULL). + * Each member is N_TENUMMEMBER with str=name and + * lhs=value-expr (NULL means auto-increment from previous). */ + advance(p); + Node *n = newnode(p->a, N_TENUM, pp); + if (p->cur.kind != TK_LBRACE) + n->lhs = parsetype(p); + expect(p, TK_LBRACE); + Node *head = NULL, *tail = NULL; + while (p->cur.kind != TK_RBRACE && p->cur.kind != TK_EOF) { + Pos mp = p->cur.pos; + Node *m = newnode(p->a, N_TENUMMEMBER, mp); + m->str = expectident(p); + m->strlen = strlen(m->str); + if (accept(p, TK_ASSIGN)) + m->lhs = parseexpr(p); + if (head == NULL) head = m; + else tail->next = m; + tail = m; + if (!accept(p, TK_COMMA)) + break; + } + expect(p, TK_RBRACE); + n->list = head; + return n; + } case TK_VOID: { /* `void` keyword in type-expr context. Synthesise an * N_TNAME so type-resolution treats it like any other diff --git a/cmd/wcc/tok.c b/cmd/wcc/tok.c index bcf005cd..77728183 100644 --- a/cmd/wcc/tok.c +++ b/cmd/wcc/tok.c @@ -24,6 +24,7 @@ static const struct kwent kwtab[] = { { "def", TK_DEF }, { "defer", TK_DEFER }, { "else", TK_ELSE }, + { "enum", TK_ENUM }, { "export", TK_EXPORT }, { "false", TK_FALSE }, { "fn", TK_FN }, @@ -99,6 +100,7 @@ tokname(Tkind k) case TK_MATCH: return "match"; case TK_CONST: return "const"; case TK_UNDER: return "_"; + case TK_ENUM: return "enum"; case TK_LPAREN: return "("; case TK_RPAREN: return ")"; diff --git a/cmd/wcc/type.c b/cmd/wcc/type.c index c8ecba94..80a28fb1 100644 --- a/cmd/wcc/type.c +++ b/cmd/wcc/type.c @@ -139,6 +139,7 @@ type_isint(Type *t) case TY_UNTYPED_INT: case TY_UNTYPED_RUNE: return 1; + case TY_ENUM: return type_isint(t->sub); case TY_NAMED: return type_isint(t->under); default: return 0; } @@ -370,6 +371,9 @@ type_name(Arena *a, Type *t) } return aprintf(a, "(%s)", acc); } + case TY_ENUM: + return aprintf(a, "enum %s", + t->sub ? type_name(a, t->sub) : "i32"); } return "?"; } diff --git a/cmd/wcc/ww.h b/cmd/wcc/ww.h index 32a7a5fb..3d655a0b 100644 --- a/cmd/wcc/ww.h +++ b/cmd/wcc/ww.h @@ -178,6 +178,7 @@ typedef enum { TK_IS, /* Hare-style type test: e is T */ TK_VOID, /* `void` — both a type name and a zero-size value */ TK_YIELD, /* `yield expr;` — value-return from a match arm */ + TK_ENUM, /* Hare-style `enum [storage] { ... }` type form */ TK_LAST /* sentinel for tables */ } Tkind; @@ -300,6 +301,11 @@ typedef enum { N_TBANG, /* `!T` — error-flagged type. lhs = inner type. */ N_YIELD, /* `yield expr;` — set the enclosing match's value- * return and jump to its end label. lhs = value. */ + N_TENUM, /* `enum [storage] { ... }` type form. + * lhs = storage type expr or NULL (default i32); + * list = chain of N_TENUMMEMBER. */ + N_TENUMMEMBER, /* enum member. str=name, lhs=value expr or NULL + * (auto-increment when omitted). */ N_LAST } Nkind; @@ -377,7 +383,11 @@ typedef enum { TY_UNTYPED_STR, TY_UNTYPED_RUNE, TY_UNTYPED_BOOL, - TY_UNTYPED_NIL + TY_UNTYPED_NIL, + /* Appended at the tail to keep existing TY_* values stable — + * lib/ww/typ.ww mirrors them as explicit `def` numbers. */ + TY_ENUM /* `enum [storage] { ... }`. sub=storage, + * fields=member list (Tfield.offset = u64 value). */ } TypeKind; typedef struct Tfield Tfield; diff --git a/test/wcc/200_parse.c b/test/wcc/200_parse.c index 0d38ac9c..90e2f03e 100644 --- a/test/wcc/200_parse.c +++ b/test/wcc/200_parse.c @@ -167,6 +167,11 @@ main(void) "fn ti(r: (i64 | i32)) bool = { return r is i64; };", "fn ai(r: (i64 | i32)) i64 = { return r as i64; };", "fn br(r: (i64 | str)) i32 = { if (r is i64) { return 1; }; return 0; };", + + /* Hare-style enum types */ + "type color = enum { RED, GREEN, BLUE, };", + "type mode = enum u8 { NONE = 0, READ = 1, WRITE = 2, RDWR = READ | WRITE };", + "type whence = enum i32 { SET = 0, CUR, END };", }; int n = sizeof parses / sizeof parses[0]; for (int i = 0; i < n; i++) { diff --git a/test/wcc/300_check.c b/test/wcc/300_check.c index 56a3c0a8..85f84570 100644 --- a/test/wcc/300_check.c +++ b/test/wcc/300_check.c @@ -162,6 +162,23 @@ static const struct row rows[] = { "case str | f64 => return 9; case let b: bool => return 1; }; " "return 0; };", "case: f64 is not a variant" }, + + /* enum types */ + { "type color = enum { RED, GREEN, BLUE };\n" + "fn f() i32 = { return color.GREEN as i32; };", "ok" }, + { "type mode = enum u8 { R = 1, W = 2, RW = R | W };\n" + "fn f() i32 = { let m: mode = mode.RW; return m as i32; };", "ok" }, + { "type mode = enum u8 { R = 1, W = 2 };\n" + "fn f() i32 = { return (mode.R | mode.W) as i32; };", "ok" }, + { "type c = enum { A, B };\n" + "fn f() i32 = { return c.NOPE as i32; };", + "no enum member 'NOPE'" }, + { "type c = enum { A, A };\n" + "fn f() i32 = { return c.A as i32; };", + "duplicate enum member" }, + { "type c = enum bool { A };\n" + "fn f() i32 = { return c.A as i32; };", + "enum storage type must be integer" }, }; int diff --git a/test/wcc/700_e2e.c b/test/wcc/700_e2e.c index 22e50c5f..5b95d47c 100644 --- a/test/wcc/700_e2e.c +++ b/test/wcc/700_e2e.c @@ -1086,6 +1086,20 @@ static const struct row rows[] = { " let e: str = r as str;\n" " return e.len: i32;\n" "};", 3 }, + /* enum: auto-increment, explicit value, sibling-ref */ + { "type color = enum { RED, GREEN, BLUE };\n" + "fn main() i32 = { return color.BLUE as i32; };", 2 }, + { "type mode = enum u8 { R = 1, W = 2, RW = R | W };\n" + "fn main() i32 = {\n" + " let m: mode = mode.RW;\n" + " return m as i32;\n" + "};", 3 }, + /* enum: bitwise op between two members yields the same enum type */ + { "type mode = enum u8 { R = 1, W = 2 };\n" + "fn main() i32 = {\n" + " let m: mode = mode.R | mode.W;\n" + " return m as i32;\n" + "};", 3 }, { NULL, 0 } };