jolt/test/spec/numbers-spec.janet
Dmitri Sotnikov 3e9fe8d0fb
Comprehensive spec (#19)
* core: fix jolt-265 — syntax-quote fully-qualifies core syms to clojure.core/

Re-attempt of the deferred gap, now unblocked. The earlier uberscript break
wasn't qualification itself but an aliased-ref resolution bug it exposed:
resolve-var looked up ns-aliases (e.g. g/foo) against the analyzer's REBOUND
ctx-current-ns (jolt.analyzer, since the analyzer runs interpreted in its own
ns) instead of the namespace being compiled. A user's aliased refs failed
mid-compile (g/hello -> nil in the bundled standalone).

- resolve-var resolves aliases against :compile-ns when set (same ns
  h-current-ns uses), falling back to ctx-current-ns — correct for both modes.
- sq-symbol qualifies a resolved clojure.core name to clojure.core/<name>
  (Clojure hygiene); special forms stay bare; unresolved syms -> current ns.

Tests updated to the qualified behavior (features-test, reader-forms-spec).

* test: comprehensive spec — regex + sorted colls + random/predicate gaps

Filling the biggest untested clojure.core areas found in a coverage audit
(168 of 506 provided fns had no spec). New + expanded suites:

- regex-spec.janet (20): #"…" literals, regex?, re-find/re-matches/re-seq
  (match/no-match/groups), re-pattern, and clojure.string split/replace with
  regex (incl $1 backrefs). Whole area was previously unspecced.
- sorted-spec.janet (14): sorted-map/sorted-set construction + ordering, sorted?,
  subseq/rsubseq. Pins the working subset — get/conj/assoc/keys/vals on sorted
  colls and the by-comparator ctors are not yet first-class (jolt-ti9).
- predicates-spec +14: seqable?, integer?, reduced?/unreduced, not-empty.
- numbers-spec +5: rand/rand-int/rand-nth invariants.

Fix: sorted? was bound to core-sorted-map? so it returned false for sorted-sets;
now true for both sorted maps and sets (core-sorted?).

Filed: jolt-ti9 (sorted collections incomplete: get/conj/assoc/keys/vals don't
operate on the sorted wrapper; sorted-*-by ignore the comparator).

Gate green incl full jpm build + jpm test.

* core: close surfaced gaps — first-class sorted colls, with-out-str, rand arity, deref reduced

jolt-ti9: sorted-map/sorted-set are now first-class across the collection fns —
get/assoc/dissoc/conj/contains?/keys/vals/disj and call-as-fn all operate on the
wrapper and preserve sort order. The by-comparator constructors (sorted-map-by/
sorted-set-by) now thread the user comparator (numeric or boolean-predicate) through
all derived colls. Sorted predicates/ctors/ops moved above core-conj so the
collection fns can branch on them; jolt-invoke (interpreter) gets inline branches.

jolt-rfw: add with-out-str (binds output to a string buffer) + the macro.
jolt-ek3: (rand n) arity and deref-of-reduced (uuid? still deferred).

Specs: new io-spec.janet; sorted-spec expanded to pin the now-working map/set ops
and by-comparator ordering; predicate/number spec restorations.

* remove old doc

* core: fix stale comment — by-comparator sorted ctors are implemented

---------

Co-authored-by: Yogthos <yogthos@gmail.com>
2026-06-10 20:33:22 +08:00

136 lines
6.2 KiB
Text

# 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)"])
# Symbolic float values and float/double predicates. NOTE: Janet represents
# integers and integer-valued doubles identically, so (float? 1.0) is false
# (1.0 is indistinguishable from 1) — a documented divergence. Fractional and
# non-finite values ARE recognized as floats.
(defspec "numbers / floats & symbolic values"
["read ##Inf" "true" "(= ##Inf (/ 1.0 0.0))"]
["read ##-Inf" "true" "(< ##-Inf 0)"]
["##NaN not= itself" "true" "(not (== ##NaN ##NaN))"]
["float? fractional" "true" "(float? 1.5)"]
["double? fractional" "true" "(double? 0.25)"]
["float? integer" "false" "(float? 3)"]
["float? ##Inf" "true" "(float? ##Inf)"]
["double? ##NaN" "true" "(double? ##NaN)"]
["infinite? ##Inf" "true" "(infinite? ##Inf)"]
["infinite? ##-Inf" "true" "(infinite? ##-Inf)"]
["infinite? finite" "false" "(infinite? 1.5)"]
["NaN? ##NaN" "true" "(NaN? ##NaN)"]
["NaN? number" "false" "(NaN? 1.0)"]
["int? ##Inf false" "false" "(int? ##Inf)"]
["pos-int? ##Inf" "false" "(pos-int? ##Inf)"])
# Numeric literal syntaxes. Jolt has no true bignum/ratio/bigdec types, so the
# N (bigint) and M (bigdec) suffixes read as the plain number, ratios as the
# double quotient; radix integers (NrDDD) are parsed by base.
(defspec "numbers / literal syntax"
["bigint suffix N" "42" "42N"]
["bigint zero" "0" "0N"]
["bigdec suffix M" "1.5" "1.5M"]
["bigdec int M" "0" "0.0M"]
["ratio -> double" "0.5" "1/2"]
["ratio 3/4" "0.75" "3/4"]
["neg ratio" "-0.5" "-1/2"]
["radix binary" "10" "2r1010"]
["radix hex-ish" "255" "16rFF"]
["radix base36" "35" "36rZ"]
["hex" "255" "0xFF"]
["exponent" "1000.0" "1e3"]
["exponent neg" "0.015" "1.5e-2"])
# Strictness: numeric ops reject non-numbers like Clojure; the integer
# predicates reject non-integers; quot/rem/mod reject zero/non-finite.
(defspec "numbers / strictness (throws like Clojure)"
["odd? nil" :throws "(odd? nil)"]
["odd? fractional" :throws "(odd? 1.5)"]
["even? inf" :throws "(even? ##Inf)"]
["zero? nil" :throws "(zero? nil)"]
["pos? false" :throws "(pos? false)"]
["neg? keyword" :throws "(neg? :a)"]
["< nil" :throws "(< nil 1)"]
["> with nil" :throws "(> 1 nil)"]
["max non-number" :throws "(max 1 nil)"]
["quot by zero" :throws "(quot 10 0)"]
["quot inf" :throws "(quot ##Inf 1)"]
["< arity-1 any" "true" "(< :anything)"]
["odd? ok" "true" "(odd? 3)"]
["< ok" "true" "(< 1 2 3)"]
["quot ok" "3" "(quot 10 3)"])
(defspec "numbers / printing of inf & nan"
["str Infinity" "\"Infinity\"" "(str ##Inf)"]
["str -Infinity" "\"-Infinity\"" "(str ##-Inf)"]
["str NaN" "\"NaN\"" "(str ##NaN)"]
["pr-str Infinity" "\"Infinity\"" "(pr-str ##Inf)"]
["inf inside coll" "\"[Infinity]\"" "(str [##Inf])"])
(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))"])
(defspec "numbers / random (invariants — non-deterministic)"
["rand-int in range" "true" "(let [r (rand-int 5)] (and (integer? r) (>= r 0) (< r 5)))"]
["rand-int zero" "0" "(rand-int 1)"]
["rand in [0,1)" "true" "(let [r (rand)] (and (>= r 0) (< r 1)))"]
["rand n in [0,n)" "true" "(let [r (rand 10)] (and (>= r 0) (< r 10)))"]
["rand-nth member" "true" "(contains? #{:a :b :c} (rand-nth [:a :b :c]))"]
["rand-nth single" ":x" "(rand-nth [:x])"])