Document optional line numbers
Chapter 2 gets the rule and the refusal, chapter 4 gets the payoff for LABEL, chapter 9 says DSAVE writes the numbers it handed out and to RENUMBER first if you want gaps, chapter 10 shows a host loading numberless source, chapter 13 gets the QuickBASIC-shaped divergence, and chapter 14's source[] passage gets its second half. Chapter 14 said "two prescans" and listed two; there were three before this and there are four now, so it lists all four and says which of them reports against the right line. TODO.md section 6 records four things found on the way and deliberately not fixed: set_label() filing into the active scope rather than the root, three prescans reporting the wrong line number, duplicate written line numbers still replacing silently, and renumber.c's file-scope scratch arrays. examples/embed.c runs the same program twice, numbered and not, so the example compiles the feature rather than describing it. Its header pointed at ./build/examples/embed, which is not where the binary lands. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
55
TODO.md
55
TODO.md
@@ -1803,6 +1803,61 @@ the reference's semantics reproduced faithfully; the third is the port's own.
|
||||
of `DOPEN` is genuinely more useful to a program than a flat 519 — so this is not
|
||||
"translate everything", which is exactly why it needs deciding rather than doing.
|
||||
|
||||
### Found while making line numbers optional
|
||||
|
||||
26. **`akbasic_environment_set_label()` files into the active scope, not the root.**
|
||||
`src/environment.c:246` walks up until `obj->runtime->environment == obj`, which is the
|
||||
*currently executing* scope, despite the comment above it saying "Only the top-level
|
||||
environment creates labels". So a `LABEL` reached inside a `GOSUB` or `FOR` body is filed
|
||||
into that scope and dies when the body pops, while `akbasic_runtime_scan_labels()`
|
||||
(`src/runtime.c:1286`) files the same label into the real root.
|
||||
|
||||
**The consequence is that a prescanned label and a re-filed one behave differently**, and
|
||||
which one you get depends on whether control has passed through the `LABEL` statement
|
||||
inside a scope. Nothing in either corpus does that, so nothing catches it. The fix is one
|
||||
condition -- walk to `parent == NULL` -- plus a test that `GOSUB`s past a `LABEL` and then
|
||||
branches to it from the top level. Wants checking against deviation 32 first, because
|
||||
"`LABEL` still executes and re-files itself" is deliberate and the re-filing is the part
|
||||
worth keeping.
|
||||
|
||||
27. **Three of the four prescans report against the wrong line.**
|
||||
`akbasic_runtime_set_mode()`'s handler builds its BASIC error line from
|
||||
`environment->lineno`, which after a load is wherever the loader stopped -- usually the
|
||||
last line of the program. The label, `DATA` and `TYPE` scans therefore print `? 47 :` for
|
||||
a fault on line 3, and work around it by putting `Line %d:` in the message text
|
||||
(`src/structtype.c:342`), so the message names the number twice and the prefix is wrong.
|
||||
|
||||
`akbasic_runtime_check_targets()` does not have the problem because it points
|
||||
`environment->lineno` at the line it is walking. That is the fix, and it belongs in the
|
||||
wrapper rather than in four places: give each prescan a way to say which line it failed
|
||||
on, or have each set the cursor as the target check does. Cosmetic, but it is the first
|
||||
number a person reads when a program will not start.
|
||||
|
||||
28. **Two lines carrying the same written line number still silently keep the last.**
|
||||
`akbasic_runtime_file_line()` refuses a collision that involves an *assigned* number, but
|
||||
leaves numbered-onto-numbered alone, which is what a file with `10 PRINT "A"` twice has
|
||||
always done. A duplicate in a hand-written file is a mistake rather than an intent, and
|
||||
refusing it would be consistent with the other three cases.
|
||||
|
||||
Not changed with the rest because it is the one collision that can appear in an existing
|
||||
program, so it carries corpus risk the others do not: 41 reference cases and 22 local ones
|
||||
would all have to be shown clean first. `src/runtime.c`, in the `FAIL_NONZERO_RETURN` that
|
||||
already names the slot.
|
||||
|
||||
29. **`akbasic_renumber()` keeps two 9999-entry `static` locals.**
|
||||
`src/renumber.c` holds `map[]` and `rewritten[]` at file scope, and
|
||||
`akbasic_runtime_check_targets()` now adds a `static` scratch buffer beside them. That is
|
||||
against the no-file-scope-mutable-state rule in `MAINTENANCE.md`, and it means `RENUMBER`
|
||||
and the target prescan are not reentrant across two `akbasic_Runtime`s in one process --
|
||||
the exact thing that rule exists to guarantee.
|
||||
|
||||
They are `static` because they will not fit on a default stack, which is the same reason
|
||||
`akbasic_Runtime` itself is too big for one. The honest fix is to hang them off the
|
||||
runtime like every other pool, which costs another ~2.5MB inline per interpreter for
|
||||
something used by one verb and one prescan. Recorded rather than done, because "make it
|
||||
reentrant" and "do not grow the runtime by a third for a scratch buffer" are both right
|
||||
and picking between them is a decision.
|
||||
|
||||
## 7. Filing gaps against `libakgl`
|
||||
|
||||
When phase 7 or phase 8 needs something `libakgl` does not have, **stop and file it** in
|
||||
|
||||
Reference in New Issue
Block a user