diff --git a/TODO.md b/TODO.md index a70c093..d7a98d4 100644 --- a/TODO.md +++ b/TODO.md @@ -155,9 +155,11 @@ typedef struct akbasic_TextSink } akbasic_TextSink; ``` -`akbasic_sink_init_stdio()` in the library, `akbasic_sink_init_akgl()` in the akgl-backed -module (phase 9). The driver picks. Cursor arithmetic, wrapping and scrolling belong to the -akgl sink, not to the interpreter. +`akbasic_sink_init_stdio()` is in the library and `akbasic_sink_init_akgl()` is in the +akgl-backed module. The driver is supposed to pick: a default build selects stdio, while an +`AKBASIC_WITH_AKGL` build selects the SDL text path and mirrors its output to stdout. It does +not do that yet; §3 records the missing standalone frontend. Cursor arithmetic, wrapping and +scrolling belong to the akgl sink, not to the interpreter. ### 1.6 The interpreter steps; it does not run @@ -317,7 +319,7 @@ ASCII so the symbol tables cannot reach it — see §1.3, which is still accurat --- -## 2. What exists — **the port is complete and green** +## 2. What exists — **the core port is complete and green** Phases 0 through 6 of the original plan are done. The interpreter builds clean under `-Wall -Wextra`, reproduces the reference byte for byte, and passes under ASan and UBSan. @@ -342,7 +344,7 @@ cmake -S . -B build-cov -DAKBASIC_COVERAGE=ON # 92.3% line, 96.9% function | Runtime | `src/runtime.c` | `basicruntime.go` minus SDL | | Verbs and functions | `src/runtime_commands.c`, `src/runtime_functions.c` | `basicruntime_{commands,functions}.go` | | Text sink | `src/sink_stdio.c` | `basicruntime_graphics.go`'s stdout mirror | -| Driver | `src/main.c` | `main.go` | +| Stdio driver | `src/main.c` | `main.go` minus its SDL frontend | | Embedding examples | `examples/embed.c`, `examples/hostvars.c` | *(new)* | `akbasic_runtime_load(rt, source)` was added while writing the README's embedding @@ -370,10 +372,10 @@ such ceiling because it called `make()`. --- -## 3. The akgl-backed sink and devices — **done** +## 3. The akgl-backed sink and devices — **adaptors done; standalone frontend missing** `-DAKBASIC_WITH_AKGL=ON` builds and its suite passes. Four adaptors in the `akbasic_akgl` -target, which is the only thing here that links SDL: +target, which is the only thing here that links SDL, are complete and tested: | File | Backs | |---|---| @@ -394,6 +396,12 @@ the host already made. Each calls `akgl_error_init()` first: `akgl_game_init()` but we drive subsystems directly and never call it, and a code raised before that registration carries no name into its stack trace. It is idempotent. +The standalone executable is itself a host, and that part of the Go frontend has not been +ported. `CMakeLists.txt` links `akbasic_akgl` into `basic` and defines +`AKBASIC_HAVE_AKGL`, but `src/main.c` never tests that definition. It always initializes the +stdio sink, attaches no device backends, and runs without creating a window or pumping an SDL +event. The result is a terminal program with unused SDL code linked into it. + *Acceptance:* `tests/akgl_backends.c`, a 128x128 software renderer under the dummy video driver read back with `SDL_RenderReadPixels` — the pattern `deps/libakgl/tests/draw.c` established, which needs no display and no offscreen harness. Registered as the `akgl_backends` @@ -437,6 +445,40 @@ ring. EOF rather than an error is the contract `sink.h` states, so `INPUT` handl That editor is the next piece of work here, and it is what `WINDOW` and `KEY` in group E want as well. +### Still missing from the standalone driver + +**Port the Go program's SDL frontend when `AKBASIC_HAVE_AKGL` is defined.** This is separate +from the four adaptors above: they deliberately accept resources owned by a host, and +`src/main.c` is the host for the standalone executable. + +The work touches `src/main.c`, `CMakeLists.txt`, the text-sink interface or a new composite +sink module, and the driver tests. It must: + +1. Initialize SDL and SDL_ttf, create the 800x600 `BASIC` window and renderer, and open + `deps/basicinterpret/fonts/C64_Pro_Mono-STYLE.ttf` at 16 points, matching + `deps/basicinterpret/main.go`. +2. Initialize the akgl text, graphics, audio and input adaptors and attach the three device + records with `akbasic_runtime_set_devices()`. +3. Pump SDL events every frame, pass keyboard events to `akgl_controller_handle_event()`, and + make window close terminate the runtime cleanly. +4. Clear, draw the BASIC text with `akbasic_sink_akgl_render()`, and present the renderer every + frame. Keep interpreter execution bounded so events and repainting cannot starve. +5. Mirror `write`, `writeln` and `clear` to both the akgl sink and stdout through a composed + text-sink backend. **Do not** put a hardcoded second write in the interpreter; §1.5 made the + sink boundary specifically to avoid that coupling. +6. Preserve the current stdio-only driver when `AKBASIC_HAVE_AKGL` is absent. File input in an + AKGL build must still appear in the window and on stdout. Interactive REPL and `INPUT` + additionally depend on the line editor above; do not claim them complete until keyboard + editing, submission and echo all work in the SDL window. + +**Acceptance:** add an SDL dummy-driver CTest that launches `basic` on a short `.bas` file, +asserts its stdout byte-for-byte, and reads back the renderer to prove the same text was drawn +with the Commodore font. Add a synthetic keyboard-event test that proves the standalone event +pump feeds the input backend. Finally, run `build-akgl/basic` under a real video driver and +verify the visible window, keyboard focus, stdout mirror and clean window-close path; record +that manual check here because the dummy driver cannot prove focus or presentation to a real +display. + --- ## 4. Remaining work: language completion (goal 2) @@ -516,11 +558,12 @@ behaviour to port. They matter to somebody typing in a listing out of a C128 man 13. **Hardware verbs reach a device through a backend record, and refuse when there is none.** `akbasic_GraphicsBackend`, `_AudioBackend` and `_InputBackend` are records of function - pointers on the runtime; all three may be `NULL`, which is what the standalone driver - gives them. A verb that needs a device it was not given raises `AKBASIC_ERR_DEVICE` naming - itself. `COLOR`, `LOCATE`, `SCALE`, `ENVELOPE`, `TEMPO` and `VOL` deliberately do *not* - require one — they change interpreter state, so a program can configure itself before the - host lends it a renderer. + pointers on the runtime; all three may be `NULL`, which is what the current stdio-only + standalone driver gives them. That is correct for a default build and unfinished for an + `AKBASIC_WITH_AKGL` build; see §3. A verb that needs a device it was not given raises + `AKBASIC_ERR_DEVICE` naming itself. `COLOR`, `LOCATE`, `SCALE`, `ENVELOPE`, `TEMPO` and + `VOL` deliberately do *not* require one — they change interpreter state, so a program can + configure itself before the host lends it a renderer. 14. **`CIRCLE` is a polygon, always.** BASIC 7.0's `CIRCLE` takes two radii, a start and end angle, a rotation and a degree increment, which makes it an inc-degree polygon by @@ -879,14 +922,18 @@ keeping: What remains, in priority order: -1. **§4 — the language completion work queue.** Groups G and I and the `GET`/`GETKEY`/`SCNCLR` +1. **The standalone AKGL frontend in §3.** An AKGL-enabled `basic` currently remains the + stdio driver with unused SDL adaptors linked into it. Port the window, font, composite + stdout+SDL sink, device attachment, bounded frame loop, event pump and renderer presentation + from the Go frontend. The build option must change the executable's observable behaviour. +2. **A line editor for the akgl sink.** `readline` reports EOF, so the SDL frontend cannot + provide an interactive REPL or `INPUT` after it exists. It wants the keystroke ring plus a + cursor, and it is also what `WINDOW` and `KEY` in group E need. See §3. +3. **§4 — the language completion work queue.** Groups G and I and the `GET`/`GETKEY`/`SCNCLR` part of E are done. Of what is left, **multiple statements per line** should come first: the `COLON` token exists and nothing consumes it, and `DO`/`LOOP` in group A reads badly without it. Then groups A, B, D, F and J, none of which need anything from `libakgl`. -2. **A line editor for the akgl sink.** `readline` reports EOF, so `INPUT` does not work - through a drawn text layer. It wants the keystroke ring plus a cursor, and it is also what - `WINDOW` and `KEY` in group E need. See §3. -3. ~~**CI does not cover `-DAKBASIC_WITH_AKGL=ON`.**~~ **Done** — the `akgl_build` job in +4. ~~**CI does not cover `-DAKBASIC_WITH_AKGL=ON`.**~~ **Done** — the `akgl_build` job in `.gitea/workflows/ci.yaml`. It turned out much cheaper than the deferral assumed, and the two reasons are worth keeping because both were guesses that measurement corrected: @@ -903,9 +950,9 @@ What remains, in priority order: deliberately does not clone. Two apt packages against two more submodules is an easy trade. Every step was verified from a clean clone before being written down, not inferred. -4. **§6 items 12–17** — the defects the port uncovered. Item 12 is the one that produces a +5. **§6 items 12–17** — the defects the port uncovered. Item 12 is the one that produces a *wrong answer* rather than a refused one, and should be fixed first. -5. **Mutation survivors in `src/value.c`.** The harness is in place (`scripts/mutation_test.py`, +6. **Mutation survivors in `src/value.c`.** The harness is in place (`scripts/mutation_test.py`, the `mutation` CMake target, and a CI job on `src/symtab.c`), and a partial run over `src/value.c` turned up test gaps worth closing. These are *not* equivalent mutants; each is a real bug of that shape the suite would not notice: