From a15e172c2ada518275ab01f8bbdbbfec621988d0 Mon Sep 17 00:00:00 2001 From: Tachikoma Date: Wed, 5 Aug 2026 23:26:15 -0400 Subject: [PATCH] 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 Co-Authored-By: Claude Code (Claude Opus 5, claude-opus-5[1m]) --- CMakeLists.txt | 22 +++---- MAINTENANCE.md | 117 +++++++++++++++++++++++++++++--------- README.md | 5 +- TODO.md | 6 +- deps/libakerror | 2 +- deps/libakgl | 2 +- deps/libakstdlib | 2 +- docs/09-files-and-disk.md | 2 +- docs/11-verb-reference.md | 2 +- docs/13-differences.md | 3 +- include/akbasic/error.h | 9 ++- src/runtime_disk.c | 21 ++++--- tests/disk_verbs.c | 12 +++- 13 files changed, 140 insertions(+), 65 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 85f9079..c65261a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,12 +51,14 @@ option(AKBASIC_SANITIZE "Build with ASan + UBSan" OFF # through: the dependencies set target and directory properties that their own # builds depend on. # -# libakerror additionally namespaces its `mutation` target when embedded but not -# its `coverage` target (deps/libakerror/CMakeLists.txt:194 vs :172), so a -# coverage build collides on the `coverage` target and fails to configure at all. -# Rename the dependency's on the way past. Remove this once libakerror applies -# the same CMAKE_SOURCE_DIR test to `coverage` that it already applies to -# `mutation` -- filed as libakerror issue #15. +# All three dependencies now namespace both their `coverage` and their +# `mutation` targets when embedded, so there is no custom-target collision left +# to work around. libakerror was the last holdout -- it namespaced `mutation` +# but not `coverage`, and a coverage build collided on the bare name and failed +# to configure at all. 2.0.2 applies the same CMAKE_SOURCE_DIR test to both +# (deps/libakerror/CMakeLists.txt:429-434), closing libakerror issue #15, and +# the add_custom_target() shadow that renamed it on the way past is gone with +# this comment. # # **Only one project in a tree may shadow add_test(), and this is that project.** # CMake exposes an overridden command as `_name` and chains exactly one level: a @@ -86,14 +88,6 @@ function(set_property _scope) endif() endfunction() -function(add_custom_target _name) - if(AKBASIC_SUPPRESS_ADD_TEST AND _name STREQUAL "coverage") - _add_custom_target(akerror_coverage ${ARGN}) - else() - _add_custom_target(${ARGV}) - endif() -endfunction() - add_subdirectory(deps/libakerror EXCLUDE_FROM_ALL) add_subdirectory(deps/libakstdlib EXCLUDE_FROM_ALL) if(AKBASIC_WITH_AKGL) diff --git a/MAINTENANCE.md b/MAINTENANCE.md index dd55a0e..c93c6f6 100644 --- a/MAINTENANCE.md +++ b/MAINTENANCE.md @@ -68,12 +68,19 @@ the `akgl_*` or `aksl_*` entry point should look like, and what tests would cove decision, which is true of *changing* it and not of *reporting* it. Follow the prose-paragraph style of the entries already there. Growing the dependency to serve the interpreter is a wanted outcome, not a detour. -It works. Four gaps were filed this way — text measurement, immediate-mode drawing, audio, -and a non-blocking keystroke read — and all four landed upstream as `akgl_text_measure`, the -`akgl_draw_*` family, `akgl_audio_*` and `akgl_controller_poll_key`. `FILTER` is the one verb -still blocked on a gap, and `DIRECTORY` is refused pending an `opendir`/`readdir` wrapper in -`libakstdlib`. Both refuse at execution and say so, rather than being silently ignored: a -program that asks for a low-pass filter and gets an unfiltered square wave has been lied to. +It works. **Five** gaps were filed this way and all five landed upstream: text measurement, +immediate-mode drawing, audio and a non-blocking keystroke read became `akgl_text_measure`, +the `akgl_draw_*` family, `akgl_audio_*` and `akgl_controller_poll_key`; and the +directory-reading wrapper `DIRECTORY` was waiting on became `aksl_opendir`, `aksl_readdir`, +`aksl_closedir` and `aksl_rewinddir` — `libakstdlib` issue #10, in the revision this tree +pins. + +That leaves the two refusals in different positions, and the difference is worth keeping +straight. **`FILTER` is the one verb still blocked on a gap** — there is no filter stage in +`akgl_audio_*` to configure. **`DIRECTORY` is no longer blocked on anything**; it is simply +unwritten, and its refusal says so rather than naming a wrapper that now exists. Both refuse +at execution and say so, rather than being silently ignored: a program that asks for a +low-pass filter and gets an unfiltered square wave has been lied to. ### The Go reference @@ -156,10 +163,10 @@ guards every dependency with `if(NOT TARGET ...)`, so a top-level build must def `akerror::akerror` and `akstdlib::akstdlib` from `deps/libakerror` and `deps/libakstdlib` **before** `add_subdirectory(deps/libakgl)`, or the targets are declared twice. -That order is load-bearing for a second reason: `deps/libakerror` is at **2.0.1**, whose 2.0.0 +That order is load-bearing for a second reason: `deps/libakerror` is at **2.0.2**, whose 2.0.0 was a source and ABI break carrying an soname (`libakerror.so.2`). `libakstdlib` and `libakgl` must be compiled against that header, not a 1.x one, and an installed `libakerror.so.1` must -not be picked up. The break is quiet if you get it wrong: `__akerr_last_ignored` became +not be picked up. The break is quiet if you get it wrong: the context behind `IGNORE` became thread-local and `akerr_next_error()` now returns a context that already holds a reference, so a mixed build leaks pool slots or frees one twice rather than failing to link. @@ -167,10 +174,47 @@ a mixed build leaks pool slots or frees one twice rather than failing to link. | Submodule | Version | soname | ABI rule | Version API | |---|---|---|---|---| -| `deps/libakerror` | 2.0.1 | `libakerror.so.2` | major only | **none** — no version macro; `include/akbasic/error.h` feature-tests `AKERR_THREAD_SAFE` and `AKERR_EXIT_STATUS_UNREPRESENTABLE` instead | +| `deps/libakerror` | 2.0.2 | `libakerror.so.2` | major only | **none** — no version macro; `include/akbasic/error.h` feature-tests `AKERR_THREAD_SAFE` and `AKERR_EXIT_STATUS_UNREPRESENTABLE` instead | | `deps/libakstdlib` | 0.2.0 | `libakstdlib.so.0.2` | **`MAJOR.MINOR` while major is 0** | `AKSL_VERSION_*`, `aksl_version()`, `AKSL_VERSION_CHECK()` | | `deps/libakgl` | 0.9.0 | `libakgl.so.0.9` | **`MAJOR.MINOR` while major is 0** | `AKGL_VERSION*`, `akgl_version()`, `AKGL_VERSION_AT_LEAST()` | +**The `libakerror` row is what `project(akerror VERSION ...)` declares, and it disagrees with +that library's own release notes**, which carry a "Release 2.0.3" section describing CI-only +changes. The version the build stamps into the soname and into +`akerrorConfigVersion.cmake` is 2.0.2; nothing consumers can observe says 2.0.3. Read the +table as the ABI and the notes as the changelog, and do not "correct" this row to 2.0.3 +without the upstream `project()` bump to go with it. Reported as `libakerror` issue #38. + +#### What 2.0.2's `IGNORE` change costs, measured + +`IGNORE` now takes a *copy* of the context so the pool slot can be released, which fixes a +real leak. The copy lives in the public header as a file-scope `static`: + +```c norun +static AKERR_THREAD_LOCAL akerr_ErrorContext akerr_last_ignored; +``` + +`static` means one per translation unit, and `AKERR_THREAD_LOCAL` means each one is +thread-local. `sizeof(akerr_ErrorContext)` is **37,296 bytes**, so: + +| Build | TLS segment of `build/basic` | +|---|---| +| libakerror 2.0.1 | none at all | +| libakerror 2.0.2 | 1,417,248 bytes — **1.35 MiB**, per thread | + +38 copies × 37,296 accounts for the segment exactly; `readelf -sW build/basic` shows all 38. +It scales with translation-unit count, not with anything about the program, and every thread +a host game spawns pays it again — which is worth knowing in a tree that spent a release +taking `akbasic_Runtime` from 10.75 MiB to 2.40 MiB. + +It also emits **84 `-Wunused-variable` warnings**, one per TU that includes `akerror.h` +without using `IGNORE`. Nothing here builds with `-Werror`, so it is noise rather than a +failure — but a consumer that does cannot include the header. + +**Both are `libakerror` issue #37 and neither is worked around here**, per the rule above: a +one-line `extern` in the header and a definition in `src/error.c` fixes it upstream, and +patching our copy would fork the submodule. Delete this subsection when it lands. + For both 0.x libraries the soname carries `MAJOR.MINOR` deliberately: 0.1 and 0.2 are *different* ABIs, and both become major-only at 1.0. Do not read `0.1 → 0.2` as a compatible bump — both libraries have actually made that jump, so anything built against the 0.1 headers @@ -196,16 +240,32 @@ precedes the build tree on the include path and a stray copy there would shadow one and pin every consumer. It publishes `AKGL_VERSION_AT_LEAST(major, minor, patch)` — the compile-time test `libakstdlib` could not write against `libakerror` — and `akgl_version()`. -**Version-pinning in `find_package` is asymmetric, and that is deliberate.** +**Version-pinning in `find_package` used to be asymmetric. It no longer is.** `find_package(akstdlib 0.1)` and `find_package(akgl 0.1)` both work; each ships a -`ConfigVersion.cmake` at `SameMinorVersion`, mirroring its soname. `find_package(akerror 1.0)` -**fails against a correct install**, because `libakerror` ships `akerrorConfig.cmake` and -`akerrorTargets.cmake` but no `akerrorConfigVersion.cmake`. Ask for `akerror` unversioned. Its -floor is enforced instead by an `#error` feature-testing `AKERR_FIRST_CONSUMER_STATUS`, which -`akstdlib.h`, `akgl/error.h` and our own `include/akbasic/error.h` all carry — include any of -them and you inherit the guard. The missing version file is filed in -`libakstdlib` issue #5 and `libakerror` issue #16; when it lands, add the `1.0` floor to the `find_dependency` -calls. +`ConfigVersion.cmake` at `SameMinorVersion`, mirroring its soname. `libakerror` shipped +`akerrorConfig.cmake` and `akerrorTargets.cmake` but no `akerrorConfigVersion.cmake`, so any +versioned request failed against a correct install and the advice here was to ask for +`akerror` unversioned. That was `libakerror` issue #16 — closed — and `libakstdlib` issue #5, +which tracks the same fix from the other side and is still open only because nobody has shut +it. It has landed: `libakerror` now writes `akerrorConfigVersion.cmake` at +**`SameMajorVersion`**, matching the soname's major-only rule, rather than the +`SameMinorVersion` the other two use to match theirs. + +Two things follow, and the second is the one that bites: + +- **A versioned request now works** — but the floor to ask for is **`2.0`**, not the `1.0` + this file used to say. `find_package(akerror 1.0)` fails *harder* than before: it is a + request for major 1 against a major-2 install, which `SameMajorVersion` correctly rejects. +- **There is nothing in this repository to change.** `akbasic` reaches all three dependencies + by `add_subdirectory`, not `find_package`, and ships no CMake package config of its own — + so it has no `find_dependency` calls to add a floor to. The instruction that used to live + here was written for a consumer this project never became. It matters to anyone *installing* + these libraries and linking `akbasic` against the installed copies, which is why it is + recorded rather than deleted. + +The compile-time floor is unchanged and still the real guard: an `#error` feature-testing +`AKERR_FIRST_CONSUMER_STATUS`, which `akstdlib.h`, `akgl/error.h` and our own +`include/akbasic/error.h` all carry — include any of them and you inherit it. ### Embedding all three dependencies collides four ways @@ -250,14 +310,19 @@ CTest names. `libakstdlib` still uses bare `test_` targets. **Name every t this repo `akbasic_test_`** — it costs nothing and it is the collision that actually stopped a build. -**3. Duplicate custom targets.** `libakerror` namespaces its `mutation` target when embedded -but **not** its `coverage` target, so any coverage-enabled top-level build fails with *"another -target with the same name already exists"*. We shadow `add_custom_target` and rename that one -to `akerror_coverage` on the way past. `libakstdlib` (both targets) and `libakgl` (its -`mutation` target) namespace themselves correctly. **The real fix is upstream in -`libakerror`** — the same `CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR` test it already -applies to `mutation` — and it is filed as `libakerror` issue #15. Delete the -workaround when it lands. +**3. Duplicate custom targets — fixed upstream, and the workaround is gone.** `libakerror` +used to namespace its `mutation` target when embedded but **not** its `coverage` target, so +any coverage-enabled top-level build failed with *"another target with the same name already +exists"*. This project shadowed `add_custom_target` and renamed that one to +`akerror_coverage` on the way past, and recorded the real fix as `libakerror` issue #15: the +same `CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR` test it already applied to +`mutation`. + +That landed in 2.0.2. `libakerror` now picks `akerror_coverage` itself when embedded, so the +shadow was dead code that could only ever fire on a name the dependency had stopped using — +and it is deleted. All three dependencies namespace both targets correctly now, so **there is +no custom-target collision left**; only collisions 1, 2 and 4 below are live. The heading says +four because four is what a reader coming from the issue tracker will be looking for. **4. Stale build trees poison the coverage report.** See below; it is the reason for `cmake -S . -B build`. diff --git a/README.md b/README.md index d571cfc..5ab5fc8 100644 --- a/README.md +++ b/README.md @@ -138,12 +138,13 @@ API documentation builds with `doxygen Doxyfile`, into `build/docs/html`. Everything is a submodule; `git submodule update --init --recursive` gets all of it. There is nothing to install first. -* [libakerror](https://source.starfort.tech/andrew/libakerror) 2.0.1 — TRY/CATCH-style error +* [libakerror](https://source.starfort.tech/andrew/libakerror) 2.0.2 — TRY/CATCH-style error contexts. Every function that can fail returns one. 2.0.0 made it thread safe and broke the ABI; anything built against a 1.x header must be rebuilt rather than relinked. * [libakstdlib](https://source.starfort.tech/andrew/libakstdlib) 0.2.0 — libc wrappers that report through `libakerror`. String-to-number conversion goes straight to it, which is why - `VAL("garbage")` is an error rather than a silent `0`. + `VAL("garbage")` is an error rather than a silent `0`. Its public header now also pulls in + `` and `` for the directory and file-metadata wrappers. * [libakgl](https://source.starfort.tech/andrew/libakgl) 0.9.0 — **optional**, only for `-DAKBASIC_WITH_AKGL=ON`. Pulls in SDL3. Its soname carries `MAJOR.MINOR` while the major is 0, so rebuild rather than relink. diff --git a/TODO.md b/TODO.md index 8b2a855..fb96df9 100644 --- a/TODO.md +++ b/TODO.md @@ -2339,9 +2339,9 @@ Dependency baseline: | Submodule | Version | Notes | |---|---|---| -| `deps/libakerror` | 2.0.1 | Private ownership-enforced status registry. akbasic reserves 512–767 in `akbasic_error_register()`. Does not namespace its `coverage` target when embedded — worked around in our `CMakeLists.txt`. **2.0.0 is thread safe and an ABI break** (`libakerror.so.2`): `__akerr_last_ignored` is thread-local and `akerr_next_error()` returns an owned reference, neither of which fails to link when mismatched. **2.0.1 fixes an exit status that mattered more to this band than to any other** — see below. | -| `deps/libakstdlib` | 0.2.0 | soname `libakstdlib.so.0.2`. `AKSL_VERSION_CHECK()` asserted in `tests/version_check.c`. This release fixed all six confirmed defects the port was working around — see §1.9, where the bans are now lifted. | -| `deps/libakgl` | 0.7.0 | soname `libakgl.so.0.7`. Owns status codes 256–260. Linked and tested under `-DAKBASIC_WITH_AKGL=ON`, which still defaults OFF so the core library and its whole suite build on a machine with no SDL. **0.3.0 closed every API gap this port had filed** — see §7 — so the four workarounds §3 used to list are gone. 0.4.0 was a leak-and-overread release that changed no public struct. **0.5.0 is the first that broke our source as well as our ABI**: it namespaced every exported symbol, so `akgl_render_bind2d` is `akgl_render_2d_bind`, `akgl_sprite_sheet_coords_for_frame` is `akgl_spritesheet_coords_for_frame`, the `renderer`/`camera`/`window` globals carry the prefix, and `_akgl_renderer`/`_akgl_camera` are `akgl_default_renderer`/`akgl_default_camera`. `include/akbasic/akgl.h` asserts the floor. **0.6.0 and 0.7.0 broke nothing here** — 0.6.0 is three arcade-physics fixes and a `physics.max_timestep` property this port does not use, and 0.7.0 reports failures `libakstdlib`'s wrappers were already catching and takes `libakerror` 2.0.1. The floor moved anyway, because deciding for ourselves which of libakgl's minor releases were really compatible is the judgement the soname exists to take away. | +| `deps/libakerror` | 2.0.2 | Private ownership-enforced status registry. akbasic reserves 512–767 in `akbasic_error_register()`. **2.0.0 is thread safe and an ABI break** (`libakerror.so.2`): the `IGNORE` context is thread-local and `akerr_next_error()` returns an owned reference, neither of which fails to link when mismatched. **2.0.1 fixes an exit status that mattered more to this band than to any other** — see below. **2.0.2** namespaces its embedded `coverage` target (issue #15 — our `add_custom_target` shadow is deleted), installs `akerrorConfigVersion.cmake` at `SameMajorVersion` (issue #16), fixes the `AKERR_USE_STDLIB=OFF` build, retires `PATH_MAX` for `AKERR_MAX_ERROR_FNAME_LENGTH`, and turns the `IGNORE` slot into a released copy — `__akerr_last_ignored` was an `extern` pointer and is now a per-TU `static akerr_last_ignored`. **That last one costs us 1.35 MiB of thread-local storage**: 38 translation units x 37,296 bytes, measured as the whole TLS segment of `build/basic`, where 2.0.1 produced no TLS segment at all. Filed upstream as libakerror issue #37, along with the 84 `-Wunused-variable` warnings it emits. Its `UPGRADING.md` describes a 2.0.3 that `project()` never stamped — libakerror issue #38, which is why the version above reads 2.0.2. | +| `deps/libakstdlib` | 0.2.0 | soname `libakstdlib.so.0.2`. `AKSL_VERSION_CHECK()` asserted in `tests/version_check.c`. This release fixed all six confirmed defects the port was working around — see §1.9, where the bans are now lifted. Since then, and with no version bump: **directory wrappers landed** (`aksl_opendir`/`readdir`/`closedir`/`rewinddir`, issue #10) — the gap `DIRECTORY` was refused for, so that refusal now says only that the verb is unwritten; file-metadata wrappers landed; and `aksl_snprintf` keeps its `int *count` but now reports the *required* length on truncation rather than 0. Only read on success here, so nothing moved. | +| `deps/libakgl` | 0.9.0 | soname `libakgl.so.0.9`. Owns status codes 256–262. Linked and tested under `-DAKBASIC_WITH_AKGL=ON`, which still defaults OFF so the core library and its whole suite build on a machine with no SDL. **0.3.0 closed every API gap this port had filed** — see §7 — so the four workarounds §3 used to list are gone. 0.4.0 was a leak-and-overread release that changed no public struct. **0.5.0 is the first that broke our source as well as our ABI**: it namespaced every exported symbol, so `akgl_render_bind2d` is `akgl_render_2d_bind`, `akgl_sprite_sheet_coords_for_frame` is `akgl_spritesheet_coords_for_frame`, the `renderer`/`camera`/`window` globals carry the prefix, and `_akgl_renderer`/`_akgl_camera` are `akgl_default_renderer`/`akgl_default_camera`. `include/akbasic/akgl.h` asserts the floor. **0.6.0 and 0.7.0 broke nothing here** — 0.6.0 is three arcade-physics fixes and a `physics.max_timestep` property this port does not use, and 0.7.0 reports failures `libakstdlib`'s wrappers were already catching and takes `libakerror` 2.0.1. **0.8.0 and 0.9.0 broke nothing here either** — 0.8.0 is the collision subsystem (`AKGL_ERR_COLLISION`, code 261, and the vendored `libccd` and `tg` submodules that come with it), and 0.9.0 is the `akgl_ui` subsystem (`AKGL_ERR_UI`, code 262, and vendored `clay`), which is what `src/ui_akgl.c` draws through. Both widen libakgl's reserved band from five codes to seven; the range map in `MAINTENANCE.md` carries it. The floor moved anyway, because deciding for ourselves which of libakgl's minor releases were really compatible is the judgement the soname exists to take away. **Note `deps/libakgl/deps/` pins its own `libakerror` and `libakstdlib` older than ours**; the top-level build declares both targets first and libakgl's `if(NOT TARGET ...)` guards mean its copies are never configured, so what libakgl actually compiles against is what this tree pins. | **An unhandled error in this band used to exit zero, and 512 is the worst possible base for that.** `libakerror`'s default unhandled-error handler ended in `exit(errctx->status)`, and a diff --git a/deps/libakerror b/deps/libakerror index 5eaa956..ee38eed 160000 --- a/deps/libakerror +++ b/deps/libakerror @@ -1 +1 @@ -Subproject commit 5eaa956f504611a08204dcaf288e1ad1a571ee21 +Subproject commit ee38eed31c0c0733233676a289d9c6dd9a19aa9d diff --git a/deps/libakgl b/deps/libakgl index bbb7b8f..77091ed 160000 --- a/deps/libakgl +++ b/deps/libakgl @@ -1 +1 @@ -Subproject commit bbb7b8f494d7784f88e33280f2ea740e3608204f +Subproject commit 77091ed177cf322c09e1116edaf4b1e3bbad5a57 diff --git a/deps/libakstdlib b/deps/libakstdlib index 2b79aca..821200e 160000 --- a/deps/libakstdlib +++ b/deps/libakstdlib @@ -1 +1 @@ -Subproject commit 2b79aca103d46f4ea4165172f5dc7f7c8dbaba75 +Subproject commit 821200e618153432ae1bae8a20a7cd9e2b66650d diff --git a/docs/09-files-and-disk.md b/docs/09-files-and-disk.md index c16e9e5..f3dba3b 100644 --- a/docs/09-files-and-disk.md +++ b/docs/09-files-and-disk.md @@ -126,7 +126,7 @@ exists, and reading into one you never sized writes over something else. | `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 | +| `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. diff --git a/docs/11-verb-reference.md b/docs/11-verb-reference.md index 3e610ad..084d91d 100644 --- a/docs/11-verb-reference.md +++ b/docs/11-verb-reference.md @@ -35,7 +35,7 @@ for the reasoning in each case. | `DIALOG` | `DIALOG ["text"]` | Show a text panel across the bottom of the screen. No argument takes it down. See Chapter 19. | | `DIM` | `DIM A#(n [,...])` | Make an array. Subscripts start at zero; `n` is the count. | | `DIM` … `AS` | `DIM S@ AS T`, `DIM P@ AS PTR TO T` | Make a structure, or a strict pointer to one. See Chapter 16. | -| `DIRECTORY` | `DIRECTORY` | **Refused.** Needs a directory-reading wrapper that does not exist yet. | +| `DIRECTORY` | `DIRECTORY` | **Refused.** Not written yet; the standard-library wrapper it waited on has landed. | | `DLOAD` | `DLOAD "name"` | Load a program from a file. | | `DO` | `DO [WHILE c | UNTIL c]` | Start a loop. The condition may be here, on the `LOOP`, or neither. | | `DOPEN` | `DOPEN n, "name" [,W]` | Open a file on channel `n`. `W` opens it for writing. | diff --git a/docs/13-differences.md b/docs/13-differences.md index 4765c6b..33e58c6 100644 --- a/docs/13-differences.md +++ b/docs/13-differences.md @@ -199,7 +199,8 @@ interpreter's error code, which bears no relation to a Commodore error number. P - **`BLOAD` requires a length.** - **`HEADER`, `COLLECT`, `BACKUP` and `BOOT` are refused.** They operate on a physical disk. -- **`DIRECTORY` is refused** pending a wrapper in the standard library. +- **`DIRECTORY` is refused** because it is not written yet. The standard-library + wrapper it was waiting on has landed, so the remaining work is the verb. ## Machine diff --git a/include/akbasic/error.h b/include/akbasic/error.h index 2dc42d0..e181e08 100644 --- a/include/akbasic/error.h +++ b/include/akbasic/error.h @@ -12,9 +12,12 @@ * libakerror 2.0.0 is the floor, raised from 1.0.0 because 2.0.0 is an ABI break * that a compile against the wrong header cannot survive quietly: * - * - `__akerr_last_ignored` became thread-local. `IGNORE` expands at *our* call - * site, so our objects reference that symbol under whichever storage model - * the header on the include path declared. + * - The context behind `IGNORE` became thread-local. `IGNORE` expands at *our* + * call site, so our objects reference that storage under whichever model the + * header on the include path declared. 2.0.2 went further and made it a + * per-translation-unit `static` snapshot named `akerr_last_ignored`, copied + * from the pool slot so the slot can be released; the old spelling + * `__akerr_last_ignored` was an `extern` pointer and no longer exists. * - `akerr_next_error()` now returns a context that already holds a reference, * and `ENSURE_ERROR_READY` no longer increments. Objects compiled against a * 1.x header count every reference twice and never give a slot back. diff --git a/src/runtime_disk.c b/src/runtime_disk.c index 2abea6e..33565a1 100644 --- a/src/runtime_disk.c +++ b/src/runtime_disk.c @@ -407,17 +407,22 @@ akerr_ErrorContext *akbasic_cmd_directory(akbasic_Runtime *obj, akbasic_ASTLeaf FAIL_ZERO_RETURN(errctx, (obj != NULL && dest != NULL), AKERR_NULLPOINTER, "NULL argument in DIRECTORY"); /* - * Refused rather than half-built. Listing a directory needs opendir/readdir, - * which `libakstdlib` does not wrap -- and this project's rule is that a - * missing capability gets filed upstream rather than worked around here - * (MAINTENANCE.md). Filed as libakstdlib issue #10. + * Refused rather than half-built. This was blocked upstream: listing a + * directory needs opendir/readdir, `libakstdlib` did not wrap them, and + * this project's rule is that a missing capability gets filed upstream + * rather than worked around here (MAINTENANCE.md). That was libakstdlib + * issue #10, and it landed -- aksl_opendir, aksl_readdir, aksl_closedir + * and aksl_rewinddir all exist as of the revision this tree pins. * - * The alternative was shelling out to `ls`, which a library has no business - * doing, or calling readdir directly and stepping outside the error - * convention every other call in this file follows. + * So the blocker is gone and only the work is left. Writing the verb needs + * decisions this commit is not the place for: what a listing looks like on + * a filesystem with no disk-image block counts, which of the Commodore + * wildcard forms to honour, and where the entries go. Tracked as akbasic + * issue #55; the refusal stays honest until then rather than growing a + * half-listing nobody specified. */ FAIL_RETURN(errctx, AKBASIC_ERR_DEVICE, - "DIRECTORY is not implemented: libakstdlib has no directory-reading wrapper yet"); + "DIRECTORY is not implemented yet"); } /* ------------------------------------------------------------ BSAVE/BLOAD -- */ diff --git a/tests/disk_verbs.c b/tests/disk_verbs.c index f8e3ac7..e74535a 100644 --- a/tests/disk_verbs.c +++ b/tests/disk_verbs.c @@ -244,10 +244,16 @@ static void test_no_drive_verbs(void) harness_stop(); } - /* DIRECTORY is refused for a different reason, and says which. */ + /* + * DIRECTORY is refused for a different reason than the five above: not for + * want of a drive, but because it is unwritten. It used to name the missing + * libakstdlib wrapper; that wrapper landed, so naming it would be a lie. + */ TEST_REQUIRE_OK(run_program("10 DIRECTORY\n")); - TEST_REQUIRE(strstr(HARNESS_OUTPUT, "libakstdlib") != NULL, - "DIRECTORY should name the missing wrapper, got \"%s\"", HARNESS_OUTPUT); + TEST_REQUIRE(strstr(HARNESS_OUTPUT, "not implemented") != NULL, + "DIRECTORY should say it is unwritten, got \"%s\"", HARNESS_OUTPUT); + TEST_REQUIRE(strstr(HARNESS_OUTPUT, "libakstdlib") == NULL, + "DIRECTORY must not still blame libakstdlib, got \"%s\"", HARNESS_OUTPUT); harness_stop(); }