Refuse a branch to a line the program did not number
GOTO 100 in a script written without line numbers finds the hundredth line and branches there. Silent, plausible and wrong: the test for it loops forever printing the second line when the check is removed. akbasic_runtime_check_targets() is a fourth prescan beside the label, DATA and TYPE ones, run on every entry into MODE_RUN -- the earliest the check can be made and the only place all four ways a program arrives pass through. A target naming an empty line is still allowed, for the same reason RENUMBER leaves one alone, and a fully numbered program is unaffected, which is every program that existed before this. It shares RENUMBER's walk rather than repeating it. renumber.c grows an akbasic_TargetWalk -- a self pointer and a visit function -- and rewrite_line() takes one. RENUMBER's visitor substitutes the number a line moved to; the check's substitutes the number unchanged and raises. One walk, so the two cannot disagree about what a branch target is. The check points environment->lineno at the line being walked so the "? N :" prefix names the offending line. The other three prescans do not and report whichever line the loader stopped on; TODO.md section 5 item 64 records it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
34
TODO.md
34
TODO.md
@@ -1385,6 +1385,40 @@ deviations from the reference's *program*: `main.go` and the SDL half of
|
||||
the last, as they always have. That is a separate decision with its own corpus risk and it
|
||||
is not this one.
|
||||
|
||||
64. **Branching by number to a line the program did not number is refused before it runs.**
|
||||
The other half of deviation 63, and the reason `akbasic_SourceLine` carries a flag rather
|
||||
than the loader just picking slots quietly. In a script written without line numbers
|
||||
`GOTO 100` finds the hundredth line and branches there: plausible, silent and wrong, which
|
||||
is the worst thing to hand somebody at run time.
|
||||
|
||||
`akbasic_runtime_check_targets()` is a fourth prescan, run beside the label, `DATA` and
|
||||
`TYPE` ones on every entry into `AKBASIC_MODE_RUN` — the earliest the check can be made and
|
||||
the only place all four ways a program arrives pass through. It refuses with
|
||||
`AKBASIC_ERR_SYNTAX`, naming the line and saying what to do instead:
|
||||
|
||||
```text
|
||||
? 1 : PARSE ERROR Line 1: branch to line 4, which the program did not number.
|
||||
Branch by LABEL, or RENUMBER first
|
||||
```
|
||||
|
||||
Two things are deliberately **not** refused. A target naming an *empty* line, for the same
|
||||
reason `RENUMBER` leaves one alone — `GOTO 9999` in a program with no line 9999 is already
|
||||
broken and inventing a rule about it would hide that. And a target in a fully numbered
|
||||
program, which is every program that existed before this, so nothing checked in changed.
|
||||
|
||||
**It shares `RENUMBER`'s walk rather than repeating it.** `src/renumber.c` grew an
|
||||
`akbasic_TargetWalk` — a `self` pointer and a `visit` function — and `rewrite_line()` now
|
||||
takes one. `RENUMBER`'s visitor substitutes the number a line moved to; the check's
|
||||
substitutes the number unchanged and raises if it names an assigned line. One walk, so the
|
||||
two cannot disagree about what a branch target is, which they would have within a release.
|
||||
|
||||
Worth knowing: the check points `environment->lineno` at the line it is walking, so the
|
||||
`? N :` prefix names the offending line. **The other three prescans do not**, so a
|
||||
malformed `TYPE` or a bad `DATA` item still reports whichever line the loader stopped on —
|
||||
usually the last line of the program. They work around it by putting `Line %d:` in the
|
||||
message text, which is why the message above says the number twice. Worth fixing in
|
||||
`akbasic_runtime_set_mode()`'s wrapper rather than in four places; not fixed here.
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user