commit ca311eebbda0e929da9daacc946b20770cb90489
parent 5572d18365e4e8b17a2059a5b90feb81cc42ee27
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Sat, 25 Apr 2026 16:26:31 -0700
docs: drop scheme-shell-todo audit (gaps closed)
The audit served its purpose: spawn-via-run bug fixed, set!/cond=>/length
gaps closed in scheme1, and the test caveats it tracked are addressed
by the recent test reorg.
Diffstat:
1 file changed, 0 insertions(+), 214 deletions(-)
diff --git a/docs/scheme-shell-todo.md b/docs/scheme-shell-todo.md
@@ -1,214 +0,0 @@
-# scheme1 → shell.scm TODO
-
-Checklist for getting `lisp/shell.scm` running under scheme1.
-
-**Workflow:** every item is red-green TDD. Add a failing
-`tests/scheme1/NN-*.scm` (with `.expected-exit` and/or `.expected`) first,
-run the suite to confirm it fails for the expected reason, then
-implement until green. Multi-arch suite must stay clean before moving on.
-
-```
-# Run 1+ specific tests
-scripts/run-tests.sh --suite scheme1 --arch aarch64 70-type-predicates
-```
-
-# Audit: deviations and known issues
-
-Everything below is a real bug, hack, or spec
-gap that must be addressed before calling scheme1 shippable.
-
-## Open bugs
-
-- [ ] **Prelude `spawn` reached through `run` errors with "unbound variable"
- in the parent.** `(run prog)` from user code fails even though
- `(spawn prog)` inline at user level with the identical body works.
- Root cause not identified (`apply_build_args` walking the variadic
- list, closure env capture, or env extension with the dotted-tail
- param `args` are all suspects). See test
- `tests/scheme1/45-shell-spawn.scm` — it works around the bug by
- redefining `spawn` at user level. Until this is understood, the
- prelude's `spawn` and `run` are effectively unverified.
-
-## Spec features still missing
-
-Per LISP.md and LISP-C.md, but not implemented:
-
-- [ ] **Special forms missing**: `set!`, `pmatch`, `cond`'s
- `=>` arrow form.
-
-### Records: target R7RS-small semantics
-
-Decision: track R7RS-small `define-record-type` exactly. The form
-introduces a disjoint type and binds **only** these identifiers at
-runtime:
-
-1. The constructor name (e.g. `make-point`) — a procedure.
-2. The predicate name (e.g. `point?`) — a procedure.
-3. One accessor per field (e.g. `point-x`) — a procedure.
-4. One mutator per `(field acc mut)` clause (e.g. `set-point-y!`) — a
- procedure.
-
-Explicitly **not** bound: the type name itself (`point` after
-`(define-record-type point …)`). It is purely syntactic; the type
-descriptor is not a user-visible value. There is no `record-type-of`,
-no way to grab the TD from Scheme code, no `<point-td>`-style
-generated binding. R7RS does not provide reflective access; we don't
-add one.
-
-Also **not** in the user-facing API:
-
-- `record?`, `record-is-a?`, `record-type?` (LISP-C.md currently lists
- these — drop from the spec).
-- The six `%record-*` primitives (`%make-record-td`, `%make-record`,
- `%record-ref`, `%record-set!`, `%record-is-a?`, `%record-td`).
- Internal only; they must not appear in `prim_table`.
-
-Concrete cleanup tasks:
-
-- [ ] Remove `%make-record-td` / `%make-record` / `%record-ref` /
- `%record-set!` / `%record-is-a?` / `%record-td` rows from
- `prim_table` in `scheme1/scheme1.P1pp`. The parameterized record
- PRIMs (`prim_ctor_entry`, `prim_pred_entry`, `prim_field_ref_entry`,
- `prim_field_set_entry`) reach the entry labels directly via the
- closure data slot, so removing the public bindings is local.
- Tests `38-record-internal-prims.scm` (rewritten to assert unbound)
- and `39-record-typename-unbound.scm` are the red-green guards.
-- [ ] Update `docs/LISP-C.md`:
- - In the `define-record-type` desugaring example (currently lines
- 643+), drop the generated `(define <point-td> …)` line. Replace
- the `%record-is-a?` / `%record-ref` / `%make-record` references
- in the desugaring body with the parameterized-PRIM mechanism, or
- flag them as illustrative pseudocode. The user-visible result is
- just the four kinds of bindings listed above.
- - Drop `record?`, `record-is-a?`, `record-type?` from the
- Predicates / equality public-primitive list (line 660–662). Adjust
- the "13" predicate count to 10.
- - Move the `%record-*` count from "55 user-facing primitives plus 6
- internal record ops" to keep the 6 internal but ensure no
- duplication or claim of public exposure.
-
-Note: `equal?` on records (test 59) keeps working — it walks the
-internal layout via the runtime, not via user-callable primitives.
-
-## Hacks and fragile invariants
-
-These work today but are easy to break.
-
-- [ ] **Bytevector NUL-termination via headroom.** `bv_capacity_for`
- returns the smallest power of two strictly greater than `n`. The
- byte at index `length` is the zero-init NUL terminator and we hand
- the raw `data_ptr` directly to syscalls expecting C strings
- (`sys-openat`, `sys-execve`, the per-arg pointers in
- `build_execve_argv`). If user code calls `bytevector-u8-set!` past
- `length`, that NUL is gone and the next syscall reads garbage.
- Capacity is never reset by `bytevector-copy!` or any other op, so
- the invariant only protects fresh / never-overwritten bytevectors.
-
-- [ ] **`%record-*` primitives are exposed publicly** — see the
- Records: target R7RS-small semantics section above for the full
- fix. Tracked there to keep desugaring + spec + tests + table
- changes together.
-
-## Test suite caveats
-
-Issues *in* the test files themselves that need fixing or revisiting
-before the suite can be considered authoritative.
-
-- [ ] **`tests/scheme1/15-dot-symbol.scm`** — defines `.foo` (a leading
- `.` identifier). LISP.md says "a lone `.` is **not** a symbol — it's
- reserved for dotted-pair syntax", but the spec is silent on whether
- `.foo` is admissible. Behavior depends on whether the byte after `.`
- is whitespace/paren (handled by `parse_list`'s peek). Useful as a
- regression test for the dotted-tail detector but not necessarily
- desired surface syntax.
-
-- [ ] **`tests/scheme1/19-letstar.scm`** — comment claims "outer x;
- let*'s x must shadow inside the body" but the test only checks the
- inner shadow path. Nothing exercises that the outer `x` is *not*
- affected after the `let*` body returns.
-
-- [ ] **`tests/scheme1/20-letrec.scm`** — uses `(if n n (f #t))` to test
- letrec self-reference. Recurses *once* (n=44 → truthy → returns 44)
- so it doesn't actually trigger the recursive case. The comment
- acknowledges the workaround ("Without numeric primitives we
- terminate by passing #t at the recursive call"). Needs a real
- recursion test now that the let family + arith primitives are
- available; `21-letrec-recursion.scm` partially fills this.
-
-- [ ] **`tests/scheme1/22-named-let.scm`** — recursion is bounded by a
- flag (`first`) flipping from `#t` to `#f`. Deep iteration not
- exercised.
-
-- [ ] **`tests/scheme1/27-apply.scm`** — only tests 2-arg
- `(apply f arglist)` and 3-arg `(apply f x arglist)`. `(apply f)` is
- unspecified; `(apply f a b … last)` for N>3 is unverified.
-
-- [ ] **`tests/scheme1/40-sys-argv.scm`** — hard-codes `expected-exit =
- 2`, the count of argv entries the runner happens to pass
- (`./binary tests/scheme1/40-sys-argv.scm`). Any change to
- `scripts/run-tests.sh`'s invocation or a wrapper that injects extra
- args breaks this test silently.
-
-- [ ] **`tests/scheme1/41-fileio.scm`** — opens itself by reading
- `(car (cdr (sys-argv)))` and passing the bytevector as a path.
- Relies on the `bv_capacity_for` headroom invariant for NUL
- termination (no explicit `chars->bv`). Doesn't exercise the
- `(#f . errno)` branch of `sys-openat` (e.g., a non-existent path).
- Hard-codes `O_RDONLY = 0` and `mode = 0` instead of using named
- constants.
-
-- [ ] **`tests/scheme1/42-clone-wait.scm`** — bypasses `sys-wait` /
- `decode-wait-status` entirely; reads `siginfo_t.si_status` (offset
- 24) directly from the buffer with `bytevector-u8-ref`. Encodes
- Linux-x86_64-and-aarch64 siginfo layout; non-portable to other
- Linux ABIs and to any non-Linux target.
-
-- [ ] **`tests/scheme1/43-prelude.scm`** — verifies `for-each` only by
- running `(for-each (lambda (x) x) ys)` and checking it doesn't
- error; doesn't check that `for-each` actually invokes the lambda
- for each element (no side-effect verification).
-
-- [ ] **`tests/scheme1/44-shell-run.scm`** — name is misleading. It
- tests `sys-wait` + `decode-wait-status` against a `sys-clone` child
- but never calls `run`. `run` is what test 45 was supposed to cover,
- and it doesn't because of the spawn-via-run bug.
-
-- [ ] **`tests/scheme1/45-shell-spawn.scm`** — works around the prelude
- spawn bug by redefining `spawn` at user level. The prelude's
- `spawn` / `run` are therefore covered by zero passing tests.
-
-- [ ] **`tests/scheme1/38-record-internal-prims.scm`** — rewritten to
- assert that `%make-record-td` is **unbound** at user level, matching
- the R7RS target (see the records spec section above). Currently red:
- the prim is still in `prim_table`. Goes green once the table cleanup
- lands.
-
-- [ ] **`tests/scheme1/39-record-typename-unbound.scm`** — asserts
- `(define-record-type point …)` does not bind `point` to anything at
- runtime; bare reference is unbound. Currently green (matches
- existing impl) and protects against regressions that would add a
- hidden TD binding.
-
-- [ ] **No test verifies `(set-car! …)` / `(set-cdr! …)`** — the
- primitives don't exist; spec requires them.
-
-- [ ] **No test verifies that mutating a literal pair (`'(1 2 3)`) is
- UB** — undefined behavior is policy, but the policy isn't pinned
- down by a test.
-
-- [ ] **No test verifies tail-call correctness on deep recursion** —
- named let, `letrec`, and the eval/apply tail positions all rely on
- `%tail`/`%tailr`, but nothing recurses thousands of times to confirm
- no host-stack growth.
-
-- [ ] **No `(define x …)` followed by `(set! x …)` test** because
- `set!` doesn't exist.
-
-- [ ] **No quoted-pair test (`'(1 . 2)`)** — only quoted lists are
- tested. The reader handles dotted pairs but no test pins this.
-
-- [ ] **`tests/scheme1/16-cond.scm`** — verifies short-circuit in the
- positive direction (later truthy clauses don't fire). Doesn't
- verify that a `(cond)` with no matching clause and no `else` returns
- UNSPEC (or whatever the policy is — currently it does, but
- unspecified by spec).