Files
rose-ash/spec/tests/test-eval.sx
giles 036022cc17 spec: rational numbers — 1/3 literals, arithmetic, numeric tower integration
SxRational type in OCaml (Rational of int * int, stored reduced, denom>0)
and JS (SxRational class with _rational marker). n/d reader syntax in
spec/parser.sx. Arithmetic contagion: int op rational → rational, rational
op float → float. JS keeps int/int → float for CSS backward compatibility.
OCaml as_number + safe_eq extended for cross-type rational equality so
(= 2.5 5/2) → true. 62 tests in test-rationals.sx, all pass.
JS: 2232 passed. OCaml: 4532 passed (+11 vs pre-fix baseline).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 17:27:27 +00:00

625 lines
16 KiB
Plaintext

;; ==========================================================================
;; test-eval.sx — Tests for the core evaluator and primitives
;;
;; Requires: test-framework.sx loaded first.
;; Modules tested: eval.sx, primitives.sx
;; ==========================================================================
;; --------------------------------------------------------------------------
;; Literals and types
;; --------------------------------------------------------------------------
(defsuite
"literals"
(deftest
"numbers are numbers"
(assert-type "number" 42)
(assert-type "number" 3.14)
(assert-type "number" -1))
(deftest
"strings are strings"
(assert-type "string" "hello")
(assert-type "string" ""))
(deftest
"booleans are booleans"
(assert-type "boolean" true)
(assert-type "boolean" false))
(deftest "nil is nil" (assert-type "nil" nil) (assert-nil nil))
(deftest
"lists are lists"
(assert-type "list" (list 1 2 3))
(assert-type "list" (list)))
(deftest "dicts are dicts" (assert-type "dict" {:b 2 :a 1})))
;; --------------------------------------------------------------------------
;; Arithmetic
;; --------------------------------------------------------------------------
(defsuite
"arithmetic"
(deftest
"addition"
(assert-equal 3 (+ 1 2))
(assert-equal 0 (+ 0 0))
(assert-equal -1 (+ 1 -2))
(assert-equal 10 (+ 1 2 3 4)))
(deftest
"subtraction"
(assert-equal 1 (- 3 2))
(assert-equal -1 (- 2 3)))
(deftest
"multiplication"
(assert-equal 6 (* 2 3))
(assert-equal 0 (* 0 100))
(assert-equal 24 (* 1 2 3 4)))
(deftest
"division"
(assert-equal 2 (/ 6 3))
(assert-equal 2.5 (/ 5 2)))
(deftest
"modulo"
(assert-equal 1 (mod 7 3))
(assert-equal 0 (mod 6 3))))
;; --------------------------------------------------------------------------
;; Comparison
;; --------------------------------------------------------------------------
(defsuite
"comparison"
(deftest
"equality"
(assert-true (= 1 1))
(assert-false (= 1 2))
(assert-true (= "a" "a"))
(assert-false (= "a" "b")))
(deftest
"deep equality"
(assert-true
(equal?
(list 1 2 3)
(list 1 2 3)))
(assert-false
(equal? (list 1 2) (list 1 3)))
(assert-true (equal? {:a 1} {:a 1}))
(assert-false (equal? {:a 1} {:a 2})))
(deftest
"ordering"
(assert-true (< 1 2))
(assert-false (< 2 1))
(assert-true (> 2 1))
(assert-true (<= 1 1))
(assert-true (<= 1 2))
(assert-true (>= 2 2))
(assert-true (>= 3 2))))
;; --------------------------------------------------------------------------
;; String operations
;; --------------------------------------------------------------------------
(defsuite
"strings"
(deftest
"str concatenation"
(assert-equal "abc" (str "a" "b" "c"))
(assert-equal "hello world" (str "hello" " " "world"))
(assert-equal "42" (str 42))
(assert-equal "" (str)))
(deftest
"string-length"
(assert-equal 5 (string-length "hello"))
(assert-equal 0 (string-length "")))
(deftest
"substring"
(assert-equal "ell" (substring "hello" 1 4))
(assert-equal "hello" (substring "hello" 0 5)))
(deftest
"string-contains?"
(assert-true (string-contains? "hello world" "world"))
(assert-false (string-contains? "hello" "xyz")))
(deftest
"upcase and downcase"
(assert-equal "HELLO" (upcase "hello"))
(assert-equal "hello" (downcase "HELLO")))
(deftest
"trim"
(assert-equal "hello" (trim " hello "))
(assert-equal "hello" (trim "hello")))
(deftest
"split and join"
(assert-equal (list "a" "b" "c") (split "a,b,c" ","))
(assert-equal "a-b-c" (join "-" (list "a" "b" "c")))))
;; --------------------------------------------------------------------------
;; List operations
;; --------------------------------------------------------------------------
(defsuite
"lists"
(deftest
"constructors"
(assert-equal
(list 1 2 3)
(list 1 2 3))
(assert-equal (list) (list))
(assert-length 3 (list 1 2 3)))
(deftest
"first and rest"
(assert-equal 1 (first (list 1 2 3)))
(assert-equal
(list 2 3)
(rest (list 1 2 3)))
(assert-nil (first (list)))
(assert-equal (list) (rest (list))))
(deftest
"nth"
(assert-equal
1
(nth (list 1 2 3) 0))
(assert-equal
2
(nth (list 1 2 3) 1))
(assert-equal
3
(nth (list 1 2 3) 2)))
(deftest
"last"
(assert-equal 3 (last (list 1 2 3)))
(assert-nil (last (list))))
(deftest
"cons and append"
(assert-equal
(list 0 1 2)
(cons 0 (list 1 2)))
(assert-equal
(list 1 2 3 4)
(append (list 1 2) (list 3 4))))
(deftest
"reverse"
(assert-equal
(list 3 2 1)
(reverse (list 1 2 3)))
(assert-equal (list) (reverse (list))))
(deftest
"empty?"
(assert-true (empty? (list)))
(assert-false (empty? (list 1))))
(deftest
"len"
(assert-equal 0 (len (list)))
(assert-equal 3 (len (list 1 2 3))))
(deftest
"contains?"
(assert-true
(contains? (list 1 2 3) 2))
(assert-false
(contains? (list 1 2 3) 4)))
(deftest
"flatten"
(assert-equal
(list 1 2 3 4)
(flatten
(list (list 1 2) (list 3 4))))))
;; --------------------------------------------------------------------------
;; Dict operations
;; --------------------------------------------------------------------------
(defsuite
"dicts"
(deftest
"dict literal"
(assert-type "dict" {:b 2 :a 1})
(assert-equal 1 (get {:a 1} "a"))
(assert-equal 2 (get {:b 2 :a 1} "b")))
(deftest
"assoc"
(assert-equal {:b 2 :a 1} (assoc {:a 1} "b" 2))
(assert-equal {:a 99} (assoc {:a 1} "a" 99)))
(deftest "dissoc" (assert-equal {:b 2} (dissoc {:b 2 :a 1} "a")))
(deftest
"keys and vals"
(let
((d {:b 2 :a 1}))
(assert-length 2 (keys d))
(assert-length 2 (vals d))
(assert-contains "a" (keys d))
(assert-contains "b" (keys d))))
(deftest
"has-key?"
(assert-true (has-key? {:a 1} "a"))
(assert-false (has-key? {:a 1} "b")))
(deftest
"merge"
(assert-equal {:c 3 :b 2 :a 1} (merge {:b 2 :a 1} {:c 3}))
(assert-equal {:b 2 :a 99} (merge {:b 2 :a 1} {:a 99}))))
;; --------------------------------------------------------------------------
;; Predicates
;; --------------------------------------------------------------------------
(defsuite
"predicates"
(deftest
"nil?"
(assert-true (nil? nil))
(assert-false (nil? 0))
(assert-false (nil? false))
(assert-false (nil? "")))
(deftest
"number?"
(assert-true (number? 42))
(assert-true (number? 3.14))
(assert-false (number? "42")))
(deftest
"string?"
(assert-true (string? "hello"))
(assert-false (string? 42)))
(deftest
"list?"
(assert-true (list? (list 1 2)))
(assert-false (list? "not a list")))
(deftest
"dict?"
(assert-true (dict? {:a 1}))
(assert-false (dict? (list 1))))
(deftest
"boolean?"
(assert-true (boolean? true))
(assert-true (boolean? false))
(assert-false (boolean? nil))
(assert-false (boolean? 0)))
(deftest
"not"
(assert-true (not false))
(assert-true (not nil))
(assert-false (not true))
(assert-false (not 1))
(assert-false (not "x"))))
;; --------------------------------------------------------------------------
;; Special forms
;; --------------------------------------------------------------------------
(defsuite
"special-forms"
(deftest
"if"
(assert-equal "yes" (if true "yes" "no"))
(assert-equal "no" (if false "yes" "no"))
(assert-equal "no" (if nil "yes" "no"))
(assert-nil (if false "yes")))
(deftest
"when"
(assert-equal "yes" (when true "yes"))
(assert-nil (when false "yes")))
(deftest
"cond"
(assert-equal "a" (cond true "a" :else "b"))
(assert-equal "b" (cond false "a" :else "b"))
(assert-equal "c" (cond false "a" false "b" :else "c")))
(deftest
"cond with 2-element predicate as first test"
(assert-equal 0 (cond (nil? nil) 0 :else 1))
(assert-equal 1 (cond (nil? "x") 0 :else 1))
(assert-equal "empty" (cond (empty? (list)) "empty" :else "not-empty"))
(assert-equal
"not-empty"
(cond (empty? (list 1)) "empty" :else "not-empty"))
(assert-equal "yes" (cond (not false) "yes" :else "no"))
(assert-equal "no" (cond (not true) "yes" :else "no")))
(deftest
"cond with 2-element predicate and no :else"
(assert-equal "found" (cond (nil? nil) "found" (nil? "x") "other"))
(assert-equal "b" (cond (nil? "x") "a" (not false) "b")))
(deftest
"and"
(assert-true (and true true))
(assert-false (and true false))
(assert-false (and false true))
(assert-equal 3 (and 1 2 3)))
(deftest
"or"
(assert-equal 1 (or 1 2))
(assert-equal 2 (or false 2))
(assert-equal "fallback" (or nil false "fallback"))
(assert-false (or false false)))
(deftest
"let"
(assert-equal
3
(let ((x 1) (y 2)) (+ x y)))
(assert-equal
"hello world"
(let ((a "hello") (b " world")) (str a b))))
(deftest
"let clojure-style"
(assert-equal 3 (let (x 1 y 2) (+ x y))))
(deftest
"do / begin"
(assert-equal 3 (do 1 2 3))
(assert-equal "last" (begin "first" "middle" "last")))
(deftest "define" (define x 42) (assert-equal 42 x))
(deftest
"set!"
(define x 1)
(set! x 2)
(assert-equal 2 x)))
;; --------------------------------------------------------------------------
;; Lambda and closures
;; --------------------------------------------------------------------------
(defsuite
"lambdas"
(deftest
"basic lambda"
(let
((add (fn (a b) (+ a b))))
(assert-equal 3 (add 1 2))))
(deftest
"closure captures env"
(let
((x 10))
(let
((add-x (fn (y) (+ x y))))
(assert-equal 15 (add-x 5)))))
(deftest
"lambda as argument"
(assert-equal
(list 2 4 6)
(map
(fn (x) (* x 2))
(list 1 2 3))))
(deftest
"recursive lambda via define"
(define
factorial
(fn
(n)
(if
(<= n 1)
1
(* n (factorial (- n 1))))))
(assert-equal 120 (factorial 5)))
(deftest
"higher-order returns lambda"
(let
((make-adder (fn (n) (fn (x) (+ n x)))))
(let
((add5 (make-adder 5)))
(assert-equal 8 (add5 3)))))
(deftest
"multi-body lambda returns last value"
(let
((f (fn (x) (+ x 1) (+ x 2) (+ x 3))))
(assert-equal 13 (f 10))))
(deftest
"multi-body lambda side effects via dict mutation"
(let
((state (dict "a" 0 "b" 0)))
(let
((f (fn () (dict-set! state "a" 1) (dict-set! state "b" 2) "done")))
(assert-equal "done" (f))
(assert-equal 1 (get state "a"))
(assert-equal 2 (get state "b")))))
(deftest
"multi-body lambda two expressions"
(assert-equal
20
((fn (x) (+ x 1) (* x 2)) 10))
(assert-equal 42 ((fn () (+ 1 2) 42)))))
;; --------------------------------------------------------------------------
;; Higher-order forms
;; --------------------------------------------------------------------------
(defsuite
"higher-order"
(deftest
"map"
(assert-equal
(list 2 4 6)
(map
(fn (x) (* x 2))
(list 1 2 3)))
(assert-equal (list) (map (fn (x) x) (list))))
(deftest
"filter"
(assert-equal
(list 2 4)
(filter
(fn (x) (= (mod x 2) 0))
(list 1 2 3 4)))
(assert-equal
(list)
(filter (fn (x) false) (list 1 2 3))))
(deftest
"reduce"
(assert-equal
10
(reduce
(fn (acc x) (+ acc x))
0
(list 1 2 3 4)))
(assert-equal
0
(reduce (fn (acc x) (+ acc x)) 0 (list))))
(deftest
"some"
(assert-true
(some
(fn (x) (> x 3))
(list 1 2 3 4 5)))
(assert-false
(some
(fn (x) (> x 10))
(list 1 2 3))))
(deftest
"every?"
(assert-true
(every?
(fn (x) (> x 0))
(list 1 2 3)))
(assert-false
(every?
(fn (x) (> x 2))
(list 1 2 3))))
(deftest
"map-indexed"
(assert-equal
(list "0:a" "1:b" "2:c")
(map-indexed (fn (i x) (str i ":" x)) (list "a" "b" "c")))))
;; --------------------------------------------------------------------------
;; Components
;; --------------------------------------------------------------------------
(defsuite
"components"
(deftest
"defcomp creates component"
(defcomp ~test-comp (&key title) (div title))
(assert-true (not (nil? ~test-comp))))
(deftest
"component renders with keyword args"
(defcomp ~greeting (&key name) (span (str "Hello, " name "!")))
(assert-true (not (nil? ~greeting))))
(deftest
"component with children"
(defcomp ~box (&key &rest children) (div :class "box" children))
(assert-true (not (nil? ~box))))
(deftest
"component with default via or"
(defcomp ~label (&key text) (span (or text "default")))
(assert-true (not (nil? ~label))))
(deftest
"defcomp default affinity is auto"
(defcomp ~aff-default (&key x) (div x))
(assert-equal "auto" (component-affinity ~aff-default)))
(deftest
"defcomp affinity client"
(defcomp ~aff-client (&key x) :affinity :client (div x))
(assert-equal "client" (component-affinity ~aff-client)))
(deftest
"defcomp affinity server"
(defcomp ~aff-server (&key x) :affinity :server (div x))
(assert-equal "server" (component-affinity ~aff-server)))
(deftest
"defcomp affinity preserves body"
(defcomp ~aff-body (&key val) :affinity :client (span val))
(assert-equal "client" (component-affinity ~aff-body))
(assert-true (not (nil? ~aff-body)))))
;; --------------------------------------------------------------------------
;; Macros
;; --------------------------------------------------------------------------
(defsuite
"macros"
(deftest
"defmacro creates macro"
(defmacro
unless
(cond &rest body)
(quasiquote (if (not (unquote cond)) (do (splice-unquote body)))))
(assert-equal "yes" (unless false "yes"))
(assert-nil (unless true "no")))
(deftest
"quasiquote and unquote"
(let
((x 42))
(assert-equal
(list 1 42 3)
(quasiquote (1 (unquote x) 3)))))
(deftest
"splice-unquote"
(let
((xs (list 2 3 4)))
(assert-equal
(list 1 2 3 4 5)
(quasiquote (1 (splice-unquote xs) 5))))))
;; --------------------------------------------------------------------------
;; Threading macro
;; --------------------------------------------------------------------------
(defsuite
"threading"
(deftest
"thread-first"
(assert-equal 8 (-> 5 (+ 1) (+ 2)))
(assert-equal "HELLO" (-> "hello" upcase))
(assert-equal "HELLO WORLD" (-> "hello" (str " world") upcase))))
;; --------------------------------------------------------------------------
;; Truthiness
;; --------------------------------------------------------------------------
(defsuite
"truthiness"
(deftest
"truthy values"
(assert-true (if 1 true false))
(assert-true (if "x" true false))
(assert-true (if (list 1) true false))
(assert-true (if true true false)))
(deftest
"falsy values"
(assert-false (if false true false))
(assert-false (if nil true false))))
;; --------------------------------------------------------------------------
;; Edge cases and regression tests
;; --------------------------------------------------------------------------
(defsuite
"edge-cases"
(deftest
"nested let scoping"
(let
((x 1))
(let ((x 2)) (assert-equal 2 x))))
(deftest
"recursive map"
(assert-equal
(list (list 2 4) (list 6 8))
(map
(fn (sub) (map (fn (x) (* x 2)) sub))
(list (list 1 2) (list 3 4)))))
(deftest
"keyword as value"
(assert-equal "class" :class)
(assert-equal "id" :id))
(deftest
"dict with evaluated values"
(let ((x 42)) (assert-equal 42 (get {:val x} "val"))))
(deftest
"nil propagation"
(assert-nil (get {:a 1} "missing"))
(assert-equal "default" (or (get {:a 1} "missing") "default")))
(deftest
"empty operations"
(assert-equal (list) (map (fn (x) x) (list)))
(assert-equal (list) (filter (fn (x) true) (list)))
(assert-equal
0
(reduce (fn (acc x) (+ acc x)) 0 (list)))
(assert-equal 0 (len (list)))
(assert-equal "" (str))))