core: Stage 3 turn 2b — host IO, ns introspection, thread-binding family
The names turn 2a's leak removal exposed as honestly missing, now proper: - slurp/spit/flush (host-classified): path-based IO over Janet files; spit takes :append; flush flushes *out*. printf prints formatted (no newline) over the existing format. file-seq walks paths via two host dir primitives through the overlay's tree-seq. - ns-map / ns-unmap / ns-refers (ctx fns). ns-refers required fixing the refer MODEL: refer/use/:refer now map the SOURCE VAR into the target ns (the Clojure model) instead of copying its value into a new var — so source-ns redefinitions propagate, the :macro flag travels for free, and refers are identifiable by the var's home :ns. - Thread-binding family: with-bindings*/with-bindings, bound-fn*/bound-fn, bound?, thread-bound?, get-thread-bindings. The captured binding map is a Janet struct keyed by the var tables — the exact frame representation var-get reads — so it re-pushes correctly (a phm frame is invisible to var lookup). - load-string and eval interned as VALUES at the api layer (they need the loader's compile-or-interpret routing); the eval special form still handles direct calls. Suite 4532 -> 4572 (baseline floor 4540 across timeout variance, clean 86), conformance 326x3, stdlib battery, all specs+unit (+21 turn-2b rows). Coverage: missing-portable 27 -> 10 (left: the *in*-model readers, the with-local-vars/with-precision/extenders tail).
This commit is contained in:
parent
a75a26860b
commit
d61c86a068
8 changed files with 197 additions and 34 deletions
|
|
@ -145,6 +145,21 @@
|
|||
# clojure.core and compiled by the self-hosted pipeline (or interpreted when
|
||||
# :compile? is off). Phase 4 kernel-shrink seam — see that file.
|
||||
(load-core-overlay! ctx)
|
||||
# load-string and eval as VALUES need the loader's compile-or-interpret
|
||||
# routing, which lives above the evaluator — intern them here. (The eval
|
||||
# special form still handles direct calls; this covers value position,
|
||||
# e.g. (map eval forms).)
|
||||
(let [core (ctx-find-ns ctx "clojure.core")]
|
||||
(ns-intern core "load-string"
|
||||
(fn [s]
|
||||
(var cur s)
|
||||
(var result nil)
|
||||
(while (> (length (string/trim cur)) 0)
|
||||
(def [form rest-src] (parse-next cur))
|
||||
(set cur rest-src)
|
||||
(when (not (nil? form)) (set result (eval-toplevel ctx form))))
|
||||
result))
|
||||
(ns-intern core "eval" (fn [form] (eval-toplevel ctx form))))
|
||||
ctx))
|
||||
|
||||
# --- Context snapshot/fork (cheap isolated copies) --------------------------
|
||||
|
|
|
|||
|
|
@ -1832,6 +1832,53 @@
|
|||
# Host time source for the `time` macro (monotonic, milliseconds).
|
||||
(defn core-current-time-ms [] (* 1000 (os/clock :monotonic)))
|
||||
|
||||
# Host IO (host-classified in the spec): path-based slurp/spit, *out* flush.
|
||||
(defn core-slurp [path] (string (slurp path)))
|
||||
|
||||
(defn core-spit [path content & opts]
|
||||
(def append? (do (var a false) (var i 0)
|
||||
(while (< i (length opts))
|
||||
(when (and (= :append (in opts i)) (in opts (+ i 1))) (set a true))
|
||||
(+= i 2))
|
||||
a))
|
||||
(def f (file/open path (if append? :a :w)))
|
||||
(file/write f (str-render-one content))
|
||||
(file/close f)
|
||||
nil)
|
||||
|
||||
(defn core-flush []
|
||||
(def out (dyn :out))
|
||||
(when out (file/flush out))
|
||||
nil)
|
||||
|
||||
# Thread-binding introspection over the frame stack (types/cur-binding-stack).
|
||||
(defn core-get-thread-bindings []
|
||||
# Innermost frame wins: merge frames oldest-first. The result is a Janet
|
||||
# STRUCT keyed by the var tables themselves — the exact frame representation
|
||||
# var-get reads (identity-keyed get) — so the map can be re-pushed by
|
||||
# with-bindings*/bound-fn* and remains lookup-able with (get m the-var).
|
||||
(def acc @{})
|
||||
(each frame (snapshot-bindings)
|
||||
(each entry (realize-for-iteration frame)
|
||||
(put acc (in entry 0) (in entry 1))))
|
||||
(table/to-struct acc))
|
||||
|
||||
(defn core-thread-bound?* [v]
|
||||
(var found false)
|
||||
(each frame (snapshot-bindings)
|
||||
(each entry (realize-for-iteration frame)
|
||||
(when (= (in entry 0) v) (set found true))))
|
||||
found)
|
||||
|
||||
# Directory primitives for file-seq (paths, not File objects — host-classified).
|
||||
(defn core-dir? [path]
|
||||
(def st (os/stat path))
|
||||
(and st (= :directory (st :mode))))
|
||||
|
||||
(defn core-list-dir [path]
|
||||
(def entries (os/dir path))
|
||||
(map (fn [e] (string path "/" e)) (sort entries)))
|
||||
|
||||
# Clojure compare: a total order over comparable values. nil sorts first;
|
||||
# numbers numerically; strings/keywords lexically; symbols by ns then name;
|
||||
# booleans false<true; chars by codepoint; vectors by length then elementwise;
|
||||
|
|
@ -3071,6 +3118,13 @@
|
|||
"int?" core-integer?
|
||||
"compare" core-compare
|
||||
"type" core-type
|
||||
"slurp" core-slurp
|
||||
"spit" core-spit
|
||||
"flush" core-flush
|
||||
"get-thread-bindings" core-get-thread-bindings
|
||||
"__thread-bound?" core-thread-bound?*
|
||||
"__dir?" core-dir?
|
||||
"__list-dir" core-list-dir
|
||||
"parse-long" core-parse-long
|
||||
"parse-double" core-parse-double
|
||||
"parse-boolean" core-parse-boolean
|
||||
|
|
|
|||
|
|
@ -382,10 +382,9 @@
|
|||
(let [name (if (struct? refer-sym) (refer-sym :name) refer-sym)
|
||||
v (ns-find source-ns name)]
|
||||
(when v
|
||||
# Preserve macro-ness: a referred macro must stay a macro, so copy
|
||||
# the :macro flag onto the interned var (not just its value).
|
||||
(let [nv (ns-intern target-ns name (var-get v))]
|
||||
(when (get v :macro) (put nv :macro true))))))))
|
||||
# Share the SOURCE var (the Clojure model): macro-ness travels with
|
||||
# it and source-ns redefinitions propagate to the referer.
|
||||
(put (target-ns :mappings) name v))))))
|
||||
nil))
|
||||
|
||||
(defn- bind-put
|
||||
|
|
@ -813,9 +812,11 @@
|
|||
src-name (sym-name-str ns-sym)]
|
||||
(maybe-require-ns ctx src-name)
|
||||
(let [source-ns (ctx-find-ns ctx src-name)]
|
||||
# Refer maps the SOURCE VAR itself (the Clojure model): redefinitions in
|
||||
# the source ns propagate, the :macro flag travels for free, and
|
||||
# ns-refers can identify refers by the var's home :ns.
|
||||
(loop [[sym v] :pairs (source-ns :mappings)]
|
||||
(let [nv (ns-intern target-ns sym (var-get v))]
|
||||
(when (get v :macro) (put nv :macro true)))))))
|
||||
(put (target-ns :mappings) sym v)))))
|
||||
nil)
|
||||
|
||||
(defn import-impl
|
||||
|
|
@ -1144,13 +1145,32 @@
|
|||
nil))
|
||||
# ns-publics: {symbol -> var} (jolt has no private vars, so publics = interns).
|
||||
# Keys are symbol structs (value-hashed), matching Clojure's symbol keys.
|
||||
(def mappings->symbol-map (fn [ns pred]
|
||||
(var m (make-phm))
|
||||
(loop [[nm v] :pairs (ns :mappings)]
|
||||
(when (pred nm v)
|
||||
(set m (phm-assoc m {:jolt/type :symbol :ns nil :name nm} v))))
|
||||
m))
|
||||
(ns-intern core "ns-publics"
|
||||
(fn [&opt ns-d]
|
||||
(mappings->symbol-map (ns-or-current ns-d) (fn [nm v] true))))
|
||||
# ns-map: all mappings (interns + refers; jolt has no class imports in maps).
|
||||
(ns-intern core "ns-map"
|
||||
(fn [&opt ns-d]
|
||||
(mappings->symbol-map (ns-or-current ns-d) (fn [nm v] true))))
|
||||
# ns-refers: mappings whose var's HOME ns differs from this ns (copied in by
|
||||
# refer/use/require :refer).
|
||||
(ns-intern core "ns-refers"
|
||||
(fn [&opt ns-d]
|
||||
(def ns (ns-or-current ns-d))
|
||||
(var m (make-phm))
|
||||
(loop [[nm v] :pairs (ns :mappings)]
|
||||
(set m (phm-assoc m {:jolt/type :symbol :ns nil :name nm} v)))
|
||||
m))
|
||||
(def my-name (ns :name))
|
||||
(mappings->symbol-map ns (fn [nm v]
|
||||
(and (table? v) (not= (get v :ns) my-name))))))
|
||||
(ns-intern core "ns-unmap"
|
||||
(fn [ns-d sym]
|
||||
(def ns (ns-or-current ns-d))
|
||||
(put (ns :mappings) (if (struct? sym) (sym :name) (string sym)) nil)
|
||||
nil))
|
||||
core)
|
||||
|
||||
# Dispatch a special form by its string name.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue