wcc+ww: //ww:module-reset boundary directive; package-less files keep module "" (#16 PREP-main)

Driver twins emit the line-comment directive only before package-less
files (peekpackage==nil); both lexers tokenize it (TK_MODRESET, appended
=87 so existing token values hold) and both parsers reset curmod — a
package-less file's decls get module "" instead of inheriting the last
bundled package (the sticky-curmod leak, task #11). Withdrawn
alternative: injecting 'package main' flips non-entry symbols
bare->main-prefixed (FFI-visible, broke 764). Codegen-neutral by proof:
bare symbols preserved, both stages emit byte-identical asm for a
directive-bearing combined. Transitional until strict-package rejects
package-less files outright. Includes 737 bad-deep pin for the
PREP-peek >2048 edge + 904/toktest rows for the new token.
This commit is contained in:
2026-06-10 14:07:35 +09:00
parent 2227d698c6
commit 49a5173f3f
17 changed files with 358 additions and 22 deletions

View File

@@ -56,6 +56,9 @@ type lex = struct {
line: i32,
col: i32,
errs: i32,
// a `//ww:module-reset` directive was seen in the last skipped run;
// lexnext emits TK_MODRESET before the next real token (#16 opt-B).
modreset: i32,
};
export fn lexinit(l: *lex, file: str, src: *u8, len: u64) void = {
@@ -66,6 +69,7 @@ export fn lexinit(l: *lex, file: str, src: *u8, len: u64) void = {
l.line = 1;
l.col = 1;
l.errs = 0;
l.modreset = 0;
};
// srcb — byte at offset; helper that lifts the cast out of indexing.
@@ -127,6 +131,26 @@ fn skipws(l: *lex) bool = {
let c2: i32 = lpeek(l, 1u64);
if (c2 == '/') {
lget(l); lget(l); // consume '//'
// #16 opt-B: recognize the driver's curmod-reset
// boundary directive `//ww:module-reset` (whole
// line) and flag it; lexnext emits TK_MODRESET.
// The body is then skipped like any comment.
// Mirrors cstage lex.c skipws. Compare via lpeek
// (no consume) so the skip loop below is unchanged.
let dir: str = "ww:module-reset";
let di: i32 = 0;
let matched: bool = true;
for (di < dir.len) {
if (lpeek(l, di: u64) != dir[di]: i32) {
matched = false; break;
};
di += 1;
};
if (matched) {
let nx: i32 = lpeek(l, dir.len: u64);
if (nx == '\n') { l.modreset = 1; }
else { if (nx < 0) { l.modreset = 1; }; };
};
for (true) {
let cx: i32 = lpeek(l, 0u64);
if (cx < 0) { return false; };
@@ -629,12 +653,19 @@ export fn lexnext(l: *lex, out: *tok) void = {
out.text = empty;
out.tsuffix = empty;
if (!skipws(l)) {
let p: pos; curpos(l, &p);
emitsimple(&p, tkind.TK_EOF, out);
let more: bool = skipws(l);
let start: pos; curpos(l, &start);
// A `//ww:module-reset` seen in the skipped run surfaces as its own
// token before the next real one (#16 opt-B boundary reset).
if (l.modreset != 0) {
l.modreset = 0;
emitsimple(&start, tkind.TK_MODRESET, out);
return;
};
if (!more) {
emitsimple(&start, tkind.TK_EOF, out);
return;
};
let start: pos; curpos(l, &start);
let c: i32 = lpeek(l, 0u64);
if (c >= 0) {

View File

@@ -118,7 +118,10 @@ type tkind = enum i32 {
TK_YIELD = 84,
TK_ENUM = 85,
TK_MODULE = 86, // `module foo;` — directory-as-module decl
TK_LAST = 87,
TK_MODRESET = 87, // `//ww:module-reset` driver bundle boundary:
// reset curmod to "" before a package-less file
// (#16 option-B; cstage TK_MODRESET twin)
TK_LAST = 88,
};
// ---- Pos / Tok --------------------------------------------------------
@@ -240,6 +243,7 @@ export fn tokname(k: tkind) str = {
case tkind.TK_UNDER: return "_";
case tkind.TK_ENUM: return "enum";
case tkind.TK_MODULE: return "package";
case tkind.TK_MODRESET: return "//ww:module-reset";
case tkind.TK_LPAREN: return "(";
case tkind.TK_RPAREN: return ")";

View File

@@ -128,11 +128,12 @@ fn checkname(k: tkind, want: str) void = {
signalled = 185; checkname(tkind.TK_ARROW, "->");
signalled = 186; checkname(tkind.TK_FATARROW, "=>");
signalled = 187; checkname(tkind.TK_LAST, "<last>");
signalled = 187; checkname(tkind.TK_MODRESET, "//ww:module-reset");
signalled = 188; checkname(tkind.TK_LAST, "<last>");
// Unknown kind → the post-switch fallback. TK_LAST is the highest
// named value (87); 88 is out of band, exercising the "<?>" tail.
signalled = 188; checkname(88: tkind, "<?>");
// named value (88); 89 is out of band, exercising the "<?>" tail.
signalled = 189; checkname(89: tkind, "<?>");
};
fn checkkw(s: str, want: tkind) void = {

View File

@@ -411,6 +411,22 @@ export fn parsefile(p: *parser) *node = {
p.curmod = name;
continue;
};
// `//ww:module-reset` — bundle boundary before a package-less
// file. Reset curmod to "" so the file's decls (and its own
// `import os;`) are attributed to the primary module, not the
// preceding bundled package. Codegen-neutral: "" curmod keeps
// bare symbols. (#16 option-B; closes task #11.)
// Driver-emitted ONLY before package-less files; a hand-placed
// directive after a mid-file `package` would strip subsequent
// decls to bare — that usage is deliberate-only.
if (p.curkind == tkind.TK_MODRESET) {
advance(p);
let empty: str;
empty.ptr = nil;
empty.len = 0;
p.curmod = empty;
continue;
};
let attrs = parseattrs(p);
let exported: i32 = 0;
if (p.curkind == tkind.TK_EXPORT) { exported = 1; advance(p); };