Files
rose-ash/lib/kernel/tests/standard.sx
giles 6d8f11e093
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 32s
kernel: apply combinator + 7 tests [shapes-reflective]
(apply F (list V1 V2 V3)) ≡ (F V1 V2 V3). Unwrap applicative first to
skip auto-eval (args are values), then kernel-combine with the
underlying operative. Universal pattern in reflective Lisps —
sketched into the combiner.sx API. 296 tests total.
2026-05-11 21:17:24 +00:00

419 lines
16 KiB
Plaintext

;; lib/kernel/tests/standard.sx — exercises the Kernel standard env.
;;
;; Phase 4 tests verify that the standard env is rich enough to run
;; classic Kernel programs: factorial via recursion, list operations,
;; first-class environment manipulation. Each test starts from a fresh
;; standard env via `(kernel-standard-env)`.
(define ks-test-pass 0)
(define ks-test-fail 0)
(define ks-test-fails (list))
(define
ks-test
(fn
(name actual expected)
(if
(= actual expected)
(set! ks-test-pass (+ ks-test-pass 1))
(begin
(set! ks-test-fail (+ ks-test-fail 1))
(append! ks-test-fails {:name name :actual actual :expected expected})))))
(define
ks-eval
(fn (src) (kernel-eval (kernel-parse src) (kernel-standard-env))))
(define ks-eval-in (fn (src env) (kernel-eval (kernel-parse src) env)))
(define
ks-eval-all
(fn (src env) (kernel-eval-program (kernel-parse-all src) env)))
;; ── $if ──────────────────────────────────────────────────────────
(ks-test "if: true branch" (ks-eval "($if #t 1 2)") 1)
(ks-test "if: false branch" (ks-eval "($if #f 1 2)") 2)
(ks-test "if: predicate"
(ks-eval "($if (<=? 1 2) ($quote yes) ($quote no))") "yes")
(ks-test
"if: untaken branch not evaluated"
(ks-eval "($if #t 42 nope)")
42)
;; ── $define! + arithmetic ───────────────────────────────────────
(ks-test
"define!: returns value"
(let ((env (kernel-standard-env))) (ks-eval-in "($define! x 5)" env))
5)
(ks-test
"define!: bound in env"
(let
((env (kernel-standard-env)))
(ks-eval-in "($define! x 5)" env)
(ks-eval-in "x" env))
5)
(ks-test "arith: +" (ks-eval "(+ 2 3)") 5)
(ks-test "arith: -" (ks-eval "(- 10 4)") 6)
(ks-test "arith: *" (ks-eval "(* 6 7)") 42)
(ks-test "arith: /" (ks-eval "(/ 20 5)") 4)
(ks-test "cmp: < true" (ks-eval "(< 1 2)") true)
(ks-test "cmp: < false" (ks-eval "(< 2 1)") false)
(ks-test "cmp: >=" (ks-eval "(>=? 2 2)") true)
(ks-test "cmp: <=" (ks-eval "(<=? 2 3)") true)
(ks-test "cmp: =" (ks-eval "(=? 7 7)") true)
;; ── $sequence ────────────────────────────────────────────────────
(ks-test "sequence: empty" (ks-eval "($sequence)") nil)
(ks-test "sequence: single" (ks-eval "($sequence 99)") 99)
(ks-test
"sequence: multi-effect"
(let
((env (kernel-standard-env)))
(ks-eval-in "($sequence ($define! a 1) ($define! b 2) (+ a b))" env))
3)
;; ── list primitives ──────────────────────────────────────────────
(ks-test
"list: builds"
(ks-eval "(list 1 2 3)")
(list 1 2 3))
(ks-test "list: empty" (ks-eval "(list)") (list))
(ks-test
"cons: prepend"
(ks-eval "(cons 0 (list 1 2 3))")
(list 0 1 2 3))
(ks-test "car: head" (ks-eval "(car (list 10 20 30))") 10)
(ks-test
"cdr: tail"
(ks-eval "(cdr (list 10 20 30))")
(list 20 30))
(ks-test "length: 3" (ks-eval "(length (list 1 2 3))") 3)
(ks-test "length: 0" (ks-eval "(length (list))") 0)
(ks-test "null?: empty" (ks-eval "(null? (list))") true)
(ks-test "null?: nonempty" (ks-eval "(null? (list 1))") false)
(ks-test "pair?: empty" (ks-eval "(pair? (list))") false)
(ks-test "pair?: nonempty" (ks-eval "(pair? (list 1))") true)
;; ── $quote ───────────────────────────────────────────────────────
(ks-test "quote: symbol" (ks-eval "($quote foo)") "foo")
(ks-test
"quote: list"
(ks-eval "($quote (+ 1 2))")
(list "+" 1 2))
;; ── boolean / not ────────────────────────────────────────────────
(ks-test "not: true" (ks-eval "(not #t)") false)
(ks-test "not: false" (ks-eval "(not #f)") true)
;; ── factorial ────────────────────────────────────────────────────
(ks-test
"factorial: 5!"
(let
((env (kernel-standard-env)))
(ks-eval-in
"($define! factorial ($lambda (n) ($if (<=? n 1) 1 (* n (factorial (- n 1))))))"
env)
(ks-eval-in "(factorial 5)" env))
120)
(ks-test
"factorial: 0! = 1"
(let
((env (kernel-standard-env)))
(ks-eval-in
"($define! factorial ($lambda (n) ($if (<=? n 1) 1 (* n (factorial (- n 1))))))"
env)
(ks-eval-in "(factorial 0)" env))
1)
(ks-test
"factorial: 10!"
(let
((env (kernel-standard-env)))
(ks-eval-in
"($define! factorial ($lambda (n) ($if (<=? n 1) 1 (* n (factorial (- n 1))))))"
env)
(ks-eval-in "(factorial 10)" env))
3628800)
;; ── recursive list operations ────────────────────────────────────
(ks-test
"sum: recursive over list"
(let
((env (kernel-standard-env)))
(ks-eval-in
"($define! sum ($lambda (xs) ($if (null? xs) 0 (+ (car xs) (sum (cdr xs))))))"
env)
(ks-eval-in "(sum (list 1 2 3 4 5))" env))
15)
(ks-test
"len: recursive count"
(let
((env (kernel-standard-env)))
(ks-eval-in
"($define! mylen ($lambda (xs) ($if (null? xs) 0 (+ 1 (mylen (cdr xs))))))"
env)
(ks-eval-in "(mylen (list 1 2 3 4))" env))
4)
(ks-test
"map-add1: build new list"
(let
((env (kernel-standard-env)))
(ks-eval-in
"($define! add1-all ($lambda (xs) ($if (null? xs) (list) (cons (+ 1 (car xs)) (add1-all (cdr xs))))))"
env)
(ks-eval-in "(add1-all (list 10 20 30))" env))
(list 11 21 31))
;; ── eval as a first-class applicative ────────────────────────────
(ks-test
"eval: applies to constructed form"
(ks-eval "(eval (list ($quote +) 2 3) (get-current-environment))")
5)
(ks-test
"eval: with a fresh make-environment"
(guard
(e (true :raised))
(ks-eval "(eval ($quote (+ 1 2)) (make-environment))"))
:raised)
(ks-test
"eval: in extended env sees parent's bindings"
(let
((env (kernel-standard-env)))
(ks-eval-in "($define! shared 7)" env)
(ks-eval-in
"(eval ($quote shared) (make-environment (get-current-environment)))"
env))
7)
;; ── get-current-environment ──────────────────────────────────────
(ks-test
"get-current-environment: returns env"
(kernel-env? (ks-eval "(get-current-environment)"))
true)
(ks-test
"get-current-environment: contains $if"
(let
((env (ks-eval "(get-current-environment)")))
(kernel-env-has? env "$if"))
true)
(ks-test
"make-environment: empty"
(let ((env (ks-eval "(make-environment)"))) (kernel-env-has? env "$if"))
false)
(ks-test
"make-environment: child sees parent"
(let
((env (kernel-standard-env)))
(ks-eval-in "($define! marker 123)" env)
(let
((child (ks-eval-in "(make-environment (get-current-environment))" env)))
(kernel-env-has? child "marker")))
true)
;; ── closures and lexical scope ───────────────────────────────────
(ks-test
"closure: captures binding"
(let
((env (kernel-standard-env)))
(ks-eval-in
"($define! make-adder ($lambda (n) ($lambda (x) (+ x n))))"
env)
(ks-eval-in "($define! add5 (make-adder 5))" env)
(ks-eval-in "(add5 10)" env))
15)
(ks-test
"closure: nested lookups"
(let
((env (kernel-standard-env)))
(ks-eval-in
"($define! curry-add ($lambda (a) ($lambda (b) ($lambda (c) (+ a (+ b c))))))"
env)
(ks-eval-in "(((curry-add 1) 2) 3)" env))
6)
;; ── operative defined in standard env can reach $define! ─────────
(ks-test
"custom: define-via-vau"
(let
((env (kernel-standard-env)))
(ks-eval-in
"($define! $let-it ($vau (name expr) e ($sequence ($define! tmp (eval expr e)) (eval (list ($quote $define!) name (list ($quote $quote) tmp)) e) tmp)))"
env)
(ks-eval-in "($let-it z 77)" env)
(ks-eval-in "z" env))
77)
;; ── quasiquote ──────────────────────────────────────────────────
(ks-test "qq: plain atom" (ks-eval "`hello") "hello")
(ks-test "qq: plain list" (ks-eval "`(a b c)") (list "a" "b" "c"))
(ks-test "qq: unquote splices value"
(let ((env (kernel-standard-env)))
(ks-eval-in "($define! x 42)" env)
(ks-eval-in "`(a ,x b)" env)) (list "a" 42 "b"))
(ks-test "qq: unquote-splicing splices list"
(let ((env (kernel-standard-env)))
(ks-eval-in "($define! xs (list 1 2 3))" env)
(ks-eval-in "`(a ,@xs b)" env)) (list "a" 1 2 3 "b"))
(ks-test "qq: unquote-splicing at end"
(let ((env (kernel-standard-env)))
(ks-eval-in "($define! xs (list 9 8))" env)
(ks-eval-in "`(a b ,@xs)" env)) (list "a" "b" 9 8))
(ks-test "qq: unquote-splicing at start"
(let ((env (kernel-standard-env)))
(ks-eval-in "($define! xs (list 1 2))" env)
(ks-eval-in "`(,@xs c)" env)) (list 1 2 "c"))
(ks-test "qq: nested list with unquote inside"
(let ((env (kernel-standard-env)))
(ks-eval-in "($define! x 5)" env)
(ks-eval-in "`(a (b ,x) c)" env))
(list "a" (list "b" 5) "c"))
(ks-test "qq: error on bare unquote-splicing into non-list"
(let ((env (kernel-standard-env)))
(ks-eval-in "($define! x 42)" env)
(guard (e (true :raised))
(ks-eval-in "`(a ,@x b)" env)))
:raised)
;; ── $cond / $when / $unless ─────────────────────────────────────
(ks-test "cond: first match"
(ks-eval "($cond (#f 1) (#t 2) (#t 3))") 2)
(ks-test "cond: else fallback"
(ks-eval "($cond (#f 1) (else 99))") 99)
(ks-test "cond: no match returns nil"
(ks-eval "($cond (#f 1) (#f 2))") nil)
(ks-test "cond: empty clauses returns nil"
(ks-eval "($cond)") nil)
(ks-test "cond: multi-expr body"
(ks-eval "($cond (#t 1 2 3))") 3)
(ks-test "cond: doesn't evaluate untaken clauses"
;; If the second clause's test were evaluated, the unbound `nope` would error.
(ks-eval "($cond (#t 7) (nope ignored))") 7)
(ks-test "cond: predicate evaluation"
(let ((env (kernel-standard-env)))
(ks-eval-in "($define! n 5)" env)
(ks-eval-in "($cond ((< n 0) ($quote negative)) ((= n 0) ($quote zero)) (else ($quote positive)))" env))
"positive")
(ks-test "when: true runs body"
(ks-eval "($when #t 1 2 3)") 3)
(ks-test "when: false returns nil"
(ks-eval "($when #f 1 2 3)") nil)
(ks-test "when: skips body when false"
(ks-eval "($when #f nope)") nil)
(ks-test "unless: false runs body"
(ks-eval "($unless #f 99)") 99)
(ks-test "unless: true returns nil"
(ks-eval "($unless #t 99)") nil)
(ks-test "unless: skips body when true"
(ks-eval "($unless #t nope)") nil)
;; ── $and? / $or? short-circuit ──────────────────────────────────
(ks-test "and: empty returns true" (ks-eval "($and?)") true)
(ks-test "and: single returns value" (ks-eval "($and? 42)") 42)
(ks-test "and: all true returns last"
(ks-eval "($and? 1 2 3)") 3)
(ks-test "and: first false short-circuits"
(ks-eval "($and? #f nope)") false)
(ks-test "and: false in middle short-circuits"
(ks-eval "($and? 1 #f nope)") false)
(ks-test "or: empty returns false" (ks-eval "($or?)") false)
(ks-test "or: single returns value" (ks-eval "($or? 42)") 42)
(ks-test "or: first truthy short-circuits"
(ks-eval "($or? 99 nope)") 99)
(ks-test "or: all false returns last"
(ks-eval "($or? #f #f #f)") false)
(ks-test "or: middle truthy"
(ks-eval "($or? #f 42 nope)") 42)
;; ── variadic arithmetic ─────────────────────────────────────────
(ks-test "+: zero args = 0" (ks-eval "(+)") 0)
(ks-test "+: one arg = arg" (ks-eval "(+ 7)") 7)
(ks-test "+: two args" (ks-eval "(+ 3 4)") 7)
(ks-test "+: five args" (ks-eval "(+ 1 2 3 4 5)") 15)
(ks-test "*: zero args = 1" (ks-eval "(*)") 1)
(ks-test "*: one arg" (ks-eval "(* 7)") 7)
(ks-test "*: four args" (ks-eval "(* 1 2 3 4)") 24)
(ks-test "-: one arg negates" (ks-eval "(- 10)") -10)
(ks-test "-: two args" (ks-eval "(- 10 3)") 7)
(ks-test "-: four args fold" (ks-eval "(- 100 1 2 3)") 94)
(ks-test "/: two args" (ks-eval "(/ 20 5)") 4)
(ks-test "/: three args fold" (ks-eval "(/ 100 2 5)") 10)
;; ── variadic chained comparison ─────────────────────────────────
(ks-test "<: chained ascending" (ks-eval "(< 1 2 3 4 5)") true)
(ks-test "<: not strict" (ks-eval "(< 1 2 2 3)") false)
(ks-test "<: anti-monotonic" (ks-eval "(< 5 3)") false)
(ks-test ">: chained descending" (ks-eval "(> 5 4 3 2 1)") true)
(ks-test "<=? ascending equals" (ks-eval "(<=? 1 1 2 3 3)") true)
(ks-test "<=? violation" (ks-eval "(<=? 1 2 1)") false)
(ks-test ">=? descending equals" (ks-eval "(>=? 3 3 2 1)") true)
;; ── list combinators ────────────────────────────────────────────
(ks-test "map: square"
(ks-eval "(map ($lambda (x) (* x x)) (list 1 2 3 4))")
(list 1 4 9 16))
(ks-test "map: empty list"
(ks-eval "(map ($lambda (x) x) (list))") (list))
(ks-test "map: identity preserves"
(ks-eval "(map ($lambda (x) x) (list 1 2 3))") (list 1 2 3))
(ks-test "map: with closure over outer"
(let ((env (kernel-standard-env)))
(ks-eval-in "($define! k 10)" env)
(ks-eval-in "(map ($lambda (x) (+ x k)) (list 1 2 3))" env))
(list 11 12 13))
(ks-test "filter: positives"
(ks-eval "(filter ($lambda (x) (< 0 x)) (list -2 -1 0 1 2))")
(list 1 2))
(ks-test "filter: empty result"
(ks-eval "(filter ($lambda (x) #f) (list 1 2 3))") (list))
(ks-test "filter: all match"
(ks-eval "(filter ($lambda (x) #t) (list 1 2 3))") (list 1 2 3))
(ks-test "reduce: sum"
(ks-eval "(reduce ($lambda (a b) (+ a b)) 0 (list 1 2 3 4 5))") 15)
(ks-test "reduce: product"
(ks-eval "(reduce ($lambda (a b) (* a b)) 1 (list 1 2 3 4))") 24)
(ks-test "reduce: empty returns init"
(ks-eval "(reduce ($lambda (a b) (+ a b)) 42 (list))") 42)
(ks-test "reduce: build list"
(ks-eval "(reduce ($lambda (acc x) (cons x acc)) () (list 1 2 3))")
(list 3 2 1))
;; ── apply ────────────────────────────────────────────────────────
(ks-test "apply: + over list"
(ks-eval "(apply + (list 1 2 3 4 5))") 15)
(ks-test "apply: lambda"
(ks-eval "(apply ($lambda (a b c) (* a (+ b c))) (list 2 3 4))") 14)
(ks-test "apply: list identity"
(ks-eval "(apply list (list 1 2 3))") (list 1 2 3))
(ks-test "apply: empty args list"
(ks-eval "(apply + (list))") 0)
(ks-test "apply: single arg list"
(ks-eval "(apply ($lambda (x) (* x 10)) (list 7))") 70)
(ks-test "apply: built via map+apply"
;; (apply + (map ($lambda (x) (* x x)) (list 1 2 3))) → 1+4+9 = 14
(ks-eval
"(apply + (map ($lambda (x) (* x x)) (list 1 2 3)))") 14)
(ks-test "apply: error on non-list args"
(guard (e (true :raised))
(ks-eval "(apply + 5)"))
:raised)
(define ks-tests-run! (fn () {:total (+ ks-test-pass ks-test-fail) :passed ks-test-pass :failed ks-test-fail :fails ks-test-fails}))