diff --git a/docs/spec/coverage.md b/docs/spec/coverage.md index 37fe708..64be83c 100644 --- a/docs/spec/coverage.md +++ b/docs/spec/coverage.md @@ -3,16 +3,16 @@ Generated 2026-06-10 by `tools/spec_coverage.py` — do not edit by hand. Surface: **694** clojure.core vars (ClojureDocs export; 648 with -community examples). jolt interns 552 of them. +community examples). jolt interns 553 of them. | Status | Count | Meaning | |---|---|---| | implemented+tested | 415 | in jolt and exercised by spec/conformance | -| implemented-untested | 137 | in jolt, no direct test — spec entries will add them | +| implemented-untested | 138 | in jolt, no direct test — spec entries will add them | | resolvable-not-interned | 2 | works in code but invisible to ns introspection (conformance finding) | | missing-portable | 10 | portable semantics, jolt lacks it — implementation gap | | special-form | 15 | specified in §3, not a library var | -| dynamic-var | 31 | classification needed: portable default vs host-dependent | +| dynamic-var | 30 | classification needed: portable default vs host-dependent | | agents-taps | 22 | out of scope pending concurrency design note | | stm-refs | 11 | out of scope pending concurrency design note | | jvm-specific | 53 | catalogued, not specified | @@ -47,7 +47,7 @@ UNVERIFIED field; that column will be added as entries land. | `*fn-loader*` | dynamic-var | | | `*in*` | dynamic-var | | | `*math-context*` | dynamic-var | | -| `*ns*` | dynamic-var | ✓ | +| `*ns*` | implemented-untested | ✓ | | `*out*` | dynamic-var | ✓ | | `*print-dup*` | dynamic-var | ✓ | | `*print-length*` | dynamic-var | ✓ | diff --git a/src/jolt/core.janet b/src/jolt/core.janet index 883572b..500636b 100644 --- a/src/jolt/core.janet +++ b/src/jolt/core.janet @@ -1612,6 +1612,12 @@ # Readable rendering of a value (Clojure pr semantics): strings quoted, # keywords with leading ':', symbols by name, collections with their reader # syntax. Used by both pr-str (readable) and str (collection elements). +# A namespace's :name may be a string or a symbol struct depending on the +# creation path — normalize for display. +(defn- ns-display-name [ns] + (def n (ns :name)) + (if (and (struct? n) (= :symbol (get n :jolt/type))) (n :name) (string n))) + (var pr-render nil) # Format a number the way Clojure prints it: infinity and NaN have named forms @@ -1686,6 +1692,10 @@ (and (struct? v) (= :jolt/inst (v :jolt/type))) (do (buffer/push-string buf "#inst \"") (buffer/push-string buf (inst->rfc3339 v)) (buffer/push-string buf "\"")) + (= :jolt/namespace (get v :jolt/type)) + (do (buffer/push-string buf "#namespace[") + (buffer/push-string buf (ns-display-name v)) + (buffer/push-string buf "]")) (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) "#{" "}") @@ -1718,6 +1728,7 @@ (if (v :ns) (string (v :ns) "/" (v :name)) (v :name)) (and (struct? v) (= :jolt/uuid (v :jolt/type))) (v :str) (and (struct? v) (= :jolt/inst (v :jolt/type))) (inst->rfc3339 v) + (= :jolt/namespace (get v :jolt/type)) (ns-display-name v) (and (table? v) (= :jolt/var (get v :jolt/type))) (var-display v) (number? v) (fmt-number v) (= true v) "true" diff --git a/src/jolt/evaluator.janet b/src/jolt/evaluator.janet index cd10fad..1e66112 100644 --- a/src/jolt/evaluator.janet +++ b/src/jolt/evaluator.janet @@ -796,9 +796,9 @@ quoted symbol arrives evaluated." [ctx sym] (def ns-name (if (and (struct? sym) (= :symbol (sym :jolt/type))) (sym :name) (string sym))) - (ctx-find-ns ctx ns-name) + (def the-ns-obj (ctx-find-ns ctx ns-name)) (ctx-set-current-ns ctx ns-name) - nil) + the-ns-obj) (defn use-impl "(use '[ns ...] ...) — refer ALL public vars of each used ns into the CURRENT ns. @@ -953,6 +953,12 @@ # Var/namespace lookups that need the ctx (the rest of the var fns — var-get/ # var-set/var?/alter-var-root/alter-meta!/reset-meta! — are plain core-bindings). (ns-intern core "find-var" (fn [sym] (find-var ctx sym))) + # *ns*: the current-namespace dynamic var. Its root is kept in sync by + # ctx-set-current-ns via the cached var table (env :ns-var); a thread + # binding (binding [*ns* ...]) shadows the root through var-get as usual. + (def ns-var (ns-intern core "*ns*" (ctx-find-ns ctx (ctx-current-ns ctx)))) + (put ns-var :dynamic true) + (put (ctx :env) :ns-var ns-var) (ns-intern core "intern" (fn [ns-name sym-name &opt val] (def ns (ctx-find-ns ctx (if (struct? ns-name) (ns-name :name) ns-name))) @@ -969,7 +975,7 @@ (keyword? x) (string x) nil))) (def ns-of (fn [x] - (if (and (table? x) (not (nil? (x :mappings)))) + (if (= :jolt/namespace (get x :jolt/type)) x (let [nm (ns-name-of x)] (if nm (get (get (ctx :env) :namespaces) nm) nil))))) diff --git a/src/jolt/types.janet b/src/jolt/types.janet index d92afd8..87ce951 100644 --- a/src/jolt/types.janet +++ b/src/jolt/types.janet @@ -509,9 +509,13 @@ (get (ctx :env) :current-ns)) (defn ctx-set-current-ns - "Set the current namespace symbol." + "Set the current namespace symbol. Also keeps the *ns* dynamic var's root in + sync (the var table is cached on the env by install-stateful-fns! — one table + put on this hot path, no ns lookup chain)." [ctx ns-sym] - (put (ctx :env) :current-ns ns-sym)) + (put (ctx :env) :current-ns ns-sym) + (when-let [v (get (ctx :env) :ns-var)] + (put v :root (ctx-find-ns ctx ns-sym)))) (defn all-ns "Return a list of all namespaces in the context." diff --git a/test/spec/ns-var-spec.janet b/test/spec/ns-var-spec.janet new file mode 100644 index 0000000..aa44480 --- /dev/null +++ b/test/spec/ns-var-spec.janet @@ -0,0 +1,16 @@ +# Specification: *ns* — the current-namespace dynamic var (stage 3). +# *ns* holds the current NAMESPACE OBJECT; (str *ns*) is its name; it tracks +# in-ns at the top level and works with the ns-introspection fns. +(use ../support/harness) + +(defspec "*ns* / identity & printing" + ["str of *ns*" "\"user\"" "(str *ns*)"] + ["ns-name of *ns*" "(quote user)" "(ns-name *ns*)"] + ["*ns* is find-ns" "true" "(= (ns-name *ns*) (ns-name (find-ns (quote user))))"] + ["*ns* not a map" "false" "(map? *ns*)"] + ["tracks in-ns" "\"jolt.test-ns-a\"" "(do (in-ns (quote jolt.test-ns-a)) (str *ns*))"] + ["in-ns returns ns" "\"jolt.test-ns-b\"" "(str (in-ns (quote jolt.test-ns-b)))"] + ["usable with ns fns" "true" + "(do (require (quote clojure.string)) (alias (quote nsv) (quote clojure.string)) (some? (get (ns-aliases *ns*) (quote nsv))))"] + ["ns-unalias via *ns*" "true" + "(do (require (quote clojure.string)) (alias (quote nsw) (quote clojure.string)) (ns-unalias *ns* (quote nsw)) (nil? (get (ns-aliases *ns*) (quote nsw))))"])