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>
3.5 KiB
9. Files and disk
There is no 1541. The disk verbs work against the filesystem where that means something, and refuse by name where it does not.
Program storage
10 PRINT "HI"
DSAVE "myprogram.bas"
DLOAD "myprogram.bas"
VERIFY "myprogram.bas"
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.
VERIFY compares what is in memory against the file and prints OK, or reports how
many lines differ.
Files
Ten channels, numbered 0 to 9.
10 DOPEN 1, "scores.txt", W
20 PRINT #1, "ADA 4000"
30 PRINT #1, "GRACE 3800"
40 DCLOSE 1
50 DOPEN 2, "scores.txt"
60 INPUT #2, LINE$
70 PRINT LINE$
80 DCLOSE 2
ADA 4000
| Verb | What it does |
|---|---|
DOPEN n, "name" |
open for reading |
DOPEN n, "name", W |
open for writing |
APPEND n, "name" |
open for writing at the end |
DCLOSE n |
close one channel |
DCLOSE |
close all of them |
PRINT #n, expr |
write a line |
INPUT #n, VAR |
read a line |
RECORD n, r |
position at record r |
Note the space before the #. A C128 writes PRINT#1,A$; here the # has to be
its own word, because PRINT# would otherwise scan as a variable name. See Chapter 13.
Reading past the end of a file leaves the variable empty rather than raising, so a read loop tests what it got:
10 DOPEN 1, "data.txt"
20 DO
30 INPUT #1, L$
40 IF L$ = "" THEN EXIT
50 PRINT L$
60 LOOP
70 DCLOSE 1
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.
RECORD counts lines, not fixed-length records: a filesystem file has no record
length to seek by, so it rewinds and reads forward.
Managing files
10 COPY "a.txt", "b.txt"
20 CONCAT "a.txt", "b.txt"
30 RENAME "b.txt", "c.txt"
40 SCRATCH "c.txt"
COPY duplicates, CONCAT appends one file to another, RENAME moves, SCRATCH
deletes. Deleting a file that is not there is reported rather than ignored.
Binary blocks
10 A$ = "BINARY DATA"
20 B$ = "............"
30 BSAVE "block.dat", POINTER(A$), POINTER(A$) + 12
40 BLOAD "block.dat", POINTER(B$), 12
50 PRINT B$
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. 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
| Verb | Why not |
|---|---|
HEADER |
formats a disk. On a filesystem that would have to mean "delete everything here" |
COLLECT |
validates a disk's block allocation map. There is no map |
BACKUP |
duplicates one disk onto another. There are no disks |
BOOT |
loads and runs a boot sector. There is no boot sector |
DIRECTORY / CATALOG |
needs a directory-reading wrapper the standard library does not have yet. Filed upstream |
DCLEAR is the exception among the drive verbs: resetting a drive also closes its
channels, and closing the channels is real, so that is what it does.
Each refusal names itself and says why:
10 HEADER "DISK"
? 10 : RUNTIME ERROR HEADER formats a disk, and there is no disk drive here -- only a filesystem