diff --git a/lib/ww/ast.ww b/lib/ww/ast.ww index 3fa295f2..77700d81 100644 --- a/lib/ww/ast.ww +++ b/lib/ww/ast.ww @@ -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 ) { diff --git a/lib/ww/lex/lex.combined.ww b/lib/ww/lex/lex.combined.ww new file mode 100644 index 00000000..f8bd64dd --- /dev/null +++ b/lib/ww/lex/lex.combined.ww @@ -0,0 +1,1014 @@ +// MODULE: os +// os — process and filesystem facade. The body of each call lands +// either in libwwrt.a (rt_syscall trampoline) or libc bindings, +// depending on how the program was linked. + +@symbol("rt_syscall") fn syscall0(num: i64) i64; +@symbol("rt_syscall") fn syscall1(num: i64, a: i64) i64; +@symbol("rt_syscall") fn syscall2(num: i64, a: i64, b: i64) i64; +@symbol("rt_syscall") fn syscall3(num: i64, a: i64, b: i64, c: i64) i64; +@symbol("rt_syscall") fn syscall4(num: i64, a: i64, b: i64, c: i64, d: i64) i64; + +@symbol("rt_alloc") fn alloc(n: u64) *void; +@symbol("rt_free") fn free(p: *void, n: u64) void; +@symbol("rt_abort") fn abort(msg: str) void; + +// Hare-style runtime check. Caller passes a message that's printed +// to stderr before exit(1). +export fn assert(cond: bool, msg: str) void = { + if (!cond) { abort(msg); }; +}; + +def SYS_READ: i64 = 0; +def SYS_WRITE: i64 = 1; +def SYS_OPEN: i64 = 2; +def SYS_CLOSE: i64 = 3; +def SYS_LSEEK: i64 = 8; +def SYS_ACCESS: i64 = 21; +def SYS_DUP2: i64 = 33; +def SYS_GETPID: i64 = 39; +def SYS_FORK: i64 = 57; +def SYS_EXECVE: i64 = 59; +def SYS_EXIT: i64 = 60; +def SYS_WAIT4: i64 = 61; +def SYS_UNLINK: i64 = 87; +def SYS_GETCWD: i64 = 79; +def SYS_GETDENTS64: i64 = 217; + +// open(2) flags. Linux values, matching . +def O_RDONLY: i32 = 0; +def O_WRONLY: i32 = 1; +def O_RDWR: i32 = 2; +def O_CREAT: i32 = 64; // 0x40 +def O_TRUNC: i32 = 512; // 0x200 + +// lseek(2) whence. +def SEEK_SET: i32 = 0; +def SEEK_CUR: i32 = 1; +def SEEK_END: i32 = 2; + +export fn exit(code: i32) void = { + syscall1(SYS_EXIT, code: i64); +}; + +// Raw, non-fallible primitives. These return Linux's int conventions +// (negative = -errno, non-negative = bytes/fd/etc). Callers wanting a +// Hare-style fallible API use the wrappers below. +export fn write(fd: i32, buf: *u8, n: u64) i64 = { + return syscall3(SYS_WRITE, fd: i64, buf: i64, n: i64); +}; + +export fn read(fd: i32, buf: *u8, n: u64) i64 = { + return syscall3(SYS_READ, fd: i64, buf: i64, n: i64); +}; + +export fn close(fd: i32) i32 = { + return syscall1(SYS_CLOSE, fd: i64): i32; +}; + +// dup2(2): make `newfd` refer to the same description as `oldfd`, +// closing `newfd` first if open. Returns `newfd` on success or a +// negative errno. Used by w6c_ww to redirect stdout into an output +// file without changing the cgen emit path. +export fn dup2(oldfd: i32, newfd: i32) i32 = { + return syscall2(SYS_DUP2, oldfd: i64, newfd: i64): i32; +}; + +// Fallible wrappers. The error variant is a plain str (Plan 9 errstr +// model, see lib/errors); the sum type makes success/failure explicit +// without overloading length-zero. +export fn tryread(fd: i32, buf: *u8, n: u64) (i64 | str) = { + let r: i64 = read(fd, buf, n); + if (r < 0) { return "read failed"; }; + return r; +}; + +export fn trywrite(fd: i32, buf: *u8, n: u64) (i64 | str) = { + let r: i64 = write(fd, buf, n); + if (r < 0) { return "write failed"; }; + return r; +}; + +// open — Linux open(2). Path must be NUL-terminated; callers using ww +// `str` must ensure the bytes are followed by a 0 byte (literals are, +// arena-copied paths usually are by construction). Returns -errno on +// failure, fd otherwise. Higher-level callers prefer `tryopen`. +export fn open(path: *u8, flags: i32, mode: i32) i32 = { + return syscall3(SYS_OPEN, path: i64, flags: i64, mode: i64): i32; +}; + +export fn tryopen(path: *u8, flags: i32, mode: i32) (i32 | str) = { + let fd: i32 = open(path, flags, mode); + if (fd < 0) { return "open failed"; }; + return fd; +}; + +// lseek — set/inspect the fd's position. Returns the new offset or +// a negative errno. We use this for fstat-free file-size discovery +// (open ⇒ lseek to end ⇒ lseek back). +export fn lseek(fd: i32, off: i64, whence: i32) i64 = { + return syscall3(SYS_LSEEK, fd: i64, off, whence: i64); +}; + +// filesize — convenience: returns the byte length of an open fd by +// seeking to the end and back. -1 on error. +export fn filesize(fd: i32) i64 = { + let end: i64 = lseek(fd, 0i64, SEEK_END); + if (end < 0) { return -1i64; }; + let r: i64 = lseek(fd, 0i64, SEEK_SET); + if (r < 0) { return -1i64; }; + return end; +}; + +// readall — keep reading until `n` bytes have arrived or the fd +// closes early. Returns bytes read (0..=n) or -1 on read error. +// Hare name (io::readall); the buffer is caller-supplied, matching +// the Plan 9 subset convention. +export fn readall(fd: i32, buf: *u8, n: u64) i64 = { + let got: u64 = 0u64; + for (got < n) { + let r: i64 = read(fd, buf + got, n - got); + if (r < 0) { return -1i64; }; + if (r == 0) { return got: i64; }; // short read: caller decides + got += r: u64; + }; + return got: i64; +}; + +// writeall — keep writing until `n` bytes have been accepted or the +// fd refuses progress. Returns bytes written or -1. Hare name +// (io::writeall). +export fn writeall(fd: i32, buf: *u8, n: u64) i64 = { + let sent: u64 = 0u64; + for (sent < n) { + let r: i64 = write(fd, buf + sent, n - sent); + if (r < 0) { return -1i64; }; + if (r == 0) { return sent: i64; }; + sent += r: u64; + }; + return sent: i64; +}; + +// ---- process and filesystem helpers used by the `ww` driver ---------- + +// access(2): returns 0 if the file is reachable, negative errno +// otherwise. mode is the bitset described in (F_OK=0). +export fn access(path: *u8, mode: i32) i32 = { + return syscall2(SYS_ACCESS, path: i64, mode: i64): i32; +}; + +// remove — unlink(2). Hare name; the underlying syscall is unlink(2). +export fn remove(path: *u8) i32 = { + return syscall1(SYS_UNLINK, path: i64): i32; +}; + +// getpid(2). Used by the driver to mint unique scratch paths. +export fn getpid() i32 = { + return syscall0(SYS_GETPID): i32; +}; + +// fork(2): 0 in the child, child pid in the parent, negative errno +// on failure. +export fn fork() i32 = { + return syscall0(SYS_FORK): i32; +}; + +// execve(2): on success, does not return. +export fn execve(path: *u8, argv: **u8, envp: **u8) i32 = { + return syscall3(SYS_EXECVE, path: i64, argv: i64, envp: i64): i32; +}; + +// wait4(2): wait for `pid` (or any child if -1), store status in +// `*status`, return the pid that ended (or negative errno). +export fn wait4(pid: i32, status: *i32, options: i32, rusage: *void) i32 = { + return syscall4(SYS_WAIT4, pid: i64, status: i64, + options: i64, rusage: i64): i32; +}; + +// getcwd(2) — Linux flavour. Writes the NUL-terminated cwd into `buf` +// and returns the number of bytes written (including the NUL), or a +// negative errno. The driver uses it to expand `.` to the cwd's +// basename for `ww build` / `ww test`. +export fn getcwd(buf: *u8, n: u64) i64 = { + return syscall2(SYS_GETCWD, buf: i64, n: i64); +}; + +// getdents64(2) — Linux directory enumeration. The fd must be opened +// with O_RDONLY on a directory. `buf` receives a packed sequence of +// linux_dirent64 records: +// +// struct linux_dirent64 { +// u64 d_ino; // 0..7 +// i64 d_off; // 8..15 +// u16 d_reclen; // 16..17 — total bytes for this record +// u8 d_type; // 18 — DT_REG/DT_DIR/... +// u8 d_name[]; // 19.. — NUL-terminated name + padding +// }; +// +// Returns bytes written into `buf` (advance by d_reclen to walk), +// 0 at end-of-directory, or a negative errno. +export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = { + return syscall3(SYS_GETDENTS64, fd: i64, buf: i64, n: i64); +}; + +// MODULE: ascii +// ascii — rune-class predicates and case folding for the ASCII range. +// Matches Hare's ascii::isdigit family (rune-taking signature). Runes +// outside 0..127 always answer `false`. The lexer hot path uses these +// inline; they are expected to inline to a couple of compares. + +export fn isdigit(c: rune) bool = { + if (c < 48) { return false; }; + if (c > 57) { return false; }; + return true; +}; + +export fn isupper(c: rune) bool = { + if (c < 65) { return false; }; + if (c > 90) { return false; }; + return true; +}; + +export fn islower(c: rune) bool = { + if (c < 97) { return false; }; + if (c > 122) { return false; }; + return true; +}; + +export fn isalpha(c: rune) bool = { + if (isupper(c)) { return true; }; + return islower(c); +}; + +export fn isalnum(c: rune) bool = { + if (isalpha(c)) { return true; }; + return isdigit(c); +}; + +// isspace — the C/Hare set: space, tab, NL, VT, FF, CR. +export fn isspace(c: rune) bool = { + if (c == 32) { return true; }; // ' ' + if (c == 9) { return true; }; // '\t' + if (c == 10) { return true; }; // '\n' + if (c == 11) { return true; }; // '\v' + if (c == 12) { return true; }; // '\f' + if (c == 13) { return true; }; // '\r' + return false; +}; + +export fn isxdigit(c: rune) bool = { + if (isdigit(c)) { return true; }; + if (c >= 65) { + if (c <= 70) { return true; }; // 'A'..'F' + }; + if (c >= 97) { + if (c <= 102) { return true; }; // 'a'..'f' + }; + return false; +}; + +// digitval — value of `c` as a hex/decimal digit. void variant means +// `c` isn't a hex digit. Useful when scanning numeric literals. +export fn digitval(c: rune) (i32 | void) = { + if (isdigit(c)) { return (c - 48): i32; }; + if (c >= 65) { + if (c <= 70) { return ((c - 65) + 10): i32; }; + }; + if (c >= 97) { + if (c <= 102) { return ((c - 97) + 10): i32; }; + }; + return; +}; + +// isidstart / isidpart — identifier classes used by the lexer. +// Alpha or '_' starts; alnum or '_' continues. +export fn isidstart(c: rune) bool = { + if (isalpha(c)) { return true; }; + if (c == 95) { return true; }; // '_' + return false; +}; + +export fn isidpart(c: rune) bool = { + if (isalnum(c)) { return true; }; + if (c == 95) { return true; }; + return false; +}; + +// tolower / toupper — fold ASCII case. Non-letters pass through. +export fn tolower(c: rune) rune = { + if (isupper(c)) { return c + 32; }; + return c; +}; + +export fn toupper(c: rune) rune = { + if (islower(c)) { return c - 32; }; + return c; +}; + +// MODULE: lex +// lib/ww/lex/lex.ww — port of cmd/wcc/lex.c. +// +// The DFA, the helpers, and the order of decisions all mirror the C +// version exactly. The 990_selfhost test diffs the resulting token +// stream against the C-side wwdump byte-for-byte; any divergence is +// a port bug. +// +// Calling-convention note: w6c can't yet pass or return structs >16 +// bytes by value, so `tok` and `pos` are passed by pointer (out +// params). The C version passes `Tok` by value; we differ here only +// in shape, not in observable behaviour. Token kind values stay +// numerically identical. + +use os; +use ascii; +use mem; +use tok; + +type lex = struct { + file: str, + src: *u8, // raw bytes; not necessarily NUL-terminated + srclen: u64, + lpos: u64, + line: i32, + col: i32, + a: *arena, + errs: i32, + module: str, // current module from `// MODULE: foo` directive; "" if none +}; + +export fn lexinit(l: *lex, a: *arena, file: str, src: *u8, len: u64) void = { + l.file = file; + l.src = src; + l.srclen = len; + l.lpos = 0u64; + l.line = 1; + l.col = 1; + l.a = a; + l.errs = 0; + let empty: str; + empty.ptr = nil; + empty.len = 0; + l.module = empty; +}; + +// srcb — byte at offset; helper that lifts the cast out of indexing. +fn srcb(l: *lex, off: u64) i32 = { + let i: i32 = off: i32; + let b: u8 = l.src[i]; + return b: i32; +}; + +fn lpeek(l: *lex, ahead: u64) i32 = { + let p: u64 = l.lpos + ahead; + if (p >= l.srclen) { return -1; }; + return srcb(l, p); +}; + +fn lget(l: *lex) i32 = { + if (l.lpos >= l.srclen) { return -1; }; + let c: i32 = srcb(l, l.lpos); + l.lpos += 1u64; + if (c == 10) { // '\n' + l.line += 1; + l.col = 1; + } else { + l.col += 1; + }; + return c; +}; + +fn curpos(l: *lex, out: *pos) void = { + out.file = l.file; + out.line = l.line; + out.col = l.col; +}; + +// putuint — write `v` (signed, but always non-negative here) to fd 2 +// in decimal. Standalone so errat doesn't drag in fmt and create a +// dependency cycle with strconv. +fn putuint(fd: i32, v: i32) void = { + let tmp: [16]u8; + let i: i32 = 0; + let n: i32 = v; + for (n > 0) { + tmp[i] = ((n % 10) + 48): u8; + n = n / 10; + i += 1; + }; + if (i == 0) { tmp[0] = 48u8; i = 1; }; + let buf: [16]u8; + let m: i32 = 0; + for (i > 0) { i -= 1; buf[m] = tmp[i]; m += 1; }; + os.write(fd, buf.ptr, m: u64); +}; + +fn errat(l: *lex, p: *pos, msg: str) void = { + let pf: str = p.file; + os.write(2, pf.ptr, pf.len: u64); + os.write(2, ":".ptr, 1u64); + putuint(2, p.line); + os.write(2, ":".ptr, 1u64); + putuint(2, p.col); + os.write(2, ": error: ".ptr, 9u64); + os.write(2, msg.ptr, msg.len: u64); + os.write(2, "\n".ptr, 1u64); + l.errs += 1; +}; + +fn skipws(l: *lex) bool = { + for (true) { + let c: i32 = lpeek(l, 0u64); + if (c < 0) { return false; }; + if (c == 32) { lget(l); continue; }; + if (c == 9) { lget(l); continue; }; + if (c == 13) { lget(l); continue; }; + if (c == 10) { lget(l); continue; }; + if (c == 47) { // '/' + let c2: i32 = lpeek(l, 1u64); + if (c2 == 47) { + lget(l); lget(l); // consume '//' + // Driver injects `// MODULE: foo` before each + // source file's contents; capture so cgen can + // mangle private symbols by module. + if (lpeek(l, 0u64) == 32) { // ' ' + if (lpeek(l, 1u64) == 77) { // 'M' + if (lpeek(l, 2u64) == 79) { // 'O' + if (lpeek(l, 3u64) == 68) { // 'D' + if (lpeek(l, 4u64) == 85) { // 'U' + if (lpeek(l, 5u64) == 76) { // 'L' + if (lpeek(l, 6u64) == 69) { // 'E' + if (lpeek(l, 7u64) == 58) { // ':' + if (lpeek(l, 8u64) == 32) { // ' ' + let i: i32 = 0; + for (i < 9) { lget(l); i += 1; }; + let start: u64 = l.lpos; + for (true) { + let cx: i32 = lpeek(l, 0u64); + if (cx < 0) { break; }; + if (cx == 10) { break; }; + if (cx == 13) { break; }; + lget(l); + }; + let n: u64 = l.lpos - start; + l.module = astrndup(l.a, l.src + start, n); + };};};};};};};};}; + for (true) { + let cx: i32 = lpeek(l, 0u64); + if (cx < 0) { return false; }; + if (cx == 10) { break; }; + lget(l); + }; + continue; + }; + if (c2 == 42) { // '*' + lget(l); lget(l); + let prev: i32 = -1; + for (true) { + let x: i32 = lget(l); + if (x < 0) { + let cp: pos; + curpos(l, &cp); + errat(l, &cp, "unterminated /* comment"); + return false; + }; + if (prev == 42) { + if (x == 47) { break; }; + }; + prev = x; + }; + continue; + }; + }; + return true; + }; + return false; +}; + +fn parseint(p: *u8, n: u64, base: i32, ok: *bool) u64 = { + let v: u64 = 0u64; + let got: bool = false; + let i: u64 = 0u64; + for (i < n) { + let ix: i32 = i: i32; + let c: u8 = p[ix]; + if (c == 95u8) { // '_' + i += 1u64; + continue; + }; + let d: i32 = -1; + if (c >= 48u8) { + if (c <= 57u8) { d = (c - 48u8): i32; }; + }; + if (d < 0) { + if (c >= 97u8) { + if (c <= 102u8) { d = ((c - 97u8) + 10u8): i32; }; + }; + }; + if (d < 0) { + if (c >= 65u8) { + if (c <= 70u8) { d = ((c - 65u8) + 10u8): i32; }; + }; + }; + if (d < 0) { *ok = false; return 0u64; }; + if (d >= base) { *ok = false; return 0u64; }; + v = v * (base: u64) + (d: u64); + got = true; + i += 1u64; + }; + *ok = got; + return v; +}; + +fn escape(l: *lex, out: *i32) bool = { + let c: i32 = lget(l); + if (c < 0) { return false; }; + if (c == 110) { *out = 10; return true; }; + if (c == 116) { *out = 9; return true; }; + if (c == 114) { *out = 13; return true; }; + if (c == 92) { *out = 92; return true; }; + if (c == 39) { *out = 39; return true; }; + if (c == 34) { *out = 34; return true; }; + if (c == 48) { *out = 0; return true; }; + if (c == 97) { *out = 7; return true; }; + if (c == 98) { *out = 8; return true; }; + if (c == 102) { *out = 12; return true; }; + if (c == 118) { *out = 11; return true; }; + if (c == 120) { + let hi: i32 = lget(l); + let lo: i32 = lget(l); + if (hi < 0) { return false; }; + if (lo < 0) { return false; }; + if (!ascii.isxdigit(hi: rune)) { + let cp: pos; curpos(l, &cp); + errat(l, &cp, "bad \\x escape"); + return false; + }; + if (!ascii.isxdigit(lo: rune)) { + let cp: pos; curpos(l, &cp); + errat(l, &cp, "bad \\x escape"); + return false; + }; + let hr: (i32 | void) = ascii.digitval(hi: rune); + let lr: (i32 | void) = ascii.digitval(lo: rune); + let h: i32 = 0; + let lv: i32 = 0; + match (hr) { + case let v: i32 => h = v; + case void => { return false; }; + }; + match (lr) { + case let v: i32 => lv = v; + case void => { return false; }; + }; + *out = (h << 4) | lv; + return true; + }; + let cp: pos; curpos(l, &cp); + errat(l, &cp, "bad escape"); + return false; +}; + +// scandecimalrun — consume a run of decimal digits and underscores. +fn scandecimalrun(l: *lex) void = { + for (true) { + let c: i32 = lpeek(l, 0u64); + if (c < 0) { break; }; + if (!ascii.isdigit(c: rune)) { + if (c != 95) { break; }; + }; + lget(l); + }; +}; + +fn scanhexrun(l: *lex) void = { + for (true) { + let c: i32 = lpeek(l, 0u64); + if (c < 0) { break; }; + if (!ascii.isxdigit(c: rune)) { + if (c != 95) { break; }; + }; + lget(l); + }; +}; + +fn scanbinrun(l: *lex) void = { + for (true) { + let c: i32 = lpeek(l, 0u64); + if (c == 48) { lget(l); continue; }; + if (c == 49) { lget(l); continue; }; + if (c == 95) { lget(l); continue; }; + break; + }; +}; + +fn scanoctrun(l: *lex) void = { + for (true) { + let c: i32 = lpeek(l, 0u64); + if (c < 48) { break; }; + if (c > 55) { + if (c != 95) { break; }; + }; + lget(l); + }; +}; + +// scanexp — consume the [eE][+-]?[0-9]+ tail of a float, if present. +fn scanexp(l: *lex) void = { + let e: i32 = lpeek(l, 0u64); + if (e != 101) { if (e != 69) { return; }; }; // 'e' or 'E' + lget(l); + let s: i32 = lpeek(l, 0u64); + if (s == 43) { lget(l); } + else { if (s == 45) { lget(l); }; }; + for (true) { + let c: i32 = lpeek(l, 0u64); + if (c < 0) { break; }; + if (!ascii.isdigit(c: rune)) { break; }; + lget(l); + }; +}; + +fn lexnum(l: *lex, start: *pos, out: *tok) void = { + out.kind = TK_INT; + out.file = start.file; + out.line = start.line; + out.col = start.col; + let begin: u64 = l.lpos; + let base: i32 = 10; + let isfloat: bool = false; + + let c0: i32 = lpeek(l, 0u64); + let c1: i32 = lpeek(l, 1u64); + + if (c0 == 48) { // '0' + if (c1 == 120) { // 'x' + lget(l); lget(l); base = 16; scanhexrun(l); + } else { if (c1 == 88) { // 'X' + lget(l); lget(l); base = 16; scanhexrun(l); + } else { if (c1 == 98) { // 'b' + lget(l); lget(l); base = 2; scanbinrun(l); + } else { if (c1 == 66) { // 'B' + lget(l); lget(l); base = 2; scanbinrun(l); + } else { if (c1 == 111) { // 'o' + lget(l); lget(l); base = 8; scanoctrun(l); + } else { if (c1 == 79) { // 'O' + lget(l); lget(l); base = 8; scanoctrun(l); + } else { + scandecimalrun(l); + if (lpeek(l, 0u64) == 46) { + let after: i32 = lpeek(l, 1u64); + if (after >= 48) { + if (after <= 57) { + isfloat = true; + lget(l); + scandecimalrun(l); + scanexp(l); + }; + }; + }; + };};};};};}; + } else { + scandecimalrun(l); + if (lpeek(l, 0u64) == 46) { + let after: i32 = lpeek(l, 1u64); + if (after >= 48) { + if (after <= 57) { + isfloat = true; + lget(l); + scandecimalrun(l); + scanexp(l); + }; + }; + }; + }; + + let n: u64 = l.lpos - begin; + out.text = astrndup(l.a, l.src + begin, n); + + if (isfloat) { + // out.fval is already 0 from the top-of-lexnext clear. + // We don't strtod the literal yet — the diff fixtures we + // care about are float-free; any TK_FLOAT seen in source + // gets a placeholder value until we wire a real parser. + out.kind = TK_FLOAT; + } else { + let digs: *u8 = l.src + begin; + let dn: u64 = n; + if (base != 10) { + digs = digs + 2u64; + dn -= 2u64; + }; + let ok: bool = false; + out.uval = parseint(digs, dn, base, &ok); + if (!ok) { + errat(l, start, "bad integer literal"); + out.kind = TK_ERR; + }; + }; + + let pc: i32 = lpeek(l, 0u64); + if (pc >= 0) { + if (ascii.isidstart(pc: rune)) { + let sb: u64 = l.lpos; + for (true) { + let cc: i32 = lpeek(l, 0u64); + if (cc < 0) { break; }; + if (!ascii.isidpart(cc: rune)) { break; }; + lget(l); + }; + let sl: u64 = l.lpos - sb; + let p: *u8 = l.src + sb; + let isok: bool = false; + if (sl == 2u64) { + if (p[0] == 105u8) { + if (p[1] == 56u8) { isok = true; }; // i8 + }; + if (p[0] == 117u8) { + if (p[1] == 56u8) { isok = true; }; // u8 + }; + }; + if (sl == 3u64) { + if (p[0] == 105u8) { + if (p[1] == 49u8) { if (p[2] == 54u8) { isok = true; }; }; // i16 + if (p[1] == 51u8) { if (p[2] == 50u8) { isok = true; }; }; // i32 + if (p[1] == 54u8) { if (p[2] == 52u8) { isok = true; }; }; // i64 + }; + if (p[0] == 117u8) { + if (p[1] == 49u8) { if (p[2] == 54u8) { isok = true; }; }; + if (p[1] == 51u8) { if (p[2] == 50u8) { isok = true; }; }; + if (p[1] == 54u8) { if (p[2] == 52u8) { isok = true; }; }; + }; + if (p[0] == 102u8) { + if (p[1] == 51u8) { if (p[2] == 50u8) { isok = true; }; }; // f32 + if (p[1] == 54u8) { if (p[2] == 52u8) { isok = true; }; }; // f64 + }; + }; + if (isok) { + out.tsuffix = astrndup(l.a, p, sl); + } else { + l.lpos = sb; + }; + }; + }; +}; + +fn lexident(l: *lex, start: *pos, out: *tok) void = { + let begin: u64 = l.lpos; + for (true) { + let c: i32 = lpeek(l, 0u64); + if (c < 0) { break; }; + if (!ascii.isidpart(c: rune)) { break; }; + lget(l); + }; + let n: u64 = l.lpos - begin; + let p: *u8 = l.src + begin; + out.file = start.file; + out.line = start.line; + out.col = start.col; + // Bare '_' is the discard marker. `_x`, `_1` are normal idents. + if (n == 1u64) { + if (p[0] == 95u8) { + out.kind = TK_UNDER; + out.text = astrndup(l.a, p, n); + return; + }; + }; + let k: i32 = kwlookup(p, n: i32); + if (k != TK_NONE) { + out.kind = k; + } else { + out.kind = TK_IDENT; + }; + out.text = astrndup(l.a, p, n); +}; + +fn lexstr(l: *lex, start: *pos, out: *tok) void = { + let cap: u64 = 32u64; + let nb: u64 = 0u64; + let buf: *u8 = amalloc(l.a, cap): *u8; + for (true) { + let c: i32 = lpeek(l, 0u64); + if (c < 0) { + errat(l, start, "unterminated string"); + out.kind = TK_ERR; + out.file = start.file; + out.line = start.line; + out.col = start.col; + out.text = astrndup(l.a, "".ptr, 0u64); + return; + }; + if (c == 34) { lget(l); break; }; + let ch: i32 = 0; + if (c == 92) { + lget(l); + if (!escape(l, &ch)) { ch = 0; }; + } else { + ch = lget(l); + }; + if (nb + 1u64 >= cap) { + let ncap: u64 = cap * 2u64; + let nb2: *u8 = amalloc(l.a, ncap): *u8; + let i: u64 = 0u64; + for (i < nb) { + let ix: i32 = i: i32; + nb2[ix] = buf[ix]; + i += 1u64; + }; + buf = nb2; + cap = ncap; + }; + let nbi: i32 = nb: i32; + buf[nbi] = ch: u8; + nb += 1u64; + }; + out.kind = TK_STR; + out.file = start.file; + out.line = start.line; + out.col = start.col; + let s: str; + s.ptr = buf; + s.len = nb: i32; + out.text = s; +}; + +fn lexrune(l: *lex, start: *pos, out: *tok) void = { + let c: i32 = lpeek(l, 0u64); + if (c < 0) { + errat(l, start, "unterminated rune"); + out.kind = TK_ERR; + out.file = start.file; + out.line = start.line; + out.col = start.col; + out.text = astrndup(l.a, "".ptr, 0u64); + return; + }; + let ch: i32 = 0; + if (c == 92) { + lget(l); + if (!escape(l, &ch)) { ch = 0; }; + } else { + ch = lget(l); + }; + if (lpeek(l, 0u64) != 39) { + errat(l, start, "rune literal missing closing '"); + out.kind = TK_ERR; + out.file = start.file; + out.line = start.line; + out.col = start.col; + out.text = astrndup(l.a, "".ptr, 0u64); + return; + }; + lget(l); + out.kind = TK_RUNE; + out.file = start.file; + out.line = start.line; + out.col = start.col; + out.uval = ch: u64; +}; + +fn emitsimple(start: *pos, k: i32, out: *tok) void = { + out.kind = k; + out.file = start.file; + out.line = start.line; + out.col = start.col; +}; + +// setposfrom — copy file/line/col from a *pos into a tok. Used by +// the err-token path where we already have a pos. +fn setposfrom(out: *tok, p: *pos) void = { + out.file = p.file; + out.line = p.line; + out.col = p.col; +}; + +export fn lexnext(l: *lex, out: *tok) void = { + // Reset the out token so callers can rely on stale fields being + // cleared (they only inspect kind, pos, text, uval, fval, tsuffix + // per kind). + out.kind = TK_NONE; + out.uval = 0u64; + // out.fval starts cleared by the caller's stack-local init (lex.ww + // allocates the tok with `let t: tok;` which zeroes). We avoid + // writing a 0.0 literal here so this file itself stays float-free + // and the C/ww wwdump diff over it is byte-identical. + let empty: str; + empty.ptr = nil; + empty.len = 0; + out.text = empty; + out.tsuffix = empty; + + if (!skipws(l)) { + let p: pos; curpos(l, &p); + emitsimple(&p, TK_EOF, out); + return; + }; + let start: pos; curpos(l, &start); + let c: i32 = lpeek(l, 0u64); + + if (c >= 0) { + if (ascii.isidstart(c: rune)) { lexident(l, &start, out); return; }; + if (ascii.isdigit(c: rune)) { lexnum(l, &start, out); return; }; + }; + + if (c == 34) { lget(l); lexstr(l, &start, out); return; }; + if (c == 39) { lget(l); lexrune(l, &start, out); return; }; + + lget(l); + + if (c == 40) { emitsimple(&start, TK_LPAREN, out); return; }; + if (c == 41) { emitsimple(&start, TK_RPAREN, out); return; }; + if (c == 123) { emitsimple(&start, TK_LBRACE, out); return; }; + if (c == 125) { emitsimple(&start, TK_RBRACE, out); return; }; + if (c == 91) { emitsimple(&start, TK_LBRACK, out); return; }; + if (c == 93) { emitsimple(&start, TK_RBRACK, out); return; }; + if (c == 44) { emitsimple(&start, TK_COMMA, out); return; }; + if (c == 59) { emitsimple(&start, TK_SEMI, out); return; }; + if (c == 58) { emitsimple(&start, TK_COLON, out); return; }; + if (c == 64) { emitsimple(&start, TK_AT, out); return; }; + if (c == 63) { emitsimple(&start, TK_QUESTION, out); return; }; + if (c == 126) { emitsimple(&start, TK_TILDE, out); return; }; + + if (c == 46) { // '.' + if (lpeek(l, 0u64) == 46) { + if (lpeek(l, 1u64) == 46) { + lget(l); lget(l); + emitsimple(&start, TK_ELLIPSIS, out); return; + }; + lget(l); + emitsimple(&start, TK_DOTDOT, out); return; + }; + emitsimple(&start, TK_DOT, out); return; + }; + + if (c == 43) { + if (lpeek(l, 0u64) == 61) { lget(l); emitsimple(&start, TK_PLUSEQ, out); return; }; + emitsimple(&start, TK_PLUS, out); return; + }; + if (c == 45) { + if (lpeek(l, 0u64) == 61) { lget(l); emitsimple(&start, TK_MINUSEQ, out); return; }; + if (lpeek(l, 0u64) == 62) { lget(l); emitsimple(&start, TK_ARROW, out); return; }; + emitsimple(&start, TK_MINUS, out); return; + }; + if (c == 42) { + if (lpeek(l, 0u64) == 61) { lget(l); emitsimple(&start, TK_STAREQ, out); return; }; + emitsimple(&start, TK_STAR, out); return; + }; + if (c == 47) { + if (lpeek(l, 0u64) == 61) { lget(l); emitsimple(&start, TK_SLASHEQ, out); return; }; + emitsimple(&start, TK_SLASH, out); return; + }; + if (c == 37) { + if (lpeek(l, 0u64) == 61) { lget(l); emitsimple(&start, TK_PERCENTEQ, out); return; }; + emitsimple(&start, TK_PERCENT, out); return; + }; + if (c == 38) { + if (lpeek(l, 0u64) == 38) { lget(l); emitsimple(&start, TK_AND, out); return; }; + if (lpeek(l, 0u64) == 61) { lget(l); emitsimple(&start, TK_AMPEQ, out); return; }; + emitsimple(&start, TK_AMP, out); return; + }; + if (c == 124) { + if (lpeek(l, 0u64) == 124) { lget(l); emitsimple(&start, TK_OR, out); return; }; + if (lpeek(l, 0u64) == 61) { lget(l); emitsimple(&start, TK_PIPEEQ, out); return; }; + emitsimple(&start, TK_PIPE, out); return; + }; + if (c == 94) { + if (lpeek(l, 0u64) == 61) { lget(l); emitsimple(&start, TK_CARETEQ, out); return; }; + emitsimple(&start, TK_CARET, out); return; + }; + if (c == 61) { + if (lpeek(l, 0u64) == 61) { lget(l); emitsimple(&start, TK_EQ, out); return; }; + if (lpeek(l, 0u64) == 62) { lget(l); emitsimple(&start, TK_FATARROW, out); return; }; + emitsimple(&start, TK_ASSIGN, out); return; + }; + if (c == 33) { + if (lpeek(l, 0u64) == 61) { lget(l); emitsimple(&start, TK_NEQ, out); return; }; + emitsimple(&start, TK_NOT, out); return; + }; + if (c == 60) { + if (lpeek(l, 0u64) == 60) { + lget(l); + if (lpeek(l, 0u64) == 61) { lget(l); emitsimple(&start, TK_LSHIFTEQ, out); return; }; + emitsimple(&start, TK_LSHIFT, out); return; + }; + if (lpeek(l, 0u64) == 61) { lget(l); emitsimple(&start, TK_LE, out); return; }; + if (lpeek(l, 0u64) == 45) { lget(l); emitsimple(&start, TK_LARROW, out); return; }; + emitsimple(&start, TK_LT, out); return; + }; + if (c == 62) { + if (lpeek(l, 0u64) == 62) { + lget(l); + if (lpeek(l, 0u64) == 61) { lget(l); emitsimple(&start, TK_RSHIFTEQ, out); return; }; + emitsimple(&start, TK_RSHIFT, out); return; + }; + if (lpeek(l, 0u64) == 61) { lget(l); emitsimple(&start, TK_GE, out); return; }; + emitsimple(&start, TK_GT, out); return; + }; + + errat(l, &start, "unexpected character"); + out.kind = TK_ERR; + setposfrom(out, &start); + let one: [1]u8; + one[0] = c: u8; + out.text = astrndup(l.a, one.ptr, 1u64); +}; + diff --git a/lib/ww/lex/tok.ww b/lib/ww/lex/tok.ww index 0c5542d2..398c7a45 100644 --- a/lib/ww/lex/tok.ww +++ b/lib/ww/lex/tok.ww @@ -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 ")"; }; diff --git a/lib/ww/parse/parse.ww b/lib/ww/parse/parse.ww index 909f9321..c99872c7 100644 --- a/lib/ww/parse/parse.ww +++ b/lib/ww/parse/parse.ww @@ -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. diff --git a/lib/ww/typ.ww b/lib/ww/typ.ww index 52003923..c2634aff 100644 --- a/lib/ww/typ.ww +++ b/lib/ww/typ.ww @@ -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; }; diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 8f556376..a8cb0762 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 7a61b3fc..75e2a352 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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; };