boot2

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

commit b0dd9c3418d3dc4e783b4f960384ea2a0de6980a
parent 37f3b7510b05967ff92516f2ba46c7e9a5d70bf1
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat, 25 Apr 2026 08:17:47 -0700

Add variadic .-tail to scheme1 lambda/define

parse_list now intercepts `.` followed by ws/paren/EOF as the dotted-pair
separator: it consumes the dot, reads one more datum, splices it as the
cdr of the tail cons, then expects `)`. A `.` followed by any other byte
stays part of an identifier and falls through to parse_atom.

bind_params dispatches on params' tag at each step: PAIR continues the
lockstep walk, SYM rest-binds the remaining args list, anything else
(NIL) stops. Together these enable `(lambda (a . rest) ...)` and the
matching `(define (f a . rest) ...)` sugar.

Diffstat:
Mscheme1/scheme1.P1pp | 60+++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Atests/scheme1/12-variadic-rest.expected-exit | 1+
Atests/scheme1/12-variadic-rest.scm | 3+++
Atests/scheme1/13-define-variadic.expected-exit | 1+
Atests/scheme1/13-define-variadic.scm | 3+++
Atests/scheme1/14-empty-rest.expected-exit | 1+
Atests/scheme1/14-empty-rest.scm | 3+++
Atests/scheme1/15-dot-symbol.expected-exit | 1+
Atests/scheme1/15-dot-symbol.scm | 4++++
9 files changed, 74 insertions(+), 3 deletions(-)

diff --git a/scheme1/scheme1.P1pp b/scheme1/scheme1.P1pp @@ -540,6 +540,20 @@ %addi(a1, a0, -41) %beqz(a1, &::close) + # Dotted-pair separator: '.' followed by ws/paren/EOF (otherwise the + # '.' is part of an identifier and parse_atom handles it). + %addi(a1, a0, -46) + %bnez(a1, &::not_dot) + %addi(a2, t0, 1) + %beq(a2, t1, &::do_dot) + %readbuf_byte(a3, a2) + %is_ws_branch(a1, a3, &::do_dot) + %addi(a1, a3, -40) + %beqz(a1, &::do_dot) + %addi(a1, a3, -41) + %beqz(a1, &::do_dot) + ::not_dot + # Not ')': parse one item, append. %call(&parse_one) %li(a1, %imm_val(%IMM.NIL)) @@ -560,6 +574,31 @@ %st(a0, sp, 8) %b(&::loop) + ::do_dot + # Consume the '.', read one datum, splice it in as the cdr of the + # tail cons. Then expect a closing ')' (with optional ws). + %la(t1, &readbuf_pos) + %ld(t0, t1, 0) + %addi(t0, t0, 1) + %st(t0, t1, 0) + %call(&parse_one) + %ld(t0, sp, 8) + %st(a0, t0, 7) + %call(&skip_ws) + %la(t0, &readbuf_pos) + %ld(t0, t0, 0) + %la(t1, &readbuf_len) + %ld(t1, t1, 0) + %beq(t0, t1, &::eof) + %readbuf_byte(a0, t0) + %addi(a1, a0, -41) + %bnez(a1, &::eof) + %addi(t0, t0, 1) + %la(t1, &readbuf_pos) + %st(t0, t1, 0) + %ld(a0, sp, 0) + %eret + ::close # Consume ')' and return head. %la(t0, &readbuf_pos) @@ -1038,7 +1077,8 @@ # bind_params(params=a0, args=a1, env=a2) -> extended env (a0). # Walks params and args in lockstep, prepending (param . arg) to env. -# Fixed-arity only for now; variadic `.`-tail is a follow-up. +# Variadic `.`-tail: when params terminates with a SYM (rather than NIL), +# bind it to the remaining args list and stop. # # Frame: 24 bytes # +0 params (advanced each iteration) @@ -1051,9 +1091,14 @@ ::loop %ld(t0, sp, 0) - %li(t1, %imm_val(%IMM.NIL)) - %beq(t0, t1, &::done) + %tagof(t1, t0) + %li(t2, %TAG.PAIR) + %beq(t1, t2, &::pair) + %li(t2, %TAG.SYM) + %beq(t1, t2, &::rest_bind) + %b(&::done) + ::pair # binding = cons(car(params), car(args)) %ld(t0, sp, 0) %car(a0, t0) @@ -1075,6 +1120,15 @@ %st(t0, sp, 8) %b(&::loop) + ::rest_bind + # binding = cons(params_sym, args_list); env = cons(binding, env) + %ld(a0, sp, 0) + %ld(a1, sp, 8) + %call(&cons) + %ld(a1, sp, 16) + %call(&cons) + %st(a0, sp, 16) + ::done %ld(a0, sp, 16) }) diff --git a/tests/scheme1/12-variadic-rest.expected-exit b/tests/scheme1/12-variadic-rest.expected-exit @@ -0,0 +1 @@ +19 diff --git a/tests/scheme1/12-variadic-rest.scm b/tests/scheme1/12-variadic-rest.scm @@ -0,0 +1,3 @@ +; Lambda with .-tail rest-arg. The fixed arg `a` must still bind +; correctly when the rest absorbs additional args. +((lambda (a . xs) (sys-exit a)) 19 99 100 101) diff --git a/tests/scheme1/13-define-variadic.expected-exit b/tests/scheme1/13-define-variadic.expected-exit @@ -0,0 +1 @@ +23 diff --git a/tests/scheme1/13-define-variadic.scm b/tests/scheme1/13-define-variadic.scm @@ -0,0 +1,3 @@ +; `(define (f . xs) ...)` sugar. xs binds to the args list. +(define (head a . xs) a) +(sys-exit (head 23 1 2 3)) diff --git a/tests/scheme1/14-empty-rest.expected-exit b/tests/scheme1/14-empty-rest.expected-exit @@ -0,0 +1 @@ +41 diff --git a/tests/scheme1/14-empty-rest.scm b/tests/scheme1/14-empty-rest.scm @@ -0,0 +1,3 @@ +; Variadic with zero rest args: xs binds to (). +(define (head a . xs) a) +(sys-exit (head 41)) diff --git a/tests/scheme1/15-dot-symbol.expected-exit b/tests/scheme1/15-dot-symbol.expected-exit @@ -0,0 +1 @@ +7 diff --git a/tests/scheme1/15-dot-symbol.scm b/tests/scheme1/15-dot-symbol.scm @@ -0,0 +1,4 @@ +; A `.` followed by a non-ws byte is part of an identifier — only +; `.` + ws/paren/EOF is the dotted-pair separator. +(define .foo 7) +(sys-exit .foo)