From d7036be0e53f709ba192249df5cd4d80e630e6c7 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Mon, 11 May 2026 14:54:56 +0900 Subject: [PATCH] ww+wcc: emit and lex // MODULE: directive in combined.ww --- cmd/ww/main.c | 39 ++++++++++++++++++ selfhost/cmd/w6a/main.combined.ww | 8 ++++ selfhost/cmd/w6c/main.combined.ww | 44 ++++++++++++++++++++ selfhost/cmd/w6l/main.combined.ww | 9 ++++ selfhost/cmd/wcc/lex.ww | 31 ++++++++++++++ selfhost/cmd/ww/main.combined.ww | 61 +++++++++++++++++++++++++++- selfhost/cmd/ww/main.ww | 58 +++++++++++++++++++++++++- selfhost/cmd/wwdump/main.combined.ww | 44 ++++++++++++++++++++ selfhost/test/smoke.combined.ww | 4 ++ 9 files changed, 296 insertions(+), 2 deletions(-) diff --git a/cmd/ww/main.c b/cmd/ww/main.c index 9752052a..f67b8112 100644 --- a/cmd/ww/main.c +++ b/cmd/ww/main.c @@ -113,6 +113,36 @@ locate_import(const char *dirs, const char *name, char *out, size_t outsz) return 0; } +/* module_of — pick the source's containing-directory basename. + * lib/os/os.ww -> "os" + * selfhost/cmd/wcc/sym.ww -> "wcc" + * bare "main.ww" -> "main" + * Writes into dst (size ≥ 1); always NUL-terminates. */ +static void +module_of(const char *path, char *dst, size_t dstn) +{ + if (dstn == 0) return; + dst[0] = '\0'; + const char *last = strrchr(path, '/'); + if (last == NULL) { + /* bare filename — use the stem (path minus .ww). */ + size_t n = strlen(path); + if (n >= 3 && strcmp(path + n - 3, ".ww") == 0) n -= 3; + if (n >= dstn) n = dstn - 1; + memcpy(dst, path, n); + dst[n] = '\0'; + return; + } + /* Basename of the parent directory. */ + const char *prev = last - 1; + while (prev >= path && *prev != '/') prev--; + prev++; + size_t n = (size_t)(last - prev); + if (n >= dstn) n = dstn - 1; + memcpy(dst, prev, n); + dst[n] = '\0'; +} + /* Recursively expand `path`: for each top-level `use IDENT;` we find, * resolve the import and expand it first, then append our own bytes. * Already-visited paths are skipped. */ @@ -156,6 +186,15 @@ expand(FILE *out, const char *path, struct ImportSet *visited, expand(out, ipath, visited, libdir); } + /* Prefix a `// MODULE: ` directive so the ww-side wcc lexer + * can stamp each top-level decl with its originating module. The + * marker is a comment to every other reader (including the C-side + * wcc), so it's safe to emit unconditionally. */ + char modname[128]; + module_of(path, modname, sizeof modname); + if (modname[0] != '\0') + fprintf(out, "// MODULE: %s\n", modname); + rewind(in); int ch; while ((ch = fgetc(in)) != EOF) fputc(ch, out); diff --git a/selfhost/cmd/w6a/main.combined.ww b/selfhost/cmd/w6a/main.combined.ww index 2a39aad5..b54ea96a 100644 --- a/selfhost/cmd/w6a/main.combined.ww +++ b/selfhost/cmd/w6a/main.combined.ww @@ -1,3 +1,4 @@ +// 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. @@ -207,6 +208,7 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = { return syscall3(SYS_GETDENTS64, fd: i64, buf: i64, n: i64); }; +// MODULE: wcc // selfhost/cmd/wcc/mem.ww — port of cmd/wcc/mem.c. // // Bump arena allocator. Backed by the runtime page allocator @@ -314,6 +316,7 @@ export fn freearena(a: *arena) void = { }; }; +// MODULE: w6a // selfhost/cmd/w6a/types.ww — types + constants shared across the // w6a port. Mirrors cmd/w6a/a.h and cmd/w6c/6.out.h. @@ -508,6 +511,7 @@ type asm_ = struct { errs: i32, }; +// MODULE: w6a // selfhost/cmd/w6a/lex.ww — port of cmd/w6a/lex.c. // // Character-level helpers for w6a's line-oriented parser. The parser @@ -575,6 +579,7 @@ export fn parsenum(p: *u8, n: u64) (i64, u64) = { return v, i; }; +// MODULE: w6a // selfhost/cmd/w6a/parse.ww — port of cmd/w6a/parse.c. // // Line-oriented parser for the asm subset emitted by w6c. @@ -1151,6 +1156,7 @@ export fn parse(a: *asm_) i32 = { return a.errs; }; +// MODULE: w6a // selfhost/cmd/w6a/asm.ww — port of cmd/w6a/asm.c. // // Encode the parsed aprog list into amd64 machine bytes, appending to @@ -1833,6 +1839,7 @@ export fn encode(a: *asm_) i32 = { return a.errs; }; +// MODULE: w6a // selfhost/cmd/w6a/obj.ww — port of cmd/w6a/obj.c. // // Emit a tiny ELF64 relocatable object. Layout (in file order): @@ -2127,6 +2134,7 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { return 0; }; +// MODULE: w6a // selfhost/cmd/w6a/main.ww — port of cmd/w6a/main.c. // // w6a = amd64 assembler. Read .s, parse, encode, emit ELF .o. diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 1daab83a..75432e06 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -1,3 +1,4 @@ +// 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. @@ -207,6 +208,7 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = { return syscall3(SYS_GETDENTS64, fd: i64, buf: i64, n: i64); }; +// MODULE: wcc // selfhost/cmd/wcc/mem.ww — port of cmd/wcc/mem.c. // // Bump arena allocator. Backed by the runtime page allocator @@ -314,6 +316,7 @@ export fn freearena(a: *arena) void = { }; }; +// MODULE: strconv // strconv — number↔string conversions. Decimal i64 to/from a fixed // buffer. Two error idioms ship side by side: // - Plan 9 style (atoi64): tuple `(value, ok)`. Pre-dates the @@ -434,6 +437,7 @@ export fn parseu64(s: str) (u64 | str) = { return v; }; +// MODULE: wcc // selfhost/cmd/wcc/tok.ww — port of cmd/wcc/tok.c plus the Tkind / // Tok / Pos shapes from cmd/wcc/ww.h. // @@ -829,6 +833,7 @@ export fn tokprint(fd: i32, t: *tok) void = { fputcbyte(fd, 10u8); // '\n' }; +// MODULE: ascii // ascii — byte-class predicates and case folding for the ASCII range. // Matches Hare's ascii::isdigit family. Bytes outside 0..127 always // answer `false`. The lexer hot path uses these inline; they are @@ -922,6 +927,7 @@ export fn toupper(c: u8) u8 = { return c; }; +// MODULE: wcc // selfhost/cmd/wcc/lex.ww — port of cmd/wcc/lex.c. // // The DFA, the helpers, and the order of decisions all mirror the C @@ -949,6 +955,7 @@ type lex = struct { 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 = { @@ -960,6 +967,10 @@ export fn lexinit(l: *lex, a: *arena, file: str, src: *u8, len: u64) void = { 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. @@ -1037,6 +1048,32 @@ fn skipws(l: *lex) bool = { 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; }; @@ -1579,6 +1616,7 @@ export fn lexnext(l: *lex, out: *tok) void = { out.text = astrndup(l.a, one.ptr, 1u64); }; +// MODULE: wcc // selfhost/cmd/wcc/ast.ww — port of cmd/wcc/ast.c (Node defs + printer). // // Status: AST printer is fully ported. Constructor `newnode` is here. @@ -1915,6 +1953,7 @@ export fn astprint(fd: i32, n: *node) void = { pr(fd, n, 0); }; +// MODULE: wcc // selfhost/cmd/wcc/parse.ww — port of cmd/wcc/parse.c. // // Status: GROWING stub. Currently handles top-level `use IDENT;`, @@ -2898,6 +2937,7 @@ export fn parsefile(p: *parser) *node = { return f; }; +// MODULE: wcc // selfhost/cmd/wcc/type.ww — port of cmd/wcc/type.c. // // Status: full structural port. The C version uses module-globals for @@ -3228,6 +3268,7 @@ export fn typeeq(a: *tinfo, b: *tinfo) bool = { return true; // primitives match by kind alone }; +// MODULE: wcc // selfhost/cmd/wcc/sym.ww — port of cmd/wcc/sym.c. // // Per-scope hashtable, chained to the parent. Lookup walks up. @@ -3342,6 +3383,7 @@ export fn scopedefine(s: *scope, name: str, k: i32, t: *tinfo, decl: *node) *sym return sy; }; +// MODULE: wcc // selfhost/cmd/wcc/check.ww — minimal port of cmd/wcc/check.c. // // Status: name-resolution + primitive-type seeding only. Full type @@ -3590,6 +3632,7 @@ export fn checkfile(c: *checker, file: *node) void = { }; }; +// MODULE: wcc // selfhost/cmd/wcc/cgen.ww — port of cmd/w6c/cgen.c. // // Status: GROWING. Each subsystem we add is verified by `wwdump_ww -c` @@ -6673,6 +6716,7 @@ export fn cgfile(c: *cgen, file: *node) void = { emitdefconstants(c, file); }; +// MODULE: w6c // selfhost/cmd/w6c/main.ww — port of cmd/w6c/main.c. // // w6c = amd64 compiler. Read .ww, parse, codegen, emit Plan 9 amd64 diff --git a/selfhost/cmd/w6l/main.combined.ww b/selfhost/cmd/w6l/main.combined.ww index 155e553d..26ab7da5 100644 --- a/selfhost/cmd/w6l/main.combined.ww +++ b/selfhost/cmd/w6l/main.combined.ww @@ -1,3 +1,4 @@ +// 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. @@ -207,6 +208,7 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = { return syscall3(SYS_GETDENTS64, fd: i64, buf: i64, n: i64); }; +// MODULE: wcc // selfhost/cmd/wcc/mem.ww — port of cmd/wcc/mem.c. // // Bump arena allocator. Backed by the runtime page allocator @@ -314,6 +316,7 @@ export fn freearena(a: *arena) void = { }; }; +// MODULE: w6l // selfhost/cmd/w6l/sym.ww — port of cmd/w6l/sym.c. // // Linker symbol table. Singly-linked list, usually a few hundred @@ -416,6 +419,7 @@ export fn lookup(l: *lnk, name: str) *lsym = { return nil; }; +// MODULE: w6l // selfhost/cmd/w6l/obj.ww — port of cmd/w6l/obj.c. // // Loads relocatable ELF64 .o files emitted by w6a, appends .text to @@ -903,6 +907,7 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { return 0; }; +// MODULE: w6l // selfhost/cmd/w6l/dyn.ww — port of cmd/w6l/dyn.c. // // Load a shared object (ET_DYN) so the linker knows which symbols it @@ -1302,6 +1307,7 @@ export fn soversion(so: *lso, name: str) str = { // `streq` lives in sym.ww — same bundle, single definition. +// MODULE: w6l // selfhost/cmd/w6l/pass.ww — port of cmd/w6l/pass.c. // // Resolution + relocation. l_resolve flags every undefined symbol @@ -1418,6 +1424,7 @@ export fn relocate(l: *lnk, base: u64) i32 = { return l.errs; }; +// MODULE: w6l // selfhost/cmd/w6l/dynout.ww — port of cmd/w6l/dynout.c. // // Emit a dynamic-linked ELF executable. The shape is the simplest @@ -2162,6 +2169,7 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { return 0; }; +// MODULE: w6l // selfhost/cmd/w6l/out.ww — port of cmd/w6l/out.c. // // Emit a static ELF64 executable. File layout (per the C original): @@ -2263,6 +2271,7 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { return 0; }; +// MODULE: w6l // selfhost/cmd/w6l/main.ww — port of cmd/w6l/main.c. // // w6l = amd64 linker. Reads relocatable ELF .o files, SysV `ar` diff --git a/selfhost/cmd/wcc/lex.ww b/selfhost/cmd/wcc/lex.ww index 2f14df36..0cde5b3c 100644 --- a/selfhost/cmd/wcc/lex.ww +++ b/selfhost/cmd/wcc/lex.ww @@ -25,6 +25,7 @@ type lex = struct { 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 = { @@ -36,6 +37,10 @@ export fn lexinit(l: *lex, a: *arena, file: str, src: *u8, len: u64) void = { 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. @@ -113,6 +118,32 @@ fn skipws(l: *lex) bool = { 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; }; diff --git a/selfhost/cmd/ww/main.combined.ww b/selfhost/cmd/ww/main.combined.ww index 0fd8aaad..dee5d059 100644 --- a/selfhost/cmd/ww/main.combined.ww +++ b/selfhost/cmd/ww/main.combined.ww @@ -1,3 +1,4 @@ +// 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. @@ -207,6 +208,7 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = { return syscall3(SYS_GETDENTS64, fd: i64, buf: i64, n: i64); }; +// MODULE: wcc // selfhost/cmd/wcc/mem.ww — port of cmd/wcc/mem.c. // // Bump arena allocator. Backed by the runtime page allocator @@ -314,6 +316,7 @@ export fn freearena(a: *arena) void = { }; }; +// MODULE: ww // selfhost/cmd/ww/main.ww — port of cmd/ww/main.c. // // The user-facing driver. Plan 9 cc(1) / Hare hare(1) analogue: @@ -652,6 +655,50 @@ fn scanuse(src: *u8, len: u64) (*u8, u64) = { // Recursively expand `path` into c.out. Imported files are emitted // before their importer; cycles are broken via the visited set. +// modulename — pick the source's containing-directory basename. So +// `lib/os/os.ww` → "os"; `selfhost/cmd/wcc/sym.ww` → "wcc". Falls back +// to the file's own basename (sans .ww) when there is no parent dir. +// Returns ("",0) if `path` ends in a '/' (degenerate). +fn modulename(path: *u8, plen: u64) (*u8, u64) = { + if (plen == 0u64) { return nil, 0u64; }; + // Find the last '/'. + let last: u64 = plen; + let i: u64 = plen; + for (i > 0u64) { + i -= 1u64; + if (path[i] == 47u8) { last = i; i = 0u64; } + else { if (i == 0u64) { last = plen; }; }; + }; + if (last == plen) { + // No '/' — path is a bare filename. Use its stem. + let n: u64 = plen; + if (n >= 3u64) { + if (path[n - 3u64] == 46u8) { + if (path[n - 2u64] == 119u8) { + if (path[n - 1u64] == 119u8) { + n = n - 3u64; + }; + }; + }; + }; + return path, n; + }; + // Find the '/' before `last` — segment between is the dir basename. + let prev: u64 = 0u64; + let found: bool = false; + let j: u64 = last; + for (j > 0u64) { + j -= 1u64; + if (path[j] == 47u8) { + prev = j + 1u64; + found = true; + j = 0u64; + }; + }; + if (!found) { prev = 0u64; }; + return path + prev, last - prev; +}; + fn expand(c: *expctx, path_cs: *u8) void = { let plen: u64 = cstrlen(path_cs); let pathstr: str = astrndup(c.a, path_cs, plen); @@ -687,7 +734,19 @@ fn expand(c: *expctx, path_cs: *u8) void = { i = j + 1u64; }; - // Pass 2: emit our own bytes, then a trailing newline. + // Pass 2: prefix a `// MODULE: ` directive, then emit our + // bytes. The marker is a comment to the C-side wcc and any other + // reader; the ww-side wcc lexer recognises it and stamps each + // top-level decl with the originating module so cgen can mangle + // non-exported names by module. + let mp: *u8; + let mn: u64; + mp, mn = modulename(path_cs, plen); + if (mn > 0u64) { + os.writefull(c.out, "// MODULE: ".ptr, 11u64); + os.writefull(c.out, mp, mn); + os.writefull(c.out, "\n".ptr, 1u64); + }; os.writefull(c.out, bufp, blen); os.writefull(c.out, "\n".ptr, 1u64); }; diff --git a/selfhost/cmd/ww/main.ww b/selfhost/cmd/ww/main.ww index ba5cafd5..15075104 100644 --- a/selfhost/cmd/ww/main.ww +++ b/selfhost/cmd/ww/main.ww @@ -336,6 +336,50 @@ fn scanuse(src: *u8, len: u64) (*u8, u64) = { // Recursively expand `path` into c.out. Imported files are emitted // before their importer; cycles are broken via the visited set. +// modulename — pick the source's containing-directory basename. So +// `lib/os/os.ww` → "os"; `selfhost/cmd/wcc/sym.ww` → "wcc". Falls back +// to the file's own basename (sans .ww) when there is no parent dir. +// Returns ("",0) if `path` ends in a '/' (degenerate). +fn modulename(path: *u8, plen: u64) (*u8, u64) = { + if (plen == 0u64) { return nil, 0u64; }; + // Find the last '/'. + let last: u64 = plen; + let i: u64 = plen; + for (i > 0u64) { + i -= 1u64; + if (path[i] == 47u8) { last = i; i = 0u64; } + else { if (i == 0u64) { last = plen; }; }; + }; + if (last == plen) { + // No '/' — path is a bare filename. Use its stem. + let n: u64 = plen; + if (n >= 3u64) { + if (path[n - 3u64] == 46u8) { + if (path[n - 2u64] == 119u8) { + if (path[n - 1u64] == 119u8) { + n = n - 3u64; + }; + }; + }; + }; + return path, n; + }; + // Find the '/' before `last` — segment between is the dir basename. + let prev: u64 = 0u64; + let found: bool = false; + let j: u64 = last; + for (j > 0u64) { + j -= 1u64; + if (path[j] == 47u8) { + prev = j + 1u64; + found = true; + j = 0u64; + }; + }; + if (!found) { prev = 0u64; }; + return path + prev, last - prev; +}; + fn expand(c: *expctx, path_cs: *u8) void = { let plen: u64 = cstrlen(path_cs); let pathstr: str = astrndup(c.a, path_cs, plen); @@ -371,7 +415,19 @@ fn expand(c: *expctx, path_cs: *u8) void = { i = j + 1u64; }; - // Pass 2: emit our own bytes, then a trailing newline. + // Pass 2: prefix a `// MODULE: ` directive, then emit our + // bytes. The marker is a comment to the C-side wcc and any other + // reader; the ww-side wcc lexer recognises it and stamps each + // top-level decl with the originating module so cgen can mangle + // non-exported names by module. + let mp: *u8; + let mn: u64; + mp, mn = modulename(path_cs, plen); + if (mn > 0u64) { + os.writefull(c.out, "// MODULE: ".ptr, 11u64); + os.writefull(c.out, mp, mn); + os.writefull(c.out, "\n".ptr, 1u64); + }; os.writefull(c.out, bufp, blen); os.writefull(c.out, "\n".ptr, 1u64); }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index d1376cfb..2394e8e6 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -1,3 +1,4 @@ +// 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. @@ -207,6 +208,7 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = { return syscall3(SYS_GETDENTS64, fd: i64, buf: i64, n: i64); }; +// MODULE: wcc // selfhost/cmd/wcc/mem.ww — port of cmd/wcc/mem.c. // // Bump arena allocator. Backed by the runtime page allocator @@ -314,6 +316,7 @@ export fn freearena(a: *arena) void = { }; }; +// MODULE: strconv // strconv — number↔string conversions. Decimal i64 to/from a fixed // buffer. Two error idioms ship side by side: // - Plan 9 style (atoi64): tuple `(value, ok)`. Pre-dates the @@ -434,6 +437,7 @@ export fn parseu64(s: str) (u64 | str) = { return v; }; +// MODULE: wcc // selfhost/cmd/wcc/tok.ww — port of cmd/wcc/tok.c plus the Tkind / // Tok / Pos shapes from cmd/wcc/ww.h. // @@ -829,6 +833,7 @@ export fn tokprint(fd: i32, t: *tok) void = { fputcbyte(fd, 10u8); // '\n' }; +// MODULE: ascii // ascii — byte-class predicates and case folding for the ASCII range. // Matches Hare's ascii::isdigit family. Bytes outside 0..127 always // answer `false`. The lexer hot path uses these inline; they are @@ -922,6 +927,7 @@ export fn toupper(c: u8) u8 = { return c; }; +// MODULE: wcc // selfhost/cmd/wcc/lex.ww — port of cmd/wcc/lex.c. // // The DFA, the helpers, and the order of decisions all mirror the C @@ -949,6 +955,7 @@ type lex = struct { 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 = { @@ -960,6 +967,10 @@ export fn lexinit(l: *lex, a: *arena, file: str, src: *u8, len: u64) void = { 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. @@ -1037,6 +1048,32 @@ fn skipws(l: *lex) bool = { 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; }; @@ -1579,6 +1616,7 @@ export fn lexnext(l: *lex, out: *tok) void = { out.text = astrndup(l.a, one.ptr, 1u64); }; +// MODULE: wcc // selfhost/cmd/wcc/ast.ww — port of cmd/wcc/ast.c (Node defs + printer). // // Status: AST printer is fully ported. Constructor `newnode` is here. @@ -1915,6 +1953,7 @@ export fn astprint(fd: i32, n: *node) void = { pr(fd, n, 0); }; +// MODULE: wcc // selfhost/cmd/wcc/parse.ww — port of cmd/wcc/parse.c. // // Status: GROWING stub. Currently handles top-level `use IDENT;`, @@ -2898,6 +2937,7 @@ export fn parsefile(p: *parser) *node = { return f; }; +// MODULE: wcc // selfhost/cmd/wcc/type.ww — port of cmd/wcc/type.c. // // Status: full structural port. The C version uses module-globals for @@ -3228,6 +3268,7 @@ export fn typeeq(a: *tinfo, b: *tinfo) bool = { return true; // primitives match by kind alone }; +// MODULE: wcc // selfhost/cmd/wcc/sym.ww — port of cmd/wcc/sym.c. // // Per-scope hashtable, chained to the parent. Lookup walks up. @@ -3342,6 +3383,7 @@ export fn scopedefine(s: *scope, name: str, k: i32, t: *tinfo, decl: *node) *sym return sy; }; +// MODULE: wcc // selfhost/cmd/wcc/check.ww — minimal port of cmd/wcc/check.c. // // Status: name-resolution + primitive-type seeding only. Full type @@ -3590,6 +3632,7 @@ export fn checkfile(c: *checker, file: *node) void = { }; }; +// MODULE: wcc // selfhost/cmd/wcc/cgen.ww — port of cmd/w6c/cgen.c. // // Status: GROWING. Each subsystem we add is verified by `wwdump_ww -c` @@ -6673,6 +6716,7 @@ export fn cgfile(c: *cgen, file: *node) void = { emitdefconstants(c, file); }; +// MODULE: wwdump // selfhost/cmd/wwdump/main.ww — ww-side port of cmd/wwdump/main.c. // // Reads a .ww file, runs the ww-side lexer, prints tokens through diff --git a/selfhost/test/smoke.combined.ww b/selfhost/test/smoke.combined.ww index 5aa82d0e..0092a6de 100644 --- a/selfhost/test/smoke.combined.ww +++ b/selfhost/test/smoke.combined.ww @@ -1,3 +1,4 @@ +// 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. @@ -207,6 +208,7 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = { return syscall3(SYS_GETDENTS64, fd: i64, buf: i64, n: i64); }; +// MODULE: strconv // strconv — number↔string conversions. Decimal i64 to/from a fixed // buffer. Two error idioms ship side by side: // - Plan 9 style (atoi64): tuple `(value, ok)`. Pre-dates the @@ -327,6 +329,7 @@ export fn parseu64(s: str) (u64 | str) = { return v; }; +// MODULE: ascii // ascii — byte-class predicates and case folding for the ASCII range. // Matches Hare's ascii::isdigit family. Bytes outside 0..127 always // answer `false`. The lexer hot path uses these inline; they are @@ -420,6 +423,7 @@ export fn toupper(c: u8) u8 = { return c; }; +// MODULE: test // selfhost/test/smoke.ww — end-to-end smoke for the selfhost path. // // Exercises the patterns the real ww-side compiler port will use: