fix: pr-str/str of a var, and nREPL namespace switching

- core: pr-render/str-render now render a Jolt var as #'ns/name instead of
  falling through to the generic table printer, which recursed into the var's
  cyclic :meta/:ns refs and looped forever. Adds name-of/var-display helpers.
- jolt.nrepl: capture the post-eval namespace *after* running the form — jolt
  evaluates map-literal values right-to-left, so {:val (eval form) :ns (the-ns)}
  read the ns before in-ns ran, pinning every session to 'user'. Now in-ns/ns
  switches persist across evals. render-value dropped: pr-str handles vars.
- specs: var printing (strings-spec) and nREPL in-ns/ns-persistence + explicit
  :ns override (nrepl-test).
This commit is contained in:
Yogthos 2026-06-05 22:05:45 -04:00
parent 5b66cfaa97
commit d00c3cc117
4 changed files with 38 additions and 7 deletions

View file

@ -1532,6 +1532,25 @@
(pr-render buf (in pair 1)))
(buffer/push-string buf "}"))
(defn- name-of
"Extract a plain name string from a string, symbol struct, or a namespace/var
table (reading its :name) — never recurses into the cyclic ns structure."
[x]
(cond
(nil? x) nil
(string? x) x
(and (struct? x) (= :symbol (get x :jolt/type))) (x :name)
(or (struct? x) (table? x)) (name-of (get x :name))
(string x)))
(defn- var-display
"Render a Jolt var as #'ns/name. A var's :meta/:ns refs are cyclic, so this
reads only its :name and :ns name — printing the var's pairs would loop."
[v]
(let [nm (name-of (v :name))
ns (name-of (v :ns))]
(if ns (string "#'" ns "/" nm) (string "#'" nm))))
(set pr-render
(fn [buf v]
(cond
@ -1551,6 +1570,7 @@
(number? v) (buffer/push-string buf (fmt-number v))
(and (struct? v) (= :symbol (v :jolt/type)))
(buffer/push-string buf (if (v :ns) (string (v :ns) "/" (v :name)) (v :name)))
(and (table? v) (= :jolt/var (get v :jolt/type))) (buffer/push-string buf (var-display v))
(core-sorted-map? v) (pr-render-pairs buf (sorted-map-entries v))
(core-sorted-set? v) (pr-render-seq buf (v :items) "#{" "}")
(lazy-seq? v) (pr-render-seq buf (realize-for-iteration v) "(" ")")
@ -1580,6 +1600,7 @@
(keyword? v) (string ":" (string v))
(and (struct? v) (= :symbol (v :jolt/type)))
(if (v :ns) (string (v :ns) "/" (v :name)) (v :name))
(and (table? v) (= :jolt/var (get v :jolt/type))) (var-display v)
(number? v) (fmt-number v)
(= true v) "true"
(= false v) "false"

View file

@ -90,16 +90,13 @@
; regardless of the ambient ns.
(defn- eval-in-ns [ns-str form]
(in-ns (symbol ns-str))
(let [result (try {:val (eval form) :ns (:name (the-ns))}
; Bind the value before reading the ns: jolt evaluates map-literal values
; right-to-left, so the result ns must be captured *after* eval runs any in-ns.
(let [result (try (let [v (eval form)] {:val v :ns (:name (the-ns))})
(catch Throwable e {:err e :ns (:name (the-ns))}))]
(in-ns 'jolt.nrepl)
result))
; pr-str on a var loops forever on its cyclic ns refs, so render vars (def/defn
; results) ourselves as #'ns/name rather than printing them.
(defn- render-value [v ns-str]
(if (var? v) (str "#'" ns-str "/" (get v :name)) (pr-str v)))
(defn- eval-handler [server msg send!]
; current-ns is global ctx state shared by all fibers, so set the eval ns
; explicitly each time: requested :ns, else the session's last ns, else user.
@ -122,7 +119,7 @@
(flush-out)
(swap! server assoc :eval-ns ns)
(when err (throw err))
(respond {"ns" ns "value" (render-value val ns)})
(respond {"ns" ns "value" (pr-str val)})
(recur (next forms) ns))))
(janet/setdyn :out old-out)
(respond {"status" ["done"]}))