boot2

Playing with the boostrap
git clone https://git.ryansepassi.com/git/boot2.git
Log | Files | Refs | README

commit 4b16290a81d50db583b30d19f710fb515bc51465
parent d1669e299cb2cdd93ca6cb11bc70b22e29b9c915
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat, 25 Apr 2026 09:49:38 -0700

Add scheme1 embedded prelude

p1_main now calls eval_prelude before load_source. eval_prelude memcpy's
the embedded prelude bytes into readbuf and runs the standard read-eval
loop. The prelude defines list, length, reverse, append, list-ref, map,
for-each in plain Scheme.

Two correctness fixes were required to make this work:

1. intern now copies each new name into a stable heap buffer (via
   alloc_bytes + memcpy) before storing it in the symtab entry.
   parse_atom passes pointers into readbuf_buf; load_source overwrites
   readbuf when it slurps the user file, so symtab entries created from
   the prelude would otherwise have stale name_ptr's and be unreachable
   on subsequent intern lookups.

2. load_source resets readbuf_pos to 0 in addition to writing
   readbuf_len. Without that the user-file read loop continues from
   wherever the prelude left off in the buffer.

skip_ws now treats NUL as whitespace because M0 auto-NUL-terminates
each "..." literal in the prelude block; that yields one stray NUL
between consecutive lines that the parser would otherwise treat as
junk.

Diffstat:
Mscheme1/scheme1.P1pp | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Atests/scheme1/43-prelude.expected-exit | 1+
Atests/scheme1/43-prelude.scm | 13+++++++++++++
3 files changed, 96 insertions(+), 4 deletions(-)

diff --git a/scheme1/scheme1.P1pp b/scheme1/scheme1.P1pp @@ -220,6 +220,9 @@ %call(&intern_special_forms) %call(&register_primitives) + # Evaluate the embedded prelude before the user source. + %call(&eval_prelude) + # argv[1] is the source path (NUL-terminated cstr from the kernel). %ld(a1, sp, 0) %ld(a0, a1, 8) @@ -263,12 +266,49 @@ %la(t0, &readbuf_len) %st(a0, t0, 0) %li(a0, 0) + %la(t0, &readbuf_pos) + %st(a0, t0, 0) %eret ::fail %die(msg_load_fail, 3) }) +# eval_prelude -- copy the embedded prelude bytes into readbuf, run the +# read-eval loop until the prelude is exhausted, then return so +# load_source can later refill readbuf with the user file. +%fn(eval_prelude, 0, { + %la(a1, &prelude_src) + %la(a2, &prelude_src_end) + %sub(a2, a2, a1) + %la(t0, &readbuf_buf_ptr) + %ld(a0, t0, 0) + %call(&memcpy) + + %la(t0, &prelude_src) + %la(t1, &prelude_src_end) + %sub(t0, t1, t0) + %la(t1, &readbuf_len) + %st(t0, t1, 0) + %li(t0, 0) + %la(t1, &readbuf_pos) + %st(t0, t1, 0) + + ::loop + %call(&skip_ws) + %la(t0, &readbuf_pos) + %ld(t0, t0, 0) + %la(t1, &readbuf_len) + %ld(t1, t1, 0) + %beq(t0, t1, &::done) + %call(&parse_one) + %li(a1, %imm_val(%IMM.NIL)) + %call(&eval) + %b(&::loop) + + ::done +}) + # ========================================================================= # Heap: cons (leaf) and alloc_hdr (leaf) # ========================================================================= @@ -356,9 +396,18 @@ %die(msg_symtab_full, 5) ::append_ok + # Copy the name into a stable heap buffer. The caller-provided ptr + # may live in readbuf_buf (parse_atom), which gets overwritten when + # the next source is loaded; symtab entries must outlive that. + %ld(a0, sp, 8) + %call(&alloc_bytes) + %ld(a1, sp, 0) + %ld(a2, sp, 8) + %call(&memcpy) ; returns dst in a0 = stable copy + + %ld(t0, sp, 16) %symtab_entry(t1, t0, t2) - %ld(a0, sp, 0) - %st(a0, t1, 0) + %st(a0, t1, 0) ; entry.name_ptr = stable copy %ld(a0, sp, 8) %st(a0, t1, 8) %li(a0, %imm_val(%IMM.UNBOUND)) @@ -406,7 +455,9 @@ # The reader is called recursively from parse_list, so every state goes # through frame slots, not s-registers. -# Skip whitespace (ASCII 32, 9, 10, 13) and `;`-to-LF comments. Leaf. +# Skip whitespace (ASCII 32, 9, 10, 13), NUL bytes (M0 auto-terminates +# string literals, so the embedded prelude has NULs we want to ignore), +# and `;`-to-LF comments. Leaf. :skip_ws %scope skip_ws %la(t2, &readbuf_pos) @@ -417,7 +468,8 @@ %beq(t0, t1, &::done) %readbuf_byte(a0, t0) %is_ws_branch(a1, a0, &::step) - %addi(a1, a0, -59) ; ';' + %beqz(a0, &::step) ; NUL + %addi(a1, a0, -59) ; ';' %beqz(a1, &::comment) %b(&::done) ::comment @@ -2936,6 +2988,32 @@ &name_eof_objectq %(0) $(11) &prim_eof_objectq_entry %(0) :prim_table_end +# Embedded Scheme prelude. Length = prelude_src_end - prelude_src, +# computed at startup. Each form is parsed and evaluated under the +# global env before the user file runs, so the user's source can +# rely on these helpers without re-defining them. +:prelude_src +"(define (list . xs) xs)" '0a' +"(define (length xs)" '0a' +" (let loop ((xs xs) (n 0))" '0a' +" (if (null? xs) n (loop (cdr xs) (+ n 1)))))" '0a' +"(define (reverse xs)" '0a' +" (let loop ((xs xs) (acc (quote ())))" '0a' +" (if (null? xs) acc (loop (cdr xs) (cons (car xs) acc)))))" '0a' +"(define (append-pair a b)" '0a' +" (if (null? a) b (cons (car a) (append-pair (cdr a) b))))" '0a' +"(define (append . lists)" '0a' +" (cond ((null? lists) (quote ()))" '0a' +" ((null? (cdr lists)) (car lists))" '0a' +" (else (append-pair (car lists) (apply append (cdr lists))))))" '0a' +"(define (list-ref xs n)" '0a' +" (if (= n 0) (car xs) (list-ref (cdr xs) (- n 1))))" '0a' +"(define (map f xs)" '0a' +" (if (null? xs) (quote ()) (cons (f (car xs)) (map f (cdr xs)))))" '0a' +"(define (for-each f xs)" '0a' +" (if (null? xs) (quote ()) (begin (f (car xs)) (for-each f (cdr xs)))))" '0a' +:prelude_src_end + :msg_usage "scheme1: usage: scheme1 SOURCE.scm" '0a' '00' :msg_load_fail "scheme1: failed to read source" '0a' '00' :msg_symtab_full "scheme1: symbol table full" '0a' '00' diff --git a/tests/scheme1/43-prelude.expected-exit b/tests/scheme1/43-prelude.expected-exit @@ -0,0 +1 @@ +19 diff --git a/tests/scheme1/43-prelude.scm b/tests/scheme1/43-prelude.scm @@ -0,0 +1,13 @@ +; Verify the full embedded prelude: list, length, reverse, append, +; list-ref, map, for-each are all in scope before user code. +(define xs (list 7 8 9)) +(define ys (append xs (list 10))) ; (7 8 9 10) +(define rev (reverse ys)) ; (10 9 8 7) +(define mapped (map (lambda (n) (- n 5)) ys)) ; (2 3 4 5) +; for-each is side-effecting; just verify it runs without error by +; consuming a list with a no-op lambda. +(for-each (lambda (x) x) ys) +(sys-exit (+ (length ys) + (+ (list-ref rev 0) + (list-ref mapped 3)))) +; length=4, list-ref rev 0 = 10, list-ref mapped 3 = 5. 4+10+5 = 19