self-host: analyzer reaches full conformance parity via hybrid fallback
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.
This commit is contained in:
parent
849449aada
commit
5624e99eb6
3 changed files with 55 additions and 14 deletions
|
|
@ -100,13 +100,20 @@
|
||||||
|
|
||||||
(defn- analyze-symbol [ctx form locals]
|
(defn- analyze-symbol [ctx form locals]
|
||||||
(let [nm (h/sym-name form) ns (h/sym-ns form)]
|
(let [nm (h/sym-name form) ns (h/sym-ns form)]
|
||||||
(if (and (nil? ns) (contains? locals nm))
|
(cond
|
||||||
(ir/local nm)
|
;; local (only unqualified)
|
||||||
(let [r (h/resolve-global ctx form)]
|
(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)
|
(case (:kind r)
|
||||||
:var (ir/var-ref (:ns r) (:name r))
|
:var (ir/var-ref (:ns r) (:name r))
|
||||||
:host (ir/host-ref (:name r))
|
:host (ir/host-ref (:name r))
|
||||||
;; unresolved: a forward reference in the current ns; resolved at call time
|
;; unresolved: forward reference in the current ns (resolved at call time)
|
||||||
(ir/var-ref (h/current-ns ctx) nm))))))
|
(ir/var-ref (h/current-ns ctx) nm))))))
|
||||||
|
|
||||||
(defn- analyze-list [ctx form locals]
|
(defn- analyze-list [ctx form locals]
|
||||||
|
|
@ -119,6 +126,9 @@
|
||||||
(cond
|
(cond
|
||||||
(and hname (not shadowed) (contains? handled hname))
|
(and hname (not shadowed) (contains? handled hname))
|
||||||
(analyze-special ctx hname items locals)
|
(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))
|
(and (h/sym? head) (not shadowed) (h/macro? ctx head))
|
||||||
(analyze ctx (h/expand-1 ctx form) locals)
|
(analyze ctx (h/expand-1 ctx form) locals)
|
||||||
:else
|
:else
|
||||||
|
|
@ -136,6 +146,8 @@
|
||||||
(h/map? form) (ir/map-node (mapv (fn [p] [(analyze ctx (first p) locals)
|
(h/map? form) (ir/map-node (mapv (fn [p] [(analyze ctx (first p) locals)
|
||||||
(analyze ctx (second p) locals)])
|
(analyze ctx (second p) locals)])
|
||||||
(h/map-pairs form)))
|
(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)
|
(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"))))
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,12 @@
|
||||||
((var-get av) ctx form))
|
((var-get av) ctx form))
|
||||||
|
|
||||||
(defn compile-and-eval
|
(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]
|
[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)))
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,29 @@
|
||||||
# Compile-time environment
|
# 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-current-ns [ctx] (ctx-current-ns ctx))
|
||||||
|
|
||||||
(defn h-macro? [ctx sym]
|
(defn h-macro? [ctx sym]
|
||||||
|
|
@ -92,8 +115,8 @@
|
||||||
"list?" h-list? "vector?" h-vector? "map?" h-map? "set?" h-set? "char?" h-char?
|
"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
|
"literal?" h-literal? "elements" h-elements "vector-items" h-vector-items
|
||||||
"map-pairs" h-map-pairs "set-items" h-set-items
|
"map-pairs" h-map-pairs "set-items" h-set-items
|
||||||
"current-ns" h-current-ns "macro?" h-macro? "expand-1" h-expand-1
|
"special?" h-special? "current-ns" h-current-ns "macro?" h-macro?
|
||||||
"resolve-global" h-resolve-global "intern!" h-intern!})
|
"expand-1" h-expand-1 "resolve-global" h-resolve-global "intern!" h-intern!})
|
||||||
|
|
||||||
(defn install! [ctx]
|
(defn install! [ctx]
|
||||||
(def ns (ctx-find-ns ctx "jolt.host"))
|
(def ns (ctx-find-ns ctx "jolt.host"))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue