cl: multiple values — 15 new tests (174 eval, 346 total green)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 46s

VALUES wraps 2+ values in {:cl-type "mv"}; cl-mv-primary strips to
primary in IF/AND/OR/COND/cl-call-fn single-value contexts; cl-mv-vals
expands for MULTIPLE-VALUE-BIND, MULTIPLE-VALUE-CALL, NTH-VALUE.
This commit is contained in:
2026-05-05 11:23:12 +00:00
parent 733b1ebefa
commit 4cd8773766
3 changed files with 108 additions and 7 deletions

View File

@@ -43,6 +43,18 @@
(define cl-go-tag? (define cl-go-tag?
(fn (v) (and (dict? v) (= (get v "cl-type") "go-tag")))) (fn (v) (and (dict? v) (= (get v "cl-type") "go-tag"))))
(define cl-mv?
(fn (v) (and (dict? v) (= (get v "cl-type") "mv"))))
(define cl-mv-primary
(fn (v)
(if (cl-mv? v)
(if (> (len (get v "vals")) 0) (nth (get v "vals") 0) nil)
v)))
(define cl-mv-vals
(fn (v) (if (cl-mv? v) (get v "vals") (list v))))
(define cl-eval-body (define cl-eval-body
(fn (forms env) (fn (forms env)
(cond (cond
@@ -252,7 +264,7 @@
(reduce (fn (acc x) (concat (list x) acc)) (reduce (fn (acc x) (concat (list x) acc))
(list) (nth args 0))) (list) (nth args 0)))
"IDENTITY" (fn (args) (nth args 0)) "IDENTITY" (fn (args) (nth args 0))
"VALUES" (fn (args) (if (> (len args) 0) (nth args 0) nil)) "VALUES" (fn (args) (cond ((= (len args) 0) nil) ((= (len args) 1) (nth args 0)) (:else {:cl-type "mv" :vals args})))
"PRINT" (fn (args) (nth args 0)) "PRINT" (fn (args) (nth args 0))
"PRIN1" (fn (args) (nth args 0)) "PRIN1" (fn (args) (nth args 0))
"PRINC" (fn (args) (nth args 0)) "PRINC" (fn (args) (nth args 0))
@@ -309,6 +321,39 @@
(:else (run (+ i 1)))))))))) (:else (run (+ i 1))))))))))
(run 0)))) (run 0))))
;; ── MULTIPLE VALUES ──────────────────────────────────────────────
(define cl-eval-multiple-value-bind
(fn (args env)
(let ((vars (nth args 0))
(form (nth args 1))
(body (rest (rest args))))
(let ((vals (cl-mv-vals (cl-eval form env))))
(define bind-vars
(fn (names i e)
(if (= (len names) 0)
e
(bind-vars (rest names) (+ i 1)
(cl-env-bind-var e (nth names 0)
(if (< i (len vals)) (nth vals i) nil))))))
(cl-eval-body body (bind-vars vars 0 env))))))
(define cl-eval-multiple-value-call
(fn (args env)
(let ((fn-obj (cl-eval (nth args 0) env))
(forms (rest args)))
(let ((all-vals (reduce
(fn (acc f)
(concat acc (cl-mv-vals (cl-eval f env))))
(list) forms)))
(cl-apply fn-obj all-vals)))))
(define cl-eval-multiple-value-prog1
(fn (args env)
(let ((first-result (cl-eval (nth args 0) env)))
(for-each (fn (f) (cl-eval f env)) (rest args))
first-result)))
;; ── UNWIND-PROTECT ─────────────────────────────────────────────── ;; ── UNWIND-PROTECT ───────────────────────────────────────────────
(define cl-eval-unwind-protect (define cl-eval-unwind-protect
@@ -341,7 +386,7 @@
(define cl-eval-if (define cl-eval-if
(fn (args env) (fn (args env)
(let ((cond-val (cl-eval (nth args 0) env)) (let ((cond-val (cl-mv-primary (cl-eval (nth args 0) env)))
(then-form (nth args 1)) (then-form (nth args 1))
(else-form (if (> (len args) 2) (nth args 2) nil))) (else-form (if (> (len args) 2) (nth args 2) nil)))
(if cond-val (if cond-val
@@ -352,7 +397,7 @@
(fn (args env) (fn (args env)
(if (= (len args) 0) (if (= (len args) 0)
true true
(let ((val (cl-eval (nth args 0) env))) (let ((val (cl-mv-primary (cl-eval (nth args 0) env))))
(if (not val) (if (not val)
nil nil
(if (= (len args) 1) (if (= (len args) 1)
@@ -363,7 +408,7 @@
(fn (args env) (fn (args env)
(if (= (len args) 0) (if (= (len args) 0)
nil nil
(let ((val (cl-eval (nth args 0) env))) (let ((val (cl-mv-primary (cl-eval (nth args 0) env))))
(if val (if val
val val
(cl-eval-or (rest args) env)))))) (cl-eval-or (rest args) env))))))
@@ -373,7 +418,7 @@
(if (= (len clauses) 0) (if (= (len clauses) 0)
nil nil
(let ((clause (nth clauses 0))) (let ((clause (nth clauses 0)))
(let ((test-val (cl-eval (nth clause 0) env))) (let ((test-val (cl-mv-primary (cl-eval (nth clause 0) env))))
(if test-val (if test-val
(if (= (len clause) 1) (if (= (len clause) 1)
test-val test-val
@@ -523,7 +568,7 @@
;; Function call: evaluate name → look up fns, builtins; evaluate args ;; Function call: evaluate name → look up fns, builtins; evaluate args
(define cl-call-fn (define cl-call-fn
(fn (name args env) (fn (name args env)
(let ((evaled (map (fn (a) (cl-eval a env)) args))) (let ((evaled (map (fn (a) (cl-mv-primary (cl-eval a env))) args)))
(cond (cond
;; FUNCALL: (funcall fn arg...) ;; FUNCALL: (funcall fn arg...)
((= name "FUNCALL") ((= name "FUNCALL")
@@ -615,6 +660,13 @@
((= head "TAGBODY") (cl-eval-tagbody args env)) ((= head "TAGBODY") (cl-eval-tagbody args env))
((= head "GO") ((= head "GO")
{:cl-type "go-tag" :tag (nth args 0)}) {:cl-type "go-tag" :tag (nth args 0)})
((= head "MULTIPLE-VALUE-BIND") (cl-eval-multiple-value-bind args env))
((= head "MULTIPLE-VALUE-CALL") (cl-eval-multiple-value-call args env))
((= head "MULTIPLE-VALUE-PROG1") (cl-eval-multiple-value-prog1 args env))
((= head "NTH-VALUE")
(let ((n (cl-mv-primary (cl-eval (nth args 0) env)))
(vals (cl-mv-vals (cl-eval (nth args 1) env))))
(if (< n (len vals)) (nth vals n) nil)))
((= head "UNWIND-PROTECT") (cl-eval-unwind-protect args env)) ((= head "UNWIND-PROTECT") (cl-eval-unwind-protect args env))
((= head "BLOCK") (cl-eval-block args env)) ((= head "BLOCK") (cl-eval-block args env))
((= head "RETURN-FROM") (cl-eval-return-from args env)) ((= head "RETURN-FROM") (cl-eval-return-from args env))

View File

@@ -388,3 +388,51 @@
(cl-test "unwind-protect: nested, inner cleanup first" (cl-test "unwind-protect: nested, inner cleanup first"
(ev "(let ((n 0)) (unwind-protect (unwind-protect 1 (setq n (+ n 10))) (setq n (+ n 1))) n)") (ev "(let ((n 0)) (unwind-protect (unwind-protect 1 (setq n (+ n 10))) (setq n (+ n 1))) n)")
11) 11)
;; ── VALUES / MULTIPLE-VALUE-BIND / NTH-VALUE ────────────────────
(cl-test "values: single returns plain"
(ev "(values 42)")
42)
(cl-test "values: zero returns nil"
(ev "(values)")
nil)
(cl-test "values: multi — primary via funcall"
(ev "(car (list (values 1 2)))")
1)
(cl-test "multiple-value-bind: basic"
(ev "(multiple-value-bind (a b) (values 1 2) (+ a b))")
3)
(cl-test "multiple-value-bind: extra vars get nil"
(ev "(multiple-value-bind (a b c) (values 10 20) (list a b c))")
(list 10 20 nil))
(cl-test "multiple-value-bind: extra values ignored"
(ev "(multiple-value-bind (a) (values 1 2 3) a)")
1)
(cl-test "multiple-value-bind: single value source"
(ev "(multiple-value-bind (a b) 42 (list a b))")
(list 42 nil))
(cl-test "nth-value: 0"
(ev "(nth-value 0 (values 10 20 30))")
10)
(cl-test "nth-value: 1"
(ev "(nth-value 1 (values 10 20 30))")
20)
(cl-test "nth-value: out of range"
(ev "(nth-value 5 (values 10 20))")
nil)
(cl-test "multiple-value-call: basic"
(ev "(multiple-value-call #'+ (values 1 2) (values 3 4))")
10)
(cl-test "multiple-value-prog1: returns first"
(ev "(multiple-value-prog1 1 2 3)")
1)
(cl-test "multiple-value-prog1: side effects run"
(ev "(let ((x 0)) (multiple-value-prog1 99 (setq x 7)) x)")
7)
(cl-test "values: nil primary in if"
(ev "(if (values nil t) 'yes 'no)")
"NO")
(cl-test "values: truthy primary in if"
(ev "(if (values 42 nil) 'yes 'no)")
"YES")

View File

@@ -60,7 +60,7 @@ Core mapping:
- [x] `block` + `return-from` via captured continuation - [x] `block` + `return-from` via captured continuation
- [x] `tagbody` + `go` via per-tag continuations - [x] `tagbody` + `go` via per-tag continuations
- [x] `unwind-protect` cleanup frame - [x] `unwind-protect` cleanup frame
- [ ] `multiple-value-bind`, `multiple-value-call`, `multiple-value-prog1`, `values`, `nth-value` - [x] `multiple-value-bind`, `multiple-value-call`, `multiple-value-prog1`, `values`, `nth-value`
- [x] `defun`, `defparameter`, `defvar`, `defconstant`, `declaim`, `proclaim` (no-op) - [x] `defun`, `defparameter`, `defvar`, `defconstant`, `declaim`, `proclaim` (no-op)
- [ ] Dynamic variables — `defvar`/`defparameter` produce specials; `let` rebinds via parameterize-style scope - [ ] Dynamic variables — `defvar`/`defparameter` produce specials; `let` rebinds via parameterize-style scope
- [x] 127 tests in `lib/common-lisp/tests/eval.sx` - [x] 127 tests in `lib/common-lisp/tests/eval.sx`
@@ -128,6 +128,7 @@ _Newest first._
- 2026-05-05: Phase 3 interactive-debugger.sx — cl-debugger-hook global, cl-invoke-debugger, cl-error-with-debugger, make-policy-debugger; 7 tests (143 total). Tests wired into test.sh program suite runner. Phase 3 condition core complete. - 2026-05-05: Phase 3 interactive-debugger.sx — cl-debugger-hook global, cl-invoke-debugger, cl-error-with-debugger, make-policy-debugger; 7 tests (143 total). Tests wired into test.sh program suite runner. Phase 3 condition core complete.
- 2026-05-05: Phase 3 classic programs — `tests/programs/restart-demo.sx` (7 tests: safe-divide with use-zero + retry restarts) and `tests/programs/parse-recover.sx` (6 tests: token parser with skip-token + use-zero restarts, handler-case abort). Key gotcha: use `=` not `equal?` for list comparison in sx_server. - 2026-05-05: Phase 3 classic programs — `tests/programs/restart-demo.sx` (7 tests: safe-divide with use-zero + retry restarts) and `tests/programs/parse-recover.sx` (6 tests: token parser with skip-token + use-zero restarts, handler-case abort). Key gotcha: use `=` not `equal?` for list comparison in sx_server.
- 2026-05-05: Phase 3 conditions + restarts — `cl-condition-classes` hierarchy (15 types), `cl-condition?`/`cl-condition-of-type?`, `cl-make-condition`, `cl-define-condition`, `cl-signal`/`cl-error`/`cl-warn`/`cl-cerror`, `cl-handler-bind` (non-unwinding), `cl-handler-case` (call/cc escape), `cl-restart-case`/`cl-with-simple-restart`, `cl-find-restart`/`cl-invoke-restart`/`cl-compute-restarts`, `cl-with-condition-restarts`; 55 new tests in `tests/conditions.sx` (123 total runtime tests). Key gotcha: `cl-condition-classes` must be captured at define-time via `let` in `cl-condition-of-type?` — free-variable lookup at call-time fails through env_merge parent chain. - 2026-05-05: Phase 3 conditions + restarts — `cl-condition-classes` hierarchy (15 types), `cl-condition?`/`cl-condition-of-type?`, `cl-make-condition`, `cl-define-condition`, `cl-signal`/`cl-error`/`cl-warn`/`cl-cerror`, `cl-handler-bind` (non-unwinding), `cl-handler-case` (call/cc escape), `cl-restart-case`/`cl-with-simple-restart`, `cl-find-restart`/`cl-invoke-restart`/`cl-compute-restarts`, `cl-with-condition-restarts`; 55 new tests in `tests/conditions.sx` (123 total runtime tests). Key gotcha: `cl-condition-classes` must be captured at define-time via `let` in `cl-condition-of-type?` — free-variable lookup at call-time fails through env_merge parent chain.
- 2026-05-05: multiple values — VALUES returns {:cl-type "mv"} wrapper for 2+ values; cl-mv-primary/cl-mv-vals helpers; MULTIPLE-VALUE-BIND binds vars to value list; MULTIPLE-VALUE-CALL/PROG1/NTH-VALUE; cl-mv-primary applied in IF/AND/OR/COND/cl-call-fn for single-value contexts; 15 new tests (174 eval, 346 total green).
- 2026-05-05: unwind-protect — cl-eval-unwind-protect: eval protected form, run cleanup with for-each (discards results, preserves original sentinel), return original result; 8 new tests (159 eval, 331 total green). - 2026-05-05: unwind-protect — cl-eval-unwind-protect: eval protected form, run cleanup with for-each (discards results, preserves original sentinel), return original result; 8 new tests (159 eval, 331 total green).
- 2026-05-05: tagbody + go — cl-go-tag? sentinel; cl-eval-tagbody runs body with tag-index map (keys str-normalised for integer tags); go-tag propagation in cl-eval-body alongside block-return; 11 new tests (151 eval, 323 total green). - 2026-05-05: tagbody + go — cl-go-tag? sentinel; cl-eval-tagbody runs body with tag-index map (keys str-normalised for integer tags); go-tag propagation in cl-eval-body alongside block-return; 11 new tests (151 eval, 323 total green).
- 2026-05-05: block + return-from — sentinel propagation in cl-eval-body; cl-eval-block catches matching sentinels; BLOCK/RETURN-FROM/RETURN dispatch in cl-eval-list; 13 new tests (140 eval, 312 total green). Parser: CL strings → {:cl-type "string"} dicts. - 2026-05-05: block + return-from — sentinel propagation in cl-eval-body; cl-eval-block catches matching sentinels; BLOCK/RETURN-FROM/RETURN dispatch in cl-eval-list; 13 new tests (140 eval, 312 total green). Parser: CL strings → {:cl-type "string"} dicts.