clj-commons / rewrite-clj

Rewrite Clojure code and edn
https://cljdoc.org/d/rewrite-clj
MIT License
590 stars 55 forks source link

Add more type related helpers #114

Open lread opened 3 years ago

lread commented 3 years ago

Originally raised by me as https://github.com/lread/rewrite-cljc-playground/issues/5

Related to #113 - capturing valuable feedback from @sogaiu so I don't lose it. Need to review and evaluate more type related helpers.

Snippit from @sogaiu

(defn string-value
  "Return the string value for a node."
  [node]
  (when-let [lines (:lines node)]
    (cs/join "\n" lines)))
(defn string-node?
  "Returns true if node represents a string, else false."
  [node]
  (string-value node))
(defn string?
  "Returns true if zipper represents a string, else false."
  [zloc]
  (some-> zloc rz/node string-node?))
(defn symbol-value
  "Return the symbol value for a node."
  [node]
  (:value node))
(defn symbol-node?
  "Returns true if node represents a symbol, else false."
  [node]
  (clojure.core/symbol? (symbol-value node)))
(defn symbol?
  "Returns true if zipper represents a symbol, else false."
  [zloc]
  (some-> zloc rz/node symbol-node?))

Also: pointer to similar can be found within clj-kondo fork of rewrite-clj.

lread commented 3 years ago

Issue comments from rewrite-cljc-playground:

@lread: Was chatting with @borkdude on Slack and type came up again. Perhaps a (type node) might a good addition. @borkdude said that such a feature would allow him to avoid code like this.

@lread: More chats on Slack, @SevereOverfl0w was wondering about querying node types.

We agreed the existing granularity provided by (rewrite-cljc.node/tag n) is coarser than we'd like.

@borkdude chimed in with examples of what he uses for his custom version of rewrite-clj in clj-kondo:

(defn boolean-token? [node]
  (boolean? (:value node)))

(defn char-token? [node]
  (char? (:value node)))

(defn string-from-token [node]
  (when-let [lines (:lines node)]
    (str/join "\n" lines)))

(defn number-token? [node]
  (number? (:value node)))

(defn symbol-token? [node]
  (symbol? (:value node)))

(defn symbol-from-token [node]
  (when-let [?sym (:value node)]
    (when (symbol? ?sym)
      ?sym)))

And then @borkdude also shared another idea:

a function that returned some keyword could also be handy if you want to dispatch on something:

(defn tag+ [node] (if (symbol? (:value node)) :symbol) ...)
(case (tag+ node) :symbol ...)

I think I have something like that too in clj-kondo this could also go into some extra utils lib or utils ns

lread commented 3 years ago

I think I like the idea of a tag+ (or maybe node-type) to avoid littering our already wide node and zip APIs with a bunch of new little functions.

borkdude commented 2 years ago

One idea is to also use (isa? :rewrite-clj/symbol :rewrite-clj/token), possibly.

lread commented 2 years ago

One idea is to also use (isa? :rewrite-clj/symbol :rewrite-clj/token), possibly.

Directly related: https://github.com/clj-commons/rewrite-clj/issues/131#issuecomment-1006716628

borkdude commented 2 years ago

I already wondered where I left that comment, thanks.

lread commented 2 years ago

Ha! Yeah, I was searching for it yesterday, oddly enough!

lread commented 3 months ago

Draft 0 of hierarchy: tag+ name, example/desc, current tag, node record

Thoughts:

  1. Is the qualifier name good? Should it be rewrite-clj.tag+? I think rewrite-clj.tag is fine, it is a new thing and doesn't need the +.
  2. Consider making namespaced-map a descendant of map, or are they too different?
  3. uneval for #_ is a rewrite-clj-ism, I like the name but Cloure docs call this "discard" I suppose we could make a discard parent and have uneval as a child to fashion a synonym
  4. reader-macro is a catch-all for #things we don't distinguish.
    1. We could probably distinguish a tagged literal #foo 42 and reader conditionals. If we did that, would we still need reader-macro?
    2. And technically, plenty of the above are reader macros but I'm guessing that it is not terribly interesting to categorize them as such, but it might make sense to do so for extensibility.
  5. If more categorizations are deemed generally useful we can always add them at a later date.
  6. The category of rewrtite-clj.tag/sequence is probably not great, as a map is not really a sequence. Maybe instead rewrite-clj.tag/collection?
sogaiu commented 2 months ago

Among the "reader-macro" things, here's a fun construct I didn't know about for a long time [1] [2]:

#user.Fun[1 2]
#user.Fun{:a 1 :b 2}

Haven't seen it very much in the wild (^^;


[1] There are even some docs.

[2] Here is where I struggled with naming...

lread commented 2 months ago

Hi @sogaiu! Thanks so much for sharing! :heart:

I did not know about that syntax, thanks! I'll compare my list above with the elements that tree-sitter-clojure recognizes and look for anything else I might have missed.

sogaiu commented 2 months ago

If you see anything missing or amiss in tree-sitter-clojure, please mention!