I'm experimenting with Clojure and Instaparse. I have created a small toy language, and I'm getting stuck at how to properly treat the resulting tree. This is what i get:
[:ClassDescription
[:ClassName "Test"]
[:Properties
[:Property
[:PropertyName "ID"]
[:PropertyType "Int"]]
[:Property
[:PropertyName "Name"]
[:PropertyType "string"]]]]
Now, as an example I would like to extract all PropertyTypes. I have two main ways and I'd like a solution for both.
[:ClassDescription
:Properties :Property :PropertyType]
:PropertyType
elements, regardless of depth.For A., my first idea was to convert some parts of it to maps via insta/transform
and then use get-in
, but then I get a really clunky solution involving nested loops and get-in
.
I can also use nth
, and drill my self into the structure, but that seems cumbersome, and will break easily if I add another layer.
My other idea was a recursive solution where I treat every element the same way and loop through it and check for all matches.
For B. my only solution so far is a recursive function that just drills through everything and tries to match the first element.
I believe that these "handwritten" functions could be avoided by some clever combination of insta/transform
, map
, filter
, reduce
, etc. Can it?
For getting elements out of the parsed data, you could use tree-seq
to iterate all and pick what you need. E.g.:
(defn parsed-tree-seq [parsed]
(tree-seq #(vector? (second %)) rest parsed))
(map second
(filter
#(= (first %) :PropertyType)
(parsed-tree-seq parsed)))
; => ("Int" "string")
Yet you might be better off to shape your data already in the first place by using <...>
in your parser and/or by turning them into something more mapish, what would be easier to access via transformation. E.g. turn the :Properties
into a list of maps and turn the whole thing into a map:
(defn transform [parsed]
(insta/transform
{:Property #(into {} %&)
:Properties #(vec (concat [:Properties] [%&]))
:ClassDescription #(into {} %&)
} parsed))
(map :PropertyType (:Properties (transform parsed)))
; => ("Int" "string")