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

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:
2026-07-31 22:41:36 -04:00
parent 6f49f6a7f2
commit 342e4c07da
29 changed files with 1303 additions and 103 deletions

View File

@@ -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.