Record the unfinished standalone AKGL frontend

AKBASIC_WITH_AKGL currently builds and links four tested adaptors, but basic never selects them. It remains the stdio driver, creates no SDL resources, attaches no devices and pumps no events.

Narrow the completed claim to the core port and adaptor layer. Add the missing host responsibilities, affected files and acceptance criteria, then put the standalone frontend and its line editor at the top of the remaining-work queue.

Co-Authored-By: Codex GPT-5 <noreply@openai.com>
This commit is contained in:
2026-07-31 10:57:02 -04:00
parent ecdc6b60ef
commit 7897a49fb5

85
TODO.md
View File

@@ -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 1217** — the defects the port uncovered. Item 12 is the one that produces a
5. **§6 items 1217** — 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: