core: Phase 4 — move ex-data/ex-message/ex-cause to the overlay

The ex-info value exposes :jolt/type/:message/:data/:cause via get (it's the
caught value too), so the accessors are pure over get — no host surface. The
constructor (ex-info) stays in Janet since it wires into throw. A thrown
non-ex-info arrives wrapped as {:jolt/type :jolt/exception :value v}; the
overlay unwraps that, matching the old Janet helper. ex-cause now unwraps and
returns nil (not false) on non-ex values, matching Clojure. Added non-ex-info
and string regression cases.
This commit is contained in:
Yogthos 2026-06-07 20:17:03 -04:00
parent fcdf0ff535
commit 7f68a5872c
3 changed files with 23 additions and 13 deletions

View file

@ -236,3 +236,20 @@
(map? coll) (reduce (fn [acc k] (f acc k (get coll k))) init (keys coll))
(nil? coll) init
:else (throw (str "reduce-kv not supported on: " coll))))
;; ex-info accessors. The Janet constructor (ex-info) stays — it builds the tagged
;; value and wires into throw — but the value exposes :jolt/type/:message/:data/
;; :cause via get, so the accessors are pure over get. A thrown non-ex-info arrives
;; wrapped as {:jolt/type :jolt/exception :value v}; unwrap that first.
(defn- ex-info-val? [x] (= (get x :jolt/type) :jolt/ex-info))
(defn- ex-unwrap [e]
(if (= (get e :jolt/type) :jolt/exception) (get e :value) e))
(defn ex-data [e]
(let [e (ex-unwrap e)] (if (ex-info-val? e) (get e :data) nil)))
(defn ex-message [e]
(let [e (ex-unwrap e)]
(cond (ex-info-val? e) (get e :message)
(string? e) e
:else nil)))
(defn ex-cause [e]
(let [e (ex-unwrap e)] (if (ex-info-val? e) (get e :cause) nil)))

View file

@ -2374,14 +2374,8 @@
(defn core-ex-info [msg data & more]
@{:jolt/type :jolt/ex-info :message msg :data data
:cause (if (> (length more) 0) (in more 0) nil)})
(defn core-ex-info? [x] (and (table? x) (= :jolt/ex-info (x :jolt/type))))
(defn- unwrap-ex [e]
(if (and (or (table? e) (struct? e)) (= :jolt/exception (get e :jolt/type))) (get e :value) e))
(defn core-ex-data [e]
(let [e (unwrap-ex e)] (if (core-ex-info? e) (e :data) nil)))
(defn core-ex-message [e]
(let [e (unwrap-ex e)]
(cond (core-ex-info? e) (e :message) (string? e) e nil)))
# ex-data / ex-message / ex-cause now live in the Clojure collection tier
# (core/20-coll.clj) — pure over get on the tagged value the constructor builds.
# String split/replace that accept either a literal string or a regex value.
(defn core-str-split [pat s]
@ -2688,7 +2682,6 @@
(defn core-hash-unordered-coll [coll]
(var h 0) (each x (realize-for-iteration coll) (set h (band (+ h (h24 x)) 0xffffff))) h)
(defn core-ex-cause [e] (and (table? e) (get e :cause)))
(defn core-prefers [mm-var] (or (get mm-var :jolt/prefers) {}))
(defn core-random-uuid []
@ -2840,7 +2833,6 @@
"hash-combine" core-hash-combine
"hash-ordered-coll" core-hash-ordered-coll
"hash-unordered-coll" core-hash-unordered-coll
"ex-cause" core-ex-cause
"prefers" core-prefers
"random-uuid" core-random-uuid
"interpose" core-interpose
@ -2868,8 +2860,6 @@
"ifn?" core-ifn?
"indexed?" core-indexed?
"ex-info" core-ex-info
"ex-data" core-ex-data
"ex-message" core-ex-message
"prn-str" core-prn-str
"println-str" core-println-str
"volatile?" core-volatile?

View file

@ -35,4 +35,7 @@
["catch binds thrown value" "42"
"(try (throw 42) (catch :default e e))"]
["rethrow preserves ex" "\"inner\""
"(try (try (throw (ex-info \"inner\" {})) (catch :default e (throw e))) (catch :default e (ex-message e)))"])
"(try (try (throw (ex-info \"inner\" {})) (catch :default e (throw e))) (catch :default e (ex-message e)))"]
["ex-data on non-ex" "nil" "(ex-data 42)"]
["ex-cause on non-ex" "nil" "(ex-cause {:k 1})"]
["ex-message of string" "\"hi\"" "(ex-message \"hi\")"])