From 9227a07f91fbf5c1c6629b8e4e4e5bf264dc1cc4 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 12 May 2026 04:22:37 +0900 Subject: [PATCH] gitignore: lib/**/*.combined.ww; drop stray lex.combined.ww `ww build` on a lib/ module drops a .combined.ww snapshot next to the source. Only the bootstrap-frozen copies under selfhost/cmd/*/main.combined.ww are intentionally tracked; the lib/ ones are transient. lex.combined.ww slipped in via an ad-hoc `git add lib/`. --- .gitignore | 5 + lib/ww/lex/lex.combined.ww | 1014 ------------------------------------ 2 files changed, 5 insertions(+), 1014 deletions(-) delete mode 100644 lib/ww/lex/lex.combined.ww diff --git a/.gitignore b/.gitignore index 65685524..8c01ef63 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,11 @@ selfhost/**/*.s # source; the selfhost/test/ probes don't need them tracked. selfhost/test/*.combined.ww +# `ww build` on a lib/ module leaves a .combined.ww next to the +# source. None of these are bootstrap inputs (those are under +# selfhost/cmd/*/main.combined.ww) — they're just transient. +lib/**/*.combined.ww + # examples/ build outputs. Each example has its own Makefile that # leaves the .s/.o/.combined.ww plus a stripped binary behind. examples/**/*.o diff --git a/lib/ww/lex/lex.combined.ww b/lib/ww/lex/lex.combined.ww deleted file mode 100644 index f8bd64dd..00000000 --- a/lib/ww/lex/lex.combined.ww +++ /dev/null @@ -1,1014 +0,0 @@ -// 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); -}; -