squint

wcohen 2025-10-20T15:45:15.076339Z

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

✅ 1
wcohen 2025-10-21T14:19:17.429479Z

Works great, fix solves it!

🎉 1
borkdude 2025-10-21T16:27:49.429769Z

will your project / work be public btw? or maybe write a short blog post about how you're using cherry?

wcohen 2025-10-21T16:28:10.778499Z

It will be public once I wrestle it to the ground to a workable state!

🎉 1
wcohen 2025-10-21T16:30:44.931609Z

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

borkdude 2025-10-21T17:41:41.152239Z

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

borkdude 2025-10-20T15:54:55.146869Z

yes, you can pass a :macros argument that looks like:

{:macros {'my.namespace {'my-macro (fn [form env ...])}}}

borkdude 2025-10-20T15:55:25.377469Z

but not from JavaScript I'm afraid

borkdude 2025-10-20T15:55:58.071449Z

unless those macros are defined in CLJS somehow

wcohen 2025-10-20T16:10:49.284849Z

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 ?

wcohen 2025-10-20T16:14:59.424479Z

(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)

borkdude 2025-10-20T16:27:59.503739Z

why are you not compiling with cherry instead?

wcohen 2025-10-20T16:28:35.918289Z

cherry can work at runtime? i assumed i need compileString to take the code provided by a user

wcohen 2025-10-20T16:29:33.888899Z

Oh geez. There’s eval-string. Fantastic. Thanks

borkdude 2025-10-20T16:30:34.968199Z

This may also help: https://blog.michielborkent.nl/cherry-embed.html

wcohen 2025-10-20T16:33:42.013539Z

That should do it. Thank you as always.

borkdude 2025-10-20T16:35:30.431579Z

cool, let me know how it's going to work out :)

wcohen 2025-10-20T17:01:53.977739Z

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

borkdude 2025-10-20T17:24:00.278269Z

no, you compile this via CLJS

borkdude 2025-10-20T17:43:08.442199Z

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

borkdude 2025-10-20T17:43:30.541229Z

you will basically pull in another pre-compiled version of the CLJS library if you pull in cherry via JS

borkdude 2025-10-20T17:43:34.166619Z

just use it as a CLJS library since you are inside CLJS anyway

wcohen 2025-10-20T17:44:11.540099Z

Gotcha. Something about the macros still isn’t working. I need to debug further

borkdude 2025-10-20T17:44:28.481309Z

repro welcome.

wcohen 2025-10-20T17:47:28.322809Z

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?

wcohen 2025-10-20T17:48:13.520979Z

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

wcohen 2025-10-20T18:16:02.749149Z

Never mind, I think it’s a cherry bug. Working on repro and possible patch

borkdude 2025-10-20T18:16:30.656819Z

👍

borkdude 2025-10-20T20:14:24.931969Z

@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?

borkdude 2025-10-20T20:14:36.254879Z

in a ClojureScript project I presume?

wcohen 2025-10-20T20:14:53.665389Z

sci on jvm, cherry on js

borkdude 2025-10-20T20:15:48.690169Z

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?

borkdude 2025-10-20T20:16:31.625559Z

I mean, we can totally fix the bug of course, but I just don't understand why you are directly using it from JS :)

wcohen 2025-10-20T20:16:54.027219Z

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

wcohen 2025-10-20T20:17:38.060889Z

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

borkdude 2025-10-20T20:17:40.463529Z

so you are first compiling some macros and then you add those to the macro setting? hmm, yeah that should work

wcohen 2025-10-20T20:17:44.693699Z

exactly

wcohen 2025-10-20T20:17:50.391419Z

i know it’s cherry in cherry

wcohen 2025-10-20T20:18:05.745259Z

i’ll set up the pr, you can decide if it’s too crazy

borkdude 2025-10-20T20:18:16.082789Z

let's do it

borkdude 2025-10-20T20:53:37.256269Z

I left several comments

borkdude 2025-10-20T20:54:04.251959Z

also, check out this. you can use compiler state to achieve what you want with unqualified macros I think.

borkdude 2025-10-20T20:54:52.815849Z

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

wcohen 2025-10-20T21:05:16.930739Z

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.

borkdude 2025-10-20T21:11:55.889529Z

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"

borkdude 2025-10-20T21:13:11.185729Z

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))}}})

borkdude 2025-10-20T21:13:38.670149Z

oh right... :require-macros is processed only from the node API I think facepalm

wcohen 2025-10-20T21:15:09.123019Z

Ah yeah that’s right. At first I was considering trying to like polyfill FS from the browser to fake it that way

borkdude 2025-10-20T21:16:53.373729Z

no I think we should support it without the node API as well, by processing :require-macros in compiler-common

borkdude 2025-10-20T21:17:43.009559Z

or we can support unqualified macros by adding it in {'cljs.core {...}} in :macros. This will be the easier option

borkdude 2025-10-20T21:20:58.522219Z

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...

borkdude 2025-10-20T21:21:32.870109Z

but I need to debug this... somehow it's not working

borkdude 2025-10-20T21:28:10.467259Z

ah I made a small fix

borkdude 2025-10-20T21:28:12.805899Z

locally

borkdude 2025-10-20T21:30:21.083649Z

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)"

wcohen 2025-10-20T21:33:43.367829Z

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

borkdude 2025-10-20T21:35:45.471939Z

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

wcohen 2025-10-20T21:37:12.556389Z

Thank you!

borkdude 2025-10-20T21:46:20.796219Z

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)))))

borkdude 2025-10-20T21:53:23.092239Z

published cherry 0.4.31

wcohen 2025-10-20T21:54:25.941449Z

As always, much appreciated

borkdude 2025-10-20T19:05:17.339219Z

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

🆒 3