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:
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)