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:
2026-05-01 17:27:27 +00:00
parent e9d2003d6a
commit 036022cc17
12 changed files with 1558 additions and 859 deletions

View File

@@ -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)))