CI workflow, native-recompile back end (--emit-c), runtime library + SDL2#2
Open
leag wants to merge 18 commits into
Open
CI workflow, native-recompile back end (--emit-c), runtime library + SDL2#2leag wants to merge 18 commits into
leag wants to merge 18 commits into
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GJpGqftHVJEy28DqWTgVcr
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GJpGqftHVJEy28DqWTgVcr
- tests/tbx/test_goldens.py: sweep every corpus EXE against its committed ops and usercode golden (reusing dump_ops.canon so tool and test share one canonical format), plus coverage/orphan checks on the golden dirs. - dump_user_code.py: skip compiler-flag fixtures (non-empty toggles) -- they carry no golden by convention, but the tool used to write them, polluting the tree and silently widening the IR-snapshot sweep. - CLI: add --version (from package metadata), with a test. - Packaging: py.typed marker, [project.urls] in pyproject, CI badge in README; CLAUDE.md testing section updated to match the new gating. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GJpGqftHVJEy28DqWTgVcr
tbx/c0.py lowers the decoder's typed IR to a self-contained C translation unit for modern platforms, with an embedded runtime following Turbo Basic handbook semantics: GW-BASIC PRINT layout (sign-or-space + trailing space, .5 without leading zero, 7 significant digits), CINT banker's rounding, 16-bit \ MOD AND OR XOR NOT, single-precision default variables, suffix types (% & ! # $), static and runtime-bound DIM, DATA/READ/RESTORE, GOSUB via labels-as-values (gcc/clang), flat FOR/WHILE/DO lowered to test/jump label pairs so EXIT FOR/LOOP and cross-statement GOTO work. Fail-loud like the decoder: constructs outside the implemented vocabulary (file I/O, graphics, sound, traps, SUB/CALL, PRINT USING) raise instead of mistranslating. 272/564 corpus fixtures transpile; all 272 compile clean with gcc and run without crashes (the one timeout is a fixture whose original program is an intentional infinite loop). tests/tbx/test_c0.py pins recompiled stdout for seven fixtures end-to-end (skipped without a host cc), a transpile-coverage floor, and the fail-loud contract. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GJpGqftHVJEy28DqWTgVcr
…trol Coverage 272 -> 348 of 564 corpus fixtures; all 348 compile with gcc and run crash-free (the sole timeout is the intentionally infinite zz_ginf). - ON ERROR GOTO / ERR / ERL / RESUME [NEXT|line] / ERROR n via setjmp + computed goto; tb_erl is tracked always, so untrapped runtime errors abort with TB's error code and the emitted source's line number. Division (including \ and MOD) now raises error 11 on zero divisors. - SUB/CALL: void C functions with pointer parameters -- variables and array elements pass by reference (zz_sub1 pins the semantics), other expressions by value copy; EXIT SUB returns. Non-parameter variables, arrays, and label-family statements inside SUB bodies stay fail-loud. - Sequential file I/O: OPEN I/O/A, CLOSE, RESET, PRINT #n, WRITE [#n], INPUT #n, EOF; TB error codes for bad/unopened/missing files. PRINT columns are now per-channel, fixing a TAB(n)-into-file infinite loop. - DIM bounds generalized to (lo:hi) pairs with OPTION BASE lower bounds (fixes the 'expression tuple' failure on t1_mix3); UBOUND/LBOUND. - LOCATE/COLOR as ANSI escapes, KEY ON/OFF as a no-op, SHELL/CHDIR/ ENVIRON/KILL/NAME via libc, BIN$, EOF, LPOS, long (&) variables. tests: 16 pinned end-to-end runs including by-reference CALL, TAB layout on console and into a file, and untrapped-error exit codes; coverage floor raised to 330. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GJpGqftHVJEy28DqWTgVcr
Coverage 348 -> 534 of 564 (95%); all 534 compile with gcc and run crash-free. The 30 remaining fixtures all use machine access (PEEK/POKE/ OUT/WAIT/INP/REG/CALL ABSOLUTE/BLOAD/BSAVE/DEF SEG/CHAIN) and stay fail-loud. The three run-forever cases (zz_ginf, t1_run x2) are faithful: their originals are intentional infinite loops. - Graphics: SCREEN modes 1/2/7-10 allocate an in-memory byte-per-pixel framebuffer (no display on a headless host; TB_SCREEN_PPM=file dumps the final image in the CGA palette). PSET/PRESET, LINE with box/fill/ 16-bit style patterns (Bresenham), CIRCLE with arcs and aspect, PAINT flood fill, GET/PUT blits (XOR/PSET/PRESET/AND/OR) clipped to the target array's capacity, VIEW/WINDOW coordinate transforms, DRAW's U D L R E F G H M B N C S command subset, POINT, PMAP, PALETTE. - ON TIMER(n) GOSUB via monotonic-clock polling at statement boundaries (TB's own trap granularity); KEY/COM/PEN/PLAY handlers install as no-ops since those event sources don't exist on a modern host. - Random-access files: OPEN "R" with the calibrated 128-byte records, FIELD registrations, LSET/RSET (field-aware and plain string justification), GET/PUT record I/O, SEEK; MKI$/MKL$/MKS$/MKD$ and CVI/CVL/CVS/CVD little-endian converters (embedded-NUL caveat noted). - PRINT USING: the # . + numeric-field subset with GW field cycling and %-overflow; other format characters raise error 5. - Multi-line DEF FN with EXIT DEF; comparisons in value position materialize TB's -1/0; CEIL/CLNG/RND/INSTAT/MTIMER/CSRLIN/INKEY$/ DATE$/TIME$ set (process-local clock offset), PMAP, UBOUND/LBOUND, CLEAR/RUN via a generated whole-program reset, FILES, MKDIR/RMDIR, LPRINT-to-console, Palette-aware PPM dump. tests: 21 pinned end-to-end runs (PRINT USING format cycling checked against a file leg, multi-line DEF FN, CVI, POINT); floor raised to 510. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GJpGqftHVJEy28DqWTgVcr
- The runtime prelude now lives as ordered fragment files (header, core, fileio, terminal, graphics, play, events) concatenated verbatim by an explicit manifest in c0.py; POSIX output is byte-identical to before. - Add #ifdef _WIN32 paths (conio keyboard, VT console mode, Sleep, QueryPerformanceCounter, a DOS-wildcard fnmatch surrogate) so the output builds with MinGW-w64 gcc/clang: all 534 supported corpus programs cross-compile, and the test_c0-pinned programs run correctly under wine. - Fix two portability bugs: mkdir's two-argument POSIX form (now a tb_mkdir wrapper), and clang rejecting RETURN's goto* when a dead ON COM/KEY/PEN/PLAY handler leaves main with no &&label expression. - Refresh the stale c0.py unsupported list, the README --emit-c section (file-device surrogates, Windows note), and CLAUDE.md; add .clangd to silence diagnostics on the non-standalone fragments. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Each c0_runtime/*.c now includes tb_runtime.h (system includes plus the declarations of every cross-fragment / generated-code-facing symbol, now non-static; fragment-internal helpers stay static) and compiles on its own with gcc, clang, and MinGW-w64 at -Wall -Wextra clean, so the runtime also builds as an ordinary C library. header.c is absorbed into the header, and emit amalgamates header + fragments -- stripping the include lines -- into the same self-contained generated file as before. The repo-root .clangd suppression is no longer needed and is removed. Verified: 1283 tests, ruff, ty; all 534 supported corpus programs still cross-compile for Windows and the test_c0-pinned programs run correctly under wine; the six units link as a library on Linux and Windows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…L2 backend - machine.c: PEEK/POKE/DEF SEG/BLOAD/BSAVE against an emulated 1 MiB real-mode memory, OUT/INP as 64K port latches, WAIT returns at once, REG buffer with no-op CALL INTERRUPT, CHAIN execs the named file (error 53 if absent), CALL ABSOLUTE transpiles but aborts if reached. All 564/564 corpus programs now transpile (was 534). - --no-runtime + c0_runtime/Makefile: emit #include "tb_runtime.h" instead of embedding the fragments and link many programs against one libtbrt.a. - --sdl / -DTB_SDL: sdl.c presents the framebuffer in a real 4:3 SDL2 window via a tb_present() hook on every drawing op (no-op stubs in graphics.c otherwise); window keys feed INKEY$/INSTAT; held at exit until a key (TB_SDL_HOLD=0 skips). - tests: pin the machine round trips, BSAVE/BLOAD, CALL ABSOLUTE abort, CHAIN exec + error paths, the library link, and a headless SDL run (dummy driver); fail-loud witness is now a synthetic intrinsic. Windows paths verified under Wine with llvm-mingw (18/18 checks). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CqBnSnCq2HwNA9uyzrUnEC
… backend Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CqBnSnCq2HwNA9uyzrUnEC
… stdin Phase 0+1 of the graduation plan: - tbx/tools/oracle.py: bridge to the external headless Turbo Basic toolchain (TBX_ORACLE): compile_bas() with the real TB 1.1/1.0 compiler, run_exe() with scripted keys + screen/file capture. - tbx/tools/verify_fixture.py: automated byte-exact round trip (decompile -> emit -> oracle compile -> cmp). t1_print, t1_tab, zz_sc1, v10_t1_print all verify byte-identical. - tbx/tools/dump_dos_output.py: capture what the ORIGINAL corpus EXE visibly does on the emulated machine into tests/fixtures/dosout/ (screen text + files it wrote), with the key-script table shared with the test layer. - test_c0.py test_dos_golden: recompiled native binaries must match the DOS goldens (lines rstripped, "Error N at pgm-ctr"/"in line" both normalize to "Error N", produced files CRLF-normalized; waivers name their surrogate -- t1_bsave's image carries live DGROUP memory). - core.c: INPUT echoes the read line when stdin is not a tty, matching TB's own screen echo; t1_tab/t1_pr2 pinned outputs updated. First 19 goldens captured and passing; the full-corpus sweep fills in the rest. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CqBnSnCq2HwNA9uyzrUnEC
148 new DOS behavior goldens (tests/fixtures/dosout/) and the fixes they witness: - PEN(n) while PEN OFF raises error 5 (t1_penf); tb_pen_on tracks the event toggle - CHDIR/RMDIR of a missing path raise 76/75 (t1_chdir, t1_rmdir), with DOS '\' separators translated via a shared tb_dospath - KILL/NAME of a missing file raise 53 (t1_kill, t1_name) - SEEK on a random-mode channel raises 54 (t1_seek) - EOF() on a closed channel raises 52 (t1_filef) - graphics statements without a SCREEN mode raise 5 (t1_circle, t1_draw) instead of implicitly entering mode 1; the SDL headless test now uses a synthetic SCREEN 1 program since no corpus fixture sets a mode - LPRINT goes to a printer sink (TB_LPRINT_TXT or the null device), never the console (t1_lprint), with its own TAB/SPC column as channel 15 - KEY LIST prints the twelve empty soft keys (t1_keylist) - INP of a never-latched port reads the floating bus, 255 (t1_inpf) - PRINT expands non-integral numbers to 16 significant digits with a three-digit exponent (t1_fp); TB's own binary->decimal conversion carries ~1e-14 tail noise, so the golden comparison clips both sides to 13 significant digits - waivers: t1_shellvar (SHELL '' starts the resident shell), t1_putfile (PUT of a never-FIELDed record exposes live DGROUP memory), t1_files (host directory listing is environmental) docs/c0-graduation-plan.md records the phased plan for taking c0 out of the experimental phase; CLAUDE.md documents the oracle workflow and the dosout regression layer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Phase 3 item 4 of the graduation plan: Program.toggles is honored as
compiled, like TB itself. With Bounds ON every subscript range-checks
through tb_bix (TB error 9); with Overflow ON integer stores range-check
through tb_ichk/tb_lchk (error 6) instead of wrapping. Both default OFF,
and the flagged f*_ corpus fixtures now emit the checks while their
unflagged twins do not (test_options_toggles; the error branches are
pinned by synthetic programs since no corpus fixture trips them).
Triage of the still-running capture sweep's new goldens:
- WRITE numbers drop the leading sign-space and keep the trailing space
on the console only ("1 ,2" t1_write; compact '1,"A"' t1_writefile)
- zz_cv_mkd/mkl/mks waived: MK*$ images embed NULs that the runtime's
NUL-terminated strings truncate -- the known binary-strings divergence
the phase-3 descriptor refactor resolves
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sweep stragglers; all pass test_dos_golden unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Triage of the sweep's newest goldens: - zz_x_ldiv/zz_x_lmod witness that \ and MOD convert operands to 16-bit with x87 FIST-store semantics: out-of-range values become the integer indefinite 0x8000, so 100000 \ 7 = -4681 and 100000 MOD 7 = -1 (exactly -32768 \ 7). tb_idiv/tb_mod now convert through tb_fist16. - zz_sc3 exposes a real TB 1.1 codegen bug: CASE IS compiles the bound compare against DS:0120 (a hidden, never-written slot) instead of the selector temp, then matches the arm iff the SELECTOR equals the materialized boolean -- so CASE IS > 10 with A=20 falls to CASE ELSE. Waived: c0 keeps the handbook semantics until phase-2 probes pin the hidden slot's behavior across layouts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The full sweep captured 297/321 eligible stems (92.5%, above the >=90% target). Every absence now has a SKIP entry in dump_dos_output with its verified structural reason, so --missing reports only documented skips: TRON EXEs stall in their trace hook on the real machine (witnessed on t1_tron: prints [20], never returns to DOS), RUN and zz_ginf never exit, SCREEN 1 loses the text screen, CALL ABSOLUTE has no code to run, bare LINE INPUT gives the key script nothing to synchronize on, and t1_scr/t1_shell complete on screen but the harness cannot confirm the prompt returned. Plan doc updated with the checkpoint. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Graduation plan phase 4, items 1 and 3: - TB_RT_VERSION in tb_runtime.h and c0_runtime/README.md spelling out the surrogate contract (emulated machine, console, absent devices, graphics, file devices), the known divergences still open (NUL-terminated strings, RND, double vs x87, the CASE IS bug), and the toggle behavior. Changing a surrogate is now a reviewable event like golden regeneration. - CI gains a c0 job matrix: Linux gcc + clang with SDL2 (the headless SDL test runs instead of skipping), macOS clang, and an experimental Windows MinGW lane (continue-on-error until proven). test_c0 honors $CC so the lanes actually select their compiler; clang verified green locally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ive glob The produced-file goldens carry DOS 8.3 names (t1_pr2.file.R.TXT), which *.txt matches case-insensitively on Windows, so _dos_stems() invented stems like t1_pr2.file.R and the job failed looking for their corpus EXEs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings
mainup to date with everything since PR #1:ci.ymlhas never been on the default branch).tbx --emit-c): lowers the decoded IR to a self-contained portable C translation unit (gcc/clang, POSIX or MinGW-w64). Grown over several commits to cover error trapping, SUB/CALL by-ref, file I/O, terminal control, graphics framebuffer, ON TIMER, PRINT USING.machine.cemulates the real-mode machine (PEEK/POKE/DEF SEG/BLOAD/BSAVE over a 1 MiB image, OUT/INP port latches, REG buffer, CHAIN via exec, CALL ABSOLUTE aborts at runtime) — all 564/564 corpus programs transpile.makeintbx/c0_runtime/buildslibtbrt.a;--emit-c --no-runtimeemits programs that link against it.--emit-c --sdl/make SDL=1): real window for the graphics framebuffer, window keyboard feeding INKEY$/INSTAT.Tests: 1295 pass locally; ruff and ty clean. Windows runtime paths verified under Wine with llvm-mingw (18/18 checks).
🤖 Generated with Claude Code
https://claude.ai/code/session_01CqBnSnCq2HwNA9uyzrUnEC