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

@@ -632,6 +632,27 @@ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_println(akbasic_Runtime *obj,
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_set_mode(akbasic_Runtime *obj, int mode);
/**
* @brief Forgive the last BASIC-level error, so a host may call again.
*
* A program run latches its first runtime error and ends -- deliberately, and
* a host cannot un-decide that with akbasic_runtime_set_mode() alone: the
* latch survives the mode change, every later line is skipped, and every
* later akbasic_runtime_call_function() answers a stale value after walking
* the whole source table doing nothing.
*
* A host that absorbed a script error -- reported through the sink, actor
* marked dumb, frame preserved -- calls this beside
* `akbasic_runtime_set_mode(obj, AKBASIC_MODE_RUN)` to put the runtime back
* in service. It is for hosts between calls, not for verbs during a run: a
* running program's first error still ends it, exactly once, with one line.
*
* @param obj Object to initialize, inspect, or modify.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When `obj` is NULL.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_clear_error(akbasic_Runtime *obj);
/**
* @brief Evaluate one AST leaf, drawing scratch values from the environment.
* @param obj Object to initialize, inspect, or modify.