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:
2026-08-01 16:43:14 -04:00
parent 6273be580a
commit 28ea99a638
8 changed files with 240 additions and 5 deletions

View File

@@ -108,6 +108,19 @@ array slot, so `GOTO 500` is one assignment — `environment->nextline = 500`
search. Blank slots are skipped by the step loop. That is why line numbers are capped at
9998 and why a nine-line program still costs a 9999-entry array.
It is also why **a loaded line does not have to arrive with a number**. Execution needs a
slot, not a number somebody typed, so a line that comes without one is simply given the
slot after the last line filed. `akbasic_runtime_file_line()` is the one place that rule
lives, shared by `akbasic_runtime_load()`, `RUNSTREAM` and `DLOAD`; the step loop, the
branch verbs, the four prescans and `RENUMBER` never learn that anything changed, because
all of them already worked in slots. `akbasic_SourceLine::numbered` records which kind a
line was, and exactly one thing reads it — `akbasic_runtime_check_targets()`, which
refuses `GOTO 100` when line 100 is a number nobody wrote.
The prompt is deliberately outside all of this: `process_line_repl()` files a line only
when it *had* a number, because there the number is what separates program text from a
statement to run now.
## One step
`akbasic_runtime_step()` is the whole loop, unrolled to a single iteration. The
@@ -202,7 +215,7 @@ anything else sets it to `QUIT`, so `basic program.bas` exits. It is one field,
the whole difference between an interactive session and a script runner.
**`akbasic_runtime_set_mode()` is not just an assignment.** Entering `REPL` prints
`READY`. Entering `RUN` does two prescans of the whole program first:
`READY`. Entering `RUN` does four prescans of the whole program first:
- **Labels.** Every `LABEL` in the source is filed before anything executes, textually
rather than by parsing. Without it a label would exist only from the moment its `LABEL`
@@ -211,11 +224,23 @@ the whole difference between an interactive session and a script runner.
by label at all.
- **`DATA` items.** `READ` walks a cursor along a list built before the program runs, so a
`DATA` line above its `READ` is found.
- **`TYPE` declarations.** A declaration has to be in effect wherever control goes, so
`DIM P@ AS RECT` cannot run before `RECT` exists even if a branch skipped the lines that
declared it.
- **Branch targets.** The only one that reads nothing in and merely refuses: a numeric
`GOTO`, `GOSUB`, `RUN`, `RESTORE`, `TRAP` or `COLLISION` target naming a line the program
did not number. It shares `RENUMBER`'s walk in `src/renumber.c` rather than repeating it.
Every path into a run — `akbasic_runtime_start()`, the `RUN` verb, `CONT`, and the end of
a `RUNSTREAM` load — goes through that one function, which is why the prescans live there
and not in any of the four callers.
All four run inside one `ATTEMPT`, because **a prescan failure is the program's mistake
and not the host's** and has to leave as a BASIC error line rather than a raised context.
Three of them report against whichever line the loader stopped on and put the real one in
the message text; the branch-target scan points `environment->lineno` at the line it is
walking so the `? N :` prefix is right. `TODO.md` §5 item 64 records the difference.
The REPL's split between *file it* and *run it now* is one boolean: the scanner sets
`hadlinenumber` when the line it just read began with a number. A line typed with a
number is program text; a line typed without one is direct mode and runs immediately.