Add more type related helpers
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.
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
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.
One idea is to also use (isa? :rewrite-clj/symbol :rewrite-clj/token), possibly.
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
I already wondered where I left that comment, thanks.
Ha! Yeah, I was searching for it yesterday, oddly enough!
Draft 0 of hierarchy: tag+ name, example/desc, current tag, node record
rewrite-clj.tag/formstop-level forms holder:formsFormsNoderewrite-clj.tag/token(classifier)rewrite-clj.tag/symbolfoo:tokenSymbolNoderewrite-clj.tag/string"foo":tokenStringNoderewrite-clj.tag/string-multi-line"foobar":multi-lineStringNode
rewrite-clj.tag/keyword:mykw:tokenKeywordNoderewrite-clj.tag/booleanfalse:tokenTokenNoderewrite-clj.tag/number42:tokenTokenNoderewrite-clj.tag/character-literal\newline:tokenTokenNoderewrite-clj.tag/regex#"\d+":regexRegexNode
rewrite-clj.tag/clojure-whitespace(classifier)rewrite-clj.tag/horizontal-whitespacetab(s), space(s):whitespaceWhitespaceNoderewrite-clj.tag/newlinecarriage return(s), linefeed(s):newlineNewlineNoderewrite-clj.tag/comma,:commaCommaNode
rewrite-clj.tag/sequence(classifier)rewrite-clj.tag/map{:a 1 :b 2}:mapSeqNoderewrite-clj.tag/namespace-map#:prefix{:a 1 :b 2}:namespaced-mapNamespacedMapNoderewrite-clj.tag/vector[1 2 3]:vectorSeqNoderewrite-clj.tag/set#{1 2 3}:setSeqNode
rewrite-clj.tag/map-qualifierqualifier for namespace map:map-qualifierMapQualifierNoderewrite-clj.tag/meta^foo []:metaMetaNoderewrite.clj.tag/legacy-meta#^foo []``:meta*MetaNode
rewrite-clj.tag/comment; foo:commentCommentNoderewrite-clj.tag/uneval#_ ignore-me:unevalUnevalNoderewrite-clj.tag/reader-macro#my-macro 42:reader-macroReaderMacroNode|rewrite-clj.tag/var-quote#'my-var:varReaderNoderewrite-clj.tag/eval#=(inc 1):evalReaderNoderewrite-clj.tag/quote'sym:quoteQuoteNoderewrite-clj.tag/syntax-quote`map:syntax-quoteQuoteNoderewrite-clj.tag/unquote~mysym:unquoteQuoteNoderewrite-clj.tag/unquote-splicing~@body:unquote-splicingQuoteNode
Thoughts:
- Is the qualifier name good? Should it be
rewrite-clj.tag+? I thinkrewrite-clj.tagis fine, it is a new thing and doesn't need the+. - Consider making namespaced-map a descendant of map, or are they too different?
- 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 - reader-macro is a catch-all for #things we don't distinguish.
- We could probably distinguish a tagged literal
#foo 42and reader conditionals. If we did that, would we still need reader-macro? - 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.
- We could probably distinguish a tagged literal
- If more categorizations are deemed generally useful we can always add them at a later date.
- The category of
rewrtite-clj.tag/sequenceis probably not great, as a map is not really a sequence. Maybe insteadrewrite-clj.tag/collection?
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...
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.
If you see anything missing or amiss in tree-sitter-clojure, please mention!