Files
rose-ash/spec/tests/test-advanced.sx
giles 8f146cc810 Add strict typing mode + 139 new tests: 749/749 passing
Strict mode (spec/eval.sx):
- *strict* flag, set-strict!, set-prim-param-types!
- value-matches-type? checks values against declared types
- strict-check-args validates primitive call args at runtime
- Injected into eval-call before apply — zero cost when off
- Supports positional params, rest-type, nullable ("string?")

New test files:
- test-strict.sx (25): value-matches-type?, toggle, 12 type error cases
- test-errors.sx (74): undefined symbols, arity, permissive coercion,
  strict type mismatches, nil/empty edge cases, number edge cases,
  string edge cases, recursion patterns
- test-advanced.sx (39): nested special forms, higher-order patterns,
  define patterns, quasiquote advanced, thread-first, letrec, case/cond

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 12:12:48 +00:00

372 lines
12 KiB
Plaintext

;; ==========================================================================
;; 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" "?"))))