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

@@ -4,7 +4,7 @@
Run `basic` with no arguments and you get a prompt:
```
```sh norun
$ ./build/basic
READY
```
@@ -15,21 +15,33 @@ and after a program stops.
Anything you type **with a line number** is stored as part of a program. Anything you
type **without** one runs immediately:
```
```basic repl
PRINT 2 + 2
4
READY
```
```output
4
```
## Your first program
```
```basic repl
10 PRINT "WHAT IS YOUR NAME"
20 INPUT "> " N$
30 PRINT "HELLO, " + N$
RUN
ADA
```
```output
WHAT IS YOUR NAME
> HELLO, ADA
READY
```
The last line of the first block is not part of the program — it is what you type when
`INPUT` asks.
Type `LIST` to see it back, `RUN` to run it again, and `NEW` to throw it away.
## Line numbers
@@ -37,11 +49,14 @@ Type `LIST` to see it back, `RUN` to run it again, and `NEW` to throw it away.
Lines are stored under their numbers and run in numeric order, so the gaps are what
let you insert later:
```
```basic repl
10 PRINT "FIRST"
30 PRINT "THIRD"
20 PRINT "SECOND"
LIST
```
```output
10 PRINT "FIRST"
20 PRINT "SECOND"
30 PRINT "THIRD"
@@ -58,34 +73,53 @@ it off again.
Statements are separated by colons:
```
```basic
10 A# = 1 : B# = 2 : PRINT A# + B#
```
```output
3
```
There is one important limit: **block structures do not work inside a single line.**
```
```basic
10 FOR I# = 1 TO 3 : PRINT I# : NEXT I#
20 PRINT "DONE"
```
```output
DONE
```
prints nothing at all. The loop body is skipped entirely, because the interpreter skips
forward a *line* at a time looking for the `NEXT` and never finds one on the line it is
already past. Write loops across several lines:
```
```basic
10 FOR I# = 1 TO 3
20 PRINT I#
30 NEXT I#
```
```output
1
2
3
```
The same applies to `DO`/`LOOP`.
## Running a file
```sh
```sh setup=program
$ ./build/basic program.bas
```
```output
HELLO FROM A FILE
```
The file is read, stored, and run. It is exactly the same as typing the program in and
saving yourself the trouble.
@@ -96,13 +130,29 @@ $ echo '10 PRINT "HI"
RUN' | ./build/basic
```
```output
READY
HI
READY
```
## Saving and loading
```
```basic repl
10 PRINT "HI"
DSAVE "myprogram.bas"
NEW
DLOAD "myprogram.bas"
LIST
```
```output
10 PRINT "HI"
```
That is a whole round trip: save it, throw it away with `NEW`, load it back, and `LIST`
shows it again.
`SAVE` and `LOAD` are the same verbs under their other names. `VERIFY "myprogram.bas"`
compares what is in memory against the file and prints `OK` if they match.