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:
@@ -388,6 +388,29 @@ akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_settime(akbasic_Runtime *obj,
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_renumber(akbasic_Runtime *obj, int64_t newstart, int64_t increment, int64_t oldstart);
|
||||
|
||||
/**
|
||||
* @brief Refuse a program that branches by number to a line it did not number.
|
||||
*
|
||||
* The fourth prescan, run beside the label, `DATA` and `TYPE` ones on every entry
|
||||
* into AKBASIC_MODE_RUN. Since a loaded line may be given its number rather than
|
||||
* carry one, `GOTO 100` in a script written without numbers finds the hundredth
|
||||
* line and branches there -- plausible, silent and wrong. This says so before any
|
||||
* line executes, which is the earliest it can be said and the only place that
|
||||
* covers all four ways a program arrives.
|
||||
*
|
||||
* It walks the same `GOTO`, `GOSUB`, `RUN`, `RESTORE`, `TRAP`, `ON ... GOTO` and
|
||||
* `COLLISION` targets akbasic_renumber() rewrites, sharing that walk rather than
|
||||
* repeating it. A target naming an *empty* line is allowed through, for the same
|
||||
* reason RENUMBER leaves one alone. A `GOTO` to a label never reaches here.
|
||||
*
|
||||
* @param obj Object to initialize, inspect, or modify.
|
||||
* @return `NULL` on success, otherwise an error context owned by the caller.
|
||||
* @throws AKERR_NULLPOINTER When `obj` is NULL.
|
||||
* @throws AKBASIC_ERR_SYNTAX When a numeric target names a line the program did not number.
|
||||
* @throws AKBASIC_ERR_BOUNDS When a line does not fit the walk's buffer.
|
||||
*/
|
||||
akerr_ErrorContext AKERR_NOIGNORE *akbasic_runtime_check_targets(akbasic_Runtime *obj);
|
||||
|
||||
/**
|
||||
* @brief File every `LABEL` in the stored program before any of it runs.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user