lib/ww/lex: fputq + tokprint if-ladders -> switch (Wave-2 structural)

fputq: 6-deep escape if/else -> switch (c); named escapes \\ " \n \t \r
as explicit cases, empty-label default folds the control-char arms into
(c<' '||c==127)->fputhex2 else fputcbyte, mirroring cmd/wcc/tok.c:172.
tokprint: 4-deep kind if-ladder -> switch (t.kind) with comma-cases
(IDENT,STR,ERR -> fputq; INT,RUNE -> u64tos; default -> nothing),
mirroring tok.c:194.

toktest gains @test tokprint_cases: drives tokprint over a temp fd and
packs every fputq escape (\\ " \n \t \r, c<0x20, c==0x7f, printable)
into a TK_STR text — branches 990_selfhost's corpus never reaches.

w6c + wwdump combined.ww regenerated.
This commit is contained in:
2026-06-03 00:59:13 +09:00
parent 2303341cb0
commit 96cf957b07
4 changed files with 119 additions and 119 deletions

View File

@@ -337,33 +337,17 @@ fn fputq(fd: i32, p: *u8, n: i32) void = {
let i: i32 = 0;
for (i < n) {
let c: u8 = p[i];
if (c == '\\') {
fputsstr(fd, "\\\\");
} else {
if (c == '"') {
fputsstr(fd, "\\\"");
switch (c) {
case '\\': fputsstr(fd, "\\\\");
case '"': fputsstr(fd, "\\\"");
case '\n': fputsstr(fd, "\\n");
case '\t': fputsstr(fd, "\\t");
case '\r': fputsstr(fd, "\\r");
case:
if (c < ' ' || c == 127u8) {
fputhex2(fd, c);
} else {
if (c == '\n') {
fputsstr(fd, "\\n");
} else {
if (c == '\t') {
fputsstr(fd, "\\t");
} else {
if (c == '\r') {
fputsstr(fd, "\\r");
} else {
if (c < ' ') {
fputhex2(fd, c);
} else {
if (c == 127u8) {
fputhex2(fd, c);
} else {
fputcbyte(fd, c);
};
};
};
};
};
fputcbyte(fd, c);
};
};
i += 1;
@@ -397,24 +381,15 @@ export fn tokprint(fd: i32, t: *tok) void = {
fputcbyte(fd, ' ');
fputsstr(fd, tokname(t.kind));
if (t.kind == tkind.TK_IDENT) {
switch (t.kind) {
case tkind.TK_IDENT, tkind.TK_STR, tkind.TK_ERR:
fputcbyte(fd, ' ');
fputq(fd, ttext.ptr, ttext.len);
} else { if (t.kind == tkind.TK_STR) {
fputcbyte(fd, ' ');
fputq(fd, ttext.ptr, ttext.len);
} else { if (t.kind == tkind.TK_ERR) {
fputcbyte(fd, ' ');
fputq(fd, ttext.ptr, ttext.len);
} else { if (t.kind == tkind.TK_INT) {
case tkind.TK_INT, tkind.TK_RUNE:
fputcbyte(fd, ' ');
let us: str = strconv.u64tos(t.uval, strconv.base.DEC);
os.write(fd, us.ptr, us.len: u64);
} else { if (t.kind == tkind.TK_RUNE) {
fputcbyte(fd, ' ');
let us: str = strconv.u64tos(t.uval, strconv.base.DEC);
os.write(fd, us.ptr, us.len: u64);
};};};};};
};
// tkind.TK_FLOAT is intentionally not handled here — %g formatting
// won't byte-match across implementations. Diff fixtures must
// be float-free until we implement a stable float formatter.

View File

@@ -1,10 +1,17 @@
// toktest — functional-equivalence pin for [[tokname]] and
// [[kwlookup]] after the if-ladder → switch fold (struct fold S1).
// toktest — functional-equivalence pin for [[tokname]], [[kwlookup]]
// (struct fold S1) and [[tokprint]]/fputq (struct fold S9, the
// if-ladder → switch folds in tok.ww).
// Run with `ww run -I lib/ww/lex lib/ww/lex/toktest.ww`.
//
// tokname is checked against every tkind value (the full ladder the
// switch replaced, plus the unknown-kind fallback); kwlookup is
// checked against every keyword it recognises plus a non-keyword.
// tokprint is driven over a temp file: it pins the kind-dispatch
// switch (STR/IDENT/ERR vs INT/RUNE vs the value-less default) and,
// through the STR text, fputq's full escape switch (\\, ", \n, \t,
// \r, the c<0x20 and c==0x7f \xNN arms, and the printable tail) —
// branches the 990_selfhost corpus does not exercise (source tokens
// hold raw `\`+`n`, never a literal control byte).
// A non-zero exit pinpoints the failing row (signalled + 10), same
// convention as asciitest. `package main` + bare `import tok/lex`
// mirrors wwdump (the only other external lex consumer).
@@ -171,8 +178,76 @@ fn checkkw(s: str, want: tkind) void = {
signalled = 231; checkkw("fns", tkind.TK_NONE);
};
// checkprint — tokprint `t` to a freshly-rewound fd, read the bytes
// back, and assert they equal `want`. The fd is RDWR; we lseek to 0
// before each write so earlier (possibly longer) content past want.len
// is irrelevant — only want.len bytes from offset 0 are compared.
fn checkprint(fd: i32, t: *tok, want: str) void = {
if (os.lseek(fd, 0i64, os.whence.SET) != 0i64) { fail(); };
tokprint(fd, t);
if (os.lseek(fd, 0i64, os.whence.SET) != 0i64) { fail(); };
let rbuf: [256]u8;
let z: i32 = 0;
for (z < 256) { rbuf[z] = 0u8; z += 1; };
let rd: i64 = os.read(fd, &rbuf[0], want.len: u64);
if (rd != want.len: i64) { fail(); };
let j: i32 = 0;
for (j < want.len) {
if (rbuf[j] != want.ptr[j]) { fail(); };
j += 1;
};
};
// One token per fputq/tokprint dispatch branch. The STR text packs
// every fputq escape: \\ " \n \t \r, a c<0x20 byte (0x01), c==0x7f,
// and a printable ('A'). `want` spells the exact emitted line.
@test fn tokprint_cases() void = {
let flags: os.flag = os.flag.RDWR | os.flag.CREATE | os.flag.TRUNC;
let fd: i32 = os.open("/tmp/ww_s9_tok.tmp", flags, 384i32);
if (fd < 0) { fail(); };
let t: tok;
t.file = "t";
t.line = 1;
t.col = 1;
signalled = 300;
t.kind = tkind.TK_STR;
t.text = "\\\"\n\t\r\x01\x7fA";
checkprint(fd, &t, "t:1:1 STR \"\\\\\\\"\\n\\t\\r\\x01\\x7fA\"\n");
signalled = 301;
t.kind = tkind.TK_IDENT;
t.text = "name";
checkprint(fd, &t, "t:1:1 IDENT \"name\"\n");
signalled = 302;
t.kind = tkind.TK_ERR;
t.text = "oops";
checkprint(fd, &t, "t:1:1 ERR \"oops\"\n");
signalled = 303;
t.kind = tkind.TK_INT;
t.uval = 42u64;
checkprint(fd, &t, "t:1:1 INT 42\n");
signalled = 304;
t.kind = tkind.TK_RUNE;
t.uval = 65u64;
checkprint(fd, &t, "t:1:1 RUNE 65\n");
// Default arm: a kind outside the switch appends no value.
signalled = 305;
t.kind = tkind.TK_NONE;
checkprint(fd, &t, "t:1:1 <none>\n");
if (os.close(fd) != 0) { fail(); };
if (os.remove("/tmp/ww_s9_tok.tmp") != 0) { fail(); };
};
export fn main() i32 = {
signalled = 1; tokname_cases();
signalled = 2; kwlookup_cases();
signalled = 3; tokprint_cases();
return 0;
};

View File

@@ -6710,33 +6710,17 @@ fn fputq(fd: i32, p: *u8, n: i32) void = {
let i: i32 = 0;
for (i < n) {
let c: u8 = p[i];
if (c == '\\') {
fputsstr(fd, "\\\\");
} else {
if (c == '"') {
fputsstr(fd, "\\\"");
switch (c) {
case '\\': fputsstr(fd, "\\\\");
case '"': fputsstr(fd, "\\\"");
case '\n': fputsstr(fd, "\\n");
case '\t': fputsstr(fd, "\\t");
case '\r': fputsstr(fd, "\\r");
case:
if (c < ' ' || c == 127u8) {
fputhex2(fd, c);
} else {
if (c == '\n') {
fputsstr(fd, "\\n");
} else {
if (c == '\t') {
fputsstr(fd, "\\t");
} else {
if (c == '\r') {
fputsstr(fd, "\\r");
} else {
if (c < ' ') {
fputhex2(fd, c);
} else {
if (c == 127u8) {
fputhex2(fd, c);
} else {
fputcbyte(fd, c);
};
};
};
};
};
fputcbyte(fd, c);
};
};
i += 1;
@@ -6770,24 +6754,15 @@ export fn tokprint(fd: i32, t: *tok) void = {
fputcbyte(fd, ' ');
fputsstr(fd, tokname(t.kind));
if (t.kind == tkind.TK_IDENT) {
switch (t.kind) {
case tkind.TK_IDENT, tkind.TK_STR, tkind.TK_ERR:
fputcbyte(fd, ' ');
fputq(fd, ttext.ptr, ttext.len);
} else { if (t.kind == tkind.TK_STR) {
fputcbyte(fd, ' ');
fputq(fd, ttext.ptr, ttext.len);
} else { if (t.kind == tkind.TK_ERR) {
fputcbyte(fd, ' ');
fputq(fd, ttext.ptr, ttext.len);
} else { if (t.kind == tkind.TK_INT) {
case tkind.TK_INT, tkind.TK_RUNE:
fputcbyte(fd, ' ');
let us: str = strconv.u64tos(t.uval, strconv.base.DEC);
os.write(fd, us.ptr, us.len: u64);
} else { if (t.kind == tkind.TK_RUNE) {
fputcbyte(fd, ' ');
let us: str = strconv.u64tos(t.uval, strconv.base.DEC);
os.write(fd, us.ptr, us.len: u64);
};};};};};
};
// tkind.TK_FLOAT is intentionally not handled here — %g formatting
// won't byte-match across implementations. Diff fixtures must
// be float-free until we implement a stable float formatter.

View File

@@ -6710,33 +6710,17 @@ fn fputq(fd: i32, p: *u8, n: i32) void = {
let i: i32 = 0;
for (i < n) {
let c: u8 = p[i];
if (c == '\\') {
fputsstr(fd, "\\\\");
} else {
if (c == '"') {
fputsstr(fd, "\\\"");
switch (c) {
case '\\': fputsstr(fd, "\\\\");
case '"': fputsstr(fd, "\\\"");
case '\n': fputsstr(fd, "\\n");
case '\t': fputsstr(fd, "\\t");
case '\r': fputsstr(fd, "\\r");
case:
if (c < ' ' || c == 127u8) {
fputhex2(fd, c);
} else {
if (c == '\n') {
fputsstr(fd, "\\n");
} else {
if (c == '\t') {
fputsstr(fd, "\\t");
} else {
if (c == '\r') {
fputsstr(fd, "\\r");
} else {
if (c < ' ') {
fputhex2(fd, c);
} else {
if (c == 127u8) {
fputhex2(fd, c);
} else {
fputcbyte(fd, c);
};
};
};
};
};
fputcbyte(fd, c);
};
};
i += 1;
@@ -6770,24 +6754,15 @@ export fn tokprint(fd: i32, t: *tok) void = {
fputcbyte(fd, ' ');
fputsstr(fd, tokname(t.kind));
if (t.kind == tkind.TK_IDENT) {
switch (t.kind) {
case tkind.TK_IDENT, tkind.TK_STR, tkind.TK_ERR:
fputcbyte(fd, ' ');
fputq(fd, ttext.ptr, ttext.len);
} else { if (t.kind == tkind.TK_STR) {
fputcbyte(fd, ' ');
fputq(fd, ttext.ptr, ttext.len);
} else { if (t.kind == tkind.TK_ERR) {
fputcbyte(fd, ' ');
fputq(fd, ttext.ptr, ttext.len);
} else { if (t.kind == tkind.TK_INT) {
case tkind.TK_INT, tkind.TK_RUNE:
fputcbyte(fd, ' ');
let us: str = strconv.u64tos(t.uval, strconv.base.DEC);
os.write(fd, us.ptr, us.len: u64);
} else { if (t.kind == tkind.TK_RUNE) {
fputcbyte(fd, ' ');
let us: str = strconv.u64tos(t.uval, strconv.base.DEC);
os.write(fd, us.ptr, us.len: u64);
};};};};};
};
// tkind.TK_FLOAT is intentionally not handled here — %g formatting
// won't byte-match across implementations. Diff fixtures must
// be float-free until we implement a stable float formatter.