;; ========================================================================== ;; test-cek-advanced.sx — Advanced stress tests for the CEK machine evaluator ;; ;; Exercises complex evaluation patterns that stress the step/continue ;; dispatch loop: deep nesting, higher-order forms, macro expansion in ;; the CEK context, environment pressure, and subtle edge cases. ;; ;; Requires: test-framework.sx, frames.sx, cek.sx loaded. ;; Helpers: cek-eval (source string → value via eval-expr-cek). ;; ========================================================================== ;; -------------------------------------------------------------------------- ;; 1. Deep nesting ;; -------------------------------------------------------------------------- (defsuite "cek-deep-nesting" (deftest "deeply nested let — 5 levels" ;; Each let layer adds a binding; innermost body sees all of them. (assert-equal 15 (cek-eval "(let ((a 1)) (let ((b 2)) (let ((c 3)) (let ((d 4)) (let ((e 5)) (+ a b c d e))))))"))) (deftest "deeply nested let — 7 levels with shadowing" ;; x is rebound at each level; innermost sees 7. (assert-equal 7 (cek-eval "(let ((x 1)) (let ((x 2)) (let ((x 3)) (let ((x 4)) (let ((x 5)) (let ((x 6)) (let ((x 7)) x)))))))"))) (deftest "deeply nested if — 5 levels" ;; All true branches taken; value propagates through every level. (assert-equal 42 (cek-eval "(if true (if true (if true (if true (if true 42 0) 0) 0) 0) 0)"))) (deftest "deeply nested if — alternating true/false reaching else" ;; Outer true → inner false → its else → next true → final value. (assert-equal "deep" (cek-eval "(if true (if false \"wrong\" (if true (if false \"also-wrong\" (if true \"deep\" \"no\")) \"bad\")) \"outer-else\")"))) (deftest "deeply nested function calls f(g(h(x)))" ;; Three composed single-arg functions: inc, double, square. ;; square(double(inc(3))) = square(double(4)) = square(8) = 64 (assert-equal 64 (cek-eval "(do (define inc-fn (fn (x) (+ x 1))) (define double-fn (fn (x) (* x 2))) (define square-fn (fn (x) (* x x))) (square-fn (double-fn (inc-fn 3))))"))) (deftest "5-level deeply nested function call chain" ;; f1(f2(f3(f4(f5(0))))) with each adding 10. (assert-equal 50 (cek-eval "(do (define f1 (fn (x) (+ x 10))) (define f2 (fn (x) (+ x 10))) (define f3 (fn (x) (+ x 10))) (define f4 (fn (x) (+ x 10))) (define f5 (fn (x) (+ x 10))) (f1 (f2 (f3 (f4 (f5 0))))))"))) (deftest "deep begin/do chain — 6 sequential expressions" ;; All expressions evaluated; last value returned. (assert-equal 60 (cek-eval "(do (define acc 0) (set! acc (+ acc 10)) (set! acc (+ acc 10)) (set! acc (+ acc 10)) (set! acc (+ acc 10)) (set! acc (+ acc 10)) (set! acc (+ acc 10)) acc)"))) (deftest "let inside if inside let inside cond" ;; cond dispatches → outer let binds → if selects → inner let computes. (assert-equal 30 (cek-eval "(let ((mode \"go\")) (cond (= mode \"stop\") -1 (= mode \"go\") (let ((base 10)) (if (> base 5) (let ((factor 3)) (* base factor)) 0)) :else 0))")))) ;; -------------------------------------------------------------------------- ;; 2. Complex call patterns ;; -------------------------------------------------------------------------- (defsuite "cek-complex-calls" (deftest "higher-order function returning higher-order function" ;; make-adder-factory returns a factory that makes adders. ;; Exercises three closure levels in the CEK call handler. (assert-equal 115 (cek-eval "(do (define make-adder-factory (fn (base) (fn (offset) (fn (x) (+ base offset x))))) (let ((factory (make-adder-factory 100))) (let ((add-10 (factory 10))) (add-10 5))))"))) (deftest "curried multiplication — 3 application levels" ;; ((mul a) b) c — each level returns a lambda. (assert-equal 60 (cek-eval "(do (define mul3 (fn (a) (fn (b) (fn (c) (* a b c))))) (((mul3 3) 4) 5))"))) (deftest "function applied to itself — omega-like (non-diverging)" ;; self-apply passes f to f; f ignores its argument and returns a value. ;; Tests that call dispatch handles (f f) correctly. (assert-equal "done" (cek-eval "(do (define self-apply (fn (f) (f f))) (define const-done (fn (anything) \"done\")) (self-apply const-done))"))) (deftest "Y-combinator-like: recursive factorial without define" ;; The Z combinator (strict Y) enables self-reference via argument. ;; Tests that CEK handles the double-application (f f) correctly. (assert-equal 120 (cek-eval "(do (define Z (fn (f) ((fn (x) (f (fn (v) ((x x) v)))) (fn (x) (f (fn (v) ((x x) v))))))) (define fact (Z (fn (self) (fn (n) (if (<= n 1) 1 (* n (self (- n 1)))))))) (fact 5))"))) (deftest "recursive tree traversal via nested lists" ;; A tree is a (value left right) triple or nil leaf. ;; Sum all leaf values: (3 (1 nil nil) (2 nil nil)) → 6. (assert-equal 6 (cek-eval "(do (define tree-sum (fn (node) (if (nil? node) 0 (let ((val (nth node 0)) (left (nth node 1)) (right (nth node 2))) (+ val (tree-sum left) (tree-sum right)))))) (let ((tree (list 3 (list 1 nil nil) (list 2 nil nil)))) (tree-sum tree)))"))) (deftest "mutual recursion through 3 functions" ;; f → g → h → f cycle, counting down to 0. ;; Tests that CEK handles cross-name call dispatch across 3 branches. (assert-equal "zero" (cek-eval "(do (define f (fn (n) (if (<= n 0) \"zero\" (g (- n 1))))) (define g (fn (n) (if (<= n 0) \"zero\" (h (- n 1))))) (define h (fn (n) (if (<= n 0) \"zero\" (f (- n 1))))) (f 9))"))) (deftest "higher-order composition pipeline" ;; A list of single-arg functions applied in sequence via reduce. ;; Tests map + reduce + closure interaction in a single CEK run. (assert-equal 30 (cek-eval "(do (define pipeline (fn (fns init) (reduce (fn (acc f) (f acc)) init fns))) (let ((steps (list (fn (x) (* x 2)) (fn (x) (+ x 5)) (fn (x) (* x 2))))) (pipeline steps 5)))"))) (deftest "variable-arity: function ignoring nil-padded extra args" ;; Caller provides more args than the param list; excess are ignored. ;; The CEK call frame must bind declared params and discard extras. (assert-equal 3 (cek-eval "(do (define first-two (fn (a b) (+ a b))) (first-two 1 2))")))) ;; -------------------------------------------------------------------------- ;; 3. Macro interaction ;; -------------------------------------------------------------------------- (defsuite "cek-macro-interaction" (deftest "macro that generates an if expression" ;; my-unless wraps its condition in (not ...) and emits an if. ;; CEK must expand the macro then step through the resulting if form. (assert-equal "ran" (cek-eval "(do (defmacro my-unless (cond-expr then-expr) \`(if (not ,cond-expr) ,then-expr nil)) (my-unless false \"ran\"))"))) (deftest "macro that generates a cond expression" ;; pick-label expands to a cond clause tree. (assert-equal "medium" (cek-eval "(do (defmacro classify-num (n) \`(cond (< ,n 0) \"negative\" (< ,n 10) \"small\" (< ,n 100) \"medium\" :else \"large\")) (classify-num 42))"))) (deftest "macro that generates let bindings" ;; bind-pair expands to a two-binding let wrapping its body. (assert-equal 7 (cek-eval "(do (defmacro bind-pair (a av b bv body) \`(let ((,a ,av) (,b ,bv)) ,body)) (bind-pair x 3 y 4 (+ x y)))"))) (deftest "macro inside macro expansion (chained expansion)" ;; outer-mac expands to a call of inner-mac, which is also a macro. ;; CEK must re-enter step-eval after each expansion. (assert-equal 20 (cek-eval "(do (defmacro double-it (x) \`(* ,x 2)) (defmacro quadruple-it (x) \`(double-it (double-it ,x))) (quadruple-it 5))"))) (deftest "macro with quasiquote and splice in complex position" ;; wrap-args splices its rest args into a list call. (assert-equal (list 1 2 3 4) (cek-eval "(do (defmacro wrap-args (&rest items) \`(list ,@items)) (wrap-args 1 2 3 4))"))) (deftest "macro generating a define" ;; defconst expands to a define, introducing a binding into env. (assert-equal 99 (cek-eval "(do (defmacro defconst (name val) \`(define ,name ,val)) (defconst answer 99) answer)"))) (deftest "macro used inside lambda body" ;; The macro is expanded each time the lambda is called. (assert-equal (list 2 4 6) (cek-eval "(do (defmacro double-it (x) \`(* 2 ,x)) (let ((double-fn (fn (n) (double-it n)))) (map double-fn (list 1 2 3))))"))) (deftest "nested macro call — macro output feeds another macro" ;; negate-add: (negate-add a b) → (- (+ a b)) ;; Expands in two macro steps; CEK must loop through both. (assert-equal -7 (cek-eval "(do (defmacro my-add (a b) \`(+ ,a ,b)) (defmacro negate-add (a b) \`(- (my-add ,a ,b))) (negate-add 3 4))")))) ;; -------------------------------------------------------------------------- ;; 4. Environment stress ;; -------------------------------------------------------------------------- (defsuite "cek-environment-stress" (deftest "10 bindings in a single let — all accessible" ;; One large let frame; CEK env-extend must handle all 10 at once. (assert-equal 55 (cek-eval "(let ((a 1) (b 2) (c 3) (d 4) (e 5) (f 6) (g 7) (h 8) (i 9) (j 10)) (+ a b c d e f g h i j))"))) (deftest "10 bindings — correct value for each binding" ;; Spot-check that the env frame stores each binding at the right slot. (assert-equal "ok" (cek-eval "(let ((v1 \"a\") (v2 \"b\") (v3 \"c\") (v4 \"d\") (v5 \"e\") (v6 \"f\") (v7 \"g\") (v8 \"h\") (v9 \"i\") (v10 \"j\")) (if (and (= v1 \"a\") (= v5 \"e\") (= v10 \"j\")) \"ok\" \"fail\"))"))) (deftest "shadowing chain — x shadows x shadows x (3 levels)" ;; After 3 let layers, x == 3; unwinding restores x at each level. ;; Inner let must not mutate the outer env frames. (assert-equal (list 3 2 1) (cek-eval "(let ((results (list))) (let ((x 1)) (let ((x 2)) (let ((x 3)) (append! results x)) ;; records 3 (append! results x)) ;; records 2 after inner unwinds (append! results x)) ;; records 1 after middle unwinds results)"))) (deftest "closure capturing 5 variables from enclosing let" ;; All 5 captured vars remain accessible after the let exits. (assert-equal 150 (cek-eval "(do (define make-closure (fn () (let ((a 10) (b 20) (c 30) (d 40) (e 50)) (fn () (+ a b c d e))))) (let ((f (make-closure))) (f)))"))) (deftest "set! visible through 3 closure levels" ;; Top-level define → lambda → lambda → lambda modifies top binding. ;; CEK set! must walk the env chain and find the outermost slot. (assert-equal 999 (cek-eval "(do (define shared 0) (define make-level1 (fn () (fn () (fn () (set! shared 999))))) (let ((level2 (make-level1))) (let ((level3 (level2))) (level3))) shared)"))) (deftest "define inside let inside define — scope chain" ;; outer define → let body → inner define. The inner define mutates ;; the env that the let body executes in; later exprs must see it. (assert-equal 42 (cek-eval "(do (define outer-fn (fn (base) (let ((step 1)) (define result (* base step)) (set! result (+ result 1)) result))) (outer-fn 41))"))) (deftest "env not polluted across sibling lambda calls" ;; Two separate calls to the same lambda must not share param state. (assert-equal (list 10 20) (cek-eval "(do (define f (fn (x) (* x 2))) (list (f 5) (f 10)))"))) (deftest "large closure env — 8 closed-over variables" ;; A lambda closing over 8 variables; all used in the body. (assert-equal 36 (cek-eval "(let ((a 1) (b 2) (c 3) (d 4) (e 5) (f 6) (g 7) (h 8)) (let ((sum-all (fn () (+ a b c d e f g h)))) (sum-all)))")))) ;; -------------------------------------------------------------------------- ;; 5. Edge cases ;; -------------------------------------------------------------------------- (defsuite "cek-edge-cases" (deftest "empty begin/do returns nil" ;; The step-sf-begin handler with an empty arg list must yield nil. (assert-nil (cek-eval "(do)"))) (deftest "single-expression begin/do returns value" ;; A do with exactly one expression is equivalent to that expression. (assert-equal 42 (cek-eval "(do 42)"))) (deftest "begin/do with side-effecting expressions returns last" ;; All intermediate expressions run; only the last value is kept. (assert-equal "last" (cek-eval "(do \"first\" \"middle\" \"last\")"))) (deftest "if with only true branch — false path returns nil" ;; No else clause: the make-if-frame must default else to nil. (assert-nil (cek-eval "(if false 42)"))) (deftest "if with only true branch — true path returns value" (assert-equal 7 (cek-eval "(if true 7)"))) (deftest "and with all truthy values returns last" ;; SX and: short-circuit stops at first falsy; last truthy is returned. (assert-equal "c" (cek-eval "(and \"a\" \"b\" \"c\")"))) (deftest "and with leading falsy short-circuits — returns false" (assert-false (cek-eval "(and 1 false 3)"))) (deftest "and with no args returns true" (assert-true (cek-eval "(and)"))) (deftest "or with all falsy returns last falsy" ;; SX or: if all falsy, the last falsy value is returned. (assert-false (cek-eval "(or false false false)"))) (deftest "or returns first truthy value" (assert-equal 1 (cek-eval "(or false nil 1 2 3)"))) (deftest "or with no args returns false" (assert-false (cek-eval "(or)"))) (deftest "keyword evaluated as string in call position" ;; A keyword in non-call position evaluates to its string name. (assert-equal "color" (cek-eval "(let ((k :color)) k)"))) (deftest "keyword as dict key in evaluation context" ;; Dict literal with keyword key; the keyword must be converted to ;; string so (get d \"color\") succeeds. (assert-equal "red" (cek-eval "(let ((d {:color \"red\"})) (get d \"color\"))"))) (deftest "quote preserves list structure — no evaluation inside" ;; (quote (+ 1 2)) must return the list (+ 1 2), not 3. (assert-equal 3 (cek-eval "(len (quote (+ 1 2)))"))) (deftest "quote preserves nested structure" ;; Deeply nested quoted form is returned verbatim as a list tree. (assert-equal 2 (cek-eval "(len (quote (a (b c))))"))) (deftest "quasiquote with nested unquote" ;; `(a ,(+ 1 2) c) → the list (a 3 c). (assert-equal 3 (cek-eval "(let ((x (+ 1 2))) (nth \`(a ,x c) 1))"))) (deftest "quasiquote with splice — list flattened into result" ;; `(1 ,@(list 2 3) 4) → (1 2 3 4). (assert-equal (list 1 2 3 4) (cek-eval "(let ((mid (list 2 3))) \`(1 ,@mid 4))"))) (deftest "quasiquote with nested unquote-splice at multiple positions" ;; Mixed literal and spliced elements across the template. (assert-equal (list 0 1 2 3 10 11 12 99) (cek-eval "(let ((xs (list 1 2 3)) (ys (list 10 11 12))) \`(0 ,@xs ,@ys 99))"))) (deftest "cond with no matching clause returns nil" ;; No branch taken, no :else → nil. (assert-nil (cek-eval "(cond false \"a\" false \"b\")"))) (deftest "nested cond: outer selects branch, inner dispatches value" ;; Two cond forms nested; CEK must handle the double-dispatch. (assert-equal "cold" (cek-eval "(let ((season \"winter\") (temp -5)) (cond (= season \"winter\") (cond (< temp 0) \"cold\" :else \"cool\") (= season \"summer\") \"hot\" :else \"mild\"))"))) (deftest "lambda with no params — nullary function" ;; () → 42 via CEK call dispatch with empty arg list. (assert-equal 42 (cek-eval "((fn () 42))"))) (deftest "immediately invoked lambda with multiple body forms" ;; IIFE with a do-style body; last expression is the value. (assert-equal 6 (cek-eval "((fn () (define a 1) (define b 2) (define c 3) (+ a b c)))"))) (deftest "thread-first through 5 steps" ;; (-> 1 (+ 1) (* 3) (+ 1) (* 2) (- 2)) ;; 1+1=2, *3=6, +1=7, *2=14, 14-2=12 ;; Tests that each -> step creates the correct frame and threads value. (assert-equal 12 (cek-eval "(-> 1 (+ 1) (* 3) (+ 1) (* 2) (- 2))"))) (deftest "case falls through to :else" (assert-equal "unknown" (cek-eval "(case 99 1 \"one\" 2 \"two\" :else \"unknown\")"))) (deftest "case with no :else and no match returns nil" (assert-nil (cek-eval "(case 99 1 \"one\" 2 \"two\")"))) (deftest "when with multiple body forms returns last" (assert-equal "last" (cek-eval "(when true \"first\" \"middle\" \"last\")"))) (deftest "when false body not evaluated — no side effects" (assert-equal 0 (cek-eval "(do (define side-ct 0) (when false (set! side-ct 1)) side-ct)"))) (deftest "define followed by symbol lookup returns bound value" ;; define evaluates its RHS and returns the value. ;; The subsequent symbol reference must find the binding in env. (assert-equal 7 (cek-eval "(do (define q 7) q)"))) (deftest "set! in deeply nested scope updates the correct frame" ;; set! inside a 4-level let must find the binding defined at level 1. (assert-equal 100 (cek-eval "(let ((target 0)) (let ((a 1)) (let ((b 2)) (let ((c 3)) (set! target 100)))) target)"))) (deftest "list literal (non-call) evaluated element-wise" ;; A list whose head is a number — treated as data list, not a call. ;; All elements are evaluated; numbers pass through unchanged. (assert-equal 3 (cek-eval "(len (list 10 20 30))"))) (deftest "recursive fibonacci — tests non-tail call frame stacking" ;; fib(7) = 13. Non-tail recursion stacks O(n) CEK frames; tests ;; that the continuation frame list handles deep frame accumulation. (assert-equal 13 (cek-eval "(do (define fib (fn (n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))) (fib 7))")))) ;; -------------------------------------------------------------------------- ;; 8. Data-first higher-order forms ;; -------------------------------------------------------------------------- (defsuite "data-first-ho" (deftest "map — data-first arg order" (assert-equal (list 2 4 6) (map (list 1 2 3) (fn (x) (* x 2))))) (deftest "filter — data-first arg order" (assert-equal (list 3 4 5) (filter (list 1 2 3 4 5) (fn (x) (> x 2))))) (deftest "reduce — data-first arg order" (assert-equal 10 (reduce (list 1 2 3 4) + 0))) (deftest "some — data-first arg order" (assert-true (some (list 1 2 3) (fn (x) (> x 2)))) (assert-false (some (list 1 2 3) (fn (x) (> x 5))))) (deftest "every? — data-first arg order" (assert-true (every? (list 2 4 6) (fn (x) (> x 1)))) (assert-false (every? (list 2 4 6) (fn (x) (> x 3))))) (deftest "for-each — data-first arg order" (let ((acc (list))) (for-each (list 10 20 30) (fn (x) (set! acc (append acc (list x))))) (assert-equal (list 10 20 30) acc))) (deftest "map-indexed — data-first arg order" (assert-equal (list "0:a" "1:b" "2:c") (map-indexed (list "a" "b" "c") (fn (i v) (str i ":" v))))) (deftest "fn-first still works — map" (assert-equal (list 2 4 6) (map (fn (x) (* x 2)) (list 1 2 3)))) (deftest "fn-first still works — reduce" (assert-equal 10 (reduce + 0 (list 1 2 3 4))))) ;; -------------------------------------------------------------------------- ;; 9. Threading with HO forms ;; -------------------------------------------------------------------------- (defsuite "thread-ho" (deftest "-> map" (assert-equal (list 2 4 6) (-> (list 1 2 3) (map (fn (x) (* x 2)))))) (deftest "-> filter" (assert-equal (list 3 4 5) (-> (list 1 2 3 4 5) (filter (fn (x) (> x 2)))))) (deftest "-> filter then map pipeline" (assert-equal (list 30 40 50) (-> (list 1 2 3 4 5) (filter (fn (x) (> x 2))) (map (fn (x) (* x 10)))))) (deftest "-> reduce" (assert-equal 15 (-> (list 1 2 3 4 5) (reduce + 0)))) (deftest "-> map then reduce" (assert-equal 12 (-> (list 1 2 3) (map (fn (x) (* x 2))) (reduce + 0)))) (deftest "-> some" (assert-true (-> (list 1 2 3) (some (fn (x) (> x 2))))) (assert-false (-> (list 1 2 3) (some (fn (x) (> x 5)))))) (deftest "-> every?" (assert-true (-> (list 2 4 6) (every? (fn (x) (> x 1)))))) (deftest "-> full pipeline: map filter reduce" ;; Double each, keep > 4, sum (assert-equal 24 (-> (list 1 2 3 4 5) (map (fn (x) (* x 2))) (filter (fn (x) (> x 4))) (reduce + 0)))))