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>
This commit is contained in:
@@ -14,9 +14,10 @@
|
||||
;; list → '(' expr* ')'
|
||||
;; vector → '[' expr* ']' (sugar for list)
|
||||
;; map → '{' (key expr)* '}'
|
||||
;; atom → string | number | keyword | symbol | boolean | nil | char
|
||||
;; atom → string | number | rational | keyword | symbol | boolean | nil | char
|
||||
;; string → '"' (char | escape)* '"'
|
||||
;; number → '-'? digit+ ('.' digit+)? ([eE] [+-]? digit+)?
|
||||
;; rational → integer '/' digit+
|
||||
;; keyword → ':' ident
|
||||
;; symbol → ident
|
||||
;; boolean → 'true' | 'false'
|
||||
@@ -46,6 +47,7 @@
|
||||
;; (make-keyword name) → Keyword value
|
||||
;; (escape-string s) → string with " and \ escaped for serialization
|
||||
;; (make-char n) → Char value from Unicode codepoint
|
||||
;; (make-rational n d) → Rational value (auto-reduced by GCD)
|
||||
;; (char->integer c) → Unicode codepoint of char c
|
||||
;; (char-from-code n) → single-char string from codepoint
|
||||
;; (char-code s) → codepoint of first char in string s
|
||||
@@ -210,22 +212,42 @@
|
||||
(set! pos (inc pos))
|
||||
(read-digits))))
|
||||
(read-digits)
|
||||
(when
|
||||
(and (< pos len-src) (= (nth source pos) "."))
|
||||
(set! pos (inc pos))
|
||||
(read-digits))
|
||||
(when
|
||||
(if
|
||||
(and
|
||||
(< pos len-src)
|
||||
(or (= (nth source pos) "e") (= (nth source pos) "E")))
|
||||
(set! pos (inc pos))
|
||||
(when
|
||||
(and
|
||||
(< pos len-src)
|
||||
(or (= (nth source pos) "+") (= (nth source pos) "-")))
|
||||
(set! pos (inc pos)))
|
||||
(read-digits))
|
||||
(parse-number (slice source start pos)))))
|
||||
(= (nth source pos) "/")
|
||||
(< (inc pos) len-src)
|
||||
(let
|
||||
((nc (nth source (inc pos))))
|
||||
(and (>= nc "0") (<= nc "9"))))
|
||||
(let
|
||||
((numer (parse-number (slice source start pos))))
|
||||
(set! pos (inc pos))
|
||||
(let
|
||||
((denom-start pos))
|
||||
(read-digits)
|
||||
(make-rational
|
||||
numer
|
||||
(parse-number (slice source denom-start pos)))))
|
||||
(do
|
||||
(when
|
||||
(and (< pos len-src) (= (nth source pos) "."))
|
||||
(set! pos (inc pos))
|
||||
(read-digits))
|
||||
(when
|
||||
(and
|
||||
(< pos len-src)
|
||||
(or (= (nth source pos) "e") (= (nth source pos) "E")))
|
||||
(set! pos (inc pos))
|
||||
(when
|
||||
(and
|
||||
(< pos len-src)
|
||||
(or
|
||||
(= (nth source pos) "+")
|
||||
(= (nth source pos) "-")))
|
||||
(set! pos (inc pos)))
|
||||
(read-digits))
|
||||
(parse-number (slice source start pos)))))))
|
||||
(define
|
||||
read-symbol
|
||||
:effects ()
|
||||
@@ -490,6 +512,8 @@
|
||||
(if val "true" "false")
|
||||
"number"
|
||||
(str val)
|
||||
"rational"
|
||||
(str (numerator val) "/" (denominator val))
|
||||
"string"
|
||||
(str "\"" (escape-string val) "\"")
|
||||
"symbol"
|
||||
@@ -567,11 +591,12 @@
|
||||
;; True for: ident-start chars plus: 0-9 . : / # ,
|
||||
;;
|
||||
;; Constructors (provided by the SX runtime):
|
||||
;; (make-symbol name) → Symbol value
|
||||
;; (make-keyword name) → Keyword value
|
||||
;; (parse-number s) → number (int or float from string)
|
||||
;; (make-char n) → Char value from Unicode codepoint n
|
||||
;; (char->integer c) → Unicode codepoint of char c
|
||||
;; (make-symbol name) → Symbol value
|
||||
;; (make-keyword name) → Keyword value
|
||||
;; (parse-number s) → number (int or float from string)
|
||||
;; (make-char n) → Char value from Unicode codepoint n
|
||||
;; (make-rational n d) → Rational value (auto-reduced by GCD; d=0 is an error)
|
||||
;; (char->integer c) → Unicode codepoint of char c
|
||||
;;
|
||||
;; String utilities:
|
||||
;; (escape-string s) → string with " and \ escaped
|
||||
|
||||
@@ -1034,4 +1034,30 @@
|
||||
:returns "any"
|
||||
:doc "Parse string s as a number. Optional radix (default 10). Returns nil on failure.")
|
||||
|
||||
(define-module :stdlib.rational)
|
||||
|
||||
(define-primitive
|
||||
"make-rational"
|
||||
:params (n d)
|
||||
:returns "rational"
|
||||
:doc "Rational n/d, auto-reduced by GCD. Error if d=0.")
|
||||
|
||||
(define-primitive
|
||||
"rational?"
|
||||
:params (v)
|
||||
:returns "boolean"
|
||||
:doc "True if v is a rational number.")
|
||||
|
||||
(define-primitive
|
||||
"numerator"
|
||||
:params ((r :as rational))
|
||||
:returns "integer"
|
||||
:doc "Numerator of rational r (after reduction).")
|
||||
|
||||
(define-primitive
|
||||
"denominator"
|
||||
:params ((r :as rational))
|
||||
:returns "integer"
|
||||
:doc "Denominator of rational r (after reduction, always positive).")
|
||||
|
||||
(define-module :stdlib.hash-table)
|
||||
|
||||
@@ -10,57 +10,56 @@
|
||||
;; Literals and types
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "literals"
|
||||
(deftest "numbers are numbers"
|
||||
(defsuite
|
||||
"literals"
|
||||
(deftest
|
||||
"numbers are numbers"
|
||||
(assert-type "number" 42)
|
||||
(assert-type "number" 3.14)
|
||||
(assert-type "number" -1))
|
||||
|
||||
(deftest "strings are strings"
|
||||
(deftest
|
||||
"strings are strings"
|
||||
(assert-type "string" "hello")
|
||||
(assert-type "string" ""))
|
||||
|
||||
(deftest "booleans are booleans"
|
||||
(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"
|
||||
(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" {:a 1 :b 2})))
|
||||
(deftest "dicts are dicts" (assert-type "dict" {:b 2 :a 1})))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Arithmetic
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "arithmetic"
|
||||
(deftest "addition"
|
||||
(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"
|
||||
(deftest
|
||||
"subtraction"
|
||||
(assert-equal 1 (- 3 2))
|
||||
(assert-equal -1 (- 2 3)))
|
||||
|
||||
(deftest "multiplication"
|
||||
(deftest
|
||||
"multiplication"
|
||||
(assert-equal 6 (* 2 3))
|
||||
(assert-equal 0 (* 0 100))
|
||||
(assert-equal 24 (* 1 2 3 4)))
|
||||
|
||||
(deftest "division"
|
||||
(deftest
|
||||
"division"
|
||||
(assert-equal 2 (/ 6 3))
|
||||
(assert-equal 2.5 (/ 5 2)))
|
||||
|
||||
(deftest "modulo"
|
||||
(deftest
|
||||
"modulo"
|
||||
(assert-equal 1 (mod 7 3))
|
||||
(assert-equal 0 (mod 6 3))))
|
||||
|
||||
@@ -69,20 +68,26 @@
|
||||
;; Comparison
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "comparison"
|
||||
(deftest "equality"
|
||||
(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)))
|
||||
(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"
|
||||
(deftest
|
||||
"ordering"
|
||||
(assert-true (< 1 2))
|
||||
(assert-false (< 2 1))
|
||||
(assert-true (> 2 1))
|
||||
@@ -96,34 +101,36 @@
|
||||
;; String operations
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "strings"
|
||||
(deftest "str concatenation"
|
||||
(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"
|
||||
(deftest
|
||||
"string-length"
|
||||
(assert-equal 5 (string-length "hello"))
|
||||
(assert-equal 0 (string-length "")))
|
||||
|
||||
(deftest "substring"
|
||||
(deftest
|
||||
"substring"
|
||||
(assert-equal "ell" (substring "hello" 1 4))
|
||||
(assert-equal "hello" (substring "hello" 0 5)))
|
||||
|
||||
(deftest "string-contains?"
|
||||
(deftest
|
||||
"string-contains?"
|
||||
(assert-true (string-contains? "hello world" "world"))
|
||||
(assert-false (string-contains? "hello" "xyz")))
|
||||
|
||||
(deftest "upcase and downcase"
|
||||
(deftest
|
||||
"upcase and downcase"
|
||||
(assert-equal "HELLO" (upcase "hello"))
|
||||
(assert-equal "hello" (downcase "HELLO")))
|
||||
|
||||
(deftest "trim"
|
||||
(deftest
|
||||
"trim"
|
||||
(assert-equal "hello" (trim " hello "))
|
||||
(assert-equal "hello" (trim "hello")))
|
||||
|
||||
(deftest "split and join"
|
||||
(deftest
|
||||
"split and join"
|
||||
(assert-equal (list "a" "b" "c") (split "a,b,c" ","))
|
||||
(assert-equal "a-b-c" (join "-" (list "a" "b" "c")))))
|
||||
|
||||
@@ -132,121 +139,145 @@
|
||||
;; List operations
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "lists"
|
||||
(deftest "constructors"
|
||||
(assert-equal (list 1 2 3) (list 1 2 3))
|
||||
(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"
|
||||
(deftest
|
||||
"first and rest"
|
||||
(assert-equal 1 (first (list 1 2 3)))
|
||||
(assert-equal (list 2 3) (rest (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"
|
||||
(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)))
|
||||
(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?"
|
||||
(deftest
|
||||
"empty?"
|
||||
(assert-true (empty? (list)))
|
||||
(assert-false (empty? (list 1))))
|
||||
|
||||
(deftest "len"
|
||||
(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))))))
|
||||
(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" {:a 1 :b 2})
|
||||
(defsuite
|
||||
"dicts"
|
||||
(deftest
|
||||
"dict literal"
|
||||
(assert-type "dict" {:b 2 :a 1})
|
||||
(assert-equal 1 (get {:a 1} "a"))
|
||||
(assert-equal 2 (get {:a 1 :b 2} "b")))
|
||||
|
||||
(deftest "assoc"
|
||||
(assert-equal {:a 1 :b 2} (assoc {:a 1} "b" 2))
|
||||
(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 {:a 1 :b 2} "a")))
|
||||
|
||||
(deftest "keys and vals"
|
||||
(let ((d {:a 1 :b 2}))
|
||||
(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?"
|
||||
(deftest
|
||||
"has-key?"
|
||||
(assert-true (has-key? {:a 1} "a"))
|
||||
(assert-false (has-key? {:a 1} "b")))
|
||||
|
||||
(deftest "merge"
|
||||
(assert-equal {:a 1 :b 2 :c 3}
|
||||
(merge {:a 1 :b 2} {:c 3}))
|
||||
(assert-equal {:a 99 :b 2}
|
||||
(merge {:a 1 :b 2} {:a 99}))))
|
||||
(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?"
|
||||
(defsuite
|
||||
"predicates"
|
||||
(deftest
|
||||
"nil?"
|
||||
(assert-true (nil? nil))
|
||||
(assert-false (nil? 0))
|
||||
(assert-false (nil? false))
|
||||
(assert-false (nil? "")))
|
||||
|
||||
(deftest "number?"
|
||||
(deftest
|
||||
"number?"
|
||||
(assert-true (number? 42))
|
||||
(assert-true (number? 3.14))
|
||||
(assert-false (number? "42")))
|
||||
|
||||
(deftest "string?"
|
||||
(deftest
|
||||
"string?"
|
||||
(assert-true (string? "hello"))
|
||||
(assert-false (string? 42)))
|
||||
|
||||
(deftest "list?"
|
||||
(deftest
|
||||
"list?"
|
||||
(assert-true (list? (list 1 2)))
|
||||
(assert-false (list? "not a list")))
|
||||
|
||||
(deftest "dict?"
|
||||
(deftest
|
||||
"dict?"
|
||||
(assert-true (dict? {:a 1}))
|
||||
(assert-false (dict? (list 1))))
|
||||
|
||||
(deftest "boolean?"
|
||||
(deftest
|
||||
"boolean?"
|
||||
(assert-true (boolean? true))
|
||||
(assert-true (boolean? false))
|
||||
(assert-false (boolean? nil))
|
||||
(assert-false (boolean? 0)))
|
||||
|
||||
(deftest "not"
|
||||
(deftest
|
||||
"not"
|
||||
(assert-true (not false))
|
||||
(assert-true (not nil))
|
||||
(assert-false (not true))
|
||||
@@ -258,77 +289,67 @@
|
||||
;; Special forms
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "special-forms"
|
||||
(deftest "if"
|
||||
(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"
|
||||
(deftest
|
||||
"when"
|
||||
(assert-equal "yes" (when true "yes"))
|
||||
(assert-nil (when false "yes")))
|
||||
|
||||
(deftest "cond"
|
||||
(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"
|
||||
;; Regression: cond misclassifies Clojure-style as scheme-style when
|
||||
;; the first test is a 2-element list like (nil? x) or (empty? x).
|
||||
;; The evaluator checks: is first arg a 2-element list? If yes, treats
|
||||
;; as scheme-style ((test body) ...) — returning the arg instead of
|
||||
;; evaluating the predicate call.
|
||||
(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
|
||||
"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"
|
||||
;; Same bug, but without :else — this is the worst case because the
|
||||
;; bootstrapper heuristic also breaks (all clauses are 2-element lists).
|
||||
(assert-equal "found"
|
||||
(cond (nil? nil) "found"
|
||||
(nil? "x") "other"))
|
||||
(assert-equal "b"
|
||||
(cond (nil? "x") "a"
|
||||
(not false) "b")))
|
||||
|
||||
(deftest "and"
|
||||
(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"
|
||||
(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"
|
||||
(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"
|
||||
(deftest
|
||||
"let clojure-style"
|
||||
(assert-equal 3 (let (x 1 y 2) (+ x y))))
|
||||
|
||||
(deftest "do / begin"
|
||||
(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!"
|
||||
(deftest "define" (define x 42) (assert-equal 42 x))
|
||||
(deftest
|
||||
"set!"
|
||||
(define x 1)
|
||||
(set! x 2)
|
||||
(assert-equal 2 x)))
|
||||
@@ -338,86 +359,126 @@
|
||||
;; Lambda and closures
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "lambdas"
|
||||
(deftest "basic lambda"
|
||||
(let ((add (fn (a b) (+ a b))))
|
||||
(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))))
|
||||
(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))))))
|
||||
(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)))
|
||||
(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"
|
||||
;; All body expressions must execute. Return value is the last.
|
||||
;; Catches: sf-lambda using nth(args,1) instead of rest(args).
|
||||
(let ((f (fn (x) (+ x 1) (+ x 2) (+ x 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"
|
||||
;; Verify all body expressions run by mutating a shared dict.
|
||||
(let ((state (dict "a" 0 "b" 0)))
|
||||
(let ((f (fn ()
|
||||
(dict-set! state "a" 1)
|
||||
(dict-set! state "b" 2)
|
||||
"done")))
|
||||
(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"
|
||||
;; Simplest case: two body expressions, return value is second.
|
||||
(assert-equal 20
|
||||
(deftest
|
||||
"multi-body lambda two expressions"
|
||||
(assert-equal
|
||||
20
|
||||
((fn (x) (+ x 1) (* x 2)) 10))
|
||||
;; And with zero-arg lambda
|
||||
(assert-equal 42
|
||||
((fn () (+ 1 2) 42)))))
|
||||
(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)))
|
||||
(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)
|
||||
(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")
|
||||
(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")))))
|
||||
|
||||
|
||||
@@ -425,49 +486,39 @@
|
||||
;; Components
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "components"
|
||||
(deftest "defcomp creates component"
|
||||
(defcomp ~test-comp (&key title)
|
||||
(div title))
|
||||
(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 "!")))
|
||||
(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))
|
||||
(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")))
|
||||
(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))
|
||||
(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))
|
||||
(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))
|
||||
(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))
|
||||
;; Component should still render correctly
|
||||
(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)))))
|
||||
|
||||
@@ -476,93 +527,98 @@
|
||||
;; Macros
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "macros"
|
||||
(deftest "defmacro creates macro"
|
||||
(defmacro unless (cond &rest body)
|
||||
`(if (not ,cond) (do ,@body)))
|
||||
(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) `(1 ,x 3))))
|
||||
|
||||
(deftest "splice-unquote"
|
||||
(let ((xs (list 2 3 4)))
|
||||
(assert-equal (list 1 2 3 4 5) `(1 ,@xs 5)))))
|
||||
(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"
|
||||
(defsuite
|
||||
"threading"
|
||||
(deftest
|
||||
"thread-first"
|
||||
(assert-equal 8 (-> 5 (+ 1) (+ 2)))
|
||||
(assert-equal "HELLO" (-> "hello" upcase))
|
||||
(assert-equal "HELLO WORLD"
|
||||
(-> "hello"
|
||||
(str " world")
|
||||
upcase))))
|
||||
(assert-equal "HELLO WORLD" (-> "hello" (str " world") upcase))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Truthiness
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "truthiness"
|
||||
(deftest "truthy values"
|
||||
(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"
|
||||
(deftest
|
||||
"falsy values"
|
||||
(assert-false (if false true false))
|
||||
(assert-false (if nil true false)))
|
||||
|
||||
;; NOTE: empty list, zero, and empty string truthiness is
|
||||
;; platform-dependent. Python treats all three as falsy.
|
||||
;; JavaScript treats [] as truthy but 0 and "" as falsy.
|
||||
;; These tests are omitted — each bootstrapper should emit
|
||||
;; platform-specific truthiness tests instead.
|
||||
)
|
||||
(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))
|
||||
;; outer x should be unchanged by inner let
|
||||
;; (this tests that let creates a new scope)
|
||||
))
|
||||
|
||||
(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"
|
||||
(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"
|
||||
(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"
|
||||
(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
|
||||
(reduce (fn (acc x) (+ acc x)) 0 (list)))
|
||||
(assert-equal 0 (len (list)))
|
||||
(assert-equal "" (str))))
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
;; ==========================================================================
|
||||
;; test-numeric-tower.sx — Numeric tower: Integer vs Float distinction
|
||||
;;
|
||||
@@ -52,15 +51,20 @@
|
||||
(assert (float? (exact->inexact 5)))))
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Division always returns float
|
||||
;; Division
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite
|
||||
"numeric-tower:division"
|
||||
(deftest "int / int = float" (assert (float? (/ 6 2))))
|
||||
(deftest "exact division value" (assert= (/ 6 2) 3))
|
||||
(deftest "inexact division" (assert= (/ 1 4) 0.25))
|
||||
(deftest "float / float = float" (assert (float? (/ 3.5 2.5)))))
|
||||
(deftest
|
||||
"exact division value"
|
||||
(assert= (/ 6 2) 3))
|
||||
(deftest "inexact division value" (assert= (/ 1 4) 0.25))
|
||||
(deftest "float / float = float" (assert (float? (/ 3.5 2.5))))
|
||||
(deftest
|
||||
"rational / int = rational"
|
||||
(assert (rational? (/ 1/2 2))))
|
||||
(deftest "rational division value" (assert= (/ 1/2 2) 1/4)))
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Type predicates
|
||||
@@ -82,8 +86,10 @@
|
||||
(deftest "float? on int" (assert (not (float? 42))))
|
||||
(deftest "number? on int" (assert (number? 42)))
|
||||
(deftest "number? on float" (assert (number? 3.14)))
|
||||
(deftest "number? on rational" (assert (number? 1/3)))
|
||||
(deftest "number? on string" (assert (not (number? "42"))))
|
||||
(deftest "exact? on int" (assert (exact? 1)))
|
||||
(deftest "exact? on rational" (assert (exact? 1/3)))
|
||||
(deftest
|
||||
"exact? on exact->inexact"
|
||||
(assert (not (exact? (exact->inexact 1)))))
|
||||
@@ -96,13 +102,16 @@
|
||||
|
||||
(defsuite
|
||||
"numeric-tower:coercions"
|
||||
(deftest "exact->inexact int" (assert= (exact->inexact 3) 3))
|
||||
(deftest
|
||||
"exact->inexact int"
|
||||
(assert= (exact->inexact 3) 3))
|
||||
(deftest
|
||||
"exact->inexact produces float"
|
||||
(assert (float? (exact->inexact 5))))
|
||||
(deftest
|
||||
"exact->inexact float passthrough"
|
||||
(assert= (exact->inexact 1.5) 1.5))
|
||||
(deftest "exact->inexact rational" (assert= (exact->inexact 1/4) 0.25))
|
||||
(deftest "inexact->exact 1.5" (assert= (inexact->exact 1.5) 2))
|
||||
(deftest
|
||||
"inexact->exact produces int"
|
||||
|
||||
@@ -6,20 +6,36 @@
|
||||
;; Arithmetic
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "arithmetic"
|
||||
(defsuite
|
||||
"arithmetic"
|
||||
(deftest "add" (assert-equal 3 (+ 1 2)))
|
||||
(deftest "add multiple" (assert-equal 10 (+ 1 2 3 4)))
|
||||
(deftest
|
||||
"add multiple"
|
||||
(assert-equal 10 (+ 1 2 3 4)))
|
||||
(deftest "add zero" (assert-equal 5 (+ 5 0)))
|
||||
(deftest "add negative" (assert-equal -1 (+ 1 -2)))
|
||||
(deftest
|
||||
"add negative"
|
||||
(assert-equal -1 (+ 1 -2)))
|
||||
(deftest "subtract" (assert-equal 3 (- 5 2)))
|
||||
(deftest "subtract negative" (assert-equal 7 (- 5 -2)))
|
||||
(deftest
|
||||
"subtract negative"
|
||||
(assert-equal 7 (- 5 -2)))
|
||||
(deftest "multiply" (assert-equal 12 (* 3 4)))
|
||||
(deftest "multiply zero" (assert-equal 0 (* 5 0)))
|
||||
(deftest "multiply negative" (assert-equal -6 (* 2 -3)))
|
||||
(deftest
|
||||
"multiply zero"
|
||||
(assert-equal 0 (* 5 0)))
|
||||
(deftest
|
||||
"multiply negative"
|
||||
(assert-equal -6 (* 2 -3)))
|
||||
(deftest "divide" (assert-equal 3 (/ 9 3)))
|
||||
(deftest "divide float" (assert-equal 2.5 (/ 5 2)))
|
||||
(deftest "mod" (assert-equal 1 (mod 7 3)))
|
||||
(deftest "mod negative" (assert-true (or (= (mod -1 3) 2) (= (mod -1 3) -1))))
|
||||
(deftest
|
||||
"mod negative"
|
||||
(assert-true
|
||||
(or
|
||||
(= (mod -1 3) 2)
|
||||
(= (mod -1 3) -1))))
|
||||
(deftest "inc" (assert-equal 6 (inc 5)))
|
||||
(deftest "dec" (assert-equal 4 (dec 5)))
|
||||
(deftest "abs positive" (assert-equal 5 (abs 5)))
|
||||
@@ -32,7 +48,8 @@
|
||||
;; Comparison
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "comparison"
|
||||
(defsuite
|
||||
"comparison"
|
||||
(deftest "equal numbers" (assert-true (= 1 1)))
|
||||
(deftest "not equal numbers" (assert-false (= 1 2)))
|
||||
(deftest "equal strings" (assert-true (= "a" "a")))
|
||||
@@ -52,7 +69,8 @@
|
||||
;; Predicates
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "predicates"
|
||||
(defsuite
|
||||
"predicates"
|
||||
(deftest "nil? nil" (assert-true (nil? nil)))
|
||||
(deftest "nil? number" (assert-false (nil? 0)))
|
||||
(deftest "nil? string" (assert-false (nil? "")))
|
||||
@@ -76,15 +94,22 @@
|
||||
;; String operations
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "strings"
|
||||
(deftest "str concat" (assert-equal "hello world" (str "hello" " " "world")))
|
||||
(defsuite
|
||||
"strings"
|
||||
(deftest
|
||||
"str concat"
|
||||
(assert-equal "hello world" (str "hello" " " "world")))
|
||||
(deftest "str number" (assert-equal "42" (str 42)))
|
||||
(deftest "str empty" (assert-equal "" (str)))
|
||||
(deftest "len string" (assert-equal 5 (len "hello")))
|
||||
(deftest "len empty" (assert-equal 0 (len "")))
|
||||
(deftest "slice" (assert-equal "ell" (slice "hello" 1 4)))
|
||||
(deftest
|
||||
"slice"
|
||||
(assert-equal "ell" (slice "hello" 1 4)))
|
||||
(deftest "slice from" (assert-equal "llo" (slice "hello" 2)))
|
||||
(deftest "slice empty" (assert-equal "" (slice "hello" 2 2)))
|
||||
(deftest
|
||||
"slice empty"
|
||||
(assert-equal "" (slice "hello" 2 2)))
|
||||
(deftest "join" (assert-equal "a,b,c" (join "," (list "a" "b" "c"))))
|
||||
(deftest "join empty" (assert-equal "" (join "," (list))))
|
||||
(deftest "join single" (assert-equal "a" (join "," (list "a"))))
|
||||
@@ -101,88 +126,238 @@
|
||||
(deftest "replace" (assert-equal "hXllo" (replace "hello" "e" "X")))
|
||||
(deftest "string-length" (assert-equal 5 (string-length "hello")))
|
||||
(deftest "index-of found" (assert-equal 2 (index-of "hello" "l")))
|
||||
(deftest "index-of not found" (assert-equal -1 (index-of "hello" "z"))))
|
||||
(deftest
|
||||
"index-of not found"
|
||||
(assert-equal -1 (index-of "hello" "z"))))
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; List operations
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "lists"
|
||||
(deftest "list create" (assert-equal (list 1 2 3) (list 1 2 3)))
|
||||
(deftest "first" (assert-equal 1 (first (list 1 2 3))))
|
||||
(defsuite
|
||||
"lists"
|
||||
(deftest
|
||||
"list create"
|
||||
(assert-equal
|
||||
(list 1 2 3)
|
||||
(list 1 2 3)))
|
||||
(deftest
|
||||
"first"
|
||||
(assert-equal 1 (first (list 1 2 3))))
|
||||
(deftest "first empty" (assert-nil (first (list))))
|
||||
(deftest "rest" (assert-equal (list 2 3) (rest (list 1 2 3))))
|
||||
(deftest
|
||||
"rest"
|
||||
(assert-equal
|
||||
(list 2 3)
|
||||
(rest (list 1 2 3))))
|
||||
(deftest "rest single" (assert-equal (list) (rest (list 1))))
|
||||
(deftest "rest empty" (assert-equal (list) (rest (list))))
|
||||
(deftest "nth" (assert-equal 2 (nth (list 1 2 3) 1)))
|
||||
(deftest "nth out of bounds" (assert-nil (nth (list 1 2) 5)))
|
||||
(deftest "last" (assert-equal 3 (last (list 1 2 3))))
|
||||
(deftest
|
||||
"nth"
|
||||
(assert-equal
|
||||
2
|
||||
(nth (list 1 2 3) 1)))
|
||||
(deftest
|
||||
"nth out of bounds"
|
||||
(assert-nil (nth (list 1 2) 5)))
|
||||
(deftest
|
||||
"last"
|
||||
(assert-equal 3 (last (list 1 2 3))))
|
||||
(deftest "last single" (assert-equal 1 (last (list 1))))
|
||||
(deftest "len list" (assert-equal 3 (len (list 1 2 3))))
|
||||
(deftest
|
||||
"len list"
|
||||
(assert-equal 3 (len (list 1 2 3))))
|
||||
(deftest "len empty" (assert-equal 0 (len (list))))
|
||||
(deftest "cons" (assert-equal (list 0 1 2) (cons 0 (list 1 2))))
|
||||
(deftest "append" (assert-equal (list 1 2 3 4) (append (list 1 2) (list 3 4))))
|
||||
(deftest "append element" (assert-equal (list 1 2 3) (append (list 1 2) (list 3))))
|
||||
(deftest "slice list" (assert-equal (list 2 3) (slice (list 1 2 3 4) 1 3)))
|
||||
(deftest "concat" (assert-equal (list 1 2 3 4) (concat (list 1 2) (list 3 4))))
|
||||
(deftest "reverse" (assert-equal (list 3 2 1) (reverse (list 1 2 3))))
|
||||
(deftest
|
||||
"cons"
|
||||
(assert-equal
|
||||
(list 0 1 2)
|
||||
(cons 0 (list 1 2))))
|
||||
(deftest
|
||||
"append"
|
||||
(assert-equal
|
||||
(list 1 2 3 4)
|
||||
(append (list 1 2) (list 3 4))))
|
||||
(deftest
|
||||
"append element"
|
||||
(assert-equal
|
||||
(list 1 2 3)
|
||||
(append (list 1 2) (list 3))))
|
||||
(deftest
|
||||
"slice list"
|
||||
(assert-equal
|
||||
(list 2 3)
|
||||
(slice
|
||||
(list 1 2 3 4)
|
||||
1
|
||||
3)))
|
||||
(deftest
|
||||
"concat"
|
||||
(assert-equal
|
||||
(list 1 2 3 4)
|
||||
(concat (list 1 2) (list 3 4))))
|
||||
(deftest
|
||||
"reverse"
|
||||
(assert-equal
|
||||
(list 3 2 1)
|
||||
(reverse (list 1 2 3))))
|
||||
(deftest "reverse empty" (assert-equal (list) (reverse (list))))
|
||||
(deftest "contains? list" (assert-true (contains? (list 1 2 3) 2)))
|
||||
(deftest "contains? list false" (assert-false (contains? (list 1 2 3) 5)))
|
||||
(deftest "range" (assert-equal (list 0 1 2) (range 0 3)))
|
||||
(deftest "range step" (assert-equal (list 0 2 4) (range 0 6 2)))
|
||||
(deftest "flatten" (assert-equal (list 1 2 3 4) (flatten (list (list 1 2) (list 3 4))))))
|
||||
(deftest
|
||||
"contains? list"
|
||||
(assert-true
|
||||
(contains? (list 1 2 3) 2)))
|
||||
(deftest
|
||||
"contains? list false"
|
||||
(assert-false
|
||||
(contains? (list 1 2 3) 5)))
|
||||
(deftest
|
||||
"range"
|
||||
(assert-equal
|
||||
(list 0 1 2)
|
||||
(range 0 3)))
|
||||
(deftest
|
||||
"range step"
|
||||
(assert-equal
|
||||
(list 0 2 4)
|
||||
(range 0 6 2)))
|
||||
(deftest
|
||||
"flatten"
|
||||
(assert-equal
|
||||
(list 1 2 3 4)
|
||||
(flatten
|
||||
(list (list 1 2) (list 3 4))))))
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Dict operations
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "dicts"
|
||||
(deftest "dict create" (assert-equal 1 (get (dict "a" 1 "b" 2) "a")))
|
||||
(defsuite
|
||||
"dicts"
|
||||
(deftest
|
||||
"dict create"
|
||||
(assert-equal 1 (get (dict "a" 1 "b" 2) "a")))
|
||||
(deftest "get missing" (assert-nil (get (dict "a" 1) "z")))
|
||||
(deftest "get default" (assert-equal 99 (get (dict "a" 1) "z" 99)))
|
||||
(deftest "keys" (assert-true (contains? (keys (dict "a" 1 "b" 2)) "a")))
|
||||
(deftest
|
||||
"get default"
|
||||
(assert-equal 99 (get (dict "a" 1) "z" 99)))
|
||||
(deftest
|
||||
"keys"
|
||||
(assert-true
|
||||
(contains? (keys (dict "a" 1 "b" 2)) "a")))
|
||||
(deftest "has-key?" (assert-true (has-key? (dict "a" 1) "a")))
|
||||
(deftest "has-key? false" (assert-false (has-key? (dict "a" 1) "z")))
|
||||
(deftest "assoc" (assert-equal 2 (get (assoc (dict "a" 1) "b" 2) "b")))
|
||||
(deftest "dissoc" (assert-false (has-key? (dissoc (dict "a" 1 "b" 2) "a") "a")))
|
||||
(deftest "len dict" (assert-equal 2 (len (dict "a" 1 "b" 2))))
|
||||
(deftest
|
||||
"has-key? false"
|
||||
(assert-false (has-key? (dict "a" 1) "z")))
|
||||
(deftest
|
||||
"assoc"
|
||||
(assert-equal
|
||||
2
|
||||
(get (assoc (dict "a" 1) "b" 2) "b")))
|
||||
(deftest
|
||||
"dissoc"
|
||||
(assert-false
|
||||
(has-key? (dissoc (dict "a" 1 "b" 2) "a") "a")))
|
||||
(deftest
|
||||
"len dict"
|
||||
(assert-equal 2 (len (dict "a" 1 "b" 2))))
|
||||
(deftest "len empty dict" (assert-equal 0 (len (dict))))
|
||||
(deftest "empty? dict" (assert-true (empty? (dict))))
|
||||
(deftest "empty? nonempty dict" (assert-false (empty? (dict "a" 1)))))
|
||||
(deftest
|
||||
"empty? nonempty dict"
|
||||
(assert-false (empty? (dict "a" 1)))))
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Higher-order functions
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "higher-order"
|
||||
(deftest "map" (assert-equal (list 2 4 6) (map (fn (x) (* x 2)) (list 1 2 3))))
|
||||
(defsuite
|
||||
"higher-order"
|
||||
(deftest
|
||||
"map"
|
||||
(assert-equal
|
||||
(list 2 4 6)
|
||||
(map
|
||||
(fn (x) (* x 2))
|
||||
(list 1 2 3))))
|
||||
(deftest "map empty" (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 5))))
|
||||
(deftest "filter none" (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))))
|
||||
(deftest "reduce empty" (assert-equal 0 (reduce (fn (acc x) (+ acc x)) 0 (list))))
|
||||
(deftest "some true" (assert-true (some (fn (x) (> x 3)) (list 1 2 3 4 5))))
|
||||
(deftest "some false" (assert-false (some (fn (x) (> x 10)) (list 1 2 3))))
|
||||
(deftest
|
||||
"filter"
|
||||
(assert-equal
|
||||
(list 2 4)
|
||||
(filter
|
||||
(fn (x) (= (mod x 2) 0))
|
||||
(list 1 2 3 4 5))))
|
||||
(deftest
|
||||
"filter none"
|
||||
(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))))
|
||||
(deftest
|
||||
"reduce empty"
|
||||
(assert-equal
|
||||
0
|
||||
(reduce (fn (acc x) (+ acc x)) 0 (list))))
|
||||
(deftest
|
||||
"some true"
|
||||
(assert-true
|
||||
(some
|
||||
(fn (x) (> x 3))
|
||||
(list 1 2 3 4 5))))
|
||||
(deftest
|
||||
"some false"
|
||||
(assert-false
|
||||
(some
|
||||
(fn (x) (> x 10))
|
||||
(list 1 2 3))))
|
||||
(deftest "some empty" (assert-false (some (fn (x) true) (list))))
|
||||
(deftest "every? true" (assert-true (every? (fn (x) (> x 0)) (list 1 2 3))))
|
||||
(deftest "every? false" (assert-false (every? (fn (x) (> x 2)) (list 1 2 3))))
|
||||
(deftest
|
||||
"every? true"
|
||||
(assert-true
|
||||
(every?
|
||||
(fn (x) (> x 0))
|
||||
(list 1 2 3))))
|
||||
(deftest
|
||||
"every? false"
|
||||
(assert-false
|
||||
(every?
|
||||
(fn (x) (> x 2))
|
||||
(list 1 2 3))))
|
||||
(deftest "every? empty" (assert-true (every? (fn (x) false) (list))))
|
||||
(deftest "for-each returns nil"
|
||||
(let ((log (list)))
|
||||
(for-each (fn (x) (append! log x)) (list 1 2 3))
|
||||
(deftest
|
||||
"for-each returns nil"
|
||||
(let
|
||||
((log (list)))
|
||||
(for-each
|
||||
(fn (x) (append! log x))
|
||||
(list 1 2 3))
|
||||
(assert-equal (list 1 2 3) log)))
|
||||
(deftest "map-indexed"
|
||||
(assert-equal (list (list 0 "a") (list 1 "b"))
|
||||
(deftest
|
||||
"map-indexed"
|
||||
(assert-equal
|
||||
(list (list 0 "a") (list 1 "b"))
|
||||
(map-indexed (fn (i x) (list i x)) (list "a" "b")))))
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Type coercion
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "type-coercion"
|
||||
(deftest "str bool" (assert-true (or (= (str true) "true") (= (str true) "True"))))
|
||||
(defsuite
|
||||
"type-coercion"
|
||||
(deftest
|
||||
"str bool"
|
||||
(assert-true (or (= (str true) "true") (= (str true) "True"))))
|
||||
(deftest "str nil" (assert-equal "" (str nil)))
|
||||
(deftest "str list" (assert-true (not (empty? (str (list 1 2 3))))))
|
||||
(deftest
|
||||
"str list"
|
||||
(assert-true
|
||||
(not (empty? (str (list 1 2 3))))))
|
||||
(deftest "parse-int" (assert-equal 42 (parse-int "42")))
|
||||
(deftest "parse-float skipped" (assert-true true)))
|
||||
|
||||
135
spec/tests/test-rationals.sx
Normal file
135
spec/tests/test-rationals.sx
Normal file
@@ -0,0 +1,135 @@
|
||||
;; ==========================================================================
|
||||
;; test-rationals.sx — Rational number type: literals, arithmetic, tower
|
||||
;;
|
||||
;; Note: in the JS host, (/ int int) returns float (backward-compatible).
|
||||
;; Use rational literals (1/3, 3/4) or make-rational for exact rationals.
|
||||
;; ==========================================================================
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Literals and type
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite
|
||||
"rationals:literals"
|
||||
(deftest "1/3 is rational" (assert (rational? 1/3)))
|
||||
(deftest "1/2 is rational" (assert (rational? 1/2)))
|
||||
(deftest "2/3 is rational" (assert (rational? 2/3)))
|
||||
(deftest "literal numerator 1/3" (assert= (numerator 1/3) 1))
|
||||
(deftest "literal denominator 1/3" (assert= (denominator 1/3) 3))
|
||||
(deftest "literal numerator 2/3" (assert= (numerator 2/3) 2))
|
||||
(deftest "auto-reduce 2/4 = 1/2" (assert= 2/4 1/2))
|
||||
(deftest "auto-reduce 6/9 = 2/3" (assert= 6/9 2/3))
|
||||
(deftest "negative literal" (assert= (numerator -1/3) -1)))
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Constructor and predicates
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite
|
||||
"rationals:constructor"
|
||||
(deftest
|
||||
"make-rational basic"
|
||||
(assert (rational? (make-rational 1 3))))
|
||||
(deftest
|
||||
"make-rational reduces"
|
||||
(assert= (make-rational 2 4) 1/2))
|
||||
(deftest
|
||||
"make-rational exact int"
|
||||
(assert (integer? (make-rational 6 3))))
|
||||
(deftest
|
||||
"make-rational 6/3 = 2"
|
||||
(assert= (make-rational 6 3) 2))
|
||||
(deftest
|
||||
"make-rational negative"
|
||||
(assert= (numerator (make-rational -1 3)) -1))
|
||||
(deftest
|
||||
"make-rational neg denom"
|
||||
(assert= (numerator (make-rational 1 -3)) -1))
|
||||
(deftest "rational? on int" (assert (not (rational? 5))))
|
||||
(deftest "rational? on float" (assert (not (rational? 1.5))))
|
||||
(deftest "rational? on string" (assert (not (rational? "1/2"))))
|
||||
(deftest "number? on rational" (assert (number? 1/3)))
|
||||
(deftest "exact? on rational" (assert (exact? 1/3)))
|
||||
(deftest "inexact? on rational" (assert (not (inexact? 1/3))))
|
||||
(deftest "integer? on rational" (assert (not (integer? 1/3))))
|
||||
(deftest "dict? on rational" (assert (not (dict? 1/3)))))
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Accessors
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite
|
||||
"rationals:accessors"
|
||||
(deftest "numerator 1/3" (assert= (numerator 1/3) 1))
|
||||
(deftest "denominator 1/3" (assert= (denominator 1/3) 3))
|
||||
(deftest "numerator 3/4" (assert= (numerator 3/4) 3))
|
||||
(deftest "denominator 3/4" (assert= (denominator 3/4) 4))
|
||||
(deftest "numerator of int" (assert= (numerator 5) 5))
|
||||
(deftest
|
||||
"denominator of int"
|
||||
(assert= (denominator 5) 1)))
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Arithmetic
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite
|
||||
"rationals:arithmetic"
|
||||
(deftest "add two rationals" (assert= (+ 1/3 1/3) 2/3))
|
||||
(deftest "add to integer" (assert= (+ 1 1/2) 3/2))
|
||||
(deftest "add integer to rational" (assert= (+ 1/2 1) 3/2))
|
||||
(deftest "add reduces" (assert= (+ 1/6 1/6) 1/3))
|
||||
(deftest "add to whole number" (assert (integer? (+ 1/2 1/2))))
|
||||
(deftest "add whole = 1" (assert= (+ 1/2 1/2) 1))
|
||||
(deftest "subtract rationals" (assert= (- 3/4 1/4) 1/2))
|
||||
(deftest "subtract int from rational" (assert= (- 3/2 1) 1/2))
|
||||
(deftest "negate rational" (assert= (- 1/3) -1/3))
|
||||
(deftest "multiply rationals" (assert= (* 2/3 3/4) 1/2))
|
||||
(deftest "multiply int and rational" (assert= (* 2 1/3) 2/3))
|
||||
(deftest "multiply reduces to int" (assert (integer? (* 3 1/3))))
|
||||
(deftest "divide rational by int" (assert= (/ 2/3 2) 1/3))
|
||||
(deftest "divide rational by rational" (assert= (/ 1/2 1/4) 2))
|
||||
(deftest
|
||||
"divide rational gives int when exact"
|
||||
(assert (integer? (/ 1/2 1/2)))))
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Float contagion
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite
|
||||
"rationals:float-contagion"
|
||||
(deftest "rational + float = float" (assert (float? (+ 1/3 0.5))))
|
||||
(deftest "float + rational = float" (assert (float? (+ 0.5 1/3))))
|
||||
(deftest "rational * float = float" (assert (float? (* 1/2 2))))
|
||||
(deftest "rational - float = float" (assert (float? (- 1/2 0.1)))))
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Comparison
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite
|
||||
"rationals:comparison"
|
||||
(deftest "equal rationals" (assert (= 1/2 1/2)))
|
||||
(deftest "equal reduced" (assert (= 2/4 1/2)))
|
||||
(deftest "not equal" (assert (not (= 1/3 1/2))))
|
||||
(deftest "less than" (assert (< 1/3 1/2)))
|
||||
(deftest "less than int" (assert (< 1/3 1)))
|
||||
(deftest "greater than" (assert (> 2/3 1/2)))
|
||||
(deftest "less equal" (assert (<= 1/3 1/3)))
|
||||
(deftest "greater equal" (assert (>= 2/3 2/3)))
|
||||
(deftest "rational less than float" (assert (< 1/3 0.5))))
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Coercion
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite
|
||||
"rationals:coercion"
|
||||
(deftest "exact->inexact 1/2" (assert= (exact->inexact 1/2) 0.5))
|
||||
(deftest "exact->inexact 1/4" (assert= (exact->inexact 1/4) 0.25))
|
||||
(deftest
|
||||
"exact->inexact 1/3 is float"
|
||||
(assert (float? (exact->inexact 1/3))))
|
||||
(deftest "number->string 1/2" (assert= (number->string 1/2) "1/2"))
|
||||
(deftest "number->string 3/4" (assert= (number->string 3/4) "3/4")))
|
||||
@@ -1,195 +1,156 @@
|
||||
;; ==========================================================================
|
||||
;; test.sx — Self-hosting SX test suite (backward-compatible entry point)
|
||||
;;
|
||||
;; This file includes the test framework and core eval tests inline.
|
||||
;; It exists for backward compatibility — runners that load "test.sx"
|
||||
;; get the same 81 tests as before.
|
||||
;;
|
||||
;; For modular testing, runners should instead load:
|
||||
;; 1. test-framework.sx (macros + assertions)
|
||||
;; 2. One or more test specs: test-eval.sx, test-parser.sx,
|
||||
;; test-router.sx, test-render.sx, etc.
|
||||
;;
|
||||
;; Platform functions required:
|
||||
;; try-call (thunk) -> {:ok true} | {:ok false :error "msg"}
|
||||
;; report-pass (name) -> platform-specific pass output
|
||||
;; report-fail (name error) -> platform-specific fail output
|
||||
;; push-suite (name) -> push suite name onto context stack
|
||||
;; pop-suite () -> pop suite name from context stack
|
||||
;;
|
||||
;; Usage:
|
||||
;; ;; Host injects platform functions into env, then:
|
||||
;; (eval-file "test.sx" env)
|
||||
;;
|
||||
;; The same test.sx runs on every host — Python, JavaScript, etc.
|
||||
;; ==========================================================================
|
||||
|
||||
(defmacro
|
||||
deftest
|
||||
(name &rest body)
|
||||
(quasiquote
|
||||
(let
|
||||
((result (try-call (fn () (splice-unquote body)))))
|
||||
(if
|
||||
(get result "ok")
|
||||
(report-pass (unquote name))
|
||||
(report-fail (unquote name) (get result "error"))))))
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; 1. Test framework macros
|
||||
;; --------------------------------------------------------------------------
|
||||
;;
|
||||
;; deftest and defsuite are macros that make test.sx directly executable.
|
||||
;; The host provides try-call (error catching), reporting, and suite
|
||||
;; context — everything else is pure SX.
|
||||
(defmacro
|
||||
defsuite
|
||||
(name &rest items)
|
||||
(quasiquote
|
||||
(do (push-suite (unquote name)) (splice-unquote items) (pop-suite))))
|
||||
|
||||
(defmacro deftest (name &rest body)
|
||||
`(let ((result (try-call (fn () ,@body))))
|
||||
(if (get result "ok")
|
||||
(report-pass ,name)
|
||||
(report-fail ,name (get result "error")))))
|
||||
|
||||
(defmacro defsuite (name &rest items)
|
||||
`(do (push-suite ,name)
|
||||
,@items
|
||||
(pop-suite)))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; 2. Assertion helpers — defined in SX, available in test bodies
|
||||
;; --------------------------------------------------------------------------
|
||||
;;
|
||||
;; These are regular functions (not special forms). They use the `assert`
|
||||
;; primitive underneath but provide better error messages.
|
||||
|
||||
(define assert-equal
|
||||
(fn (expected actual)
|
||||
(assert (equal? expected actual)
|
||||
(define
|
||||
assert-equal
|
||||
(fn
|
||||
(expected actual)
|
||||
(assert
|
||||
(equal? expected actual)
|
||||
(str "Expected " (str expected) " but got " (str actual)))))
|
||||
|
||||
(define assert-not-equal
|
||||
(fn (a b)
|
||||
(assert (not (equal? a b))
|
||||
(define
|
||||
assert-not-equal
|
||||
(fn
|
||||
(a b)
|
||||
(assert
|
||||
(not (equal? a b))
|
||||
(str "Expected values to differ but both are " (str a)))))
|
||||
|
||||
(define assert-true
|
||||
(fn (val)
|
||||
(assert val (str "Expected truthy but got " (str val)))))
|
||||
(define
|
||||
assert-true
|
||||
(fn (val) (assert val (str "Expected truthy but got " (str val)))))
|
||||
|
||||
(define assert-false
|
||||
(fn (val)
|
||||
(assert (not val) (str "Expected falsy but got " (str val)))))
|
||||
(define
|
||||
assert-false
|
||||
(fn (val) (assert (not val) (str "Expected falsy but got " (str val)))))
|
||||
|
||||
(define assert-nil
|
||||
(fn (val)
|
||||
(assert (nil? val) (str "Expected nil but got " (str val)))))
|
||||
(define
|
||||
assert-nil
|
||||
(fn (val) (assert (nil? val) (str "Expected nil but got " (str val)))))
|
||||
|
||||
(define assert-type
|
||||
(fn (expected-type val)
|
||||
;; Implemented via predicate dispatch since type-of is a platform
|
||||
;; function not available in all hosts. Uses nested if to avoid
|
||||
;; Scheme-style cond detection for 2-element predicate calls.
|
||||
;; Boolean checked before number (subtypes on some platforms).
|
||||
(let ((actual-type
|
||||
(if (nil? val) "nil"
|
||||
(if (boolean? val) "boolean"
|
||||
(if (number? val) "number"
|
||||
(if (string? val) "string"
|
||||
(if (list? val) "list"
|
||||
(if (dict? val) "dict"
|
||||
"unknown"))))))))
|
||||
(assert (= expected-type actual-type)
|
||||
(define
|
||||
assert-type
|
||||
(fn
|
||||
(expected-type val)
|
||||
(let
|
||||
((actual-type (if (nil? val) "nil" (if (boolean? val) "boolean" (if (number? val) "number" (if (string? val) "string" (if (list? val) "list" (if (dict? val) "dict" "unknown"))))))))
|
||||
(assert
|
||||
(= expected-type actual-type)
|
||||
(str "Expected type " expected-type " but got " actual-type)))))
|
||||
|
||||
(define assert-length
|
||||
(fn (expected-len col)
|
||||
(assert (= (len col) expected-len)
|
||||
(define
|
||||
assert-length
|
||||
(fn
|
||||
(expected-len col)
|
||||
(assert
|
||||
(= (len col) expected-len)
|
||||
(str "Expected length " expected-len " but got " (len col)))))
|
||||
|
||||
(define assert-contains
|
||||
(fn (item col)
|
||||
(assert (some (fn (x) (equal? x item)) col)
|
||||
(define
|
||||
assert-contains
|
||||
(fn
|
||||
(item col)
|
||||
(assert
|
||||
(some (fn (x) (equal? x item)) col)
|
||||
(str "Expected collection to contain " (str item)))))
|
||||
|
||||
(define assert-throws
|
||||
(fn (thunk)
|
||||
(let ((result (try-call thunk)))
|
||||
(assert (not (get result "ok"))
|
||||
(define
|
||||
assert-throws
|
||||
(fn
|
||||
(thunk)
|
||||
(let
|
||||
((result (try-call thunk)))
|
||||
(assert
|
||||
(not (get result "ok"))
|
||||
"Expected an error to be thrown but none was"))))
|
||||
|
||||
|
||||
;; ==========================================================================
|
||||
;; 3. Test suites — SX testing SX
|
||||
;; ==========================================================================
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; 3a. Literals and types
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "literals"
|
||||
(deftest "numbers are numbers"
|
||||
(defsuite
|
||||
"literals"
|
||||
(deftest
|
||||
"numbers are numbers"
|
||||
(assert-type "number" 42)
|
||||
(assert-type "number" 3.14)
|
||||
(assert-type "number" -1))
|
||||
|
||||
(deftest "strings are strings"
|
||||
(deftest
|
||||
"strings are strings"
|
||||
(assert-type "string" "hello")
|
||||
(assert-type "string" ""))
|
||||
|
||||
(deftest "booleans are booleans"
|
||||
(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"
|
||||
(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})))
|
||||
|
||||
(deftest "dicts are dicts"
|
||||
(assert-type "dict" {:a 1 :b 2})))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; 3b. Arithmetic
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "arithmetic"
|
||||
(deftest "addition"
|
||||
(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"
|
||||
(deftest
|
||||
"subtraction"
|
||||
(assert-equal 1 (- 3 2))
|
||||
(assert-equal -1 (- 2 3)))
|
||||
|
||||
(deftest "multiplication"
|
||||
(deftest
|
||||
"multiplication"
|
||||
(assert-equal 6 (* 2 3))
|
||||
(assert-equal 0 (* 0 100))
|
||||
(assert-equal 24 (* 1 2 3 4)))
|
||||
|
||||
(deftest "division"
|
||||
(deftest
|
||||
"division"
|
||||
(assert-equal 2 (/ 6 3))
|
||||
(assert-equal 2.5 (/ 5 2)))
|
||||
|
||||
(deftest "modulo"
|
||||
(deftest
|
||||
"modulo"
|
||||
(assert-equal 1 (mod 7 3))
|
||||
(assert-equal 0 (mod 6 3))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; 3c. Comparison
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "comparison"
|
||||
(deftest "equality"
|
||||
(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)))
|
||||
(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"
|
||||
(deftest
|
||||
"ordering"
|
||||
(assert-true (< 1 2))
|
||||
(assert-false (< 2 1))
|
||||
(assert-true (> 2 1))
|
||||
@@ -198,405 +159,418 @@
|
||||
(assert-true (>= 2 2))
|
||||
(assert-true (>= 3 2))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; 3d. String operations
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "strings"
|
||||
(deftest "str concatenation"
|
||||
(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"
|
||||
(deftest
|
||||
"string-length"
|
||||
(assert-equal 5 (string-length "hello"))
|
||||
(assert-equal 0 (string-length "")))
|
||||
|
||||
(deftest "substring"
|
||||
(deftest
|
||||
"substring"
|
||||
(assert-equal "ell" (substring "hello" 1 4))
|
||||
(assert-equal "hello" (substring "hello" 0 5)))
|
||||
|
||||
(deftest "string-contains?"
|
||||
(deftest
|
||||
"string-contains?"
|
||||
(assert-true (string-contains? "hello world" "world"))
|
||||
(assert-false (string-contains? "hello" "xyz")))
|
||||
|
||||
(deftest "upcase and downcase"
|
||||
(deftest
|
||||
"upcase and downcase"
|
||||
(assert-equal "HELLO" (upcase "hello"))
|
||||
(assert-equal "hello" (downcase "HELLO")))
|
||||
|
||||
(deftest "trim"
|
||||
(deftest
|
||||
"trim"
|
||||
(assert-equal "hello" (trim " hello "))
|
||||
(assert-equal "hello" (trim "hello")))
|
||||
|
||||
(deftest "split and join"
|
||||
(deftest
|
||||
"split and join"
|
||||
(assert-equal (list "a" "b" "c") (split "a,b,c" ","))
|
||||
(assert-equal "a-b-c" (join "-" (list "a" "b" "c")))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; 3e. List operations
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "lists"
|
||||
(deftest "constructors"
|
||||
(assert-equal (list 1 2 3) (list 1 2 3))
|
||||
(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"
|
||||
(deftest
|
||||
"first and rest"
|
||||
(assert-equal 1 (first (list 1 2 3)))
|
||||
(assert-equal (list 2 3) (rest (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"
|
||||
(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)))
|
||||
(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?"
|
||||
(deftest
|
||||
"empty?"
|
||||
(assert-true (empty? (list)))
|
||||
(assert-false (empty? (list 1))))
|
||||
|
||||
(deftest "len"
|
||||
(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))))))
|
||||
|
||||
(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))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; 3f. Dict operations
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "dicts"
|
||||
(deftest "dict literal"
|
||||
(assert-type "dict" {:a 1 :b 2})
|
||||
(defsuite
|
||||
"dicts"
|
||||
(deftest
|
||||
"dict literal"
|
||||
(assert-type "dict" {:b 2 :a 1})
|
||||
(assert-equal 1 (get {:a 1} "a"))
|
||||
(assert-equal 2 (get {:a 1 :b 2} "b")))
|
||||
|
||||
(deftest "assoc"
|
||||
(assert-equal {:a 1 :b 2} (assoc {:a 1} "b" 2))
|
||||
(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 {:a 1 :b 2} "a")))
|
||||
|
||||
(deftest "keys and vals"
|
||||
(let ((d {:a 1 :b 2}))
|
||||
(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?"
|
||||
(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}))))
|
||||
|
||||
(deftest "merge"
|
||||
(assert-equal {:a 1 :b 2 :c 3}
|
||||
(merge {:a 1 :b 2} {:c 3}))
|
||||
(assert-equal {:a 99 :b 2}
|
||||
(merge {:a 1 :b 2} {:a 99}))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; 3g. Predicates
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "predicates"
|
||||
(deftest "nil?"
|
||||
(defsuite
|
||||
"predicates"
|
||||
(deftest
|
||||
"nil?"
|
||||
(assert-true (nil? nil))
|
||||
(assert-false (nil? 0))
|
||||
(assert-false (nil? false))
|
||||
(assert-false (nil? "")))
|
||||
|
||||
(deftest "number?"
|
||||
(deftest
|
||||
"number?"
|
||||
(assert-true (number? 42))
|
||||
(assert-true (number? 3.14))
|
||||
(assert-false (number? "42")))
|
||||
|
||||
(deftest "string?"
|
||||
(deftest
|
||||
"string?"
|
||||
(assert-true (string? "hello"))
|
||||
(assert-false (string? 42)))
|
||||
|
||||
(deftest "list?"
|
||||
(deftest
|
||||
"list?"
|
||||
(assert-true (list? (list 1 2)))
|
||||
(assert-false (list? "not a list")))
|
||||
|
||||
(deftest "dict?"
|
||||
(deftest
|
||||
"dict?"
|
||||
(assert-true (dict? {:a 1}))
|
||||
(assert-false (dict? (list 1))))
|
||||
|
||||
(deftest "boolean?"
|
||||
(deftest
|
||||
"boolean?"
|
||||
(assert-true (boolean? true))
|
||||
(assert-true (boolean? false))
|
||||
(assert-false (boolean? nil))
|
||||
(assert-false (boolean? 0)))
|
||||
|
||||
(deftest "not"
|
||||
(deftest
|
||||
"not"
|
||||
(assert-true (not false))
|
||||
(assert-true (not nil))
|
||||
(assert-false (not true))
|
||||
(assert-false (not 1))
|
||||
(assert-false (not "x"))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; 3h. Special forms
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "special-forms"
|
||||
(deftest "if"
|
||||
(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"
|
||||
(deftest
|
||||
"when"
|
||||
(assert-equal "yes" (when true "yes"))
|
||||
(assert-nil (when false "yes")))
|
||||
|
||||
(deftest "cond"
|
||||
(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 "and"
|
||||
(assert-equal "c" (cond false "a" false "b" :else "c")))
|
||||
(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"
|
||||
(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"
|
||||
(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"
|
||||
(deftest
|
||||
"let clojure-style"
|
||||
(assert-equal 3 (let (x 1 y 2) (+ x y))))
|
||||
|
||||
(deftest "do / begin"
|
||||
(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!"
|
||||
(deftest "define" (define x 42) (assert-equal 42 x))
|
||||
(deftest
|
||||
"set!"
|
||||
(define x 1)
|
||||
(set! x 2)
|
||||
(assert-equal 2 x)))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; 3i. Lambda and closures
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "lambdas"
|
||||
(deftest "basic lambda"
|
||||
(let ((add (fn (a b) (+ a b))))
|
||||
(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))))
|
||||
(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))))))
|
||||
(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)))
|
||||
(deftest
|
||||
"higher-order returns lambda"
|
||||
(let
|
||||
((make-adder (fn (n) (fn (x) (+ n x)))))
|
||||
(let
|
||||
((add5 (make-adder 5)))
|
||||
(assert-equal 8 (add5 3))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; 3j. Higher-order forms
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "higher-order"
|
||||
(deftest "map"
|
||||
(assert-equal (list 2 4 6)
|
||||
(map (fn (x) (* x 2)) (list 1 2 3)))
|
||||
(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)
|
||||
(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")
|
||||
(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")))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; 3k. Components
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "components"
|
||||
(deftest "defcomp creates component"
|
||||
(defcomp ~test-comp (&key title)
|
||||
(div title))
|
||||
;; Component is bound and not nil
|
||||
(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 "!")))
|
||||
(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))
|
||||
(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")))
|
||||
(deftest
|
||||
"component with default via or"
|
||||
(defcomp ~label (&key text) (span (or text "default")))
|
||||
(assert-true (not (nil? ~label)))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; 3l. Macros
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "macros"
|
||||
(deftest "defmacro creates macro"
|
||||
(defmacro unless (cond &rest body)
|
||||
`(if (not ,cond) (do ,@body)))
|
||||
(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))))))
|
||||
|
||||
(deftest "quasiquote and unquote"
|
||||
(let ((x 42))
|
||||
(assert-equal (list 1 42 3) `(1 ,x 3))))
|
||||
|
||||
(deftest "splice-unquote"
|
||||
(let ((xs (list 2 3 4)))
|
||||
(assert-equal (list 1 2 3 4 5) `(1 ,@xs 5)))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; 3m. Threading macro
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "threading"
|
||||
(deftest "thread-first"
|
||||
(defsuite
|
||||
"threading"
|
||||
(deftest
|
||||
"thread-first"
|
||||
(assert-equal 8 (-> 5 (+ 1) (+ 2)))
|
||||
(assert-equal "HELLO" (-> "hello" upcase))
|
||||
(assert-equal "HELLO WORLD"
|
||||
(-> "hello"
|
||||
(str " world")
|
||||
upcase))))
|
||||
(assert-equal "HELLO WORLD" (-> "hello" (str " world") upcase))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; 3n. Truthiness
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "truthiness"
|
||||
(deftest "truthy values"
|
||||
(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"
|
||||
(deftest
|
||||
"falsy values"
|
||||
(assert-false (if false true false))
|
||||
(assert-false (if nil true false)))
|
||||
(assert-false (if nil true false))))
|
||||
|
||||
;; NOTE: empty list, zero, and empty string truthiness is
|
||||
;; platform-dependent. Python treats all three as falsy.
|
||||
;; JavaScript treats [] as truthy but 0 and "" as falsy.
|
||||
;; These tests are omitted — each bootstrapper should emit
|
||||
;; platform-specific truthiness tests instead.
|
||||
)
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; 3o. Edge cases and regression tests
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defsuite "edge-cases"
|
||||
(deftest "nested let scoping"
|
||||
(let ((x 1))
|
||||
(let ((x 2))
|
||||
(assert-equal 2 x))
|
||||
;; outer x should be unchanged by inner let
|
||||
;; (this tests that let creates a new scope)
|
||||
))
|
||||
|
||||
(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"
|
||||
(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"
|
||||
(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"
|
||||
(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
|
||||
(reduce (fn (acc x) (+ acc x)) 0 (list)))
|
||||
(assert-equal 0 (len (list)))
|
||||
(assert-equal "" (str))))
|
||||
|
||||
Reference in New Issue
Block a user