Fix generator teardown leaks, add RETURN-in-GEN and LOOP conditions on DO EACH
All checks were successful
akbasic CI Build / cmake_build (push) Successful in 3m26s
akbasic CI Build / coverage (push) Successful in 4m0s
akbasic CI Build / sanitizers (push) Successful in 6m33s
akbasic CI Build / akgl_build (push) Successful in 9m32s
akbasic CI Build / mutation_test (push) Successful in 18m47s

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:
Ishikawa
2026-08-06 11:29:17 -04:00
parent f7e8d4b82b
commit 2cb68665d0
17 changed files with 426 additions and 13 deletions

View File

@@ -0,0 +1,8 @@
10 REM A DO EACH takes its condition on the LOOP, not on the DO line.
20 GEN ONE(N#)
30 EMIT N#
40 END GEN
50 DO EACH V# IN ONE(1) WHILE V# < 9
60 PRINT V#
70 LOOP
80 PRINT "UNREACHABLE"

View File

@@ -0,0 +1,2 @@
? 50 : PARSE ERROR DO EACH takes its WHILE/UNTIL on the LOOP, and nothing else here

View File

@@ -0,0 +1,15 @@
10 REM A WHILE or UNTIL on the LOOP composes with DO EACH: it is checked
20 REM after each trip through the body, with the loop variable still
30 REM holding that trip's value. Stopping abandons the generator cleanly.
40 GEN COUNTUP(N#)
50 FOR I# = 1 TO N#
60 EMIT I#
70 NEXT I#
80 END GEN
90 DO EACH V# IN COUNTUP(10)
100 PRINT V#
110 LOOP UNTIL V# = 3
120 DO EACH W# IN COUNTUP(4)
130 PRINT W# * 10
140 LOOP WHILE W# < 3
150 PRINT "DONE"

View File

@@ -0,0 +1,7 @@
1
2
3
10
20
30
DONE

View File

@@ -0,0 +1,15 @@
10 REM RETURN ends a GEN early, exactly as END GEN would: a GEN is a
20 REM function at heart, and only EMIT is different about it. Like a
30 REM GOSUB's or DEF's RETURN, it must stand in the GEN's own scope,
40 REM not inside a FOR or DO the body opened.
50 GEN FIRSTFEW(N#)
60 EMIT 1
70 IF N# < 2 THEN RETURN
80 EMIT 2
90 IF N# < 3 THEN RETURN
100 EMIT 3
110 END GEN
120 FOR EACH V# IN FIRSTFEW(2)
130 PRINT V#
140 NEXT V#
150 PRINT "DONE"

View File

@@ -0,0 +1,3 @@
1
2
DONE