Fork me on GitHub
#rewrite-clj
<
2019-06-04
>
sogaiu11:06:55

not always easy to be brief but clear 🙂

sogaiu11:06:11

@lee btw, i encountered a problem processing clojure's core.clj:

(require
   '[rewrite-clj.zip :as rz]
   :reload-all)

  (def source-str
    (slurp "../clojure/src/clj/clojure/core.clj"))

  (def root-zloc
    (rz/of-string ;;(subs source-str 0 26232) ; no problem
                  (subs source-str 0 26981) ; exception below
                  {:track-position? true}))

  ;; ExceptionInfo unsupported operation for uneval-node ...
  (def strings
    (loop [zloc root-zloc
           results []]
      (if (rz/end? zloc)
        results
        (let [sexpr (rz/sexpr zloc)]
          (recur (rz/next zloc)
                 (if (string? sexpr) 
                   (conj results zloc)
                   results))))))
not sure, but i think the section of difficulty in core.clj is:
;equals-based
#_(defn =
  "Equality. Returns true if x equals y, false if not. Same as Java
  x.equals(y) except it also works for nil. Boxed numbers must have
  same type. Clojure's immutable data structures define equals() (and
  thus =) as a value, not an identity, comparison."
  {:inline (fn [x y] `(. clojure.lang.Util equals ~x ~y))
   :inline-arities #{2}
   :added "1.0"}
  ([x] true)
  ([x y] (clojure.lang.Util/equals x y))
  ([x y & more]
   (if (= x y)
     (if (next more)
       (recur y (first more) (next more))
       (= y (first more)))
     false)))

borkdude11:06:53

I think that makes sense, you can’t turn an uneval into a sexpr:

$ clj
Clojure 1.10.0
user=> (require '[rewrite-clj.parser :as p])
nil
user=> (p/parse-string "#_foo")
<uneval: "#_foo">
user=> (require '[rewrite-clj.node :as n])
nil
user=> (n/sexpr (p/parse-string "#_foo"))
Execution error (UnsupportedOperationException) at rewrite_clj.node.uneval.UnevalNode/sexpr (uneval.clj:6).
null

sogaiu12:06:08

ah, right. good point. thanks!

borkdude11:06:31

or it should return nil maybe, but then you can’t distinguish between a token that represents nil

borkdude11:06:11

$ clj
Clojure 1.10.0
user=> (require '[rewrite-clj.node :as n])
nil
user=> (require '[rewrite-clj.parser :as p])
nil
user=> (n/sexpr (p/parse-string "nil"))
nil

sogaiu12:06:54

also a good point.

borkdude11:06:34

same for comments:

user=> (n/sexpr (p/parse-string ";; hello"))
Execution error (UnsupportedOperationException) at rewrite_clj.node.comment.CommentNode/sexpr (comment.clj:6).
null

lread11:06:40

Thinking about rewrite-clj sexpr feature is on my todo list. I guess it might be convenient but comes with limitation that should be documented. I’m thinking that I should probably remove internal uses of sexpr because of these limitations. I’ll also have to think about cljs vs clj differences and how sexpr handles them - like ratio is only available in clj, differences in max integers, no char in cljs etc.

sogaiu21:06:58

i think i've been using sexpr without any(!) caution. would appreciate hearing more about how to use it appropriately. do you think it's mostly a matter of checking a zipper's tag before using sexpr?

sogaiu21:06:57

i guess if that's so, it might be helpful to know exactly what supports sexpr and what doesn't. based on recent conversations and looking at the source, it looks like this includes: comment, some reader nodes, uneval, whitespace, comma, newline.

lread22:06:50

I might be wrong, but am currently thinking that sexpr should not be used in general cases.

sogaiu22:06:32

i went over where i'm using sexpr and iiuc am using it mostly in situations where i could check the tag before use. i'm leaning toward changing things to check before use.

sogaiu22:06:55

it's a bit long, but here's a typical set of uses:

(defn study-defn-to-tail
  [zloc]
  (let [{:keys [zloc] :as m}
        {:root-zloc zloc
         :zloc (rz/down zloc)}
        {:keys [zloc] :as m}
        (merge m
               (if (some #{'defn 'defn-}
                         [(rz/sexpr zloc)])
                 {:zloc (rz/right zloc)}
                 (throw (#?(:cljs js/Error. :default Exception.)
                         (str "Expected defn or defn-, but got: "
                              (rz/sexpr zloc)))))
               {:form-type zloc})
        {:keys [zloc] :as m}
        (merge m
               (if (symbol? (rz/sexpr zloc))
                 {:zloc (rz/right zloc)}
                 (throw (#?(:cljs js/Error. :default Exception.)
                         (str "Expected symbol, but got: "
                              (rz/sexpr zloc)))))
               {:fn-name zloc})
        {:keys [zloc] :as m}
        (merge m
               (if (string? (rz/sexpr zloc))
                 {:found-docstring true
                  :zloc (rz/right zloc)}
                 {:found-docstring false})
               {:docstring zloc})
        {:keys [zloc] :as m}
        (merge m
               (if (map? (rz/sexpr zloc))
                 {:found-meta true
                  :zloc (rz/right zloc)}
                 {:found-meta false})
               {:meta zloc})]
    m))

sogaiu22:06:31

there are 6 uses there.

sogaiu22:06:50

two of them could have used rz/string and it looks like in the other 4 cases, the tag could be checked, iiuc

lread22:06:08

Cool, I’m interested to learn about any other cases where you feel you still need to use sepxr - its good for me to understand various uses of rewrite-clj.

sogaiu22:06:57

if i turn any more up, intend to bug you about them 🙂

sogaiu22:06:36

btw, i noticed there are predicates like: map?, list?, etc. for some things, but i don't see string? and symbol? (and some others). does it seem practical / worth having other things like those of the latter?

sogaiu23:06:10

am starting to wonder if it's really sensible for sexpr to be a part of the Node protocol...if one were to start from scratch, would it not be more sensible for there to be a separate protocol for things that support sexpr? i don't feel i have enough of a good understanding of protocols to have a decent opinion on this though 🙂

lread23:06:18

yeah me neither, not yet anyway! simple_smile

lread23:06:01

i think borkdude mentioned he added some predicates in clj-kondo

lread23:06:22

so yeah, maybe more would be handy

lread12:06:30

My current thinking is rewrite-clj sexpr should be used cautiously if at all. What do you folks think?

sogaiu13:06:52

it appears i use it a fair bit, but likely not carefully enough. i need to go back and review in detail 🙂

lread13:06:23

I think this is another place the docs could give better guidance

borkdude12:06:37

yeah, I try to avoid calling sexpr in clj-kondo as much as I can

borkdude12:06:06

although I already filter out every uneval and comment before

lread12:06:43

I guess if you are quite certain of what you are trying to sexpr you’ll probably be ok, but if you are sexpr-ing an unknown then maybe stay away from sexpr.

sogaiu13:06:44

possibly use from w/in a try/catch might be ok?

lread13:06:37

probably depends on your use case, what would you do in the catch?

sogaiu14:06:36

just avoid having the program die?

lread14:06:59

oh I see, a no-op catch? Probably not ideal, but might be appropriate depending in what you are trying to achieve?

sogaiu20:06:10

may be i should just revisit how i've been using it in detail -- at least in some cases checking for the node's tag before deciding whether to call sexpr should be doable.

lread22:06:48

yeah maybe, please let me know how this goes.

borkdude12:06:53

yeah, exactly

borkdude12:06:26

I have also made a few predicates like symbol-token? so I don’t need to sexpr to check if it’s a symbol

borkdude12:06:51

caveat is that there might be metadata on anything in clojure

borkdude12:06:36

I wonder if it would have made better sense if the metadata was a child instead of a parent. it certainly maybe would have made my life easier, but I haven’t pondered the consequences of that

lread12:06:06

interesting, we should probably eventually bring your predicates into rewrite-clj. Also interesting thought on metadata, would make it easier to parse the meat, right?

borkdude12:06:51

right, for example: I expect the first node after defn to be a symbol, but in rewrite-clj it might be a metadata node with a symbol in it

sogaiu13:06:38

on a possibly related note, i have a sort of parser for defn -- when i wrote it, i used the following as a specification of it:

; from defn docs

;; single arity
;; (defn name doc-string? attr-map? [params*] prepost-map? body)

;; multi-arity
;; (defn name doc-string? attr-map? ([params*] prepost-map? body)+ attr-map?)
my impression is that order of things has sort of mattered for some of what i've been doing, but i need to go back and review. i've been "rewriting", so possibly that's involved...

borkdude12:06:15

I would probably make metadata a field on the defrecord of every node or something

borkdude12:06:54

but that might not work for rewriting (which I’m not concerned with) to the original expressions, including spaces, etc

lread12:06:42

hmmm... yeah I see your point. It is worth thinking about more.

borkdude12:06:43

I really like rewrite-clj btw. but I might need some clone for tuning towards clj-kondo for more performance… but not now, it’s already very fast

borkdude12:06:48

what I basically do for nodes that might be metadata, is rip out the contents and store the metadata node as proper metadata on the node

lread12:06:24

cool, it is very nice to have heavy users of rewrite-clj here like you and @sogaiu. Your feedback and ideas are greatly appreciated! simple_smile

lread12:06:13

after I finish up a cljs ticket, I’ll get back on my rewrite-clj todo list and work toward the alpha release.

borkdude12:06:52

@lee do you have commit rights to rewrite-clj?

lread12:06:55

I’ll make use of your #qa channel @borkdude

lread12:06:41

no, but I do have commit rights to clj-commons/rewrite-cljs. But... that’s not the ideal spot for my work. I think ideally there would be a rewrite-clj under clj-commons but have not managed to get an answer on this idea from the author of rewrite-clj yet.

borkdude12:06:45

I’d love to see a proper fix for namespaced maps in rewrite-clj and also support for reading ##Inf

borkdude12:06:54

I patched it in clj-kondo to make it work

lread13:06:14

but you might want to wait until alpha. I know that @sogaiu is dipping his toe into my playground as he has already provided some great feedback.

lread13:06:57

my alpha goal is to bring rewrite-cljs features up to rewrite-clj while not breaking rewrite-clj/rewrite-cljs apis - but I did include a version of my rewrite-clj namespace PR (which I still want to tweak to not use sexpr)

borkdude13:06:32

I think a fundamental mistake right now in rewrite-clj is that parsing namespaced maps depends on the namespace state from which you are calling rewrite-clj: https://github.com/xsc/rewrite-clj/issues/54#issuecomment-494445992

lread13:06:38

ah... good reminder, thanks. If memory serves, I think the namespace state requirement is to support sexpr? But surely annoying if you aren’t using sexpr.

lread13:06:44

I’ll add another todo for this issue, thanks. simple_smile

borkdude13:06:39

clj -Sdeps '{:deps {rewrite-clj {:git/url "" :sha "69ef791b949eac1e3cebf6cec154bcda0a109edc"}}}'
Cloning: 
Error building classpath. Destination path "rewrite-cljs-playground" already exists and is not an empty directory

borkdude13:06:50

not sure what this is

borkdude13:06:42

this worked: ~/.gitlibs $ rm -rf _repos/github.com/lread

borkdude13:06:25

so in summary:

$ clj -Sdeps '{:deps {rewrite-clj {:git/url "" :sha "69ef791b949eac1e3cebf6cec154bcda0a109edc"}}}'
Clojure 1.10.0
user=> (require '[rewrite-clj.parser :as p])
nil
user=> (def x (p/parse-string "#::a {:a #::a {:b 1}}"))
Syntax error (AssertionError) compiling at (REPL:1:8).
Assert failed: :namespaced-map could not resolve namespace ::a
🙂

borkdude13:06:38

I’m not even calling sexpr

lread13:06:24

yeah, I see your point and it is a very good one. simple_smile

borkdude13:06:58

FWIW, what I’m doing right now to fix it:

(p/parse-string "#::a {:a #::a {:b 1}}")
#clj_kondo.impl.node.seq.NamespacedMapNode{:ns <token: ::a>, :aliased? true, :children [<map: {:a #::a{:b 1}}>]}

borkdude13:06:22

I just store the raw node of the ns and the children as is

lread13:06:43

thanks @borkdude , I’ll add this to my notes

borkdude13:06:06

sorry for being a little bit pushy maybe 😉

lread13:06:55

I don’t find you pushy at all. I find you both considerate and helpful!

borkdude13:06:02

for the purposes of linting that wasn’t a problem for me

lread13:06:45

Thanks, I’ll be sure to take a close look