我正在尝试使用Clojure和Instaparse。我已经创建了一种小型玩具语言,而且我对如何正确处理生成的树感到困惑。这就是我得到的:
[:ClassDescription
[:ClassName "Test"]
[:Properties
[:Property
[:PropertyName "ID"]
[:PropertyType "Int"]]
[:Property
[:PropertyName "Name"]
[:PropertyType "string"]]]]
现在,作为一个例子,我想提取所有PropertyTypes。我有两种主要方式,我想为两者提供解决方案。
[:ClassDescription
:Properties :Property :PropertyType]
:PropertyType
元素,无论深度如何。对于A.,我的第一个想法是通过insta/transform
将其某些部分转换为地图,然后使用get-in
,但后来我得到了一个非常笨重的解决方案,涉及嵌套循环和{{1} }。
我也可以使用get-in
,并将自己钻进结构中,但这看起来很麻烦,如果我添加另一层,它会轻易破解。
我的另一个想法是递归解决方案,我以相同的方式处理每个元素并循环遍历并检查所有匹配。
对于B.我到目前为止唯一的解决方案是一个递归函数,只需钻取所有内容并尝试匹配第一个元素。
我相信这些“手写”功能可以通过nth
,insta/transform
,map
,filter
等的巧妙组合来避免。可以吗?
答案 0 :(得分:4)
为了从解析的数据中获取元素,您可以使用tree-seq
迭代所有元素并选择所需内容。 E.g:
(defn parsed-tree-seq [parsed]
(tree-seq #(vector? (second %)) rest parsed))
(map second
(filter
#(= (first %) :PropertyType)
(parsed-tree-seq parsed)))
; => ("Int" "string")
然而,您可能最好通过在解析器中使用<...>
和/或将它们转换为更加贴图的内容来更好地塑造数据,通过转换更容易访问。例如。将:Properties
转换为地图列表并将整个内容转换为地图:
(defn transform [parsed]
(insta/transform
{:Property #(into {} %&)
:Properties #(vec (concat [:Properties] [%&]))
:ClassDescription #(into {} %&)
} parsed))
(map :PropertyType (:Properties (transform parsed)))
; => ("Int" "string")