Reset scratch per line and unwind dead scopes in host function calls

akbasic_runtime_call_function()'s body loop drives process_line_run()
directly, skipping the per-line prologue akbasic_runtime_step() provides.
The call environment's value scratch therefore accumulated across the
whole body, and any body past about ten real lines died with 'Maximum
values per line reached' -- a limit that is supposed to be per line. The
loop now runs the same prologue step() does.

A body that died also left its call scopes active: nothing popped them,
so a host absorbing script errors drained the twelve-slot environment
pool after twelve dead calls. The loop now unwinds to the caller's
environment on every exit path.

New: akbasic_runtime_clear_error(), the missing half of host revival. A
run's first BASIC-level error latches deliberately, and set_mode(RUN)
alone cannot un-decide that; a host that absorbed the error calls this
beside it. Both defects and the revival dance are pinned in
tests/user_functions.c.

Co-authored-by: andrew <andrew@aklabs.net>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XiGgpHuXUm2mR4Wzndw3dc
This commit is contained in:
2026-08-04 08:47:05 -04:00
parent dde1d91c6e
commit 743e610f8f
3 changed files with 166 additions and 3 deletions

View File

@@ -1114,9 +1114,33 @@ akerr_ErrorContext *akbasic_runtime_call_function(akbasic_Runtime *obj, const ch
* answering wrongly -- which is worse. The fix wants the REPL's own line
* cycle, and that is a larger change than a condition.
*/
while ( obj->environment != targetenv && obj->mode == AKBASIC_MODE_RUN ) {
PASS(errctx, akbasic_runtime_process_line_run(obj));
}
ATTEMPT {
while ( obj->environment != targetenv && obj->mode == AKBASIC_MODE_RUN ) {
/*
* The same per-line prologue akbasic_runtime_step() runs. Without
* it the call environment's value scratch accumulates across the
* whole body, and a body of ten real lines dies with "Maximum
* values per line reached" -- a limit that is supposed to be per
* line, not per call. step() cannot do this for us: this loop
* drives process_line_run() directly.
*/
CATCH(errctx, akbasic_runtime_zero(obj));
CATCH(errctx, akbasic_scanner_zero(obj));
CATCH(errctx, akbasic_runtime_process_line_run(obj));
}
} CLEANUP {
/*
* A body that died mid-line -- a runtime error set run_finished_mode,
* or a scanner error escaped (issue #4) -- left its scopes active.
* Give them back, or a host absorbing script errors drains the
* twelve-slot environment pool after twelve dead calls and every
* call after that fails for a reason nobody can see in the script.
*/
while ( obj->environment != targetenv && obj->environment->parent != NULL ) {
IGNORE(akbasic_runtime_prev_environment(obj));
}
} PROCESS(errctx) {
} FINISH(errctx, true);
PASS(errctx, akbasic_environment_new_value(targetenv, &out));
PASS(errctx, akbasic_value_clone(&targetenv->returnValue, out));
*dest = out;
@@ -1918,6 +1942,15 @@ akerr_ErrorContext *akbasic_runtime_step(akbasic_Runtime *obj)
SUCCEED_RETURN(errctx);
}
akerr_ErrorContext *akbasic_runtime_clear_error(akbasic_Runtime *obj)
{
PREPARE_ERROR(errctx);
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER, "NULL runtime in clear_error");
obj->errclass = AKBASIC_ERRCLASS_NONE;
SUCCEED_RETURN(errctx);
}
akerr_ErrorContext *akbasic_runtime_run(akbasic_Runtime *obj, int maxsteps)
{
PREPARE_ERROR(errctx);