test(spec): numbers, strings, predicates — fix variadic <, seq?, index-of

Add spec suites for numbers/arithmetic, strings (str + clojure.string), and
type/value predicates. Bugs they caught and fixed:
- <, >, <=, >= were binary-only; now variadic ((< 1 2 3) chains)
- seq? was true for vectors; vectors are not ISeq in Clojure, so (seq? [1])
  is now false (true only for lists/lazy-seqs)
- clojure.string/index-of and last-index-of were 1-based (stray inc); now
  0-based per Clojure

Corrected a systematic-coverage assertion that encoded the old seq? bug.
jpm test green, conformance 218/218.
This commit is contained in:
Yogthos 2026-06-05 00:13:19 -04:00
parent b353d625f1
commit 68d6b1d6c1
6 changed files with 178 additions and 12 deletions

View file

@ -108,24 +108,23 @@
s)))
(defn index-of
"0-based index of the first occurrence of value in s, or nil."
([s value]
(let [idx (str-find value s)]
(when idx (inc idx))))
(str-find value s))
([s value from]
(let [idx (str-find value (subs s from))]
(when idx (+ from (inc idx))))))
(when idx (+ from idx)))))
(defn last-index-of
([s value]
(let [r (str-reverse-b s) sval (str-reverse-b value)
idx (str-find sval r)]
(when idx (inc (- (count s) (+ idx (count value)))))))
(when idx (- (count s) (+ idx (count value))))))
([s value from]
(let [sub (subs s 0 from) r (str-reverse-b sub) sval (str-reverse-b value)
idx (str-find sval r)]
(when idx (inc (- from (+ idx (count value))))))))
(when idx (- from (+ idx (count value)))))))
(defn re-quote-replacement
"Escape special characters (backslash and dollar) in a regex replacement

View file

@ -87,7 +87,9 @@
(defn core-symbol? [x] (and (struct? x) (= :symbol (x :jolt/type))))
(defn core-vector? [x] (jvec? x))
(defn core-map? [x] (or (phm? x) (struct? x) (if (and (table? x) (get x :jolt/deftype)) true false)))
(defn core-seq? [x] (or (array? x) (tuple? x) (pvec? x) (plist? x) (lazy-seq? x)))
# seq? is true only for actual sequences (lists, lazy-seqs) — NOT vectors, which
# are not ISeq in Clojure. (A Janet array represents a Clojure list/seq result.)
(defn core-seq? [x] (or (array? x) (plist? x) (lazy-seq? x)))
(defn core-coll? [x] (or (array? x) (tuple? x) (pvec? x) (plist? x) (struct? x) (phm? x) (set? x) (lazy-seq? x)))
(defn core-true? [x] (= true x))
@ -239,10 +241,17 @@
(defn core-not= [& args] (not (apply core-= args)))
(defn core-< [a b] (< a b))
(defn core-> [a b] (> a b))
(defn core-<= [a b] (<= a b))
(defn core->= [a b] (>= a b))
# Comparisons are variadic: (< a b c) means a < b < c.
(defn- chain-cmp [op xs]
(var ok true) (var i 0)
(while (and ok (< i (dec (length xs))))
(unless (op (in xs i) (in xs (+ i 1))) (set ok false))
(++ i))
ok)
(defn core-< [& xs] (chain-cmp < xs))
(defn core-> [& xs] (chain-cmp > xs))
(defn core-<= [& xs] (chain-cmp <= xs))
(defn core->= [& xs] (chain-cmp >= xs))
# ============================================================
# Collections

View file

@ -42,7 +42,8 @@
(assert (= true (ct-eval ctx "(set? #{1 2})")) "set?")
(assert (= false (ct-eval ctx "(set? [1 2])")) "set? false")
(assert (= true (ct-eval ctx "(seq? '(1 2))")) "seq? list")
(assert (= true (ct-eval ctx "(seq? [1 2])")) "seq? vector")
# vectors are not ISeq in Clojure — (seq? [1 2]) is false
(assert (= false (ct-eval ctx "(seq? [1 2])")) "seq? vector is false")
(assert (= true (ct-eval ctx "(coll? [1 2])")) "coll? vec")
(assert (= true (ct-eval ctx "(coll? '(1 2))")) "coll? list")
(assert (= true (ct-eval ctx "(coll? {})")) "coll? map")

View file

@ -0,0 +1,63 @@
# Specification: numbers & arithmetic.
(use ../support/harness)
(defspec "numbers / arithmetic"
["add" "6" "(+ 1 2 3)"]
["add zero args" "0" "(+)"]
["subtract" "5" "(- 10 3 2)"]
["negate" "-5" "(- 5)"]
["multiply" "24" "(* 2 3 4)"]
["multiply zero args" "1" "(*)"]
["divide" "2" "(/ 10 5)"]
["divide to fraction" "0.5" "(/ 1 2)"]
["inc" "6" "(inc 5)"]
["dec" "4" "(dec 5)"]
["quot" "3" "(quot 10 3)"]
["rem" "1" "(rem 10 3)"]
["mod" "2" "(mod -1 3)"]
["rem negative" "-1" "(rem -1 3)"]
["max" "9" "(max 3 9 1)"]
["min" "1" "(min 3 9 1)"]
["abs" "5" "(abs -5)"]
["promoting + alias" "3" "(+' 1 2)"]
["inc' alias" "6" "(inc' 5)"])
(defspec "numbers / comparison"
["less than" "true" "(< 1 2 3)"]
["less than false" "false" "(< 1 3 2)"]
["greater than" "true" "(> 3 2 1)"]
["<=" "true" "(<= 1 1 2)"]
[">=" "true" "(>= 3 3 2)"]
["= numbers" "true" "(= 2 2)"]
["= different" "false" "(= 2 3)"]
["== numeric" "true" "(== 2 2)"]
["not=" "true" "(not= 1 2)"]
["compare less" "-1" "(compare 1 2)"]
["compare equal" "0" "(compare 1 1)"]
["compare greater" "1" "(compare 2 1)"])
(defspec "numbers / predicates"
["zero?" "true" "(zero? 0)"]
["pos?" "true" "(pos? 5)"]
["neg?" "true" "(neg? -5)"]
["even?" "true" "(even? 4)"]
["odd?" "true" "(odd? 3)"]
["number?" "true" "(number? 5)"]
["number? false" "false" "(number? :a)"]
["int?" "true" "(int? 5)"]
["pos-int?" "true" "(pos-int? 5)"]
["neg-int?" "true" "(neg-int? -5)"]
["nat-int? zero" "true" "(nat-int? 0)"]
["nat-int? neg" "false" "(nat-int? -1)"]
["ratio? false" "false" "(ratio? 5)"])
(defspec "numbers / bit-ops & math"
["bit-and" "4" "(bit-and 12 6)"]
["bit-or" "14" "(bit-or 12 6)"]
["bit-xor" "10" "(bit-xor 12 6)"]
["bit-shift-left" "8" "(bit-shift-left 1 3)"]
["bit-shift-right" "2" "(bit-shift-right 8 2)"]
["bit-set" "8" "(bit-set 0 3)"]
["bit-clear" "13" "(bit-clear 15 1)"]
["bit-test true" "true" "(bit-test 4 2)"]
["bigint 64-bit" "\"9000000000\"" "(str (bigint 9000000000))"])

View file

@ -0,0 +1,61 @@
# Specification: type & value predicates.
(use ../support/harness)
(defspec "predicates / nil & boolean"
["nil? true" "true" "(nil? nil)"]
["nil? false" "false" "(nil? 0)"]
["some? true" "true" "(some? 0)"]
["some? on nil" "false" "(some? nil)"]
["true?" "true" "(true? true)"]
["false?" "true" "(false? false)"]
["boolean? true" "true" "(boolean? false)"]
["not nil" "true" "(not nil)"]
["not 0 is false" "false" "(not 0)"]
["boolean of nil" "false" "(boolean nil)"]
["boolean of value" "true" "(boolean 5)"])
(defspec "predicates / types"
["string?" "true" "(string? \"x\")"]
["number?" "true" "(number? 1)"]
["keyword?" "true" "(keyword? :a)"]
["symbol?" "true" "(symbol? (quote a))"]
["char?" "true" "(char? \\a)"]
["fn? on fn" "true" "(fn? inc)"]
["ifn? on keyword" "true" "(ifn? :a)"]
["vector?" "true" "(vector? [1])"]
["list?" "true" "(list? (list 1))"]
["map?" "true" "(map? {:a 1})"]
["set?" "true" "(set? #{1})"]
["coll? vector" "true" "(coll? [1])"]
["coll? map" "true" "(coll? {:a 1})"]
["coll? on number" "false" "(coll? 1)"]
["seq? list" "true" "(seq? (list 1))"]
["seq? vector" "false" "(seq? [1])"]
["sequential? vector" "true" "(sequential? [1])"]
["associative? map" "true" "(associative? {:a 1})"]
["associative? vec" "true" "(associative? [1])"]
["indexed? vector" "true" "(indexed? [1])"]
["counted? vector" "true" "(counted? [1])"])
(defspec "predicates / idents"
["ident? keyword" "true" "(ident? :a)"]
["ident? symbol" "true" "(ident? (quote a))"]
["simple-keyword?" "true" "(simple-keyword? :a)"]
["qualified-keyword?" "true" "(qualified-keyword? :a/b)"]
["simple-symbol?" "true" "(simple-symbol? (quote a))"]
["qualified-symbol?" "true" "(qualified-symbol? (quote a/b))"]
["name of keyword" "\"a\"" "(name :a)"]
["name of qualified" "\"b\"" "(name :a/b)"]
["namespace" "\"a\"" "(namespace :a/b)"]
["namespace simple" "nil" "(namespace :a)"])
(defspec "predicates / equality & identity"
["= same" "true" "(= 1 1)"]
["= vectors" "true" "(= [1 2] [1 2])"]
["= vector & list" "true" "(= [1 2] (list 1 2))"]
["= maps" "true" "(= {:a 1} {:a 1})"]
["= sets" "true" "(= #{1 2} #{2 1})"]
["= nested" "true" "(= {:a [1 2]} {:a [1 2]})"]
["not= differs" "true" "(not= [1 2] [1 3])"]
["identical? same kw" "true" "(identical? :a :a)"]
["compare strings" "-1" "(compare \"a\" \"b\")"])

View file

@ -0,0 +1,33 @@
# Specification: strings (str + clojure.string).
(use ../support/harness)
(defspec "string / str & basics"
["str concat" "\"abc\"" "(str \"a\" \"b\" \"c\")"]
["str of numbers" "\"12\"" "(str 1 2)"]
["str nil is empty" "\"\"" "(str nil)"]
["str mixed" "\"a1:b\"" "(str \"a\" 1 :b)"]
["str of coll" "\"[1 2]\"" "(str [1 2])"]
["count" "3" "(count \"abc\")"]
["subs from" "\"bc\"" "(subs \"abc\" 1)"]
["subs range" "\"b\"" "(subs \"abc\" 1 2)"]
["string? true" "true" "(string? \"x\")"]
["seq of string" "[\\a \\b]" "(seq \"ab\")"])
(defspec "clojure.string"
["join" "\"a,b,c\"" "(do (require (quote [clojure.string :as s])) (s/join \",\" [\"a\" \"b\" \"c\"]))"]
["join no sep" "\"abc\"" "(do (require (quote [clojure.string :as s])) (s/join [\"a\" \"b\" \"c\"]))"]
["split" "[\"a\" \"b\"]" "(do (require (quote [clojure.string :as s])) (s/split \"a,b\" #\",\"))"]
["split-lines" "[\"a\" \"b\"]" "(do (require (quote [clojure.string :as s])) (s/split-lines \"a\\nb\"))"]
["upper-case" "\"ABC\"" "(do (require (quote [clojure.string :as s])) (s/upper-case \"abc\"))"]
["lower-case" "\"abc\"" "(do (require (quote [clojure.string :as s])) (s/lower-case \"ABC\"))"]
["capitalize" "\"Abc\"" "(do (require (quote [clojure.string :as s])) (s/capitalize \"abc\"))"]
["trim" "\"x\"" "(do (require (quote [clojure.string :as s])) (s/trim \" x \"))"]
["triml" "\"x \"" "(do (require (quote [clojure.string :as s])) (s/triml \" x \"))"]
["blank? true" "true" "(do (require (quote [clojure.string :as s])) (s/blank? \" \"))"]
["blank? false" "false" "(do (require (quote [clojure.string :as s])) (s/blank? \"x\"))"]
["includes?" "true" "(do (require (quote [clojure.string :as s])) (s/includes? \"hello\" \"ell\"))"]
["starts-with?" "true" "(do (require (quote [clojure.string :as s])) (s/starts-with? \"hello\" \"he\"))"]
["ends-with?" "true" "(do (require (quote [clojure.string :as s])) (s/ends-with? \"hello\" \"lo\"))"]
["replace" "\"hexxo\"" "(do (require (quote [clojure.string :as s])) (s/replace \"hello\" \"l\" \"x\"))"]
["reverse" "\"cba\"" "(do (require (quote [clojure.string :as s])) (s/reverse \"abc\"))"]
["index-of" "2" "(do (require (quote [clojure.string :as s])) (s/index-of \"hello\" \"l\"))"])