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:
2026-08-02 00:10:24 -04:00
parent 694b446ce4
commit 8594d8471d
7 changed files with 122 additions and 24 deletions

View File

@@ -264,8 +264,9 @@ DATA 3, 9, 8, 6, 4, 5
Everything above the last four lines is a drawing nobody would ever see. `SSHAPE` and
`SPRSAV` are what make it the screen.
The game keeps its six stamps in **six separate scalars** rather than an array, and that
is not a style choice — see the fourth trap at the end of this chapter.
The game keeps its six stamps in **six separate scalars** rather than an array, which it
had to when it was written — see the fourth trap at the end of this chapter. An array
works now.
## Step 4: Find the frame boundary
@@ -704,7 +705,12 @@ is **evaluated**; the skip now releases what parsing pushed. **The listing still
its loops with `GOTO`**, which is where this chapter's own history shows: written that way
because it had to be, and left that way because it works.
### 4. `SSHAPE` and `GSHAPE` ignore the subscript on a string array
### 4. `SSHAPE` and `GSHAPE` ignoring a subscript — *fixed*
Also history. The handle used to land in element zero whatever subscript you wrote, and
`GSHAPE SH$(2)` stamped whatever was in `SH$(0)` — while ordinary assignment and `PRINT`
honoured the subscript, which is what made it expensive to find. The symptom was that
every brick came out the colour of the last stamp captured.
```basic requires=akgl
DIM SH$(4)
@@ -713,17 +719,13 @@ PRINT "[" + SH$(0) + "] [" + SH$(2) + "]"
```
```output
[SHAPE:0] []
[] [SHAPE:0]
```
The handle lands in element zero whatever subscript you write, and `GSHAPE SH$(2)` then
stamps whatever is in `SH$(0)`. Ordinary assignment and `PRINT` honour the subscript, so
this is the two verbs reading their leaf directly rather than evaluating it. The symptom
is that every brick comes out the colour of the last stamp captured.
**Keep saved shapes in separate scalars.** The six brick stamps are `S0$` to `S5$` for
this reason, and the field is drawn as six runs of one colour rather than brick by brick
— which turned out to be cheaper anyway.
An array of shapes works now. **The listing in `examples/` still keeps its six brick
stamps in six scalars** — `S0$` to `S5$` — because that is what it had to do, and the
field is still drawn as six runs of one colour rather than brick by brick, which turned
out to be cheaper anyway.
### 5. `READ` walks one cursor through every `DATA` item in the file