From bcdace7543dc442921e8dcd4377ffbf169f1c0aa Mon Sep 17 00:00:00 2001 From: Yogthos Date: Fri, 5 Jun 2026 10:19:43 -0400 Subject: [PATCH] fix: case composite constants, associative?/reversible?, update non-fn args, nth nil - case: quote list literals (read as arrays) in constant position so a wrapped list ((a b c)) matches by value instead of being evaluated as a call; symbol constants already quoted. Vector/map/set constants already worked. case errors in the suite drop to 0 (60 pass). - associative?: true only for vectors (pvec) and maps (phm/struct/sorted-map), not lists/tuples-from-seq-fns/lazy-seqs/sets. - reversible?: true for vectors and sorted-map/sorted-set only. - update: coerce f via as-fn so (update m k :kw)/(update m k a-set) work; extra args already handled. - nth: (nth nil i)/(nth nil i default) returns nil/default instead of throwing. clojure-test-suite pass 3649->3678, errors 122->105, clean files 44->46. associative?/reversible? files now fully clean. spec: predicates + control/case. jpm test green. --- src/jolt/core.janet | 18 ++++++++++++++---- test/integration/clojure-test-suite-test.janet | 4 ++-- test/spec/control-flow-spec.janet | 7 ++++++- test/spec/predicates-spec.janet | 6 ++++++ 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/jolt/core.janet b/src/jolt/core.janet index e1b25ba..5dd65e1 100644 --- a/src/jolt/core.janet +++ b/src/jolt/core.janet @@ -1023,6 +1023,7 @@ (defn core-nth "Return the nth element of a sequential collection." [coll idx &opt default] + (if (nil? coll) default # (nth nil i) -> nil / default, never throws (if (core-transient? coll) (let [a (coll :arr)] (if (and (>= idx 0) (< idx (length a))) (in a idx) default)) (if (plist? coll) @@ -1050,7 +1051,7 @@ (if (string? c) (make-char (in c idx)) (in c idx)) (if (nil? default) (error (string "Index " idx " out of bounds, length: " (length c))) - default)))))))) + default))))))))) (defn core-sort "(sort coll) or (sort comparator coll). Comparator may return a boolean or a @@ -1908,7 +1909,10 @@ [expr & clauses] (def g (gensym)) (defn make-const [c] - (if (and (struct? c) (= :symbol (c :jolt/type))) + # case constants are literals, never evaluated: quote symbols and list + # literals (read as arrays) so e.g. `sym` and a wrapped list `(a b c)` match + # by value rather than resolving/calling. + (if (or (and (struct? c) (= :symbol (c :jolt/type))) (array? c)) @[{:jolt/type :symbol :ns nil :name "quote"} c] c)) (defn make-test [c] @@ -2565,6 +2569,7 @@ # update — works on both structs and tables (defn core-update [m k f & args] + (def f (as-fn f)) (core-assoc m k (apply f (core-get m k) args))) (defn- ks-rest [ks] @@ -2914,7 +2919,11 @@ hit)) (defn core-sequential? [x] (or (tuple? x) (array? x) (pvec? x) (plist? x) (lazy-seq? x))) -(defn core-associative? [x] (or (phm? x) (struct? x) (tuple? x) (array? x) (pvec? x) (and (table? x) (not (set? x))))) +# Associative = maps and (real) vectors only. pvec is a literal/built vector; +# tuples and lists are seq results, not associative. +(defn core-associative? [x] + (or (pvec? x) (phm? x) (core-sorted-map? x) + (and (struct? x) (nil? (get x :jolt/type))))) (defn core-ifn? [x] (or (function? x) (cfunction? x) (keyword? x) (phm? x) (set? x) (tuple? x) (array? x) (pvec? x) (and (struct? x) (= :symbol (x :jolt/type))))) @@ -3109,7 +3118,8 @@ (defn core-counted? [x] (or (pvec? x) (plist? x) (phm? x) (set? x) (tuple? x) (array? x) (string? x))) -(defn core-reversible? [x] (or (pvec? x) (tuple? x) (array? x))) +# Reversible (supports rseq) = vectors and sorted collections. +(defn core-reversible? [x] (or (pvec? x) (core-sorted-map? x) (core-sorted-set? x))) (defn core-seqable? [x] (or (nil? x) (tuple? x) (array? x) (pvec? x) (plist? x) (phm? x) (set? x) (struct? x) (lazy-seq? x) (string? x) diff --git a/test/integration/clojure-test-suite-test.janet b/test/integration/clojure-test-suite-test.janet index 29c07a0..e7f39fb 100644 --- a/test/integration/clojure-test-suite-test.janet +++ b/test/integration/clojure-test-suite-test.janet @@ -18,9 +18,9 @@ # Baseline: assertions Jolt currently passes across the suite. Raise as Jolt # improves so a regression (previously-passing assertion breaking) is caught. -(def baseline-pass 3600) +(def baseline-pass 3660) # A file is "clean" when it ran with zero failures AND zero errors. -(def baseline-clean-files 43) +(def baseline-clean-files 45) # Per-file wall-clock budget (seconds). Normal files finish in well under 1s; # this only fires on infinite-sequence hangs. (def per-file-timeout 6) diff --git a/test/spec/control-flow-spec.janet b/test/spec/control-flow-spec.janet index 7c3f654..7b25174 100644 --- a/test/spec/control-flow-spec.janet +++ b/test/spec/control-flow-spec.janet @@ -15,7 +15,12 @@ ["condp" "\"two\"" "(condp = 2 1 \"one\" 2 \"two\" \"other\")"] ["case" ":b" "(case 2 1 :a 2 :b :default)"] ["case default" ":d" "(case 9 1 :a 2 :b :d)"] - ["case multi" ":ab" "(case 2 (1 2) :ab 3 :c)"]) + ["case multi" ":ab" "(case 2 (1 2) :ab 3 :c)"] + ["case symbol const" ":s" "(case 'foo foo :s :default)"] + ["case vector const" ":v" "(case [1 2] [1 2] :v :default)"] + ["case map const" ":m" "(case {:a 1} {:a 1} :m :default)"] + ["case list const" ":l" "(case '(a b) (quote (a b)) :l :default)"] + ["case keyword" ":k" "(case :x :x :k :default)"]) (defspec "control / logic" ["and all true" "3" "(and 1 2 3)"] diff --git a/test/spec/predicates-spec.janet b/test/spec/predicates-spec.janet index 498f3c1..3ca0536 100644 --- a/test/spec/predicates-spec.janet +++ b/test/spec/predicates-spec.janet @@ -34,6 +34,12 @@ ["sequential? vector" "true" "(sequential? [1])"] ["associative? map" "true" "(associative? {:a 1})"] ["associative? vec" "true" "(associative? [1])"] + ["associative? list" "false" "(associative? '(1 2))"] + ["associative? set" "false" "(associative? #{1})"] + ["reversible? vec" "true" "(reversible? [1 2])"] + ["reversible? list" "false" "(reversible? '(1 2))"] + ["reversible? smap" "true" "(reversible? (sorted-map :a 1))"] + ["reversible? hmap" "false" "(reversible? (hash-map :a 1))"] ["indexed? vector" "true" "(indexed? [1])"] ["counted? vector" "true" "(counted? [1])"])