commit e25b794c8b51b72531be053c2f73e2f7d47374df
parent f93d3f2de8178ba649e888096330c50cb1489180
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 21 Apr 2026 16:28:03 -0700
lisp.M1 step 10h: enable map/filter/fold prelude
Make skip_ws treat NUL as whitespace so adjacent M1 "..." chunks
(each carrying a trailing NUL from the literal) stitch together,
then split the prelude across 7 chunks to fit M0's quoted-literal
buffer. _start calls eval_source on [prelude_src, prelude_src_end)
before the user script. p1_gen gets SUB r2,r2,r1 to materialize
the prelude length at the call site. tests/lisp/16-prelude.scm
added to LISP_TESTS; 13/13 pass on aarch64, amd64, riscv64.
Diffstat:
4 files changed, 41 insertions(+), 28 deletions(-)
diff --git a/Makefile b/Makefile
@@ -197,8 +197,8 @@ LISP_TESTS := \
tests/lisp/13-vector.scm \
tests/lisp/14-io.scm \
tests/lisp/14-tagpred.scm \
- tests/lisp/15-pred.scm
-# tests/lisp/16-prelude.scm — re-enable once LISP.md step 10h unblocks
+ tests/lisp/15-pred.scm \
+ tests/lisp/16-prelude.scm
test-lisp: | $(IMAGE_STAMP)
@$(MAKE) --no-print-directory PROG=lisp ARCH=$(ARCH) build/$(ARCH)/lisp
diff --git a/docs/LISP.md b/docs/LISP.md
@@ -504,9 +504,10 @@ Status legend: `[x]` done · `[~]` in progress · `[ ]` not started.
~400 LOC.
4. [x] **Reader (core).** End-to-end `source → sexpr` for lists, decimal
fixnums, interned symbols, `;` comments, with line/col tracking for
- diagnostics. Extended syntax (quotes, strings, `#t`/`#f`, `#\char`,
- `#( … )`, improper `.` tail, hex/negative fixnums) and the
- source-location side table land in later steps. ~500 LOC.
+ diagnostics. Extended syntax (quotes, `#\char`, `#( … )`, improper
+ `.` tail, hex/negative fixnums) and the source-location side table
+ land in later steps. (`#t`/`#f` landed in 10c; string literals in
+ 10e.) ~500 LOC.
5. [x] **Printer.** `display`, `write`, minimal `format`. Closes a
read-print cycle. ~300 LOC.
6. [x] **Eval (non-tail).** Self-evaluators, lookup, `if`, `begin`,
@@ -520,7 +521,7 @@ Status legend: `[x]` done · `[~]` in progress · `[ ]` not started.
test-lisp` (single arch) and `make test-lisp-all` (tri-arch diff);
pass = exit 0 and expected stdout. Locks in regression coverage
before the feature surface grows.
-10. [~] **Primitives (~40).** Broken into sub-steps; ~1200 P1 LOC total
+10. [x] **Primitives (~40).** Broken into sub-steps; ~1200 P1 LOC total
(~200 harness + ~1000 primitives). Earlier ~500 estimate was
optimistic.
- [x] **10a. FFI harness.** `make_primitive` constructor (type 5
@@ -542,8 +543,6 @@ Status legend: `[x]` done · `[~]` in progress · `[ ]` not started.
length list? append reverse assoc member`.
- [x] **10e. String primitives.** `string? string-length string-ref
substring string-append string->symbol symbol->string`.
- Bodies land and pass isolated smoke tests on aarch64; full
- `12-string.scm` regression blocked on 10h.
- [x] **10f. Vector primitives.** `make-vector vector-ref
vector-set! vector-length vector->list list->vector`. Adds
`make_vector` runtime helper (absent before 10f). `make_vector`
@@ -554,22 +553,17 @@ Status legend: `[x]` done · `[~]` in progress · `[ ]` not started.
`format` dispatches `~a ~s ~d ~%`. `equal?` recurses on
pairs/strings/vectors; non-allocating. `apply` primitive
re-enters the internal `apply` label.
- - [~] **10h. Lisp prelude.** ~20 lines of Scheme defining `map`,
- `filter`, `fold` in terms of the primitives above. Embedded
- in BSS as a string; `_start` parses and evaluates it right
- after 10b finishes. **Blocker:** enabling the prelude
- `eval_source` call with any non-zero length breaks every
- subsequent script eval with `error: unbound symbol`, even for
- bare-literal scripts that reference no symbols. Suspects:
- (a) `eval_source` save/restore of `src_*` globals misbehaves
- on a second invocation, or (b) the M0 256-byte quoted-literal
- buffer chops the long `(define map …)` lines. Also: post-fix,
- re-run full 13-test `make ARCH=aarch64 test-lisp`, then port
- to amd64 + riscv64.
-11. [ ] **Reader extensions.** `'`/`` ` ``/`,`/`,@`, strings, `#\char`,
- `#( … )`, improper `.` tail, hex and negative fixnums. (`#t`/`#f`
- land early in 10c.) Source-location side table explicitly
- deferred to step 16.
+ - [x] **10h. Lisp prelude.** Scheme definitions of `map`, `filter`,
+ `fold` embedded as adjacent `"…"` chunks between `:prelude_src`
+ and `:prelude_src_end`; `_start` calls `eval_source` on the span
+ before the script eval. M1's `"…"` form appends a NUL to each
+ chunk, so `skip_ws` treats `\0` as whitespace — the chunks stitch
+ together transparently. Gate test `16-prelude.scm` passes on all
+ three arches.
+11. [ ] **Reader extensions.** `'`/`` ` ``/`,`/`,@`, `#\char`, `#( … )`,
+ improper `.` tail, hex and negative fixnums. (`#t`/`#f` landed in
+ 10c; string literals in 10e.) Source-location side table
+ explicitly deferred to step 16.
12. [ ] **Eval extensions.** `set!`, `let`/`let*`/`letrec`, `cond`,
`quasiquote`; inner `define` → `letrec`-shape rewrite.
13. [ ] **Mark-sweep GC.** Mark bitmap, root discipline, sweep, size-classed
diff --git a/src/lisp.M1 b/src/lisp.M1
@@ -203,7 +203,14 @@ DEFINE ZERO32 '0000000000000000000000000000000000000000000000000000000000000000'
li_br &_start_reg_prim_loop
b
:_start_reg_prim_done
- ## Prelude eval disabled pending investigation — see LISP.md step 10h.
+ ## Evaluate the embedded Lisp prelude (map/filter/fold) so user
+ ## scripts see those bindings.
+ li_r1 &prelude_src
+ li_r2 &prelude_src_end
+ sub_r2,r2,r1
+ li_br &eval_source
+ call
+
## Evaluate the script read from argv[1].
mov_r1,r6
mov_r2,r7
@@ -1014,6 +1021,13 @@ DEFINE ZERO32 '0000000000000000000000000000000000000000000000000000000000000000'
li_br &skip_ws_done
beq_r0,r1
+ ## NUL (0x00) — treated as whitespace so adjacent M1 "..." chunks
+ ## (which each emit a trailing NUL) concatenate cleanly in the
+ ## embedded prelude.
+ li_r1 %0
+ li_br &skip_ws_eat
+ beq_r0,r1
+
## ' ' (0x20)
li_r1 %32
li_br &skip_ws_eat
@@ -4464,9 +4478,13 @@ DEFINE ZERO32 '0000000000000000000000000000000000000000000000000000000000000000'
:str_prim_apply "apply"
:prelude_src
-"(define map (lambda (f xs) (if (null? xs) (quote ()) (cons (f (car xs)) (map f (cdr xs))))))"
-"(define filter (lambda (p xs) (if (null? xs) (quote ()) (if (p (car xs)) (cons (car xs) (filter p (cdr xs))) (filter p (cdr xs))))))"
-"(define fold (lambda (f acc xs) (if (null? xs) acc (fold f (f acc (car xs)) (cdr xs)))))"
+"(define map (lambda (f xs) (if (null? xs) (quote ()) "
+"(cons (f (car xs)) (map f (cdr xs))))))"
+"(define filter (lambda (p xs) (if (null? xs) (quote ()) "
+"(if (p (car xs)) (cons (car xs) (filter p (cdr xs))) "
+"(filter p (cdr xs))))))"
+"(define fold (lambda (f acc xs) (if (null? xs) acc "
+"(fold f (f acc (car xs)) (cdr xs)))))"
:prelude_src_end
diff --git a/src/p1_gen.py b/src/p1_gen.py
@@ -966,6 +966,7 @@ RRR_TABLE = (
('ADD','r7','r1','r2'),
('SUB','r2','r1','r6'),
('SUB','r3','r1','r6'),
+ ('SUB','r2','r2','r1'), # prelude length = end - start
('REM','r1','r1','r2'),
# bump-pointer + accumulator updates (originally kaem-minimal;
# retained in case lisp uses them — lint catches dead entries)