loader: share compile/interpret routing between load-ns and eval-one
Moves the per-form routing (compile when :compile?, stateful forms always interpret, interpreter fallback for uncompilable forms) into loader as eval-toplevel, used by both load-ns and api/eval-one. Previously load-ns's compile path called compile-and-eval directly with no fallback and no stateful-form handling, so it would have broken ns/require/defmacro under compile-by-default. Array forms whose head is not a symbol (e.g. ((fn ...) args)) compile like any other call, matching the prior eval-one behavior.
This commit is contained in:
parent
2872f17c59
commit
acf724b638
2 changed files with 56 additions and 53 deletions
|
|
@ -55,44 +55,12 @@
|
|||
(install-async! ctx)
|
||||
ctx))
|
||||
|
||||
# Stateful / context-modifying forms always use the interpreter (they mutate
|
||||
# the context: namespaces, macros, types, multimethods, dynamic vars, …).
|
||||
(defn- stateful-head? [head-name]
|
||||
(or (= head-name "defmacro") (= head-name "ns")
|
||||
(= head-name "deftype") (= head-name "defmulti") (= head-name "defmethod")
|
||||
(= head-name "require") (= head-name "in-ns")
|
||||
(= head-name "syntax-quote") (= head-name "set!")
|
||||
(= head-name "var") (= head-name ".") (= head-name "new")
|
||||
(= head-name "eval")))
|
||||
|
||||
# Hybrid compile path: try to compile the form, fall back to the interpreter for
|
||||
# anything the compiler can't (yet) handle. Only the compile step is guarded —
|
||||
# runtime errors in compiled code propagate, so we never double-eval side effects
|
||||
# or hide real errors behind a fallback. The interpreter handles every form, so
|
||||
# the result is always correct; compilation is a transparent speedup.
|
||||
(defn- try-compile-eval [ctx form]
|
||||
(let [compiled (protect (compile-ast form ctx))]
|
||||
(if (compiled 0)
|
||||
(eval-compiled (compiled 1) ctx)
|
||||
(eval-form ctx @{} form))))
|
||||
|
||||
(defn eval-one
|
||||
"Evaluate a single already-parsed form, routing to the compiler when the
|
||||
context has :compile? enabled (stateful forms always interpret; forms the
|
||||
compiler can't handle fall back to the interpreter)."
|
||||
"Evaluate a single already-parsed form. Routing (compile when :compile? is set,
|
||||
stateful forms interpret, interpreter fallback for forms the compiler can't
|
||||
handle) lives in loader/eval-toplevel so load-ns and eval-one stay in sync."
|
||||
[ctx form]
|
||||
(if (get (ctx :env) :compile?)
|
||||
(if (array? form)
|
||||
(let [first-form (first form)
|
||||
head-name (if (and (struct? first-form) (= :symbol (first-form :jolt/type)))
|
||||
(first-form :name) nil)]
|
||||
(if (stateful-head? head-name)
|
||||
(eval-form ctx @{} form)
|
||||
(try-compile-eval ctx form)))
|
||||
(if (or (and (struct? form) (= :symbol (form :jolt/type))) (tuple? form))
|
||||
(try-compile-eval ctx form)
|
||||
(eval-form ctx @{} form)))
|
||||
(eval-form ctx @{} form)))
|
||||
(eval-toplevel ctx form))
|
||||
|
||||
(defn eval-string
|
||||
"Evaluate a Clojure source string in a Jolt context.
|
||||
|
|
|
|||
|
|
@ -6,6 +6,47 @@
|
|||
(use ./compiler)
|
||||
(use ./evaluator)
|
||||
|
||||
# Stateful / context-modifying forms always interpret: they mutate the context
|
||||
# (namespaces, macros, types, multimethods, dynamic vars, …) in ways the compiler
|
||||
# doesn't model. Kept here so the compile/interpret routing lives in one place,
|
||||
# used by both load-ns and the public eval-one.
|
||||
(defn- stateful-head? [head-name]
|
||||
(or (= head-name "defmacro") (= head-name "ns")
|
||||
(= head-name "deftype") (= head-name "defmulti") (= head-name "defmethod")
|
||||
(= head-name "require") (= head-name "in-ns")
|
||||
(= head-name "syntax-quote") (= head-name "set!")
|
||||
(= head-name "var") (= head-name ".") (= head-name "new")
|
||||
(= head-name "eval")))
|
||||
|
||||
(defn- form-head-name [form]
|
||||
(when (array? form)
|
||||
(let [ff (first form)]
|
||||
(when (and (struct? ff) (= :symbol (ff :jolt/type))) (ff :name)))))
|
||||
|
||||
(defn eval-toplevel
|
||||
"Evaluate one top-level form for ctx, honoring :compile?. Stateful forms always
|
||||
interpret; otherwise the form is compiled and run, falling back to the
|
||||
interpreter when the compiler can't handle it. Only the compile step is guarded
|
||||
— runtime errors in compiled code propagate (no double-eval, no hidden errors)."
|
||||
[ctx form]
|
||||
(defn try-compile []
|
||||
(let [compiled (protect (compile-ast form ctx))]
|
||||
(if (compiled 0)
|
||||
(eval-compiled (compiled 1) ctx)
|
||||
(eval-form ctx @{} form))))
|
||||
(if (get (ctx :env) :compile?)
|
||||
(if (array? form)
|
||||
# A call/list: compile it unless its head is a stateful special form.
|
||||
(let [hn (form-head-name form)]
|
||||
(if (and hn (stateful-head? hn))
|
||||
(eval-form ctx @{} form)
|
||||
(try-compile)))
|
||||
# A bare symbol or vector literal compiles; anything else interprets.
|
||||
(if (or (and (struct? form) (= :symbol (form :jolt/type))) (tuple? form))
|
||||
(try-compile)
|
||||
(eval-form ctx @{} form)))
|
||||
(eval-form ctx @{} form)))
|
||||
|
||||
(defn load-ns
|
||||
"Load a Clojure namespace from a .clj file.
|
||||
When ctx has :compile? enabled, forms are compiled to Janet source,
|
||||
|
|
@ -41,23 +82,17 @@
|
|||
(when (nil? ns-name)
|
||||
(error (string "No ns form found in " filepath)))
|
||||
|
||||
(if compile?
|
||||
(do
|
||||
# Compile each form and eval as Janet
|
||||
(var cached (get cache ns-name))
|
||||
(when (nil? cached)
|
||||
(set cached @[])
|
||||
(put cache ns-name cached))
|
||||
|
||||
(each form forms
|
||||
(array/push cached form)
|
||||
(compile-and-eval form ctx))
|
||||
ns-name)
|
||||
# Interpreter path
|
||||
(do
|
||||
(each form forms
|
||||
(eval-form ctx @{} form))
|
||||
ns-name))))
|
||||
# Per-form routing (compile-or-interpret, stateful forms interpret) is shared
|
||||
# with eval-one via eval-toplevel. When compiling, also record the forms so a
|
||||
# namespace can be inspected / re-emitted.
|
||||
(when compile?
|
||||
(var cached (get cache ns-name))
|
||||
(when (nil? cached)
|
||||
(set cached @[])
|
||||
(put cache ns-name cached))
|
||||
(each form forms (array/push cached form)))
|
||||
(each form forms (eval-toplevel ctx form))
|
||||
ns-name))
|
||||
|
||||
(defn compiled?
|
||||
"Check if a namespace has been compiled and cached."
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue