@borkdude You are truly haunting me haha https://clojure.atlassian.net/browse/CLJ-2568
Just bumped into this issue
Yeah, that's pretty annoying, but please do try out my patch :)
Trying it out, seems to work
@kevin.van.rooijen Maybe some activity in that ticket will bring it more to the attention of the core team? ;)
I'll give it a poke haha
I believe you can also vote, if you have access to JIRA
I was using it for macroexpand-all
which didn't preserve the meta. But now I have a completely different issue and a bit of a chicken and egg situation regarding macros lol
I'm honestly not familiar with Core clojure contribution, I'll go take a look for that vote button
Ok need to follow a bit of a process to get everything set up. I'll do that later I think
OK. Meanwile I added a comment as a reaction to someone else
@borkdude Would it be possible for edamame to evaluate (specific) macros at parse time? And adding the metadata after expansion?
Maybe you can already accomplish this with :postprocess
?
I'm writing a Clojure transpiler, and if I can expand ->
at parse time, I won't have to actually implement those types of macros myself
Hmm actually maybe I could
I'd have to somehow add the metadata back to the new form, that's the main problem I have. One quick hack would be to turn the expanded form into a string, parse it again with edamame, then adding up the metadata
user=> (e/parse-string "[(twice x)]" {:postprocess (fn [{:keys [:obj :loc]}] (if (and (list? obj) (= 'twice (first obj))) `(do ~@(rest obj) ~@(rest obj)) obj))})
[(do x x)]
adding to the new form: with-meta
and vary-meta
?
Interesting, edamame parses inside out?
not really, but postprocess will hit the inner elements first, since they are parsed while the outer object is parsed
The only problem with this is that I won't have any metadata for the inner element (the do
and the two x
s). The x
will have meta data if you use vary-meta
, but it will be "outdated" since it's pre-expansion
I see yeah, maybe postwalk the expression with the same location metadata
The only other option would be to postwalk the entire AST afterwards. This is very common in Lisps: a clear separation between the reader and the evaluator
which is also what babashka and sci do
Postwalk the entire ast while holding on to the old metadata you mean?
yeah, or maybe not postwalk, but just something that recursively walks over the AST
you can take a look at sci for how this is done
maybe you can even use sci for the transpilation btw
but also maybe not, depends
Not sure how sci would help. I'm currently just generating strings mostly
But maybe I'm missing an important detail
I could use sci to evaluate things. But I'd still need to preserve the relationship between clojure code and the generated output
In sci you can override clojure.core functions and macros to do what you want, e.g. generate strings
Yeah, I'm not doing any much different right now. Basically traversing the datastructure and matching on types / specific keywords
While holding on to the context of the current line / defined functions / variables
(require '[sci.core :as sci]
'[clojure.string :as str])
(println
(sci/eval-string "(println (/ 1 2 (+ 1 2 3)))"
{:bindings
{'println (fn [& args]
(format "console.log(%s);"
(str/join ", " args)))
'/ (fn [& args]
(format "(%s)"
(str/join " / " args)))
'+ (fn [& args]
(format "(%s)"
(str/join " + " args)))}}))
;; $ clojure /tmp/transpile.clj | node
;; 0.08333333333333333
I don't think you're able to keep track of how deep you're going into the datastructure using this method, or could you? I'm transpiling to GDScript, which is based on Python sigh. So I need to keep track of the indentation
Very painful
don't know
I assume there is a 1 to 1 translation of nested sexprs to indented python forms, but you probably have more experience with that syntax than I do
also I don't think you can override special forms in sci (right now)
well maybe you can, except do
probably. also not def
. it's just never been needed so far ;)
All the & body
values in defn
would have to be iterated over, with 1 indentation extra. Then anything in any of those forms, you'd have to indent as well
But I think at that point you're not using the bindings anymore, unless the body values are evaluated before defn (I guess that would be correct?)
Then again, they wouldn't have a context of how deep they are
user=> (sci/eval-string "(defn 1 2 3)" {:bindings {'defn (fn [& xs] (prn xs))}})
(1 2 3)
nil
it depends if your binding is a function or a macro
Yeah but at this point, why not just use multimethods?
user=> (sci/eval-string "(defn 1 (+ 1 2 3) 3)" {:bindings {'defn ^:sci/macro (fn [&form &env & xs] (prn :form &form) (prn xs))}})
:form (defn 1 (+ 1 2 3) 3)
(1 (+ 1 2 3) 3)
nil
multimethods are functions
so by definition, their arguments are evaluated first
anyway, I'm afk now :)
All right, thanks for the input!
Sadly this won't work either because I want to be able to display compilation errors. And using the :bindings
method means I wouldn't know where these definitions are (I think).
you know if you make these bindings macros, so you can look at the form (the first arg of the macro)
and this form contains metadata
see this one: https://clojurians.slack.com/archives/C015LCR9MHD/p1596749179435200
Oh that's really cool
Does sci have something like a default binding? I don't see it in the options at least. Since if the user inputs an unknown function (foo 1 2 3)
it'll need the default treatment, which would be "foo(1, 2, 3)"
It's getting a it late. I'm probably going to head off. Thanks again!