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

@@ -5,12 +5,17 @@ something, and refuse by name where it does not.
## Program storage
```
```basic repl
10 PRINT "HI"
DSAVE "myprogram.bas"
DLOAD "myprogram.bas"
VERIFY "myprogram.bas"
```
```output
OK
```
`SAVE` and `LOAD` are the same verbs under their other names, and `DVERIFY` is
`VERIFY`. A program is saved as plain text with its line numbers, so you can edit it in
anything.
@@ -22,7 +27,7 @@ many lines differ.
Ten channels, numbered 0 to 9.
```
```basic
10 DOPEN 1, "scores.txt", W
20 PRINT #1, "ADA 4000"
30 PRINT #1, "GRACE 3800"
@@ -33,6 +38,10 @@ Ten channels, numbered 0 to 9.
80 DCLOSE 2
```
```output
ADA 4000
```
| Verb | What it does |
|---|---|
| `DOPEN n, "name"` | open for reading |
@@ -50,7 +59,7 @@ its own word, because `PRINT#` would otherwise scan as a variable name. See Chap
Reading past the end of a file leaves the variable empty rather than raising, so a read
loop tests what it got:
```
```basic setup=textfiles
10 DOPEN 1, "data.txt"
20 DO
30 INPUT #1, L$
@@ -60,6 +69,11 @@ loop tests what it got:
70 DCLOSE 1
```
```output
ONE
TWO
```
Reading a channel opened for writing — or writing one opened for reading — is refused,
as is using a channel that is not open.
@@ -68,7 +82,7 @@ length to seek by, so it rewinds and reads forward.
## Managing files
```
```basic setup=textfiles
10 COPY "a.txt", "b.txt"
20 CONCAT "a.txt", "b.txt"
30 RENAME "b.txt", "c.txt"
@@ -80,15 +94,23 @@ deletes. Deleting a file that is not there is reported rather than ignored.
## Binary blocks
```
```basic
10 A$ = "BINARY DATA"
20 BSAVE "block.dat", POINTER(A$), POINTER(A$) + 12
30 BLOAD "block.dat", POINTER(B$), 12
20 B$ = "............"
30 BSAVE "block.dat", POINTER(A$), POINTER(A$) + 12
40 BLOAD "block.dat", POINTER(B$), 12
50 PRINT B$
```
```output
BINARY DATA
```
`BSAVE` writes a range of memory and `BLOAD` reads one back. **`BLOAD` requires a
length**, unlike a C128's, because the address is a real address in this process and a
file longer than you expected would write past whatever you pointed at.
file longer than you expected would write past whatever you pointed at. For the same
reason line 20 is not optional: `POINTER` gives you the address of a string that already
exists, and reading into one you never sized writes over something else.
## What is refused, and why
@@ -105,6 +127,11 @@ channels, and closing the channels is real, so that is what it does.
Each refusal names itself and says why:
```basic
10 HEADER "DISK"
```
```output
? 10 : RUNTIME ERROR HEADER formats a disk, and there is no disk drive here -- only a filesystem
```