;; ========================================================================== ;; test-advanced.sx — Tests for advanced evaluation patterns ;; ;; Requires: test-framework.sx loaded first. ;; Modules tested: eval.sx (nested forms, higher-order patterns, define, ;; quasiquote, thread-first, letrec, case/cond) ;; ========================================================================== ;; -------------------------------------------------------------------------- ;; Nested special forms ;; -------------------------------------------------------------------------- (defsuite "nested-special-forms" (deftest "let inside let" (let ((x 1)) (let ((y (let ((z 10)) (+ x z)))) (assert-equal 11 y)))) (deftest "if inside let" (let ((flag true) (result (if true "yes" "no"))) (assert-equal "yes" result)) (let ((result (if false "yes" "no"))) (assert-equal "no" result))) (deftest "let inside if" (assert-equal 15 (if true (let ((a 5) (b 10)) (+ a b)) 0)) (assert-equal 0 (if false 99 (let ((x 0)) x)))) (deftest "cond inside let" (let ((n 2) (label (cond (= 2 1) "one" (= 2 2) "two" :else "other"))) (assert-equal "two" label))) (deftest "when inside when (nested conditional)" ;; Inner when only runs when outer when runs (let ((result "none")) (when true (when true (set! result "both"))) (assert-equal "both" result)) (let ((result "none")) (when true (when false (set! result "inner"))) (assert-equal "none" result)) (let ((result "none")) (when false (when true (set! result "inner"))) (assert-equal "none" result))) (deftest "do inside let body" (let ((x 0)) (do (set! x (+ x 1)) (set! x (+ x 1)) (set! x (+ x 1))) (assert-equal 3 x))) (deftest "let inside map callback" ;; Each map iteration creates its own let scope (let ((result (map (fn (x) (let ((doubled (* x 2)) (label (str "item-" x))) (str label "=" doubled))) (list 1 2 3)))) (assert-equal "item-1=2" (nth result 0)) (assert-equal "item-2=4" (nth result 1)) (assert-equal "item-3=6" (nth result 2))))) ;; -------------------------------------------------------------------------- ;; Higher-order patterns ;; -------------------------------------------------------------------------- (defsuite "higher-order-patterns" (deftest "map then filter (pipeline)" ;; Double each number, then keep only those > 4 (let ((result (filter (fn (x) (> x 4)) (map (fn (x) (* x 2)) (list 1 2 3 4 5))))) (assert-equal (list 6 8 10) result))) (deftest "filter then map" ;; Keep odd numbers, then square them (let ((result (map (fn (x) (* x x)) (filter (fn (x) (= (mod x 2) 1)) (list 1 2 3 4 5))))) (assert-equal (list 1 9 25) result))) (deftest "reduce to build a dict" ;; Build a word-length dict from a list of strings (let ((result (reduce (fn (acc s) (assoc acc s (string-length s))) {} (list "a" "bb" "ccc")))) (assert-equal 1 (get result "a")) (assert-equal 2 (get result "bb")) (assert-equal 3 (get result "ccc")))) (deftest "map returning lambdas, then calling them" ;; Produce a list of adder functions; call each with 10 (let ((adders (map (fn (n) (fn (x) (+ n x))) (list 1 2 3))) (results (list))) (for-each (fn (f) (append! results (f 10))) adders) (assert-equal (list 11 12 13) results))) (deftest "nested map (map of map)" (let ((matrix (list (list 1 2) (list 3 4) (list 5 6))) (result (map (fn (row) (map (fn (x) (* x 10)) row)) matrix))) (assert-equal (list 10 20) (nth result 0)) (assert-equal (list 30 40) (nth result 1)) (assert-equal (list 50 60) (nth result 2)))) (deftest "for-each with side effect (set! counter)" (define fe-counter 0) (for-each (fn (x) (set! fe-counter (+ fe-counter x))) (list 1 2 3 4 5)) ;; 1+2+3+4+5 = 15 (assert-equal 15 fe-counter))) ;; -------------------------------------------------------------------------- ;; Define patterns ;; -------------------------------------------------------------------------- (defsuite "define-patterns" (deftest "define inside let body" ;; define inside a let body is visible in subsequent let body expressions (let ((x 5)) (define y (* x 2)) (assert-equal 10 y))) (deftest "define inside do block" (do (define do-val 42) (assert-equal 42 do-val))) (deftest "define function then call it" (define square (fn (n) (* n n))) (assert-equal 9 (square 3)) (assert-equal 25 (square 5)) (assert-equal 0 (square 0))) (deftest "redefine a name (second define overwrites first)" (define redef-x 1) (assert-equal 1 redef-x) (define redef-x 99) (assert-equal 99 redef-x)) (deftest "define with computed value" (define base 7) (define derived (* base 6)) (assert-equal 42 derived))) ;; -------------------------------------------------------------------------- ;; Quasiquote advanced ;; -------------------------------------------------------------------------- (defsuite "quasiquote-advanced" (deftest "quasiquote with multiple unquotes" (let ((a 1) (b 2) (c 3)) (assert-equal (list 1 2 3) `(,a ,b ,c)) (assert-equal (list 10 2 30) `(,(* a 10) ,b ,(* c 10))))) (deftest "unquote-splicing at start of list" (let ((prefix (list 1 2 3))) (assert-equal (list 1 2 3 4 5) `(,@prefix 4 5)))) (deftest "unquote-splicing at end of list" (let ((suffix (list 3 4 5))) (assert-equal (list 1 2 3 4 5) `(1 2 ,@suffix)))) (deftest "unquote inside nested list" (let ((x 42)) ;; The inner list contains an unquote — it should still be spliced (let ((result `(a (b ,x) c))) (assert-length 3 result) (assert-equal 42 (nth (nth result 1) 1))))) (deftest "quasiquote preserving structure" ;; A quasiquoted form with no unquotes is identical to the quoted form (let ((q `(fn (a b) (+ a b)))) (assert-type "list" q) (assert-length 3 q) ;; First element is the symbol fn (assert-true (equal? (sx-parse-one "fn") (first q))) ;; Body is (+ a b) — a 3-element list (assert-length 3 (nth q 2))))) ;; -------------------------------------------------------------------------- ;; Thread-first ;; -------------------------------------------------------------------------- (defsuite "thread-first" (deftest "simple thread through arithmetic" ;; (-> 5 (+ 1) (* 2)) = (5+1)*2 = 12 (assert-equal 12 (-> 5 (+ 1) (* 2)))) (deftest "thread with string ops" (assert-equal "HELLO" (-> "hello" upcase)) (assert-equal "hello" (-> "HELLO" downcase))) (deftest "thread with multiple steps" ;; (-> 1 (+ 1) (+ 1) (+ 1) (+ 1)) = 5 (assert-equal 5 (-> 1 (+ 1) (+ 1) (+ 1) (+ 1))) ;; (-> 100 (- 10) (/ 2) (+ 5)) = (100-10)/2+5 = 50 (assert-equal 50 (-> 100 (- 10) (/ 2) (+ 5)))) (deftest "thread through list ops" ;; Build list, reverse, take first (assert-equal 3 (-> (list 1 2 3) reverse first)) ;; Append then get length (assert-equal 5 (-> (list 1 2 3) (append (list 4 5)) len)))) ;; -------------------------------------------------------------------------- ;; letrec ;; -------------------------------------------------------------------------- (defsuite "letrec" (deftest "simple letrec with self-reference" ;; A single binding that calls itself recursively (letrec ((count-down (fn (n) (if (<= n 0) "done" (count-down (- n 1)))))) (assert-equal "done" (count-down 5)))) (deftest "mutual recursion in letrec" (letrec ((my-even? (fn (n) (if (= n 0) true (my-odd? (- n 1))))) (my-odd? (fn (n) (if (= n 0) false (my-even? (- n 1)))))) (assert-true (my-even? 4)) (assert-false (my-even? 3)) (assert-true (my-odd? 3)) (assert-false (my-odd? 4)))) (deftest "letrec fibonacci" (letrec ((fib (fn (n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))))) (assert-equal 0 (fib 0)) (assert-equal 1 (fib 1)) (assert-equal 1 (fib 2)) (assert-equal 8 (fib 6)) (assert-equal 55 (fib 10)))) (deftest "letrec with non-recursive values too" ;; letrec can hold plain values alongside recursive fns (letrec ((base 10) (triple (fn (n) (* n 3))) (result (fn () (triple base)))) (assert-equal 10 base) (assert-equal 6 (triple 2)) (assert-equal 30 (result))))) ;; -------------------------------------------------------------------------- ;; case and cond ;; -------------------------------------------------------------------------- (defsuite "case-cond" (deftest "case with string matching" (define color-label (fn (c) (case c "red" "warm" "blue" "cool" "green" "natural" :else "unknown"))) (assert-equal "warm" (color-label "red")) (assert-equal "cool" (color-label "blue")) (assert-equal "natural" (color-label "green")) (assert-equal "unknown" (color-label "purple"))) (deftest "case with number matching" (define grade (fn (n) (case n 1 "one" 2 "two" 3 "three" :else "many"))) (assert-equal "one" (grade 1)) (assert-equal "two" (grade 2)) (assert-equal "three" (grade 3)) (assert-equal "many" (grade 99))) (deftest "case :else fallthrough" (assert-equal "fallback" (case "unrecognised" "a" "alpha" "b" "beta" :else "fallback"))) (deftest "case no match returns nil" (assert-nil (case "x" "a" "alpha" "b" "beta"))) (deftest "cond with multiple predicates" (define classify (fn (n) (cond (< n 0) "negative" (= n 0) "zero" (< n 10) "small" :else "large"))) (assert-equal "negative" (classify -5)) (assert-equal "zero" (classify 0)) (assert-equal "small" (classify 7)) (assert-equal "large" (classify 100))) (deftest "cond with (= x val) predicate style" (let ((x "b")) (assert-equal "beta" (cond (= x "a") "alpha" (= x "b") "beta" (= x "c") "gamma" :else "other")))) (deftest "cond :else" (assert-equal "default" (cond false "nope" false "also-nope" :else "default"))) (deftest "cond all false returns nil" (assert-nil (cond false "a" false "b" false "c"))) (deftest "nested cond/case" ;; cond selects a branch, that branch uses case (define describe (fn (kind val) (cond (= kind "color") (case val "r" "red" "g" "green" "b" "blue" :else "unknown-color") (= kind "size") (case val "s" "small" "l" "large" :else "unknown-size") :else "unknown-kind"))) (assert-equal "red" (describe "color" "r")) (assert-equal "green" (describe "color" "g")) (assert-equal "unknown-color" (describe "color" "x")) (assert-equal "small" (describe "size" "s")) (assert-equal "large" (describe "size" "l")) (assert-equal "unknown-kind" (describe "other" "?"))))