Is there a way to use a newly defined macro in a compileString environment with squint-cljs? Trying to define a DSL that works in JVM (via sci) and js (via squint-cljs). I know that :require-macros is a thing, but I don’t know if/how/when to provide that info to compileString so it knows about them. I can get it to work with the cli, but not in-process
Works great, fix solves it!
will your project / work be public btw? or maybe write a short blog post about how you're using cherry?
It will be public once I wrestle it to the ground to a workable state!
On that note — has anyone requested core.async for squint/cherry? Assuming the answer is no, I’m considering just making a limited port of core.async.flow to cherry via async/await as a standalone module, rather than the entirety of core async
core.async ins't going to work in cherry or squint since the go transformation is very complex perhaps building on AsyncIterable or so will help. you can use generation functions in squint and cherry as well
yes, you can pass a :macros argument that looks like:
{:macros {'my.namespace {'my-macro (fn [form env ...])}}}but not from JavaScript I'm afraid
unless those macros are defined in CLJS somehow
Okay, I’ve got a DSL that executes cross-platform in Cherry/Clojure:
(defn eval-this-dsl [code-str]
#?(:clj (sci/eval-string* *dsl-context* code-str)
:cljs (eval-dsl-cljs code-str "expr")))
eval-dsl-cljs is a Cherry-compiled function that:
1. Parses the DSL code
2. Strips out require statements
3. Compiles the remaining code with Squint using compileString
4. Sets up js.globalThis.squint_core and all the required namespaces as globals
5. Executes the compiled JS string
6. Cleans up the globals
The macros exist in macros.cljc that:
◦ SCI can handle (for JVM side)
◦ Cherry can compile (though that isn’t helpful at the moment)
Current state:
• It works on Node CLI using compiler.node.js which reads macros.cljc from disk with SCI
• It doesn’t work in browser (no filesystem)
I’m trying to end up with an ES6 module built with Cherry (dependent on Squint for runtime compilation) that bundles everything needed so that compileString can expand these macros without filesystem access.
Do you mean:
• I could define the macro functions (not defmacro forms) in CLJS and pass them as :macros?
• Or is there a way to bundle/serialize SCI macro functions from macros.cljc that compileString would accept?
• Or is filesystem access (via compiler.node.js) the only way to use :require-macros with compileString ?(If there’s no way I suppose I could just rework it to be macro-less, but I’m trying to hew closely to clojure style without having to pass a bunch of quoted unevaluated code to functions)
why are you not compiling with cherry instead?
cherry can work at runtime? i assumed i need compileString to take the code provided by a user
Oh geez. There’s eval-string. Fantastic. Thanks
This may also help: https://blog.michielborkent.nl/cherry-embed.html
https://github.com/squint-cljs/cherry/blob/main/doc/embed.md
here is an example with macros: https://github.com/nextjournal/clerk/blob/180902adfaa02fe72ca1078930f00cf5d4a84966/src/nextjournal/clerk/cherry_env.cljs#L4
That should do it. Thank you as always.
cool, let me know how it's going to work out :)
Oh wait — cherry.embed doesn’t seem available in an actual cherry environment, right? All I see available is (:require ["cherry-cljs" :as cherry]) , but (cherry/compileString code {:macros macros-map}) doesn’t expand them
no, you compile this via CLJS
or maybe I'm misunderstanding you. cherry/compileString is available, but I wouldn't use the compiled library from a CLJS environment, you will get advanced compilation mismatches
you will basically pull in another pre-compiled version of the CLJS library if you pull in cherry via JS
just use it as a CLJS library since you are inside CLJS anyway
Gotcha. Something about the macros still isn’t working. I need to debug further
repro welcome.
Sorry for being dumb, I’m getting my own self twisted. If I’m in a ‘cherry cljs environment’ — wanting to compile cherry code down to an ES6 module, where that cherry code includes a function that will itself runtime eval a squint/(or cherry)-based DSL into other JS that gets runtime evaled, to my knowledge I can’t actually depend on cherry as a JS dependency any way fancier than cherry/compileString, right?
In other words there’s no runtime equivalent of SCI, right? I’ve been using squint from cherry to try to do that. At no point am I invoking the cljs compiler, it’s all npm
Never mind, I think it’s a cherry bug. Working on repro and possible patch
👍
@wcohen I replied to the issue: > You really shouldn't be using the cherry API from JS if want to add macros. I tried to explain this on slack, but will try again. I tried to explain this above from https://clojurians.slack.com/archives/C03U8L2NXNC/p1760981040278269?thread_ts=1760975115.076339&cid=C03U8L2NXNC and further. You are using both SCI and cherry in one project right?
in a ClojureScript project I presume?
sci on jvm, cherry on js
why are you not first compiling cherry from a CLJS project so you can use that in JS? you can then write your macros in CLJS too. Isn't that much easier than writing macros in JS?
I mean, we can totally fix the bug of course, but I just don't understand why you are directly using it from JS :)
well — it’d be macros written in cherry-style cljs. if i had to compile from a cljs project, then the jvm’s gotta get pulled in and bundle stuff
i figure this would be a project that — on the jvm — depends on sci and runtime evals stuff. then on the js side it just depends on cherry, and can also run the eval there too
so you are first compiling some macros and then you add those to the macro setting? hmm, yeah that should work
exactly
i know it’s cherry in cherry
i’ll set up the pr, you can decide if it’s too crazy
let's do it
I left several comments
also, check out this. you can use compiler state to achieve what you want with unqualified macros I think.
so first in your state, compile (:require-macros [foo.bar :refer [dude]]) and then you can use (dude) unqualified.
I don't want to pollute all namespaces with unqualified macros
Makes sense. I will rethink -- something about require-macros still isn't working when I try it that way, but I'll play around with the state. Thanks for looking.
maybe it's a bug with :refer . I remember something similar I fixed in squint. perhaps bumping squint helps.
(cherry/compile-string* "(ns foo (:require-macros [foo.bar :refer [mymacro]])) (my-macro (+ 1 2 3))" {:macros {'foo.bar {'mymacro (fn [_ _ x] `(do ~x ~x))}}})
:body "my_macro.call(null, (1) + (2) + (3));\n"
hmm, no, that's not it. this doesn't work either:
(cherry/compile-string* "(ns foo (:require-macros [foo.bar :as m :refer [mymacro]])) (m (+ 1 2 3))" {:macros {'foo.bar {'mymacro (fn [_ _ x] `(do ~x ~x))}}})oh right... :require-macros is processed only from the node API I think facepalm
Ah yeah that’s right. At first I was considering trying to like polyfill FS from the browser to fake it that way
no I think we should support it without the node API as well, by processing :require-macros in compiler-common
or we can support unqualified macros by adding it in {'cljs.core {...}} in :macros. This will be the easier option
that would be picked up here: https://github.com/squint-cljs/cherry/blob/9d253be999b5753257c42d89f06e456fe52987e2/src/cherry/compiler.cljc#L208
hmm, according to the code I'm reading around there, this should work as well:
(cherry/compile-string* "(ns foo (:require [foo.bar :refer [mymacro]])) (mymacro (+ 1 2 3))" {:macros {'foo.bar {'mymacro (fn [_ _ x] `(do ~x ~x))}}}
so with a normal require...but I need to debug this... somehow it's not working
ah I made a small fix
locally
cool:
(def state (cherry/compile-string* "(ns foo (:require [foo.bar :refer [mymacro]]))" {:macros {'foo.bar {'mymacro (fn [_ _ x] `(do ~x ~x))}}}))
(cherry/compile-string* "(mymacro (+ 1 2 3))" {:macros {'foo.bar {'mymacro (fn [_ _ x] `(do ~x ~x))}}} state) ;;=>
=>
:body "(1) + (2) + (3);\n(1) + (2) + (3)"Oh! So it actually carries through via the state already! Awesome. Okay. I’ll double check tomorrow that I’m not missing something else but this seems like it’ll solve it. Sorry for bringing a sledgehammer to a pretty much solved problem
it carries state when you choose to. but it didn't look into the :macros option when you refer-ed something. This PR fixes that: https://github.com/squint-cljs/cherry/pull/161
Thank you!
now fixing the JS API:
(deftest macro-state-test
(let [js-opts (clj->js {:macros {'foo.bar {'mymacro (fn [_ _ x] `(+ ~x ~x))}}
:context :expr})
state (cherry/compileStringEx "(ns foo (:require [foo.bar :refer [mymacro]]))" js-opts nil)
state (cherry/compileStringEx "(mymacro (+ 1 2 3))" js-opts state)
body (aget state "body")]
(is (= 12 (js/eval body)))))published cherry 0.4.31
As always, much appreciated
more optimizations. assoc uses assoc! (direct mutation) when squint can detect that its argument isn't shared with anyone else.
https://mastodon.social/@borkdude/115408032267375372
what does (assoc {:a 1}) do?
It’s actually ignored / unwrapped