diff --git a/src/jolt/clojure/string.clj b/src/jolt/clojure/string.clj index 77a70a5..c435719 100644 --- a/src/jolt/clojure/string.clj +++ b/src/jolt/clojure/string.clj @@ -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 diff --git a/src/jolt/core.janet b/src/jolt/core.janet index 4078ef7..74ed09c 100644 --- a/src/jolt/core.janet +++ b/src/jolt/core.janet @@ -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 diff --git a/test/integration/systematic-coverage-test.janet b/test/integration/systematic-coverage-test.janet index 466f7bb..eee08de 100644 --- a/test/integration/systematic-coverage-test.janet +++ b/test/integration/systematic-coverage-test.janet @@ -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") diff --git a/test/spec/numbers-spec.janet b/test/spec/numbers-spec.janet new file mode 100644 index 0000000..384cf3b --- /dev/null +++ b/test/spec/numbers-spec.janet @@ -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))"]) diff --git a/test/spec/predicates-spec.janet b/test/spec/predicates-spec.janet new file mode 100644 index 0000000..7241d4e --- /dev/null +++ b/test/spec/predicates-spec.janet @@ -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\")"]) diff --git a/test/spec/strings-spec.janet b/test/spec/strings-spec.janet new file mode 100644 index 0000000..d46583a --- /dev/null +++ b/test/spec/strings-spec.janet @@ -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\"))"])