Fix generator teardown leaks, add RETURN-in-GEN and LOOP conditions on DO EACH
Review findings and follow-ups from PR #61 review: - runtime_generator.c: akbasic_runtime_release_generator() now releases the forGeneratorEnv of every scope it walks through. Abandoning a generator that was itself suspended inside a FOR EACH over another generator stranded the inner generator's pool slot; a loop doing so exhausted the twelve-slot pool and died far from the cause. - runtime.c/runtime.h: new akbasic_runtime_unwind_to_environment(), the shared teardown for the error unwinds in pump_generator() and call_function() -- both previously bare prev_environment() loops with the same suspended-generator blindness. - runtime_commands.c: bare RETURN standing in a GEN's own frame ends the generator exactly as END GEN does -- a GEN is a function at heart. RETURN with a value there is refused (values leave a GEN only through EMIT). The no-frame error message now says "GOSUB, DEF, or GEN". - runtime_structure.c: LOOP WHILE/UNTIL composes with DO EACH -- checked after each trip with the loop variable still holding that trip's value; a condition that stops the loop abandons the generator exactly as EXIT does. Previously the condition was silently ignored, while the verb reference documented it as working. - parser_commands.c: trailing tokens after the generator call on a FOR EACH/DO EACH line are refused at parse. Previously they sat unparsed and blew up only after the loop completed, when the parent scope resumed the line mid-statement -- an error at the loop's end pointing at its start. - tests/generators.c: pool-exhaustion tests for the nested-abandonment and LOOP-condition paths, RETURN semantics tests, and a direct test of the unwind primitive. Three new golden pairs cover RETURN, LOOP conditions and the misplaced-condition parse error. - docs: RETURN and LOOP-condition semantics in 04-control-flow.md and 11-verb-reference.md; corrected the self-recursion analogy (functions are re-entrant here). TODO.md 1.10 records the generator design decisions the code comments were already citing, plus the zero-arg parameter-list limitation. MAINTENANCE.md gains the abandoned-generators invariant those comments also cited. Co-Authored-By: Andrew Kesterson <andrew@aklabs.net> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
30
TODO.md
30
TODO.md
@@ -366,6 +366,36 @@ One caveat survives the upgrade unchanged: `aksl_strhash_djb2` still sign-extend
|
||||
high-bit byte hashes differently from the `unsigned char` answer. BASIC identifiers are 7-bit
|
||||
ASCII so the symbol tables cannot reach it — see §1.3, which is still accurate.
|
||||
|
||||
### 1.10 Generators share the `DEF` namespace, and the rest of their v1 semantics
|
||||
|
||||
Decided with issue #57 and its review. `GEN` and `DEF` live in the same functions table —
|
||||
one lookup, one "unknown function" error path, a name cannot be both. What follows from
|
||||
that and from the review of PR #61, all settled:
|
||||
|
||||
- A `GEN` called like a function (`X# = COUNTUP(3)`) is refused at the call, before
|
||||
anything is pushed — `akbasic_FunctionDef.isGenerator` exists for exactly this check.
|
||||
- Bare `RETURN` standing in a `GEN`'s own frame ends the generator exactly as `END GEN`
|
||||
does; `RETURN expr` there is an error, because values leave a `GEN` only through `EMIT`.
|
||||
Both inherit the interpreter-wide restriction that `RETURN` does not unwind nested
|
||||
`FOR`/`DO` scopes — lifting that everywhere (the C64 reference *does* unwind) is filed
|
||||
separately.
|
||||
- `DO EACH ... LOOP WHILE c | UNTIL c` composes: the condition is checked after each trip,
|
||||
with the loop variable still holding that trip's value, and stopping abandons the
|
||||
generator exactly as `EXIT` does. A condition on the `DO EACH` line itself is a parse
|
||||
error.
|
||||
- Self-recursion — a `GEN` reached again down its own parent chain — is refused; sibling
|
||||
and nested invocations of the same `GEN` are each a fresh pool environment and are fine.
|
||||
- Every path that discards a `FOR EACH`/`DO EACH` scope must release the generator
|
||||
suspended off it; see MAINTENANCE.md's "Abandoned generators" note for the invariant
|
||||
and `akbasic_runtime_unwind_to_environment()` for the one primitive that enforces it.
|
||||
|
||||
One known limitation, pre-existing and shared with `DEF`: `parse_def_parameters()` does
|
||||
not accept an empty parameter list, so `GEN NAME()` cannot be written — every generator
|
||||
takes at least one parameter whether it wants one or not. Location:
|
||||
`src/parser_commands.c`, `parse_def_parameters()`. Consequence: pointless parameters in
|
||||
programs. Blast radius: cosmetic, both `DEF` and `GEN` headers. Closure: teach the shared
|
||||
helper to accept `()`, one test each for `DEF` and `GEN`; filed as its own issue.
|
||||
|
||||
---
|
||||
|
||||
## 2. What exists — **the core port is complete and green**
|
||||
|
||||
Reference in New Issue
Block a user