diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 72c32ce..169cb18 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -5,6 +5,7 @@ {"_type":"issue","id":"jolt-x8s","title":"CRITICAL: collections not callable as IFn (vector/map/set)","status":"closed","priority":1,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T17:46:14Z","created_by":"Yogthos","updated_at":"2026-06-04T17:50:54Z","closed_at":"2026-06-04T17:50:54Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"jolt-alz","title":"CRITICAL: self-referential lazy-seq/lazy-cat yields only literal prefix","status":"closed","priority":1,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T17:46:13Z","created_by":"Yogthos","updated_at":"2026-06-04T18:09:15Z","closed_at":"2026-06-04T18:09:15Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"jolt-wec","title":"CRITICAL: multi-collection map returns unrealized thunk","status":"closed","priority":1,"issue_type":"bug","owner":"yogthos@gmail.com","created_at":"2026-06-04T17:46:12Z","created_by":"Yogthos","updated_at":"2026-06-04T18:06:02Z","closed_at":"2026-06-04T18:06:02Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"jolt-lko","title":"clojure-test-suite: strictness — throw on malformed calls like Clojure","description":"Make jolt throw where Clojure throws (bad-shape conj!/assoc!, arithmetic on non-numbers, out-of-range indices, etc.) to satisfy the suite's ~292 p/thrown? assertions. Targeted input validation across affected fns; guard against regressing jolt's spec/conformance.","status":"in_progress","priority":2,"issue_type":"task","assignee":"Yogthos","owner":"yogthos@gmail.com","created_at":"2026-06-05T14:45:20Z","created_by":"Yogthos","updated_at":"2026-06-05T14:45:39Z","started_at":"2026-06-05T14:45:39Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"jolt-kxb","title":"clojure-test-suite: transducers don't short-circuit over infinite seqs (hangs)","description":"(into [] (take 5) (range)) and similar transducer-over-infinite forms hang jolt's eager evaluator (no reduced/early-termination). 7 suite files time out. Make transducing honor reduced.","status":"closed","priority":2,"issue_type":"bug","assignee":"Yogthos","owner":"yogthos@gmail.com","created_at":"2026-06-05T13:05:18Z","created_by":"Yogthos","updated_at":"2026-06-05T13:18:37Z","started_at":"2026-06-05T13:09:59Z","closed_at":"2026-06-05T13:18:37Z","close_reason":"reduce-with-reduced steps lazy seqs incrementally; transducers short-circuit over infinite seqs","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"jolt-js6","title":"Port clojure-test-suite as integration battery","description":"Build a minimal clojure.test + portability shim so Jolt can load the external ~/src/clojure-test-suite (240 per-fn .cljc files in clojure.test format). Add a baseline-guarded integration runner following the jank-conformance pattern (skip if absent).","status":"closed","priority":2,"issue_type":"task","assignee":"Yogthos","owner":"yogthos@gmail.com","created_at":"2026-06-05T12:22:01Z","created_by":"Yogthos","updated_at":"2026-06-05T13:05:04Z","started_at":"2026-06-05T12:22:14Z","closed_at":"2026-06-05T13:05:04Z","close_reason":"Ported clojure-test-suite as baseline-guarded integration battery; surfaced+fixed 2 evaluator macro bugs","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"jolt-i3g","title":"Clear final 2 SCI bootstrap forms (Var/IVar collision + clojure.repl/template)","description":"namespaces.cljc forms 126/127 fail: (126) lang_stubs IVar collides with real vars.cljc IVar so unbind dispatch misses on sci.lang.Var; (127) clojure.repl + clojure.template namespaces missing. Fix: trim bootstrap stubs to let real modules own canonical protocols/types; port clojure.repl + clojure.template.","status":"closed","priority":2,"issue_type":"task","owner":"yogthos@gmail.com","created_at":"2026-06-05T03:21:40Z","created_by":"Yogthos","updated_at":"2026-06-05T03:35:43Z","closed_at":"2026-06-05T03:35:43Z","close_reason":"Done — SCI bootstrap now loads 422/422 with 0 failures. Fixed deftype protocol-method registration, with-meta on functions, select-keys over vectors, require :as+:refer, defmethod-on-fn; added clojure.set/join and host-module stubs. test-load-sci asserts 0 failures.","dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/src/jolt/core.janet b/src/jolt/core.janet index cfb25a2..ca6fab6 100644 --- a/src/jolt/core.janet +++ b/src/jolt/core.janet @@ -122,11 +122,20 @@ (defn core-false? [x] (= false x)) (defn core-identical? [a b] (= a b)) -(defn core-zero? [x] (and (number? x) (= x 0))) -(defn core-pos? [x] (and (number? x) (> x 0))) -(defn core-neg? [x] (and (number? x) (< x 0))) -(defn core-even? [n] (= 0 (% n 2))) -(defn core-odd? [n] (not= 0 (% n 2))) +# Strictness helpers: like Clojure, numeric ops reject non-numbers, and the +# integer ops (odd?/even?) reject non-integers (incl. infinities, NaN, fractions). +(defn- finite-num? [x] (and (number? x) (= x x) (< (if (< x 0) (- x) x) math/inf))) +(defn- need-num [x op] + (if (number? x) x (error (string op " requires a number, got " (type x))))) +(defn- need-int [x op] + (if (and (number? x) (= x x) (< (if (< x 0) (- x) x) math/inf) (= x (math/floor x))) x + (error (string op " requires an integer")))) + +(defn core-zero? [x] (= (need-num x "zero?") 0)) +(defn core-pos? [x] (> (need-num x "pos?") 0)) +(defn core-neg? [x] (< (need-num x "neg?") 0)) +(defn core-even? [n] (= 0 (% (need-int n "even?") 2))) +(defn core-odd? [n] (not= 0 (% (need-int n "odd?") 2))) (defn core-integer? [x] (and (number? x) (= x (math/floor x)))) (defn core-boolean? [x] (or (= x true) (= x false))) @@ -173,14 +182,17 @@ (def core-dec dec) # Clojure integer division: quot truncates toward zero; rem matches the sign of # the dividend; mod matches the sign of the divisor (floored). -(def core-quot (fn [n d] (let [q (/ n d)] (if (< q 0) (math/ceil q) (math/floor q))))) +(def core-quot (fn [n d] + (when (or (not (finite-num? n)) (not (finite-num? d))) (error "quot requires finite numbers")) + (when (= d 0) (error "Divide by zero")) + (let [q (/ n d)] (if (< q 0) (math/ceil q) (math/floor q))))) (def core-rem (fn [n d] (- n (* (core-quot n d) d)))) (def core-mod (fn [n d] (let [m (core-rem n d)] (if (or (= m 0) (= (> n 0) (> d 0))) m (+ m d))))) -(defn core-max [& args] (apply max args)) -(defn core-min [& args] (apply min args)) +(defn core-max [& args] (each x args (need-num x "max")) (apply max args)) +(defn core-min [& args] (each x args (need-num x "min")) (apply min args)) (defn core-abs [x] (if (neg? x) (- 0 x) x)) (defn core-rand [] (math/random)) @@ -268,16 +280,18 @@ (defn core-not= [& args] (not (apply core-= args))) # Comparisons are variadic: (< a b c) means a < b < c. -(defn- chain-cmp [op xs] +(defn- chain-cmp [op opname xs] + # 1-arity (e.g. (< x)) is true regardless of x and does no type check. + (when (>= (length xs) 2) (each x xs (need-num x opname))) (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)) +(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/clojure-test-suite-test.janet b/test/integration/clojure-test-suite-test.janet index 5d6c0e0..711a7ac 100644 --- a/test/integration/clojure-test-suite-test.janet +++ b/test/integration/clojure-test-suite-test.janet @@ -18,7 +18,7 @@ # Baseline: assertions Jolt currently passes across the suite. Raise as Jolt # improves so a regression (previously-passing assertion breaking) is caught. -(def baseline-pass 3690) +(def baseline-pass 3730) # A file is "clean" when it ran with zero failures AND zero errors. (def baseline-clean-files 45) # Per-file wall-clock budget (seconds). Normal files finish in well under 1s; diff --git a/test/integration/systematic-coverage-test.janet b/test/integration/systematic-coverage-test.janet index eee08de..14fd485 100644 --- a/test/integration/systematic-coverage-test.janet +++ b/test/integration/systematic-coverage-test.janet @@ -59,7 +59,8 @@ (let [ctx (init)] (assert (= true (ct-eval ctx "(zero? 0)")) "zero? 0") (assert (= false (ct-eval ctx "(zero? 1)")) "zero? 1") - (assert (= false (ct-eval ctx "(zero? nil)")) "zero? nil") + # zero?/pos?/neg? now reject non-numbers (Clojure-strict), like the JVM. + (assert (not ((protect (ct-eval ctx "(zero? nil)")) 0)) "zero? nil throws") (assert (= true (ct-eval ctx "(pos? 1)")) "pos?") (assert (= false (ct-eval ctx "(pos? 0)")) "pos? 0") (assert (= false (ct-eval ctx "(pos? -1)")) "pos? -1") diff --git a/test/spec/numbers-spec.janet b/test/spec/numbers-spec.janet index d79eb7d..54f32cc 100644 --- a/test/spec/numbers-spec.janet +++ b/test/spec/numbers-spec.janet @@ -90,6 +90,25 @@ ["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)"]