Port the README from the Go version and document embedding

Carries the reference README across, adjusted where C changes the answer: cmake
instead of make, the ak* libraries instead of the go-sdl2 bindings, and the
limits table now includes the three ceilings the Go version did not need because
it called make().

The new "Embedding the interpreter" section is the point of the rewrite, so it
states the four rules the library holds to -- nothing terminates the process,
nothing calls malloc, no file-scope mutable state, the host owns the loop -- and
each was checked against the tree rather than asserted.

Adds akbasic_runtime_load(). Writing the section turned up a real gap: a host
usually already holds its script as a string and wants the sink reserved for
output, and the only path that existed was AKBASIC_MODE_RUNSTREAM reading the
program through the sink's readline, which forces a game to point its output
device at its source text. The alternative was reaching into the header's
"internal API" block for store_line. Neither is something to put in a README.

Adds examples/embed.c, which is the code the README quotes -- a custom sink, a
bounded per-frame run, and the PASS-not-CATCH rule for a loop inside an ATTEMPT.
It is built by every build and registered as a CTest case, so a signature change
breaks the build instead of rotting the document. The README's own snippet is
compiled separately as a check; both were run before committing.

The "What Isn't Implemented / Isn't Working" section leads with the eleven
inherited defects rather than burying them, because five of them were found by
this port and a reader deserves to know that 1 - 2 - 3 computes 1 - 2 before
they hit it. Corrected two claims while verifying: the runtime is 10.1MB rather
than the ~8MB first written, and its largest single cost is the environment pool
at 4.1MB, not the source table.

ctest 60/60; ASan+UBSan 60/60; no warnings under -Wall -Wextra.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-31 06:42:08 -04:00
parent 7531df57ec
commit cde6fa8f59
7 changed files with 586 additions and 4 deletions

View File

@@ -149,6 +149,28 @@ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_run(akbasic_Runtime *obj, int
/** @brief Point the runtime at an input stream and choose a starting mode. */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_start(akbasic_Runtime *obj, int mode);
/**
* @brief Load a whole program from memory, filing each line under its line number.
*
* The entry point for an embedding host, which usually already holds its script
* as a string and wants the sink reserved for output. The alternative --
* AKBASIC_MODE_RUNSTREAM -- reads the program through the sink's readline, which
* works for a file-backed driver but forces a game to point its output device at
* its source text.
*
* Lines are separated by `\n`; a `\r` before it is tolerated. Blank lines are
* skipped. A line with no line number is filed under the last one seen, matching
* what the scanner does in RUNSTREAM mode. This does not run anything: follow it
* with akbasic_runtime_start(obj, AKBASIC_MODE_RUN).
*
* @param obj Object to initialize, inspect, or modify.
* @param source Whole program text; must not be NULL.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When `obj` or `source` is NULL.
* @throws AKBASIC_ERR_BOUNDS When a line is longer than AKBASIC_MAX_LINE_LENGTH or its number is out of range.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_load(akbasic_Runtime *obj, const char *source);
/* --- Internal API: exposed for the scanner, parser and verb handlers only. --- */
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_new_variable(akbasic_Runtime *obj, akbasic_Variable **dest);