Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow specifying macros as argument to compileString #500

Closed
martinklepsch opened this issue Mar 31, 2024 · 20 comments
Closed

Allow specifying macros as argument to compileString #500

martinklepsch opened this issue Mar 31, 2024 · 20 comments

Comments

@martinklepsch
Copy link
Contributor

martinklepsch commented Mar 31, 2024

To upvote this issue, give it a thumbs up. See this list for the most upvoted issues.

Is your feature request related to a problem? Please describe.

Currently macros rely on a "classpath style" construct via squint.edn. For tools like Vite and general flexibility it would be nice to be able to provide macros to the compileString function.

Describe the solution you'd like

Document (and implement if not already) an extra input to compileString that takes macros. This could be plain JS functions.

@borkdude
Copy link
Member

It sounds like this is a solution proposal rather than a problem statement. The current solution that is proposed, how would that play with macros from libraries?

@martinklepsch
Copy link
Contributor Author

Fair, it's taking into account some things you already told me about the macro API.

As to how it considers dependencies: it wouldn't. Much like compileString doesn't really consider dependencies. I'm thinking of this as a low level extension mechanism through which other tools can provide macro implementations (from wherever).

Very open to other ideas as well, I guess what I'm suggesting is more about a "no assumptions" API to provide macros. Hope that makes sense!

@borkdude
Copy link
Member

borkdude commented Mar 31, 2024

I don't think it will be very convenient if users would have to add library macros manually in the plugin configuration though

@martinklepsch
Copy link
Contributor Author

martinklepsch commented Mar 31, 2024

That's not what I'm suggesting. compileString is called by the plugin and the plugin could construct the macros input.

@borkdude
Copy link
Member

That's only one piece of the puzzle though. The other maybe harder piece would be: where would the plugin get the information from?

I guess another approach could be to still have a squint.edn such that compileString resolves (:require-macros [...]) based on that configuration. Similarly (:require [foo.bar]) could also be resolved by compileString using the same logic that is used for squint.edn currently.

@martinklepsch
Copy link
Contributor Author

The plugin could also look in src or even have some built in logic to find namespaces in node_modules or even jars.

This is far out of course but I'm not seeing this API as something that end users would use but rather build tools that work on top of squint (like the Vite plugin).

squint.edn could of course still exist as an alternative or even work in conjunction with these tools.

@nasser
Copy link

nasser commented Jun 26, 2024

I am trying to use squint to compile user code using compileString entirely in the browser, so I don't have access to squint.edn. At the very least I need to expose some of my own macros to the compiler so that user code has access to them, but ideally users could define their own macros as well.

@borkdude
Copy link
Member

@nasser compileString already accepts a map of :macros like this: {'namespace {'macro-name (fn [form env] ...)}}.

Currently it only works on the CLJ/CLJS side, but it could be made to work on the JS side as well.

Demo in JVM Clojure:

user=> (sq/compile-string "(dude/foo 1)" {:macros {'dude {'foo (fn [form env x] `(do (js/console.log ~x) ~x))}}})
"import * as squint_core from 'squint-cljs/core.js';\nconsole.log(1);\n1;\n"

Squint doesn't support "inline" defmacro, only macros defined in another file which is referred to with :require-macros and currently only the NodeJS squint command line tool pre-processes those, the lower level compile-string function just ignores those. I can provide more details about why that is, but maybe let's focus on the first use case for now.

Note that scittle (based on SCI = Clojure interpreter) does support inline macros without any problems since how it works is closer to how JVM Clojure works: https://babashka.org/scittle/

@nasser
Copy link

nasser commented Jun 26, 2024

oh very cool! thanks for the background!

yeah i am less interested in the inline defmacro case and it is not lost on me how hard it is to get that stuff to work.

could you point me in the direction of what to change to expose the :macros key to the javascript api? happy to take out a PR to that effect if that's helpful.

@borkdude
Copy link
Member

yes:

#?(:cljs
(defn clj-ize-opts [opts]
(let [opts (js->clj opts :keywordize-keys true)]
(cond-> opts
(:context opts) (update :context keyword)
(:ns opts) (update :ns symbol)
(:elide_imports opts) (assoc :elide-imports (:elide_imports opts))
(:elide_exports opts) (assoc :elide-exports (:elide_exports opts))))))

@nasser
Copy link

nasser commented Jun 26, 2024

awesome i am on it.

one thing -- do macros referenced this way always need to be fully qualified? i.e. in your example is there a way i could say (foo 1) instead if (dude/foo 1)?

@borkdude
Copy link
Member

@nasser Before we proceed, one more question. How would you write macros in pure JS or squint? Macros typically deal with CLJS data structures with symbols and keywords. Squint doesn't have the concept of keywords and/or symbols. This is why macros run in SCI when using squint with the command line.

@borkdude
Copy link
Member

Cherry does have runtime keywords and symbols btw.

@nasser
Copy link

nasser commented Jun 27, 2024

thats a good question. is there a way i could use cherry to compile or run the macros?

@borkdude
Copy link
Member

No, since cherry is advanced compiled in a different build than squint and their symbol/keyword/data-structure stuff isn't interchangeable. It's way easier to use squint from ClojureScript, mix in your macros, then compile that to a final artifact and use that instead.

@nasser
Copy link

nasser commented Jun 27, 2024

you're totally right, and given that i don't need macros beyond the few built-in ones in my system i think that's the path of least resistance. exposing my own compile-string like this

(ns eighth-floor.core
  (:require [squint.compiler :as sq]))

(defn genstr [s]
  (-> (js/Math.random) (.toString 32) (.replace "0." (str s "."))))

(aset js/globalThis "$CYCLES" #js {})

(def default-opts
  {:macros
   {'<> (fn [form env x]
          (let [name (genstr "cycle")]
            `(do
               (when-not (aget js/globalThis.$CYCLES ~name)
                 (aset js/globalThis.$CYCLES ~name (cycle ~x)))
               (aget js/globalThis.$CYCLES ~name))))}})

(defn compile-string
  ([s] (compile-string s nil))
  ([s opts]
   (sq/compile-string s (-> opts
                            sq/clj-ize-opts
                            (merge default-opts)))))

means i can do this from JS

import { compileString } from './lib/eighth-floor.js';
const compiled = compileString("(fn [] (+ 1 (<> 10 (str [1 2 3 4]))))", { 'elide-imports': true, context: 'expr' });
console.log(compiled)

// (function () {
// return (1) + ((() => {
// if (squint_core.truth_(globalThis.$CYCLES["cycle.fggh95bnobo"])) {
// } else {
// squint_core.aset(globalThis.$CYCLES, "cycle.fggh95bnobo", squint_core.cycle(10))};
// return globalThis.$CYCLES["cycle.fggh95bnobo"];
// })());
// });

which is exactly what i need! thanks for your guidance @borkdude.

no PR coming from me in that case, especially given your point that it is not clear how to run the macros from the JS API side.

@borkdude
Copy link
Member

Excellent, thank you! And also thank you for letting me think through this issue more, in hindsight it doesn't make that much sense to allow this to be done from the JS side, so I'll close this.

@borkdude
Copy link
Member

Btw, @nasser, I'm curious what you're creating :)

@nasser
Copy link

nasser commented Jun 27, 2024

porting https://8fl.live/ to the browser 👀

@borkdude
Copy link
Member

nice!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants