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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user