Skip to content

Instantly share code, notes, and snippets.

@mfikes
Last active June 13, 2024 04:14
Show Gist options
  • Save mfikes/66a120e18b75b6f4a3ecd0db8a976d84 to your computer and use it in GitHub Desktop.
Save mfikes/66a120e18b75b6f4a3ecd0db8a976d84 to your computer and use it in GitHub Desktop.
eval in ClojureScript

ClojureScript master now has cljs.core/eval. This delegates to cljs.core/*eval* which, by default throws, but you can bind it to any implementation that can compile and evaluate ClojureScript forms.

If you require the cljs.js namespace (which is the main support namespace for self-hosted ClojureScript), then cljs.core/*eval* is set to an implementation that uses self-hosted ClojureScript for this capability. This means that all self-hosted ClojureScript environments will now have a first-class eval implementation that just works. For example, Planck master:

$ planck -q
cljs.user=> (eval '(+ 2 3))
5

But, this doesn't magically cause eval to start working for regular JVM-based ClojureScript environments.

Having said this, you can play around with this in regular JVM-based ClojureScript if you evaluate the forms in setup.cljs below. All this does is appropriate the self-hosted implementation of eval and make it (somewhat) useable from JVM ClojureScript.

You can launch a browser REPL using the clj command here that sets things up to use ClojureScript master and the code in setup.cljs:

clj -Sdeps '{:deps {github-mfikes/cljs-eval {:git/url "https://gist.github.com/mfikes/66a120e18b75b6f4a3ecd0db8a976d84" :sha "542208abcc4b77c90300d70e9ebd7a89d88fe460"}}}' -m cljs.main -e "(require 'cljs.js)" -i @setup.cljs -r

Note: The above employs an extra -e to work around CLJS-2663.

With this, try eval directly in your JVM-based REPL:

cljs.user=> (eval '(+ 1 2))
3
cljs.user=> (eval (list inc 10))
11

That last example is neat in that it compiling to code that embeds a reference to the inc function value in the emitted JavaScript. For more details on this twist see ClojureScript eval, which describes the basis for the technique used in ClojureScript master.

Does this mean you can use eval in your regular ClojureScript code now? Not really, but perhaps you can in limited use cases. The reason is that normally ClojureScript is compiled down to JavaScript that executes without a compiler environment, and in particular lacks the state needed to support the things that eval really needs. In fact, the "trick" used here is limited in that it lets its own compiler state—via (cljs.js/empty-state)—that lives only on the JavaScript side of things (in your browser), and is independent of your JVM ClojureScript compiler state. Even though both share the same JavaScript evaluation enviornment, you can see the effect of the two separate compiler states via example:

cljs.user=> (def a 3)
#'cljs.user/a
cljs.user=> a
3
cljs.user=> (eval 'a)
WARNING: Use of undeclared Var cljs.user/a
3
cljs.user=> (eval '(def b 5))
#'cljs.user/b
cljs.user=> (eval 'b)
5
cljs.user=> b
WARNING: Use of undeclared Var cljs.user/b at line 1 <cljs repl>
5
{:paths ["."]
:deps {org.clojure/clojurescript
{:git/url "https://github.com/clojure/clojurescript"
:sha "a095d308484fa2ccea11b6d7256fb310d5d57c33"}}}
(require 'cljs.js)
(let [eval *eval*
st (cljs.js/empty-state)]
(set! *eval*
(fn [form]
(binding [cljs.env/*compiler* st
*ns* (find-ns cljs.analyzer/*cljs-ns*)
cljs.js/*eval-fn* cljs.js/js-eval]
(eval form)))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment