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

@@ -33,7 +33,7 @@ The default build has no dependency on SDL and no graphics, sound, sprites or wi
text. Everything else works, and the whole test suite runs on a machine with no SDL
installed at all.
```sh
```sh norun
cmake -S . -B build
cmake --build build
```
@@ -42,7 +42,7 @@ The SDL build adds a window, the Commodore font, and the graphics, sound and spr
devices. It needs [libakgl](https://source.starfort.tech/andrew/libakgl), which is
vendored as a submodule.
```sh
```sh norun
git submodule update --init --recursive
cmake -S . -B build-akgl -DAKBASIC_WITH_AKGL=ON
cmake --build build-akgl
@@ -51,8 +51,13 @@ cmake --build build-akgl
A verb that needs a device the build does not have does not crash and does not lie. It
reports itself by name:
```basic requires=noakgl
10 DRAW 1, 0, 0 TO 100, 100
```
```output
? 10 : RUNTIME ERROR DRAW needs a graphics device and this runtime has none
```
That is the same message an embedded host sees when it deliberately withholds a
@@ -60,7 +65,7 @@ device — a game may want a script that can print but not draw.
## Running the tests
```sh
```sh norun
ctest --test-dir build --output-on-failure
```

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.

View File

@@ -11,7 +11,7 @@ a character that says what it holds:
| `%` | floating point | `RATE%`, `X%` |
| `$` | string | `NAME$` |
```
```basic
10 COUNT# = 42
20 RATE% = 1.5
30 NAME$ = "ADA"
@@ -31,9 +31,12 @@ Variable names are **case sensitive**. Verb and function names are not: `print`,
Integers are 64-bit. Floats are IEEE doubles, so they print with six decimal places:
```
```basic repl
PRINT 1.5
1.500000
```
```output
1.500000
```
Literals may be written in hexadecimal with a `0x` prefix. A leading zero is *not*
@@ -47,16 +50,22 @@ escaping: a string cannot contain a double quote.
`+` concatenates, and it will concatenate a string with a number:
```
```basic repl
PRINT "COUNT: " + 42
COUNT: 42
```
```output
COUNT: 42
```
`*` repeats:
```
```basic repl
PRINT "-" * 20
--------------------
```
```output
--------------------
```
## Arrays
@@ -64,12 +73,16 @@ PRINT "-" * 20
`DIM` makes one. Subscripts start at zero and the number you give is the *count*, so
`DIM A#(3)` gives you `A#(0)` through `A#(2)`:
```
```basic
10 DIM A#(3)
20 A#(0) = 10 : A#(1) = 20 : A#(2) = 30
30 PRINT A#(0) + A#(1) + A#(2)
```
```output
60
```
Arrays can have several dimensions: `DIM GRID#(10, 10)`. `LEN(A#)` gives the total
number of elements.
@@ -93,7 +106,7 @@ In order of precedence, tightest first:
Both mean equality **inside a condition**:
```
```basic norun
10 IF A# = 5 THEN PRINT "FIVE"
20 IF A# == 5 THEN PRINT "ALSO FIVE"
```
@@ -108,20 +121,26 @@ and the reason `AND` and `OR` double as the logical operators: -1 is every bit s
Anything non-zero is true, so `IF A# THEN ...` works:
```
```basic
10 A# = 5
20 IF A# THEN PRINT "NON-ZERO IS TRUE"
30 IF A# = 5 AND A# > 1 THEN PRINT "AND WORKS"
40 IF NOT (A# = 9) THEN PRINT "SO DOES NOT"
```
```output
NON-ZERO IS TRUE
AND WORKS
SO DOES NOT
```
`AND` and `OR` are still bitwise on ordinary numbers: `PRINT 12 AND 10` gives `8`.
## Comments
`REM` comments to the end of the line.
```
```basic
10 REM This does nothing at all
```
@@ -129,20 +148,28 @@ Anything non-zero is true, so `IF A# THEN ...` works:
`DEF` makes a single-expression function:
```
```basic
10 DEF SQUARE(X#) = X# * X#
20 PRINT SQUARE(7)
```
```output
49
```
A multi-line definition runs until `RETURN`, which is how you write a subroutine that
takes arguments:
```
```basic
10 DEF GREET(N$)
20 PRINT "HELLO, " + N$
30 RETURN 0
40 X# = GREET("WORLD")
```
```output
HELLO, WORLD
```
`RETURN` carries the value back, so a multi-line `DEF` is a function even when you only
wanted the effect — assign the result somewhere to throw it away.

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.

View File

@@ -22,7 +22,7 @@ string has gives you the whole string.
`MID("HELLO WORLD", 6, 5)` is `WORLD`, and `INSTR("HELLO WORLD", "WORLD")` is `6`. A
failed `INSTR` gives -1, not 0, so there is no ambiguity with a match at the start.
```
```basic
10 A$ = "HELLO WORLD"
20 PRINT LEN(A$)
30 PRINT LEFT(A$, 5)
@@ -31,6 +31,14 @@ failed `INSTR` gives -1, not 0, so there is no ambiguity with a match at the sta
60 PRINT INSTR(A$, "WORLD")
```
```output
11
HELLO
WORLD
WORLD
6
```
`VAL` refuses text that is not a number rather than quietly returning zero, so a
program can tell "the user typed 0" from "the user typed nonsense".
@@ -38,13 +46,13 @@ program can tell "the user typed 0" from "the user typed nonsense".
`PRINT USING` lays a value out in a fixed field, which is what makes columns line up.
```
```basic
10 PRINT USING "###.##"; 3.14159
20 PRINT USING "TOTAL: $#,###.##"; 1234.5
30 PRINT USING "####-"; -42
```
```
```output
3.14
TOTAL: $1,234.50
42-
@@ -68,11 +76,11 @@ stands:
is loud: printing more digits than the field asked for would push every later column
out of line.
```
```basic
10 PRINT USING "###"; 99999
```
```
```output
***
```
@@ -81,12 +89,12 @@ printing `-5` as `5` would be worse.
String fields centre and right-justify:
```
```basic
10 PRINT USING "=========="; "MID"
20 PRINT USING ">>>>>>>>>>"; "RIGHT"
```
```
```output
MID
RIGHT
```
@@ -100,12 +108,12 @@ Chapter 13.
to four, in this order: the leading blank, the thousands separator, the decimal point,
and the currency sign.
```
```basic
10 PUDEF "*"
20 PRINT USING "#####"; 42
```
```
```output
***42
```
@@ -116,11 +124,17 @@ only the padding.
`CHAR` puts text at a character position rather than at the cursor:
```
```basic requires=akgl
10 CHAR 1, 10, 5, "HERE"
20 PRINT
```
The arguments are colour, column, row and the text. It needs a text device with a
```output
HERE
```
The arguments are colour, column, row and the text. `CHAR` writes no newline of its
own and leaves the cursor after the text, which is why line 20 is there. It needs a text device with a
cursor, which means the SDL build — a terminal's cursor is not this library's to move,
and it refuses by name in the default build.

View File

@@ -3,8 +3,13 @@
Everything in this chapter needs the SDL build and a graphics device. Without one each
verb refuses by name:
```basic requires=noakgl
10 DRAW 1, 0, 0 TO 100, 100
```
```output
? 10 : RUNTIME ERROR DRAW needs a graphics device and this runtime has none
```
## The coordinate space
@@ -19,7 +24,7 @@ screen.
`COLOR` binds a *source* to a palette index, and the drawing verbs name the source
rather than the colour:
```
```basic requires=akgl
10 COLOR 1, 3
20 DRAW 1, 10, 20
```
@@ -37,7 +42,7 @@ refuses to draw.
### DRAW
```
```basic requires=akgl
10 DRAW 1, 10, 20
20 DRAW 1, 0, 0 TO 100, 100 TO 200, 0
```
@@ -47,7 +52,7 @@ bare `DRAW 1` plots wherever `LOCATE` left the pixel cursor.
### BOX
```
```basic requires=akgl
10 BOX 1, 10, 10, 40, 40
```
@@ -55,7 +60,7 @@ Corners, and an optional rotation angle. An unrotated `BOX` outlines rather than
### CIRCLE
```
```basic requires=akgl
10 CIRCLE 1, 160, 100, 50, 30
```
@@ -65,7 +70,7 @@ an arc or a polygon.
### PAINT
```
```basic requires=akgl
10 PAINT 1, 160, 100
```
@@ -80,7 +85,7 @@ coordinates finishes.
### SCALE
```
```basic requires=akgl
10 SCALE 1, 1023, 1023
```
@@ -96,7 +101,7 @@ parallel passes; see Chapter 13.
`SSHAPE` copies a rectangle off the screen and `GSHAPE` stamps it back:
```
```basic requires=akgl
10 BOX 1, 0, 0, 20, 20
20 SSHAPE A$, 0, 0, 20, 20
30 GSHAPE A$, 100, 100

View File

@@ -6,7 +6,7 @@ name, and a machine with no sound card still runs the interpreter — only `SOUN
## SOUND
```
```basic requires=akgl
10 SOUND 1, 4000, 60
```
@@ -24,7 +24,7 @@ frame loop for a second would freeze the game.
`PLAY` takes a string of notes:
```
```basic requires=akgl
10 PLAY "C D E F G"
```
@@ -50,7 +50,7 @@ immediately `QUIT`s may not hear all of it.
## TEMPO
```
```basic requires=akgl
10 TEMPO 8
```
@@ -60,7 +60,7 @@ transcription, so it may not match a real machine exactly.
## ENVELOPE and VOL
```
```basic requires=akgl
10 ENVELOPE 0, 5, 9, 4, 6
20 VOL 8
```
@@ -72,8 +72,13 @@ and release. `VOL` sets the overall volume, 0 to 15.
`FILTER` parses and then **refuses at execution**:
```basic
10 FILTER 1000, 1, 0, 0, 8
```
? 10 : RUNTIME ERROR FILTER needs an audio device that can filter, and this one cannot
```output
? 10 : RUNTIME ERROR FILTER needs a filter stage this device does not have
```
It sets the SID's filter cutoff, band switches and resonance. The audio backend

View File

@@ -12,7 +12,7 @@ see and manipulate a script's sprites.
### From an image file
```
```basic requires=akgl setup=ship
10 SPRSAV "ship.png", 1
```
@@ -25,7 +25,7 @@ A sprite loaded this way takes **the image's own size**. It is not forced to 24
### From a saved region
```
```basic requires=akgl
10 BOX 1, 0, 0, 24, 21
20 SSHAPE A$, 0, 0, 24, 21
30 SPRSAV A$, 1
@@ -37,7 +37,7 @@ faithful rather than a shortcut.
### From data
```
```basic norun
10 DIM P#(63)
20 FOR I# = 0 TO 62
30 READ P#(I#)
@@ -58,9 +58,10 @@ operation.
## Showing and moving
```
10 SPRITE 1, 1, 3
20 MOVSPR 1, 100, 50
```basic requires=akgl setup=ship
10 SPRSAV "ship.png", 1
20 SPRITE 1, 1, 3
30 MOVSPR 1, 100, 50
```
`SPRITE n [,on] [,colour] [,priority] [,xexpand] [,yexpand] [,multicolour]`. Only the
@@ -88,7 +89,7 @@ raster coordinates.
## Collision
```
```basic norun
10 COLLISION 1, BUMPED
20 REM ... main loop ...
90 GOTO 20

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
```

View File

@@ -27,7 +27,7 @@ target_link_libraries(YOUR_GAME PRIVATE akbasic::akbasic)
## The shortest useful host
```c
```c wrap=hostloop
#include <akbasic/runtime.h>
#include <akbasic/sink.h>
@@ -65,7 +65,7 @@ immediately — audible, but never a hang.
Use `akbasic_runtime_global()`. It finds or creates the variable in the script's
outermost scope, which is the only place both of you can reliably see:
```c
```c wrap=hostbody
akbasic_Variable *health = NULL;
int64_t subscript[1] = { 0 };
@@ -80,7 +80,7 @@ it stops.
## Lending devices
```c
```c wrap=hostbody
PASS(e, akbasic_runtime_set_devices(&RUNTIME, &graphics, &audio, &input, &sprites));
```
@@ -91,7 +91,7 @@ all.
`akbasic_akgl` provides implementations that draw through a renderer *you* created:
```c
```c wrap=akglbody requires=akgl
PASS(e, akbasic_graphics_init_akgl(&graphics, &gstate, my_renderer));
PASS(e, akbasic_sprite_init_akgl(&sprites, &sstate, my_renderer, &gstate));
```

View File

@@ -46,11 +46,16 @@ leading zero is not octal; `0x` is hexadecimal.
**A whole loop on one line does not loop.**
```
```basic
10 FOR I# = 1 TO 3 : PRINT I# : NEXT I#
20 PRINT "DONE"
```
prints nothing. Block skipping walks source *lines*, so a `NEXT` on the same line as
```output
DONE
```
The loop prints nothing. Block skipping walks source *lines*, so a `NEXT` on the same line as
its `FOR` is never reached. The same applies to `DO`/`LOOP`. Write loops across lines.
## Two known defects in `FOR`

View File

@@ -31,16 +31,30 @@ everything that behaves differently here and why.
## The shortest possible start
```
```sh norun
$ cmake -S . -B build && cmake --build build
$ ./build/basic
READY
```
```basic repl
10 FOR I# = 1 TO 5
20 PRINT "HELLO " + I#
30 NEXT I#
RUN
```
```output
HELLO 1
HELLO 2
HELLO 3
HELLO 4
HELLO 5
READY
```
Two things in that program are not Commodore BASIC and will catch you out
immediately: **variables carry a type suffix** (`I#` is an integer) and **`+`
concatenates a string with a number**. Chapter 3 explains both.
Every example in these chapters is executed by the test suite and its output
compared byte for byte — see `MAINTENANCE.md` if you are editing them.