From e52c4451139861c69e9bf5840d7bf9d83c7194bd Mon Sep 17 00:00:00 2001 From: Yogthos Date: Thu, 4 Jun 2026 20:53:45 -0400 Subject: [PATCH] feat: stub SCI host modules, fix require :as+:refer, +promoting ops/eduction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Push SCI loading much further: the giant clojure.core aggregation map in namespaces.cljc now resolves ALL its symbols (it previously failed on dozens). - host_stubs.clj: forward-decl no-op fns for SCI's host-layer modules (parser/read/load/macroexpand/resolve/proxy/reify) — Jolt supplies its own reader/loader, so these only need to resolve when the binding map is built. Stubs are non-nil fns so :refer re-interns them (a nil-valued var is dropped). - Load Jolt's clojure.string/set/walk/edn before namespaces.cljc. - Add real clojure.core: ==, print-str, eduction/->Eduction, seq-to-map-for-destructuring, split-lines, postwalk-demo/prewalk-demo, +'/-'/*'/inc'/dec' (Jolt doesn't overflow, so == non-quoted), proxy-super and friends (resolve-only, JVM-specific). Fix a real require bug: eval-require stopped at the first option, so [ns :as x :refer [...]] silently dropped the :refer. Now processes all options. SCI load: clojure.core map now builds; only two forms remain (a Var-protocol method on sci.lang.Var, and the clojure.repl namespace). conformance 218/218, features 78/78, jank 120. --- src/jolt/clojure/sci/host_stubs.clj | 49 +++++++++++++++++++++++++++++ src/jolt/clojure/string.clj | 5 +++ src/jolt/clojure/walk.clj | 10 ++++++ src/jolt/core.janet | 47 +++++++++++++++++++++++++++ src/jolt/evaluator.janet | 18 +++++------ test/test-load-sci.janet | 6 ++++ 6 files changed, 125 insertions(+), 10 deletions(-) create mode 100644 src/jolt/clojure/sci/host_stubs.clj diff --git a/src/jolt/clojure/sci/host_stubs.clj b/src/jolt/clojure/sci/host_stubs.clj new file mode 100644 index 0000000..ba7177b --- /dev/null +++ b/src/jolt/clojure/sci/host_stubs.clj @@ -0,0 +1,49 @@ +;; Forward-declaration stubs for SCI's host-layer modules. +;; +;; sci.impl.{parser,read,load,macroexpand,resolve,proxy,reify} implement SCI's +;; reader, loader, macroexpander and JVM proxy/reify support. On the JVM those +;; lean on tools.reader, java.io and reflection; Jolt has its own native +;; reader/loader, so rather than load SCI's host implementation we provide just +;; the var names that sci.impl.namespaces references when it builds the +;; clojure.core binding map. The map is built at load time but these are only +;; *called* through SCI's own runtime, which Jolt replaces — so no-op +;; placeholders are sufficient. (They must be non-nil so `:refer` re-interns +;; them: interning a nil-valued var drops the binding.) + +(ns sci.impl.parser) +(defn parse-next [& _] nil) +(def data-readers {}) +(defn default-data-reader-fn [& _] nil) +(defn read-eval [& _] nil) +(defn reader-resolver [& _] nil) +(defn suppress-read [& _] nil) + +(ns sci.impl.read) +(defn read [& _] nil) +(defn read-string [& _] nil) +(defn source-logging-reader [& _] nil) + +(ns sci.impl.load) +(defn load-string [& _] nil) +(defn load-reader [& _] nil) +(defn add-loaded-lib [& _] nil) +(defn eval-refer [& _] nil) +(defn eval-refer-global [& _] nil) +(defn eval-require [& _] nil) +(defn eval-require-global [& _] nil) +(defn eval-use [& _] nil) + +(ns sci.impl.macroexpand) +(defn macroexpand [& _] nil) +(defn macroexpand-1 [& _] nil) + +(ns sci.impl.resolve) +(defn resolve-symbol [& _] nil) + +(ns sci.impl.proxy) +(defn proxy [& _] nil) +(defn proxy* [& _] nil) + +(ns sci.impl.reify) +(defn reify [& _] nil) +(defn reify* [& _] nil) diff --git a/src/jolt/clojure/string.clj b/src/jolt/clojure/string.clj index a34229c..77a70a5 100644 --- a/src/jolt/clojure/string.clj +++ b/src/jolt/clojure/string.clj @@ -56,6 +56,11 @@ ([s re limit] (vec (take limit (str-split re s))))) +(defn split-lines + "Split s on \\n or \\r\\n, returning a vector of lines." + [s] + (vec (str-split #"\r?\n" s))) + (defn starts-with? [s substr] diff --git a/src/jolt/clojure/walk.clj b/src/jolt/clojure/walk.clj index d41cc93..f2ba5e2 100644 --- a/src/jolt/clojure/walk.clj +++ b/src/jolt/clojure/walk.clj @@ -17,6 +17,16 @@ [f form] (walk (partial prewalk f) identity (f form))) +(defn postwalk-demo + "Demonstrates the behavior of postwalk by printing each form as it is walked." + [form] + (postwalk (fn [x] (print "Walked: ") (prn x) x) form)) + +(defn prewalk-demo + "Demonstrates the behavior of prewalk by printing each form as it is walked." + [form] + (prewalk (fn [x] (print "Walked: ") (prn x) x) form)) + (defn postwalk-replace [smap form] (postwalk (fn [x] (if (contains? smap x) (get smap x) x)) form)) diff --git a/src/jolt/core.janet b/src/jolt/core.janet index 11408b2..6694839 100644 --- a/src/jolt/core.janet +++ b/src/jolt/core.janet @@ -2812,6 +2812,36 @@ (defn core-proxy-call-with-super [f proxy meth] (f)) (defn core-proxy-mappings [proxy] {}) (defn core-update-proxy [proxy mappings] proxy) +(defn core-numeric= [& args] + (if (< (length args) 2) true + (do (var ok true) (var i 0) + (while (and ok (< i (dec (length args)))) + (unless (= (in args i) (in args (+ i 1))) (set ok false)) (++ i)) + ok))) +(defn core-print-str [& xs] + (var parts @[]) (each x xs (array/push parts (str-render-one x))) + (string/join parts " ")) +(defn core-memfn [& args] (error "memfn: JVM method handles are not supported in Jolt")) +(defn core-seq-to-map-for-destructuring [s] + # used by {:keys [...]} destructuring over a seq of k/v pairs + (if (core-sequential? s) + (let [items (realize-for-iteration s) m @{}] + (var i 0) + (while (< (+ i 1) (length items)) (put m (in items i) (in items (+ i 1))) (+= i 2)) + (table/to-struct m)) + s)) +(defn core-eduction [& args] + # (eduction xform* coll): apply the composed transducers eagerly to coll + (let [n (length args) + coll (in args (- n 1)) + xforms (array/slice args 0 (- n 1)) + xform (if (= 0 (length xforms)) (fn [rf] rf) (apply core-comp xforms))] + (core-into (make-vec @[]) xform coll))) +(defn core->Eduction [xform coll] (core-into (make-vec @[]) xform coll)) +(defn core-proxy-super [& args] (error "proxy-super: JVM proxies are not supported in Jolt")) +(defn core-construct-proxy [c & args] (error "construct-proxy: not supported in Jolt")) +(defn core-init-proxy [proxy mappings] proxy) +(defn core-get-proxy-class [& interfaces] (error "get-proxy-class: not supported in Jolt")) (defn core-undefined? [x] false) (def- char-escapes @@ -3014,6 +3044,13 @@ "/" core-/ "inc" core-inc "dec" core-dec + # auto-promoting variants — Jolt numbers don't overflow, so these are the + # same as their non-quoted counterparts + "+'" core-+ + "-'" core-sub + "*'" core-* + "inc'" core-inc + "dec'" core-dec "mod" core-mod "rem" core-rem "quot" core-quot @@ -3338,6 +3375,16 @@ "proxy-call-with-super" core-proxy-call-with-super "proxy-mappings" core-proxy-mappings "update-proxy" core-update-proxy + "==" core-numeric= + "print-str" core-print-str + "memfn" core-memfn + "seq-to-map-for-destructuring" core-seq-to-map-for-destructuring + "eduction" core-eduction + "->Eduction" core->Eduction + "proxy-super" core-proxy-super + "construct-proxy" core-construct-proxy + "init-proxy" core-init-proxy + "get-proxy-class" core-get-proxy-class "undefined?" core-undefined? "char-escape-string" core-char-escape-string "char-name-string" core-char-name-string diff --git a/src/jolt/evaluator.janet b/src/jolt/evaluator.janet index c86d769..f6e40f3 100644 --- a/src/jolt/evaluator.janet +++ b/src/jolt/evaluator.janet @@ -201,18 +201,16 @@ (var refer-syms nil) (var i 1) (let [slen (length spec)] + # Scan ALL options — a spec may carry both :as and :refer, e.g. + # [clojure.string :as str :refer [blank?]]; don't stop at the first. (while (< i slen) (let [item (in spec i)] - (if (or (= item :as) (and (struct? item) (= :symbol (item :jolt/type)) (= "as" (item :name)))) - (do - (def alias-sym (in spec (+ i 1))) - (set alias (alias-sym :name)) - (set i slen)) - (if (or (= item :refer) (and (struct? item) (= :symbol (item :jolt/type)) (= "refer" (item :name)))) - (do - (set refer-syms (in spec (+ i 1))) - (set i slen)) - (++ i)))))) + (cond + (or (= item :as) (and (struct? item) (= :symbol (item :jolt/type)) (= "as" (item :name)))) + (do (set alias ((in spec (+ i 1)) :name)) (+= i 2)) + (or (= item :refer) (and (struct? item) (= :symbol (item :jolt/type)) (= "refer" (item :name)))) + (do (set refer-syms (in spec (+ i 1))) (+= i 2)) + (++ i))))) (maybe-require-ns ctx ns-name) (when alias (let [current-ns (ctx-find-ns ctx (ctx-current-ns ctx))] diff --git a/test/test-load-sci.janet b/test/test-load-sci.janet index 2abcc1e..bdecc20 100644 --- a/test/test-load-sci.janet +++ b/test/test-load-sci.janet @@ -19,6 +19,12 @@ (load-stubs ctx "src/jolt/clojure/sci/lang_stubs.clj") (load-stubs ctx "src/jolt/clojure/sci/io_stubs.clj") +(load-stubs ctx "src/jolt/clojure/sci/host_stubs.clj") + +# namespaces.cljc copies vars out of Jolt's own clojure.string/set/walk/edn, so +# make sure those are loaded before it runs. +(each lib ["clojure.string" "clojure.set" "clojure.walk" "clojure.edn"] + (protect (eval-form ctx @{} (first (parse-next (string "(require '[" lib "])")))))) (defn load-file [ctx path] (var s (slurp path))