boot2

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

commit 82c6a330b5de3aaeb5f7ec8aa79fa97f957e3d9d
parent 80d020d670c6ae7d9443d603301d7a989b1a7c7a
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat, 25 Apr 2026 14:13:18 -0700

scheme1: string? / set-car! / set-cdr! / length / list-ref primitives

Adds five leaf primitives backing R7RS pair- and predicate-level
operations. string? / bytevector? share an entry (HEAP-tagged HDR.BV
test); set-car! / set-cdr! mutate in place and return UNSPEC with no
type check (consistent with car/cdr's lax stance); length forwards to
the existing list_length helper and tags the result; list-ref untags
the index, walks via cdr, and cars the final pair. Length and list-ref
shadowed prelude defines, so those entries are deleted to ensure the
tests exercise the new primitives.

Diffstat:
Mscheme1/prelude.scm | 7-------
Mscheme1/scheme1.P1pp | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atests/scheme1/59-stringq.expected-exit | 1+
Atests/scheme1/59-stringq.scm | 18++++++++++++++++++
Atests/scheme1/60-set-pair-bang.expected-exit | 1+
Atests/scheme1/60-set-pair-bang.scm | 26++++++++++++++++++++++++++
Atests/scheme1/61-length.expected-exit | 1+
Atests/scheme1/61-length.scm | 19+++++++++++++++++++
Atests/scheme1/62-list-ref.expected-exit | 1+
Atests/scheme1/62-list-ref.scm | 21+++++++++++++++++++++
10 files changed, 156 insertions(+), 7 deletions(-)

diff --git a/scheme1/prelude.scm b/scheme1/prelude.scm @@ -31,10 +31,6 @@ #t (if (pair? x) (list? (cdr x)) #f))) -(define (length xs) - (let loop ((xs xs) (n 0)) - (if (null? xs) n (loop (cdr xs) (+ n 1))))) - (define (reverse xs) (let loop ((xs xs) (acc (quote ()))) (if (null? xs) acc (loop (cdr xs) (cons (car xs) acc))))) @@ -47,9 +43,6 @@ ((null? (cdr lists)) (car lists)) (else (append-pair (car lists) (apply append (cdr lists)))))) -(define (list-ref xs n) - (if (= n 0) (car xs) (list-ref (cdr xs) (- n 1)))) - (define (assoc key alist) (if (null? alist) #f diff --git a/scheme1/scheme1.P1pp b/scheme1/scheme1.P1pp @@ -2191,6 +2191,64 @@ %ret %endscope +# (string? x) -- #t iff x is a HEAP-tagged HDR.BV. Bytevectors back the +# string type until characters get a distinct repr; this prim is also +# the bytevector? predicate. +:prim_stringq_entry +%scope prim_stringq + %car(t0, a0) + %li(a0, %imm_val(%IMM.FALSE)) + %tagof(t1, t0) + %li(t2, %TAG.HEAP) + %bne(t1, t2, &::end) + %hdr_type(t1, t0) + %li(t2, %HDR.BV) + %bne(t1, t2, &::end) + %li(a0, %imm_val(%IMM.TRUE)) + ::end + %ret +%endscope + +# (set-car! pair val) / (set-cdr! pair val) -- in-place pair mutation. +# No type check (matches car/cdr's lax stance); both return UNSPEC. +:prim_set_car_entry + %args2(t0, t1, a0) + %st(t1, t0, -1) + %li(a0, %imm_val(%IMM.UNSPEC)) + %ret + +:prim_set_cdr_entry + %args2(t0, t1, a0) + %st(t1, t0, 7) + %li(a0, %imm_val(%IMM.UNSPEC)) + %ret + +# (length xs) -- count of pairs in a proper list. Forwards to the +# list_length helper (which clobbers a0 as the cursor) and tags the +# resulting count as a fixnum. +:prim_length_entry + %car(a0, a0) + %call(&list_length) + %mkfix(a0, a0) + %ret + +# (list-ref xs n) -- 0-indexed nth element. n is a fixnum; we untag, +# advance via cdr, then car. Out-of-range is undefined behavior, same +# as car/cdr on '(). +:prim_list_ref_entry +%scope prim_list_ref + %args2(t0, t1, a0) + %sari(t1, t1, 3) + ::loop + %beqz(t1, &::done) + %cdr(t0, t0) + %addi(t1, t1, -1) + %b(&::loop) + ::done + %car(a0, t0) + %ret +%endscope + :prim_zeroq_entry %scope prim_zeroq %car(t0, a0) @@ -4146,6 +4204,11 @@ :name_cdr "cdr" :name_nullq "null?" :name_pairq "pair?" +:name_stringq "string?" +:name_set_car "set-car!" +:name_set_cdr "set-cdr!" +:name_length "length" +:name_list_ref "list-ref" :name_zeroq "zero?" :name_not "not" :name_eqq "eq?" @@ -4215,6 +4278,11 @@ &name_cdr %(0) $(3) &prim_cdr_entry %(0) &name_nullq %(0) $(5) &prim_nullq_entry %(0) &name_pairq %(0) $(5) &prim_pairq_entry %(0) +&name_stringq %(0) $(7) &prim_stringq_entry %(0) +&name_set_car %(0) $(8) &prim_set_car_entry %(0) +&name_set_cdr %(0) $(8) &prim_set_cdr_entry %(0) +&name_length %(0) $(6) &prim_length_entry %(0) +&name_list_ref %(0) $(8) &prim_list_ref_entry %(0) &name_zeroq %(0) $(5) &prim_zeroq_entry %(0) &name_not %(0) $(3) &prim_not_entry %(0) &name_eqq %(0) $(3) &prim_eqq_entry %(0) diff --git a/tests/scheme1/59-stringq.expected-exit b/tests/scheme1/59-stringq.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/scheme1/59-stringq.scm b/tests/scheme1/59-stringq.scm @@ -0,0 +1,18 @@ +; (string? x) -- #t iff x is a bytevector. scheme1 uses bytevectors as +; the underlying string representation, so string? and bytevector? are +; the same predicate (LISP.md:strings; we'll split if/when characters +; gain a distinct repr). + +(if (string? "abc") 0 (sys-exit 1)) +(if (string? "") 0 (sys-exit 2)) +(if (string? (make-bytevector 4 0)) 0 (sys-exit 3)) + +(if (not (string? 42)) 0 (sys-exit 4)) +(if (not (string? 'foo)) 0 (sys-exit 5)) +(if (not (string? '())) 0 (sys-exit 6)) +(if (not (string? '(1 2))) 0 (sys-exit 7)) +(if (not (string? #t)) 0 (sys-exit 8)) +(if (not (string? #f)) 0 (sys-exit 9)) +(if (not (string? car)) 0 (sys-exit 10)) + +(sys-exit 0) diff --git a/tests/scheme1/60-set-pair-bang.expected-exit b/tests/scheme1/60-set-pair-bang.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/scheme1/60-set-pair-bang.scm b/tests/scheme1/60-set-pair-bang.scm @@ -0,0 +1,26 @@ +; (set-car! pair val) and (set-cdr! pair val) -- in-place pair mutation. +; Both return unspecified; the visible effect is via car/cdr. + +(define p (cons 1 2)) +(set-car! p 10) +(if (= (car p) 10) 0 (sys-exit 1)) +(if (= (cdr p) 2) 0 (sys-exit 2)) + +(set-cdr! p 20) +(if (= (car p) 10) 0 (sys-exit 3)) +(if (= (cdr p) 20) 0 (sys-exit 4)) + +; Mutation visible through aliases. +(define q p) +(set-car! q 99) +(if (= (car p) 99) 0 (sys-exit 5)) + +; cdr can be a non-pair, a pair, or '(). +(set-cdr! p (cons 7 '())) +(if (= (car (cdr p)) 7) 0 (sys-exit 6)) +(if (null? (cdr (cdr p))) 0 (sys-exit 7)) + +(set-cdr! p '()) +(if (null? (cdr p)) 0 (sys-exit 8)) + +(sys-exit 0) diff --git a/tests/scheme1/61-length.expected-exit b/tests/scheme1/61-length.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/scheme1/61-length.scm b/tests/scheme1/61-length.scm @@ -0,0 +1,19 @@ +; (length xs) -- count of pairs in a proper list. Behavior on improper +; / circular lists is unspecified; we just check the proper-list cases +; that LISP.md commits to. + +(if (= (length '()) 0) 0 (sys-exit 1)) +(if (= (length '(a)) 1) 0 (sys-exit 2)) +(if (= (length '(a b c d e)) 5) 0 (sys-exit 3)) +(if (= (length (cons 1 (cons 2 (cons 3 '())))) 3) 0 (sys-exit 4)) + +; Same length, different element types. +(if (= (length (list 1 'two "three" '(4))) 4) 0 (sys-exit 5)) + +; Built dynamically. +(define (range n) + (let loop ((i 0) (acc '())) + (if (= i n) acc (loop (+ i 1) (cons i acc))))) +(if (= (length (range 17)) 17) 0 (sys-exit 6)) + +(sys-exit 0) diff --git a/tests/scheme1/62-list-ref.expected-exit b/tests/scheme1/62-list-ref.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/scheme1/62-list-ref.scm b/tests/scheme1/62-list-ref.scm @@ -0,0 +1,21 @@ +; (list-ref xs n) -- 0-indexed nth element. Out-of-range is undefined +; behavior (consistent with car/cdr policy); we only test the in-range +; cases here. + +(define xs '(a b c d e)) + +(if (eq? (list-ref xs 0) 'a) 0 (sys-exit 1)) +(if (eq? (list-ref xs 1) 'b) 0 (sys-exit 2)) +(if (eq? (list-ref xs 4) 'e) 0 (sys-exit 3)) + +; Mixed types. +(define ys (list 1 "two" 'three '(4 5))) +(if (= (list-ref ys 0) 1) 0 (sys-exit 4)) +(if (bytevector=? (list-ref ys 1) "two") 0 (sys-exit 5)) +(if (eq? (list-ref ys 2) 'three) 0 (sys-exit 6)) +(if (equal? (list-ref ys 3) '(4 5)) 0 (sys-exit 7)) + +; Independently of `length` (don't entangle the tests). +(if (= (list-ref (cons 10 (cons 20 (cons 30 '()))) 2) 30) 0 (sys-exit 8)) + +(sys-exit 0)