← board

.( prints nothing — uforth's driver suite, 8 of 11 identical

Measured with the correct invocation ("<file>" INCLUDE, see [[bug-nilpy-uforth-rc4-corpus-stack-underflow]] for why that matters), comparing pxx's binary against CPython running the same uforth.py:

tests/_drv_c.fth       IDENTICAL       testje.for        IDENTICAL
tests/_drv_file0.fth   IDENTICAL       testjefixed.for   IDENTICAL
tests/_drv_locals.fth  IDENTICAL       testjefix2.for    IDENTICAL
tests/_drv_t.fth       IDENTICAL       testjefix3.for    IDENTICAL
tests/_drv_string.fth  DIFF            tests/_drv_x.fth  DIFF
tests/_drv_file.fth    DIFF

(It was 2/11 before the repeat-count and closure-row fixes of 2026-08-08.)

NARROWED 2026-08-08: two of the three are the word .(

Not a trailing-newline problem — that was checked and ruled out (a file whose last line has no newline INCLUDEs identically on both sides). The missing text is the last line's OUTPUT, and that line is CR .( End of String word tests) CR.

Reduced to one word, at the REPL, no files involved:

CR .( hello) CR      CPython: "hello) CR"      pxx: nothing at all
.( hello)            CPython: "hello)"         pxx: nothing at all

No error, no diagnostic — .( runs and emits an empty string. (CPython including the ) in its output is uforth's own quirk; both sides must agree on it either way.)

w_dot_paren (uforth.py ~3263) does four things, and EACH of them was probed in isolation against the CPython oracle and PASSES:

So the fault needs uforth's real state, not the shapes. The obvious next step — instrumenting w_dot_paren — is BLOCKED by a second finding, below.

Instrumenting uforth makes the pxx build HANG

Adding a print(...) inside w_dot_paren, or appending to a debug list field, produces a binary that times out at startup (>60 s, no output) while the uninstrumented build of the same file runs fine and CPython runs the instrumented file fine. That is a lead in its own right and probably a separate bug; it also means the usual print-probe is unavailable here. Reach for the .map + gdb route instead, or bisect uforth's own startup.

Superseded: the last-line theory

(Original framing, kept for the record.) _drv_string and _drv_x produce every line CPython does except the final one:

 Test utilities loaded
 **********
-End of String word tests) CR          <- pxx stops here

This read as a dropped trailing line; it is not. See the narrowing above.

The third is the FILE word set

_drv_file diverges earlier than CPython's own failure and for a different reason — both sides fail, at different points:

cpython: ...******************ERROR: filetest.fth:278: THROW -13 (line: '0 SI_INC !')
pxx:     ...*************ERROR: filetest.fth:217: INCLUDE expects a string filename
                                on stack (line: '  INCLUDE required-helper1.fth')

Note pxx's message is about INCLUDE required-helper1.fth — the BARE-word form, which the ANS test file uses and which uforth's own INCLUDE does not implement (it pops a string). CPython gets past line 217, so uforth must have a route that handles it there and pxx does not reach. Establish what CPython actually does with that line BEFORE assuming a compiler bug — this exact form is what produced a withdrawn ticket already.

Gate

All 11 byte-identical to the CPython run, make test-uforth still PASS, plus the per-fix loop. Worth splitting once the last-line cause is known: it is plainly one bug, and the FILE word set is plainly another.

ROOT CAUSE 2026-08-08 — and a RETRACTION

Retracted first: I recorded a root cause of "NilPy strings are byte strings, so uforth's byte offset into a str lands past the end". That was WRONG. The disproof was already in my own measurements before I wrote it: .( abc) is pure ASCII on every path and failed identically. I had taken a real but unrelated len=53 vs len=67 divergence, seen on a COMMENT line during startup, and attached it to a failure it does not explain.

The actual cause

.( is not the w_dot_paren native at all — EXTRA.UFO REDEFINES it as a Forth word with a PYTHON body:

while True:
    tok = vm.next_token()
    if tok is None:
        break
    ...

next_token() is -> Optional[str], and return None from a str-returning def handed back the TEXT 'None'. So tok is None was always False and the loop's only exit could never fire: it ran to the end of the line and then appended the string 'None' forever. Hence "prints nothing" for .( and a HANG for an isolated copy of the same body — the same bug, two faces.

Fixed in [[bug-nilpy-return-none-from-a-str-returning-def-yields-the-text-None]].

How it was actually found

uforth's OWN --trace, diffed between the two runs, which sidestepped the instrumented-build hang noted above:

cpython: [00027] .(:0 PyInline(src="... while True: tok = vm.next_token() ...")
pxx    : [00027] .(:0

— the trace named the word's body, which is what revealed .( was a PYTHON word rather than the native I had been reading.

Result

uforth's driver suite: 10 of 11 byte-identical to CPython (2/11 at the start of this arc, 8/11 before this fix). Only tests/_drv_file.fth differs, on the FILE word set — described above and still open.

Log