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>
284 lines
5.3 KiB
Markdown
284 lines
5.3 KiB
Markdown
# 4. Control flow
|
|
|
|
## IF ... THEN ... ELSE
|
|
|
|
```basic
|
|
10 A# = 5
|
|
20 IF A# = 5 THEN PRINT "FIVE" ELSE PRINT "NOT FIVE"
|
|
```
|
|
|
|
```output
|
|
FIVE
|
|
```
|
|
|
|
The condition is a whole expression, so `AND`, `OR` and `NOT` all work in one:
|
|
|
|
```basic
|
|
10 A# = 5
|
|
20 IF A# > 0 AND A# < 10 THEN PRINT "IN RANGE"
|
|
```
|
|
|
|
```output
|
|
IN RANGE
|
|
```
|
|
|
|
**Everything after `THEN` on the line belongs to the condition**, which matters as soon
|
|
as you put several statements on a line:
|
|
|
|
| Line | Condition | What runs |
|
|
|---|---|---|
|
|
| `IF C THEN A : B` | true | `A`, `B` |
|
|
| `IF C THEN A : B` | false | nothing |
|
|
| `IF C THEN A ELSE B : D` | true | `A` |
|
|
| `IF C THEN A ELSE B : D` | false | `B`, `D` |
|
|
|
|
The remainder always belongs to whichever arm was written *last*.
|
|
|
|
### Blocks
|
|
|
|
`BEGIN` and `BEND` make an `IF` span lines:
|
|
|
|
```basic
|
|
10 A# = 5
|
|
20 IF A# = 5 THEN BEGIN
|
|
30 PRINT "IN THE BLOCK"
|
|
40 PRINT "STILL IN IT"
|
|
50 BEND
|
|
60 PRINT "AFTER"
|
|
```
|
|
|
|
```output
|
|
IN THE BLOCK
|
|
STILL IN IT
|
|
AFTER
|
|
```
|
|
|
|
When the condition is false every line up to the `BEND` is skipped.
|
|
|
|
## FOR ... NEXT
|
|
|
|
```basic
|
|
10 FOR I# = 1 TO 5
|
|
20 PRINT I#
|
|
30 NEXT I#
|
|
```
|
|
|
|
```output
|
|
1
|
|
2
|
|
3
|
|
4
|
|
5
|
|
```
|
|
|
|
`STEP` sets the stride, and a negative one counts down:
|
|
|
|
```basic
|
|
10 FOR I# = 10 TO 0 STEP -2
|
|
20 PRINT I#
|
|
30 NEXT I#
|
|
```
|
|
|
|
```output
|
|
10
|
|
8
|
|
6
|
|
4
|
|
2
|
|
0
|
|
```
|
|
|
|
The counter is an ordinary variable and the body may assign to it. Two things to know:
|
|
|
|
- **The counter does not survive the loop.** It lives in the loop's own scope, so
|
|
reading it afterwards gives zero. On a C128 it keeps its final value.
|
|
- **A step that overshoots runs the body one extra time.** `FOR I = 1 TO 9 STEP 3` runs
|
|
with 1, 4, 7 *and 10*. Both are recorded as known defects; see Chapter 13.
|
|
|
|
`EXIT` leaves the loop early:
|
|
|
|
```basic
|
|
10 FOR I# = 1 TO 100
|
|
20 IF I# = 5 THEN EXIT
|
|
30 NEXT I#
|
|
40 PRINT "OUT"
|
|
```
|
|
|
|
```output
|
|
OUT
|
|
```
|
|
|
|
## DO ... LOOP
|
|
|
|
The condition can go on either end, or neither:
|
|
|
|
```basic
|
|
10 I# = 0
|
|
20 DO WHILE I# < 3
|
|
30 PRINT I#
|
|
40 I# = I# + 1
|
|
50 LOOP
|
|
```
|
|
|
|
```output
|
|
0
|
|
1
|
|
2
|
|
```
|
|
|
|
```basic
|
|
10 I# = 0
|
|
20 DO
|
|
30 PRINT I#
|
|
40 I# = I# + 1
|
|
50 LOOP UNTIL I# = 3
|
|
```
|
|
|
|
```output
|
|
0
|
|
1
|
|
2
|
|
```
|
|
|
|
A condition on the `DO` is tested before the body, so the body may run zero times. A
|
|
condition on the `LOOP` is tested after, so it runs at least once. `DO` with no
|
|
condition at all loops forever until an `EXIT` or a `GOTO` leaves it.
|
|
|
|
`EXIT` works here too.
|
|
|
|
## GOTO and GOSUB
|
|
|
|
```basic
|
|
10 GOSUB 100
|
|
20 PRINT "BACK"
|
|
30 END
|
|
100 PRINT "IN THE SUBROUTINE"
|
|
110 RETURN
|
|
```
|
|
|
|
```output
|
|
IN THE SUBROUTINE
|
|
BACK
|
|
```
|
|
|
|
`RETURN` goes back to the line after the `GOSUB`.
|
|
|
|
## Labels
|
|
|
|
A label is a name with no type suffix. It marks a line, and anything that takes a line
|
|
number takes a label instead:
|
|
|
|
```basic
|
|
10 GOTO SETUP
|
|
20 PRINT "SKIPPED"
|
|
100 LABEL SETUP
|
|
110 PRINT "ARRIVED"
|
|
```
|
|
|
|
```output
|
|
ARRIVED
|
|
```
|
|
|
|
**Labels are filed before the program runs**, so a forward `GOTO` works. This is worth
|
|
using for its own sake: a program written with labels is immune to `RENUMBER`, because
|
|
there is no number to rewrite.
|
|
|
|
Follow that one step further and the numbers stop earning their keep at all. A program
|
|
in a file can leave them out — see
|
|
[Chapter 2](02-getting-started.md#a-program-in-a-file-does-not-need-them) — and then
|
|
every branch it makes is by name:
|
|
|
|
```basic
|
|
GOSUB GREET
|
|
GOTO FINISH
|
|
|
|
PRINT "NOT REACHED"
|
|
|
|
LABEL GREET
|
|
PRINT "HELLO"
|
|
RETURN
|
|
|
|
LABEL FINISH
|
|
PRINT "DONE"
|
|
```
|
|
|
|
```output
|
|
HELLO
|
|
DONE
|
|
```
|
|
|
|
Nothing in that program can be broken by inserting a line into it, which is the whole
|
|
argument.
|
|
|
|
## ON
|
|
|
|
`ON` picks the *n*th target from a list, counting from one:
|
|
|
|
```basic
|
|
10 CHOICE# = 2
|
|
20 ON CHOICE# GOTO 100, 200, 300
|
|
30 PRINT "CHOICE WAS OUT OF RANGE"
|
|
40 END
|
|
100 PRINT "FIRST"
|
|
110 END
|
|
200 PRINT "SECOND"
|
|
210 END
|
|
300 PRINT "THIRD"
|
|
```
|
|
|
|
```output
|
|
SECOND
|
|
```
|
|
|
|
Out of range is not an error — it falls through to the next statement, which is what
|
|
lets you write the check as the line after. `ON ... GOSUB` works the same way and
|
|
returns.
|
|
|
|
## Trapping errors
|
|
|
|
`TRAP` sends an error to a handler instead of stopping the program:
|
|
|
|
```basic
|
|
10 TRAP HANDLER
|
|
20 DIM Q#(2)
|
|
30 PRINT Q#(9)
|
|
40 PRINT "CARRIED ON"
|
|
50 END
|
|
100 LABEL HANDLER
|
|
110 PRINT "CAUGHT " + ERR(ER#) + " ON LINE " + EL#
|
|
120 RESUME NEXT
|
|
```
|
|
|
|
```output
|
|
CAUGHT Out Of Bounds ON LINE 30
|
|
CARRIED ON
|
|
```
|
|
|
|
Two variables are set when the trap fires: **`ER#`** is the error code and **`EL#`** is
|
|
the line it happened on. `ERR(ER#)` gives the message text. On a C128 these are called
|
|
`ER` and `EL` with no suffix; this dialect has no bare variable names.
|
|
|
|
**[Chapter 15](15-error-codes.md) is the complete list** of what `ER#` can hold and
|
|
what each code means. The short version is that the interpreter's own codes are 512 to
|
|
519 and are the only numbers worth comparing against; two codes render as the same
|
|
`ERR()` text, so compare the number rather than the text.
|
|
|
|
`RESUME` comes in three forms:
|
|
|
|
| Form | Where it goes |
|
|
|---|---|
|
|
| `RESUME` | back to the line that failed, and tries again |
|
|
| `RESUME NEXT` | on to the line after the one that failed |
|
|
| `RESUME (line)` | to a line you name |
|
|
|
|
Bare `RESUME` only terminates if the handler fixed whatever was wrong — that is what
|
|
it is for.
|
|
|
|
`TRAP` with no argument turns trapping off. An error *inside* a handler is reported
|
|
normally rather than re-entering it, so a broken handler cannot loop forever.
|
|
|
|
## STOP, END and CONT
|
|
|
|
`STOP` stops the program and returns to the prompt; `CONT` resumes from there. `END`
|
|
stops it without arming `CONT`. `QUIT` ends the interpreter itself.
|