spec: multiple values — values/call-with-values/let-values/define-values

25 tests pass on both JS and OCaml hosts. Uses dict marker
{:_values true :_list [...]} for 0/2+ values; 1 value passes
through directly. step-sf-define extended to desugar shorthand
(define (name params) body) forms on both hosts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-01 08:03:17 +00:00
parent 8328e96ff6
commit 43cc1d9003
5 changed files with 498 additions and 88 deletions

172
spec/tests/test-values.sx Normal file
View File

@@ -0,0 +1,172 @@
(defsuite
"multiple-values"
(deftest
"values single returns value directly"
(do
(assert= 42 (values 42))
(assert= "hi" (values "hi"))
(assert= nil (values nil))))
(deftest
"values multiple returns marker dict"
(do
(let
((v (values 1 2 3)))
(assert (dict? v))
(assert= true (get v :_values false))
(assert-equal (list 1 2 3) (get v :_list)))))
(deftest
"call-with-values basic two values"
(do
(assert=
3
(call-with-values
(fn () (values 1 2))
(fn (a b) (+ a b))))))
(deftest
"call-with-values three values"
(do
(assert=
6
(call-with-values
(fn () (values 1 2 3))
(fn (a b c) (+ a b c))))))
(deftest
"call-with-values single value passthrough"
(do
(assert= 10 (call-with-values (fn () 10) (fn (x) x)))))
(deftest
"call-with-values passes non-values result as single arg"
(do (assert= "hello" (call-with-values (fn () "hello") (fn (x) x)))))
(deftest
"call-with-values with string concat"
(do
(assert=
"ab"
(call-with-values (fn () (values "a" "b")) (fn (a b) (str a b))))))
(deftest
"let-values basic two bindings"
(do
(let-values
(((a b) (values 10 20)))
(assert= 10 a)
(assert= 20 b))))
(deftest
"let-values computes with bindings"
(do
(let-values
(((x y) (values 3 4)))
(assert= 7 (+ x y)))))
(deftest
"let-values three values"
(do
(let-values
(((a b c) (values 1 2 3)))
(assert= 6 (+ a b c)))))
(deftest
"let-values single value binding"
(do (let-values (((x) (values 42))) (assert= 42 x))))
(deftest
"let-values multiple binding clauses"
(do
(let-values
(((a b) (values 1 2))
((c d) (values 3 4)))
(assert= 10 (+ a b c d)))))
(deftest
"let-values body is multiple expressions"
(do
(let-values
(((a b) (values 5 6)))
(define sum (+ a b))
(assert= 11 sum))))
(deftest
"let-values with no bindings evals body"
(do (let-values () (assert= 99 99))))
(deftest
"define-values binds multiple names"
(do
(define-values (x y) (values 7 8))
(assert= 7 x)
(assert= 8 y)))
(deftest
"define-values three names"
(do
(define-values (a b c) (values 10 20 30))
(assert= 10 a)
(assert= 20 b)
(assert= 30 c)))
(deftest
"define-values single name"
(do (define-values (n) (values 42)) (assert= 42 n)))
(deftest
"define-values used in computation"
(do
(define-values (w h) (values 6 7))
(assert= 42 (* w h))))
(deftest
"values in let binding"
(do
(let
((v (values 100 200)))
(assert= true (get v :_values false))
(assert= 100 (first (get v :_list))))))
(deftest
"call-with-values with swap"
(do
(define (swap a b) (values b a))
(assert=
5
(call-with-values
(fn () (swap 3 5))
(fn (first-val second-val) first-val)))))
(deftest
"let-values from function returning values"
(do
(define (min-max a b) (values (min a b) (max a b)))
(let-values
(((lo hi) (min-max 7 3)))
(assert= 3 lo)
(assert= 7 hi))))
(deftest
"nested let-values"
(do
(let-values
(((a b) (values 1 2)))
(let-values
(((c d) (values 3 4)))
(assert= 10 (+ a b c d))))))
(deftest
"call-with-values chained"
(do
(define
result
(call-with-values
(fn
()
(call-with-values
(fn () (values 4 6))
(fn (a b) (* a b))))
(fn (x) x)))
(assert= 24 result)))
(deftest
"values zero args produces dict"
(do
(let
((v (values)))
(assert (dict? v))
(assert (get v :_values false))
(assert-equal (list) (get v :_list)))))
(deftest
"let-values strings"
(do
(let-values
(((first-name last-name) (values "Alice" "Smith")))
(assert= "Alice Smith" (str first-name " " last-name)))))
(deftest
"define-values with list values"
(do
(define-values
(head tail)
(values 1 (list 2 3 4)))
(assert= 1 head)
(assert-equal (list 2 3 4) tail))))