Files
akbasic/docs/09-files-and-disk.md
Tachikoma a15e172c2a Take libakerror 2.0.2, libakstdlib and libakgl 0.9.0
Bumps all three ak* submodules to their current main, applies what their
upgrade notes require, and retires the two workarounds they make obsolete.

libakerror 2.0.1 -> 2.0.2 (63 commits). Two of its fixes were this
repository's own filed issues, and both workarounds are gone:

  - It namespaces its embedded `coverage` target now, the same
    CMAKE_SOURCE_DIR test it already applied to `mutation` (its issue #15).
    The add_custom_target() shadow that renamed it on the way past could
    only ever fire on a name the dependency has stopped using, so it is
    deleted rather than left as dead code.
  - It installs akerrorConfigVersion.cmake at SameMajorVersion (its issue
    #16). MAINTENANCE.md said to add a `1.0` floor to our find_dependency
    calls when this landed; the floor is now `2.0`, and we have no
    find_dependency calls to add it to, so the paragraph says that instead
    of an instruction nobody can follow.

Its IGNORE context also changed shape: `__akerr_last_ignored` was an extern
pointer, and is now a per-translation-unit `static akerr_last_ignored` holding
a copy, so the pool slot can be released. Nothing here referenced the symbol,
but it costs us 1.35 MiB of thread-local storage -- 38 TUs x 37,296 bytes,
measured as the entire TLS segment of build/basic, where 2.0.1 produced no TLS
segment at all -- plus 84 -Wunused-variable warnings. Filed upstream as
libakerror issue #37 and recorded in MAINTENANCE.md rather than patched here,
because patching a submodule forks it.

libakstdlib gains directory and file-metadata wrappers with no version bump.
aksl_snprintf keeps its `int *count` -- an intermediate commit removed it and
the merge put it back -- but now reports the required length on truncation
rather than 0. Every call site here reads it only after a successful return,
so nothing moved.

The directory wrappers close the gap DIRECTORY was refused for (libakstdlib
issue #10). The verb is still unwritten, so it still refuses, but it no longer
blames a wrapper that exists: the message is "DIRECTORY is not implemented
yet" and tests/disk_verbs.c asserts both that it says so and that it does not
name libakstdlib. What writing it would need is akbasic issue #55.

libakgl moves to the current main at 0.9.0. It registers libccd and tg as
submodules, so a tree that only ran `git submodule update --init --recursive`
before the bump needs it again or the configure fails on a missing
libccd/src/ccd/config.h.cmake.in.

Verified: 114/114 default, 116/116 under -DAKBASIC_WITH_AKGL=ON, docs_examples
green in both. libakerror's UPGRADING.md documents a 2.0.3 that project()
never stamped, so the version tables read 2.0.2 -- libakerror issue #38.

Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
Co-Authored-By: Claude Code (Claude Opus 5, claude-opus-5[1m]) <noreply@anthropic.com>
2026-08-05 23:26:15 -04:00

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

DSAVE always writes line numbers, including ones you did not. A program loaded from a file that had none is given them (Chapter 2), and those are what gets written back. They come out one apart, so there is no room to insert between them: RENUMBER before DSAVE if you want the gaps.

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 not written yet. It was blocked on a directory-reading wrapper in the standard library; that landed, so only the verb is outstanding

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