Document host/script variable exchange, and file the scope hazard it exposes
The host can expose variables to a BASIC program, in both directions and for every type, with no marshalling layer -- akbasic_environment_get() finds or creates by name, the type comes from the suffix as it does for BASIC code, and host and script share the same akbasic_Value. Nothing new was needed to make that work. Testing it before writing it up turned up a hazard worth stating plainly, so the README documents the pattern and section 6 item 17 records the gap. Seeding before akbasic_runtime_start() and reading after the script stops is safe, and so is updating an existing global at any time, because the parent chain is searched. Creating one mid-run is not, in two ways, and both are silent. A script suspended part-way through a bounded run() is usually inside a FOR or GOSUB scope, so a variable created through obj->environment lands in that scope and dies when it pops -- the script reads it correctly inside the loop and gets 0 immediately after. And reaching for the root explicitly does not help: akbasic_environment_get only auto-creates when the environment it is given is the active one, so with a child active it returns NULL through dest without raising, and an unchecked host dereferences it. This one is ours rather than inherited. It falls out of the environment pool meeting the bounded run(), a combination the reference never had because its run() never returned. examples/hostvars.c demonstrates both hazards rather than describing them, and prints what it observes, so the day this is fixed that output changes and the example needs revisiting. Like examples/embed.c it is built and run by every build. Both README snippets were extracted and compiled as a check. Not fixed here: akbasic_runtime_global() would close it in about fifteen lines, but what it should do when the script is suspended inside a user function's scope is a design question worth settling deliberately rather than discovering. Filed with the proposed signature and the tests it wants. ctest 61/61; ASan+UBSan 61/61; no warnings under -Wall -Wextra. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
41
TODO.md
41
TODO.md
@@ -342,7 +342,7 @@ cmake -S . -B build-cov -DAKBASIC_COVERAGE=ON # 92.3% line, 96.9% function
|
||||
| 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` |
|
||||
| Embedding example | `examples/embed.c` | *(new)* |
|
||||
| Embedding examples | `examples/embed.c`, `examples/hostvars.c` | *(new)* |
|
||||
|
||||
`akbasic_runtime_load(rt, source)` was added while writing the README's embedding
|
||||
section: a host usually holds its script as a string and wants the sink reserved for output,
|
||||
@@ -551,6 +551,41 @@ pinned by an assertion in the relevant unit test, and asserted *correctly* in
|
||||
is dead code and `PRINT$ = 1` is accepted. Fix: strip the suffix before the lookup. Pinned
|
||||
in `tests/scanner_tokens.c`.
|
||||
|
||||
### Ours, not the reference's
|
||||
|
||||
17. **A host cannot reliably create a script variable while a script is suspended.** This one
|
||||
is not inherited — it falls out of §1.4's environment pool meeting §1.6's bounded `run()`,
|
||||
a combination the reference never had because its `run()` never returned.
|
||||
|
||||
Exchanging variables with an embedding host works and is documented in `README.md`: the
|
||||
host calls `akbasic_environment_get()` to find or create a variable and
|
||||
`akbasic_variable_set_*` / `_get_subscript` to move values across. Seeding before
|
||||
`akbasic_runtime_start()` and reading after the script stops is safe, and so is *updating*
|
||||
an existing global at any time, because the parent chain is searched. **Creating** one
|
||||
mid-run is not, in two ways, and both are silent:
|
||||
|
||||
- A script suspended part-way through a bounded `akbasic_runtime_run()` is usually inside a
|
||||
`FOR` or `GOSUB` scope, so `obj->environment` is that scope. A variable created through
|
||||
it lands in the loop's scope and dies when the environment pops — the script reads the
|
||||
value correctly inside the loop and gets `0` immediately after it.
|
||||
- Reaching for the root explicitly does not help. `akbasic_environment_get` only
|
||||
auto-creates when `obj->runtime->environment == obj` (`src/environment.c:242`), so with a
|
||||
child active it returns `NULL` through `dest` **without raising**, and an unchecked host
|
||||
dereferences it.
|
||||
|
||||
`examples/hostvars.c` demonstrates both rather than describing them, and prints what it
|
||||
observes, so the day this is fixed that output changes and the example needs revisiting.
|
||||
|
||||
Fix: add `akbasic_runtime_global(akbasic_Runtime *obj, const char *name, akbasic_Variable **dest)`
|
||||
that walks `obj->environment` to the root and creates there unconditionally, and point the
|
||||
README at it instead of at `akbasic_environment_get`. Roughly fifteen lines. The one design
|
||||
question worth settling first is what it should do when the script is suspended inside a
|
||||
*user function's* scope — that environment is owned by the funcdef rather than the pool
|
||||
(§1.4), and "root" is still the right answer there, but it is worth being deliberate about
|
||||
it rather than discovering it. Tests: create a global while suspended in a `FOR` body and
|
||||
assert the script still sees it after `NEXT`; the same from inside a `GOSUB`; and the
|
||||
NULL-without-error path, which should become an impossible state.
|
||||
|
||||
---
|
||||
|
||||
## 7. Filing gaps against `libakgl`
|
||||
@@ -579,9 +614,9 @@ entire test corpus and passes clean under ASan and UBSan.
|
||||
|
||||
| Gate | Result |
|
||||
|---|---|
|
||||
| `ctest` | 60/60 — 41 golden cases, 17 unit tests, 1 embedding example, 1 known-failing |
|
||||
| `ctest` | 61/61 — 41 golden cases, 17 unit tests, 2 embedding examples, 1 known-failing |
|
||||
| Golden corpus | 41/41 byte-exact, driven in place from the submodule |
|
||||
| ASan + UBSan | 60/60 |
|
||||
| ASan + UBSan | 61/61 |
|
||||
| Line coverage | 92.3% (2823/3058) — above the 90% gate |
|
||||
| Function coverage | 96.9% (221/228) |
|
||||
| Warnings | none under `-Wall -Wextra` |
|
||||
|
||||
Reference in New Issue
Block a user