How can I make functions available to ClojureScript's eval?


How can I make functions available to ClojureScript's eval?
In this blog post by Dmitri Sotnikov a function eval-str
is provided for running a string containing ClojureScript:
eval-str
(defn eval-str [s]
(eval (empty-state)
(read-string s)
:eval js-eval
:source-map true
:context :expr
(fn [result] result)))
(defn eval-str [s]
(eval (empty-state)
(read-string s)
:eval js-eval
:source-map true
:context :expr
(fn [result] result)))
If I have some function x
that I want to be able to call from inside the eval string, how can I do that?
x
1 Answer
1
There are two parts to the answer, assuming x
is a var associated with a ClojureScript function:
x
x
cljs.js/eval
x
x
cljs.js/eval
If x
is a core function (say the var #'cljs.core/map
for example), then both of these conditions is automatically satisfied. In particular, the metadata will be produced when cljs.js/empty-state
is called (assuming :dump-core
is true
), and the implementation of the core functions will have already been loaded into the JavaScript runtime.
x
#'cljs.core/map
cljs.js/empty-state
:dump-core
true
But, let's say x
is a wholly new function that you wish to have compiled in the self-hosted environment. The “trick” is to set up and reuse compiler state: For example put the result of (cljs.js.empty-state)
into a var, and pass it to every cljs.js/eval
call. If you do that, and one of the cljs.js/eval
calls involves compiling a defn
for x
, then the compiler state will be modified (it is actually an atom), with the result being that the compiler metadata for x
will be put in the state, along with, of course, the JavaScript implementation for x
being set within the JavaScript environment (by virtue of evaluating the JavaScript produced for the defn
).
x
(cljs.js.empty-state)
cljs.js/eval
cljs.js/eval
defn
x
x
x
defn
If, on the other hand, x
is a function that is part of your “ambient” ClojureScript environment (say, pre-compiled via the JVM ClojureScript compiler, but nevertheless available in the JavaScript runtime), then it will be up to you to somehow to arrange to get the compiler analysis metadata for x
into the state passed to cljs.js/eval
. If you look at the output of the JVM-based compiler, you will see <ns-name>.cache.json
files containing such metadata. Take a look at the data that is in these files and you can ascertain its structure; with that you can see how to swap the needed information into the compiler state under [:cljs.analyzer/namespaces <ns-name>]
. The cljs.js/load-analysis-cache!
function exists as a helper for this use case.
x
x
cljs.js/eval
<ns-name>.cache.json
[:cljs.analyzer/namespaces <ns-name>]
cljs.js/load-analysis-cache!
@interstar You'd want the namespaces used by the string being evaluated to be available (which could differ from the namespace calling
cljs.js/eval
). While there could be a custom way to pass this to cljs.js/eval
, another approach is to evaluate a require
(or an ns
) form prior to such code (or make direct use of cljs.js/require
). In the event that required namespaces are already compiled, cljs.js/*load-fn*
supports passing back compiled JavaScript along with the associated analysis cache.– Mike Fikes
5 hours ago
cljs.js/eval
cljs.js/eval
require
ns
cljs.js/require
cljs.js/*load-fn*
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
This all seems incredibly complicated for what seems a fairly obvious usage requirement. Isn't there just a way to say "pass the namespace that calls the eval into the eval as its context"?
– interstar
13 hours ago