boot2

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

commit 96a683eb9e0aea5e8d4d98763f66b069cfd5fff9
parent 0b51b30098797ce891ca56c01d9d829c29530a3c
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat, 25 Apr 2026 16:26:08 -0700

scheme1: tighten cond no-else, let* shadow, letrec recursion tests

- 16-cond: pin the (cond) no-match-no-else policy via UNSPEC eq? checks
  (UNSPEC singleton eq? to itself, distinct from #t/#f/0/'()).
- 19-letstar: also assert outer x is unchanged after the let* body
  returns, not just that the inner shadow wins inside the body.
- 20-letrec: replace the one-step (if n n (f #t)) workaround with
  factorial(5) = 120 -- exercises real stack-walking recursion.

Diffstat:
Mtests/scheme1/16-cond.scm | 11+++++++++++
Mtests/scheme1/19-letstar.expected-exit | 2+-
Mtests/scheme1/19-letstar.scm | 12+++++++++---
Mtests/scheme1/20-letrec.expected-exit | 2+-
Mtests/scheme1/20-letrec.scm | 9++++++---
5 files changed, 28 insertions(+), 8 deletions(-)

diff --git a/tests/scheme1/16-cond.scm b/tests/scheme1/16-cond.scm @@ -1,2 +1,13 @@ ; First-truthy clause wins; subsequent clauses are not evaluated. + +; No-match-no-else: scheme1 returns UNSPEC. LISP.md does not pin this, +; so the checks below guard the current behavior -- a change should be +; deliberate. UNSPEC is a singleton, so two such results are eq?, and +; it is distinguishable from any other value via eq?. +(if (eq? (cond (#f 1)) (cond (#f 2))) 0 (sys-exit 1)) +(if (not (eq? (cond (#f 1)) #t)) 0 (sys-exit 2)) +(if (not (eq? (cond (#f 1)) #f)) 0 (sys-exit 3)) +(if (not (eq? (cond (#f 1)) 0)) 0 (sys-exit 4)) +(if (not (eq? (cond (#f 1)) '())) 0 (sys-exit 5)) + (sys-exit (cond (#f 1) (#t 33) (#f 2))) diff --git a/tests/scheme1/19-letstar.expected-exit b/tests/scheme1/19-letstar.expected-exit @@ -1 +1 @@ -5 +0 diff --git a/tests/scheme1/19-letstar.scm b/tests/scheme1/19-letstar.scm @@ -1,3 +1,9 @@ -; let* binds sequentially: y's init may reference the new x. -(define x 1) ; outer x; let*'s x must shadow inside the body. -(sys-exit (let* ((x 5) (y x)) y)) +; let* binds sequentially: y's init may reference the new x. The let*'s +; x shadows the outer x inside the body, and the shadow does not escape +; the body -- after the form returns, the outer x is unchanged. +(define x 1) + +(if (= 5 (let* ((x 5) (y x)) y)) 0 (sys-exit 1)) +(if (= 1 x) 0 (sys-exit 2)) + +(sys-exit 0) diff --git a/tests/scheme1/20-letrec.expected-exit b/tests/scheme1/20-letrec.expected-exit @@ -1 +1 @@ -44 +120 diff --git a/tests/scheme1/20-letrec.scm b/tests/scheme1/20-letrec.scm @@ -1,4 +1,7 @@ ; letrec: a local helper that calls itself. The closure must see its own -; binding via the same env it captured. Without numeric primitives we -; terminate by passing #t at the recursive call. -(sys-exit (letrec ((f (lambda (n) (if n n (f #t))))) (f 44))) +; binding via the captured env. Real recursion -- factorial walks the +; stack down to the base case and back, so a one-step bug would not +; produce 120. +(sys-exit (letrec ((fact (lambda (n) + (if (= n 0) 1 (* n (fact (- n 1))))))) + (fact 5))) ; 5! = 120