Honour a subscript in SSHAPE and GSHAPE
`shape_variable()` took the identifier off the leaf and looked the variable up without ever evaluating the subscript, and both verbs then addressed element zero with a literal. So `SSHAPE SH$(2), ...` wrote the handle into `SH$(0)` and `GSHAPE SH$(2)` stamped whatever was in `SH$(0)`. Ordinary assignment and `PRINT` honour the subscript, which is what made this expensive: a program keeping several saved shapes in an array got every one of them resolving to the same element, silently, and the only symptom was that every stamp came out as the last shape captured. The Breakout in examples/ keeps its six brick stamps in six separate scalars for exactly this reason. `SPRSAV` was the counter-example and is the model -- it evaluates its argument and handles an array element correctly. The subscript resolution itself is now shared: `collect_subscripts()` comes out of src/environment.c as `akbasic_environment_collect_subscripts()`, so a verb taking a variable by name resolves a subscript the same way assignment does rather than each verb deciding for itself. tests/graphics_verbs.c covers TODO.md's reduction -- which used to print "[SHAPE:0] []" and now prints "[] [SHAPE:0]" -- and the case a program actually wants: two shapes captured into two elements, each stamped back through its own, asserted against the device log so a fix that merely made the strings look right would not pass. Chapter 18's trap 4 becomes history, and Chapter 6 says an array works. TODO.md section 9 item 6, struck. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -329,12 +329,14 @@ akerr_ErrorContext *akbasic_environment_create(akbasic_Environment *obj, const c
|
||||
* identifier yields the single subscript {0}, which is how a scalar is addressed
|
||||
* -- every variable is really a one-element array.
|
||||
*/
|
||||
static akerr_ErrorContext *collect_subscripts(akbasic_Environment *obj, akbasic_ASTLeaf *lval, int64_t *subscripts, int *count)
|
||||
akerr_ErrorContext *akbasic_environment_collect_subscripts(akbasic_Environment *obj, akbasic_ASTLeaf *lval, int64_t *subscripts, int *count)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akbasic_ASTLeaf *expr = NULL;
|
||||
akbasic_Value *tval = NULL;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && lval != NULL && subscripts != NULL && count != NULL),
|
||||
AKERR_NULLPOINTER, "NULL argument in collect_subscripts");
|
||||
*count = 0;
|
||||
if ( lval->expr != NULL &&
|
||||
lval->expr->leaftype == AKBASIC_LEAF_ARGUMENTLIST &&
|
||||
@@ -452,7 +454,7 @@ akerr_ErrorContext *akbasic_environment_assign(akbasic_Environment *obj, akbasic
|
||||
PASS(errctx, akbasic_environment_get(obj, lval->identifier, &variable));
|
||||
FAIL_ZERO_RETURN(errctx, (variable != NULL), AKBASIC_ERR_UNDEFINED,
|
||||
"Identifier %s is undefined", lval->identifier);
|
||||
PASS(errctx, collect_subscripts(obj, lval, subscripts, &subscriptcount));
|
||||
PASS(errctx, akbasic_environment_collect_subscripts(obj, lval, subscripts, &subscriptcount));
|
||||
|
||||
/*
|
||||
* Resolve the slot before the type switch. The reference notes that moving
|
||||
|
||||
@@ -646,19 +646,32 @@ akerr_ErrorContext *akbasic_cmd_paint(akbasic_Runtime *obj, akbasic_ASTLeaf *exp
|
||||
/* ---------------------------------------------------- SSHAPE and GSHAPE --- */
|
||||
|
||||
/**
|
||||
* @brief Find the string variable a shape verb reads or writes.
|
||||
* @brief Find the string variable a shape verb reads or writes, and its subscript.
|
||||
*
|
||||
* SSHAPE and GSHAPE are the only two verbs in the group whose first argument is
|
||||
* an identifier rather than a number, so they walk the leaf themselves instead
|
||||
* of going through akbasic_args_numbers().
|
||||
*
|
||||
* **The subscript is evaluated**, which it was not: this used to hand back the
|
||||
* variable and both verbs then addressed element zero with a literal, so
|
||||
* `SSHAPE SH$(2), ...` wrote the handle into `SH$(0)` and `GSHAPE SH$(2)`
|
||||
* stamped whatever was in `SH$(0)`. Ordinary assignment and `PRINT` honour the
|
||||
* subscript, so a program keeping several saved shapes in an array got every one
|
||||
* of them resolving to the same element -- silently, the symptom being that
|
||||
* every stamp came out as the last shape captured. TODO.md section 9 item 6.
|
||||
*
|
||||
* `SPRSAV` was the counter-example and is the model: it evaluates its argument
|
||||
* (`src/runtime_sprite.c`) and handles an array element correctly.
|
||||
*/
|
||||
static akerr_ErrorContext *shape_variable(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, const char *verb, akbasic_Variable **dest, akbasic_ASTLeaf **rest)
|
||||
static akerr_ErrorContext *shape_variable(akbasic_Runtime *obj, akbasic_ASTLeaf *expr, const char *verb, akbasic_Variable **dest, int64_t *subscripts, int *subscriptcount, akbasic_ASTLeaf **rest)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
akbasic_ASTLeaf *arg = NULL;
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL && expr != NULL && dest != NULL && rest != NULL),
|
||||
AKERR_NULLPOINTER, "NULL argument in shape_variable");
|
||||
FAIL_ZERO_RETURN(errctx, (subscripts != NULL && subscriptcount != NULL),
|
||||
AKERR_NULLPOINTER, "NULL subscript output in shape_variable");
|
||||
arg = akbasic_leaf_first_argument(expr);
|
||||
FAIL_ZERO_RETURN(errctx, (arg != NULL), AKBASIC_ERR_SYNTAX,
|
||||
"%s expected a string variable", verb);
|
||||
@@ -667,6 +680,8 @@ static akerr_ErrorContext *shape_variable(akbasic_Runtime *obj, akbasic_ASTLeaf
|
||||
PASS(errctx, akbasic_environment_get(obj->environment, arg->identifier, dest));
|
||||
FAIL_ZERO_RETURN(errctx, (*dest != NULL), AKBASIC_ERR_UNDEFINED,
|
||||
"%s could not reach the variable %s", verb, arg->identifier);
|
||||
PASS(errctx, akbasic_environment_collect_subscripts(obj->environment, arg,
|
||||
subscripts, subscriptcount));
|
||||
*rest = arg->next;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -681,11 +696,13 @@ akerr_ErrorContext *akbasic_cmd_sshape(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
|
||||
int count = 0;
|
||||
int handle = -1;
|
||||
char encoded[AKBASIC_MAX_STRING_LENGTH];
|
||||
int64_t zerosubscript[1] = { 0 };
|
||||
int64_t subscripts[AKBASIC_MAX_ARRAY_DEPTH];
|
||||
int subscriptcount = 0;
|
||||
|
||||
(void)lval; (void)rval;
|
||||
PASS(errctx, require_graphics(obj, "SSHAPE"));
|
||||
PASS(errctx, shape_variable(obj, expr, "SSHAPE", &variable, &arg));
|
||||
PASS(errctx, shape_variable(obj, expr, "SSHAPE", &variable,
|
||||
subscripts, &subscriptcount, &arg));
|
||||
|
||||
while ( arg != NULL && count < 4 ) {
|
||||
PASS(errctx, akbasic_runtime_evaluate(obj, arg, &value));
|
||||
@@ -715,7 +732,7 @@ akerr_ErrorContext *akbasic_cmd_sshape(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
|
||||
* take its LEN and get a size. TODO.md section 5.
|
||||
*/
|
||||
snprintf(encoded, sizeof(encoded), "%s%d", SHAPE_PREFIX, handle);
|
||||
PASS(errctx, akbasic_variable_set_string(variable, encoded, zerosubscript, 1));
|
||||
PASS(errctx, akbasic_variable_set_string(variable, encoded, subscripts, subscriptcount));
|
||||
SUCCEED_TRUE(obj, dest);
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
@@ -733,11 +750,13 @@ akerr_ErrorContext *akbasic_cmd_gshape(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
|
||||
long long converted = 0;
|
||||
double x = 0.0;
|
||||
double y = 0.0;
|
||||
int64_t zerosubscript[1] = { 0 };
|
||||
int64_t subscripts[AKBASIC_MAX_ARRAY_DEPTH];
|
||||
int subscriptcount = 0;
|
||||
|
||||
(void)lval; (void)rval;
|
||||
PASS(errctx, require_graphics(obj, "GSHAPE"));
|
||||
PASS(errctx, shape_variable(obj, expr, "GSHAPE", &variable, &arg));
|
||||
PASS(errctx, shape_variable(obj, expr, "GSHAPE", &variable,
|
||||
subscripts, &subscriptcount, &arg));
|
||||
|
||||
while ( arg != NULL && count < 2 ) {
|
||||
PASS(errctx, akbasic_runtime_evaluate(obj, arg, &value));
|
||||
@@ -751,7 +770,7 @@ akerr_ErrorContext *akbasic_cmd_gshape(akbasic_Runtime *obj, akbasic_ASTLeaf *ex
|
||||
x = (count >= 1) ? coords[0] : obj->gfx.x;
|
||||
y = (count >= 2) ? coords[1] : obj->gfx.y;
|
||||
|
||||
PASS(errctx, akbasic_variable_get_subscript(variable, zerosubscript, 1, &stored));
|
||||
PASS(errctx, akbasic_variable_get_subscript(variable, subscripts, subscriptcount, &stored));
|
||||
FAIL_ZERO_RETURN(errctx,
|
||||
(strncmp(stored->stringval, SHAPE_PREFIX, strlen(SHAPE_PREFIX)) == 0),
|
||||
AKBASIC_ERR_VALUE,
|
||||
|
||||
Reference in New Issue
Block a user