Add akbasic_runtime_global: a host variable lands in the script's root scope
A host creating a variable while a script was suspended got it in whatever scope was active -- usually a FOR or GOSUB body -- and it died when the body popped, silently. Reaching for the root by hand returned NULL without raising, because environment_get only auto-creates in the active environment. Both are still true of environment_get, which is correct for what the interpreter uses it for. The README and the example now point somewhere else. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -249,8 +249,6 @@ akerr_ErrorContext *akbasic_environment_get(akbasic_Environment *obj, const char
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akbasic_Environment *walk = NULL;
|
||||
akbasic_Variable *variable = NULL;
|
||||
int64_t sizes[1] = { 1 };
|
||||
void *slot = NULL;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && varname != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||||
@@ -276,6 +274,36 @@ akerr_ErrorContext *akbasic_environment_get(akbasic_Environment *obj, const char
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
PASS(errctx, akbasic_environment_create(obj, varname, dest));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akbasic_environment_create(akbasic_Environment *obj, const char *varname, akbasic_Variable **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akbasic_Variable *variable = NULL;
|
||||
int64_t sizes[1] = { 1 };
|
||||
void *slot = NULL;
|
||||
akerr_ErrorContext *found = NULL;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && varname != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||||
"NULL argument in environment create");
|
||||
|
||||
/*
|
||||
* This scope only. Unlike akbasic_environment_get() there is no walk up the
|
||||
* parent chain: the caller has already said *which* scope it means, and
|
||||
* finding an outer one would put the variable somewhere other than where it
|
||||
* was asked for.
|
||||
*/
|
||||
*dest = NULL;
|
||||
found = akbasic_symtab_get(&obj->variables, varname, &slot, NULL);
|
||||
if ( found == NULL ) {
|
||||
*dest = (akbasic_Variable *)slot;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
found->handled = true;
|
||||
IGNORE(akerr_release_error(found));
|
||||
|
||||
PASS(errctx, akbasic_runtime_new_variable(obj->runtime, &variable));
|
||||
FAIL_ZERO_RETURN(errctx, (strlen(varname) < sizeof(variable->name)), AKBASIC_ERR_BOUNDS,
|
||||
"Variable name '%s' is too long", varname);
|
||||
|
||||
@@ -35,6 +35,29 @@ akerr_ErrorContext *akbasic_runtime_new_variable(akbasic_Runtime *obj, akbasic_V
|
||||
FAIL_RETURN(errctx, AKBASIC_ERR_BOUNDS, "Maximum runtime variables reached");
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akbasic_runtime_global(akbasic_Runtime *obj, const char *name, akbasic_Variable **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akbasic_Environment *root = NULL;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && name != NULL && dest != NULL), AKERR_NULLPOINTER,
|
||||
"NULL argument in runtime global");
|
||||
FAIL_ZERO_RETURN(errctx, (obj->environment != NULL), AKERR_NULLPOINTER,
|
||||
"Runtime has no environment; call akbasic_runtime_init() first");
|
||||
|
||||
/*
|
||||
* Walk to the root rather than using obj->environment. That is the whole
|
||||
* point: a script suspended part-way through a bounded run() is usually
|
||||
* inside a FOR or GOSUB body, and a variable created there dies when the
|
||||
* body pops -- silently, with the script reading it correctly right up
|
||||
* until it stops. See the note on the declaration.
|
||||
*/
|
||||
for ( root = obj->environment; root->parent != NULL; root = root->parent ) {
|
||||
}
|
||||
PASS(errctx, akbasic_environment_create(root, name, dest));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akbasic_runtime_new_function(akbasic_Runtime *obj, akbasic_FunctionDef **dest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
Reference in New Issue
Block a user