From 5624e99eb6b31fe2f7ad83a93a7063bd26248c4f Mon Sep 17 00:00:00 2001 From: Yogthos Date: Sat, 6 Jun 2026 06:19:45 -0400 Subject: [PATCH] self-host: analyzer reaches full conformance parity via hybrid fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Clojure analyzer + Janet back end now pass all 218 conformance cases (vs the interpreter) through the self-hosted pipeline: - host contract gains special? (the full special-form/uncompilable set), so the analyzer falls back for forms it doesn't compile instead of miscompiling them as calls; - qualified symbol refs that don't resolve to a var (janet/…, Math/…, host interop) and tagged literals (#"regex", #inst) and set literals throw uncompilable -> interpreter; - the back end's compile-and-eval is now hybrid: only the compile step is guarded, runtime errors propagate. Still interpreter-routed (coverage to grow next): loop/recur, recur in fn, try, sets, destructuring. --- jolt-core/jolt/analyzer.clj | 32 ++++++++++++++++++++++---------- src/jolt/backend.janet | 10 ++++++++-- src/jolt/host_iface.janet | 27 +++++++++++++++++++++++++-- 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/jolt-core/jolt/analyzer.clj b/jolt-core/jolt/analyzer.clj index c6266d6..2761ae4 100644 --- a/jolt-core/jolt/analyzer.clj +++ b/jolt-core/jolt/analyzer.clj @@ -100,14 +100,21 @@ (defn- analyze-symbol [ctx form locals] (let [nm (h/sym-name form) ns (h/sym-ns form)] - (if (and (nil? ns) (contains? locals nm)) - (ir/local nm) - (let [r (h/resolve-global ctx form)] - (case (:kind r) - :var (ir/var-ref (:ns r) (:name r)) - :host (ir/host-ref (:name r)) - ;; unresolved: a forward reference in the current ns; resolved at call time - (ir/var-ref (h/current-ns ctx) nm)))))) + (cond + ;; local (only unqualified) + (and (nil? ns) (contains? locals nm)) (ir/local nm) + ;; qualified: must resolve to a var, else interpret (handles janet/…, + ;; Math/…, and any host interop the back end doesn't model). + ns (let [r (h/resolve-global ctx form)] + (if (= :var (:kind r)) + (ir/var-ref (:ns r) (:name r)) + (uncompilable (str "qualified ref " ns "/" nm)))) + :else (let [r (h/resolve-global ctx form)] + (case (:kind r) + :var (ir/var-ref (:ns r) (:name r)) + :host (ir/host-ref (:name r)) + ;; unresolved: forward reference in the current ns (resolved at call time) + (ir/var-ref (h/current-ns ctx) nm)))))) (defn- analyze-list [ctx form locals] (let [items (vec (h/elements form))] @@ -119,6 +126,9 @@ (cond (and hname (not shadowed) (contains? handled hname)) (analyze-special ctx hname items locals) + ;; A special form the analyzer doesn't compile -> interpreter. + (and hname (not shadowed) (h/special? hname)) + (uncompilable (str "special form " hname)) (and (h/sym? head) (not shadowed) (h/macro? ctx head)) (analyze ctx (h/expand-1 ctx form) locals) :else @@ -136,6 +146,8 @@ (h/map? form) (ir/map-node (mapv (fn [p] [(analyze ctx (first p) locals) (analyze ctx (second p) locals)]) (h/map-pairs form))) - (h/set? form) (ir/set-node (mapv #(analyze ctx % locals) (h/set-items form))) + (h/set? form) (uncompilable "set literal") (h/list? form) (analyze-list ctx form locals) - :else (ir/const form)))) + ;; Anything else (tagged literals like #"regex"/#inst, unknown shapes) is + ;; host-specific or not a value the back end can embed — interpret it. + :else (uncompilable "unsupported form")))) diff --git a/src/jolt/backend.janet b/src/jolt/backend.janet index f913c0d..f6dab72 100644 --- a/src/jolt/backend.janet +++ b/src/jolt/backend.janet @@ -136,6 +136,12 @@ ((var-get av) ctx form)) (defn compile-and-eval - "Self-hosted compile path: analyze (portable Clojure) -> IR -> Janet -> eval." + "Self-hosted compile path: analyze (portable Clojure) -> IR -> Janet -> eval. + Hybrid: only the compile step (analyze+emit) is guarded — a form the analyzer + can't handle throws and falls back to the interpreter; runtime errors in + compiled code propagate (no double-eval, no hidden errors)." [ctx form] - (eval (emit-ir ctx (analyze-form ctx form)) (comp/ctx-janet-env ctx))) + (def compiled (protect (emit-ir ctx (analyze-form ctx form)))) + (if (compiled 0) + (eval (compiled 1) (comp/ctx-janet-env ctx)) + (eval-form ctx @{} form))) diff --git a/src/jolt/host_iface.janet b/src/jolt/host_iface.janet index 07213e4..eca0e65 100644 --- a/src/jolt/host_iface.janet +++ b/src/jolt/host_iface.janet @@ -51,6 +51,29 @@ # Compile-time environment # --------------------------------------------------------------------------- +# Names the analyzer must NOT treat as a function call: interpreter special forms +# plus definitional/host macros the compiler doesn't lower. The analyzer handles +# a subset (quote/if/do/def/fn*/let*/loop*/recur/throw/try) and falls back to the +# interpreter for the rest. Kept in sync with evaluator/special-symbol? and +# compiler/uncompilable-heads. +(def- special-names + (let [t @{}] + (each n ["quote" "syntax-quote" "unquote" "unquote-splicing" "do" "if" "def" + "defmacro" "fn*" "let*" "loop*" "recur" "throw" "try" "set!" "var" + "locking" "eval" "instance?" "defmulti" "defmethod" "deftype" "new" + "." "var-get" "var-set" "var?" "alter-var-root" "find-var" "intern" + "alter-meta!" "reset-meta!" "disj" "set?" "satisfies?" + "protocol-dispatch" "register-method" "make-reified" "prefer-method" + "remove-method" "remove-all-methods" "get-method" "methods" + "read-string" "macroexpand-1" "defonce" "ns" "in-ns" "require" + "import" "use" "refer" "defrecord" "defprotocol" "definterface" + "reify" "proxy" "extend-type" "extend-protocol" "extend" "gen-class" + "monitor-enter" "monitor-exit" "binding" "letfn"] + (put t n true)) + t)) + +(defn h-special? [name] (if (get special-names name) true false)) + (defn h-current-ns [ctx] (ctx-current-ns ctx)) (defn h-macro? [ctx sym] @@ -92,8 +115,8 @@ "list?" h-list? "vector?" h-vector? "map?" h-map? "set?" h-set? "char?" h-char? "literal?" h-literal? "elements" h-elements "vector-items" h-vector-items "map-pairs" h-map-pairs "set-items" h-set-items - "current-ns" h-current-ns "macro?" h-macro? "expand-1" h-expand-1 - "resolve-global" h-resolve-global "intern!" h-intern!}) + "special?" h-special? "current-ns" h-current-ns "macro?" h-macro? + "expand-1" h-expand-1 "resolve-global" h-resolve-global "intern!" h-intern!}) (defn install! [ctx] (def ns (ctx-find-ns ctx "jolt.host"))