;; 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-suite (refl-make-test-suite)) (define ks-test (fn (n a e) (refl-test ks-suite n a e))) (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) ;; ── append / reverse ──────────────────────────────────────────── (ks-test "append: two lists" (ks-eval "(append (list 1 2) (list 3 4))") (list 1 2 3 4)) (ks-test "append: three lists" (ks-eval "(append (list 1) (list 2) (list 3))") (list 1 2 3)) (ks-test "append: empty list" (ks-eval "(append)") (list)) (ks-test "append: one list" (ks-eval "(append (list 1 2 3))") (list 1 2 3)) (ks-test "append: empty + nonempty" (ks-eval "(append (list) (list 1 2))") (list 1 2)) (ks-test "append: nonempty + empty" (ks-eval "(append (list 1 2) (list))") (list 1 2)) (ks-test "append: error on non-list" (guard (e (true :raised)) (ks-eval "(append (list 1) 5)")) :raised) (ks-test "reverse: four elements" (ks-eval "(reverse (list 1 2 3 4))") (list 4 3 2 1)) (ks-test "reverse: empty" (ks-eval "(reverse (list))") (list)) (ks-test "reverse: single" (ks-eval "(reverse (list 99))") (list 99)) (ks-test "reverse: double reverse is identity" (ks-eval "(reverse (reverse (list 1 2 3)))") (list 1 2 3)) (define ks-tests-run! (fn () (refl-test-report ks-suite)))