Execute every documented example as a test
Some checks failed
akbasic CI Build / cmake_build (push) Successful in 3m2s
akbasic CI Build / sanitizers (push) Successful in 3m52s
akbasic CI Build / coverage (push) Failing after 3m24s
akbasic CI Build / akgl_build (push) Failing after 20s
akbasic CI Build / mutation_test (push) Has been cancelled
Some checks failed
akbasic CI Build / cmake_build (push) Successful in 3m2s
akbasic CI Build / sanitizers (push) Successful in 3m52s
akbasic CI Build / coverage (push) Failing after 3m24s
akbasic CI Build / akgl_build (push) Failing after 20s
akbasic CI Build / mutation_test (push) Has been cancelled
docs/ and README.md carry 85 fenced blocks. Every one was checked by hand exactly once, when it was written, which is not a standard that survives a changing interpreter -- and four were already wrong: two transcripts showing a leading space PRINT does not emit, akbasic_TextSink in README.md missing the two members it had grown hours earlier, and FILTER's refusal quoted with wording the code does not use. tests/docs_examples.sh reads a fence-tag vocabulary and runs what it finds. BASIC programs and transcripts run and are byte-compared against an `output` block; C snippets compile with -fsyntax-only against the real include path, which CMake writes out because it is transitive through akerror, akstdlib and akgl; shell blocks run in a sandbox. Anything that would reconfigure the build tree, hit the network or re-enter the suite is tagged norun with the reason in MAINTENANCE.md, and the two cmake blocks stay hand-maintained by decision. An untagged block is a failure rather than a default, and the pass line reports what it executed by kind. Both exist because the way a harness like this dies is by quietly matching nothing and passing -- which it duly did on the first CTest run, where a generator expression evaluating to nothing still contributed an empty argument that the script read as a filename. The count is what caught it. The excerpt check earns its own mention: a block tagged `c excerpt=include/akbasic/sink.h` must still appear in that header, comments and whitespace ignored. Compiling it would only redefine the type, so a compile check could not have found the stale struct, and did not. Registered as the CTest case docs_examples in both configurations. Fixing the four wrong examples turned up two interpreter defects, fixed in the previous commit and recorded in TODO.md section 8. MAINTENANCE.md is new: the fence-tag reference, what to do when the case fails, and the conventions that until now only existed inside source comments -- the three test lists and how two of them invert "passed", the sorted verb table, that a golden file is never edited to suit this interpreter, and that a fix gets mutation-checked with a file copy rather than git checkout. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,15 +2,24 @@
|
||||
|
||||
## 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"
|
||||
```
|
||||
10 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
|
||||
@@ -29,32 +38,56 @@ The remainder always belongs to whichever arm was written *last*.
|
||||
|
||||
`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"
|
||||
```
|
||||
10 IF A# = 5 THEN BEGIN
|
||||
20 PRINT "IN THE BLOCK"
|
||||
30 PRINT "STILL IN IT"
|
||||
40 BEND
|
||||
50 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
|
||||
@@ -64,17 +97,22 @@ The counter is an ordinary variable and the body may assign to it. Two things to
|
||||
|
||||
`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#
|
||||
@@ -82,7 +120,13 @@ The condition can go on either end, or neither:
|
||||
50 LOOP
|
||||
```
|
||||
|
||||
```output
|
||||
0
|
||||
1
|
||||
2
|
||||
```
|
||||
|
||||
```basic
|
||||
10 I# = 0
|
||||
20 DO
|
||||
30 PRINT I#
|
||||
@@ -90,6 +134,12 @@ The condition can go on either end, or neither:
|
||||
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.
|
||||
@@ -98,7 +148,7 @@ condition at all loops forever until an `EXIT` or a `GOTO` leaves it.
|
||||
|
||||
## GOTO and GOSUB
|
||||
|
||||
```
|
||||
```basic
|
||||
10 GOSUB 100
|
||||
20 PRINT "BACK"
|
||||
30 END
|
||||
@@ -106,6 +156,11 @@ condition at all loops forever until an `EXIT` or a `GOTO` leaves it.
|
||||
110 RETURN
|
||||
```
|
||||
|
||||
```output
|
||||
IN THE SUBROUTINE
|
||||
BACK
|
||||
```
|
||||
|
||||
`RETURN` goes back to the line after the `GOSUB`.
|
||||
|
||||
## Labels
|
||||
@@ -113,13 +168,17 @@ condition at all loops forever until an `EXIT` or a `GOTO` leaves it.
|
||||
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.
|
||||
@@ -128,9 +187,20 @@ there is no number to rewrite.
|
||||
|
||||
`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"
|
||||
```
|
||||
10 ON CHOICE# GOTO 100, 200, 300
|
||||
20 PRINT "CHOICE WAS OUT OF RANGE"
|
||||
|
||||
```output
|
||||
SECOND
|
||||
```
|
||||
|
||||
Out of range is not an error — it falls through to the next statement, which is what
|
||||
@@ -141,7 +211,7 @@ returns.
|
||||
|
||||
`TRAP` sends an error to a handler instead of stopping the program:
|
||||
|
||||
```
|
||||
```basic
|
||||
10 TRAP HANDLER
|
||||
20 DIM Q#(2)
|
||||
30 PRINT Q#(9)
|
||||
@@ -152,6 +222,11 @@ returns.
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user