我正在探索使用the lens package来分析和转换this AST,但我不确定它是否适合这项任务。我认为它可能是,但它的表面积是如此之大和密集,我无法分辨。
我想要做的代表性操作如下。鉴于AST,我想从树中提取“页脚”部分:
FooterAnnotation
个节点。DocBlock
或任何其他Annotation
节点结束。目前我this code负责部分工作。以下是它的内容:
node :: Node -> Env
node n = case n of
CommandAnnotation _ -> stop
DocBlock d -> do
(_, acc) <- get
ns <- nodes d
put (False, acc) -- Make sure we reset state on exiting docblock.
return $ acc ++ ns
FooterAnnotation -> start
MappingAnnotation _ -> stop
MappingsAnnotation -> stop
OptionAnnotation {} -> stop
PluginAnnotation {} -> stop
Unit u -> nodes u
_ -> do
(capture, acc) <- get
return $ if capture
then acc ++ [n]
else acc
这是在遍历AST,并使用State monad来指示我是否正在捕获“页脚”节点。我使用那些只更新状态的start
和stop
函数打开和关闭捕获。捕获时,我将每个节点累积到列表中。
所以,这是有效的,但我显然不以任何方式修改原始AST,这就是我认为镜头包可以派上用场,因为它提供了{{3}其中一些明确设计为与State monad一起使用。然而,由于我有限的福,我发现文档有点无法访问,我不知道从哪里开始。
此外,我还没有找到使用镜头库从结构中删除元素的任何示例。遍历,例如,a bunch of operators“留下相同数量的元素作为它开始的后续遍历的候选者”,所以我想知道是否需要用新的AST替换“已修剪”的节点{{1只填充它们所在的空隙的节点。这是对的吗?
答案 0 :(得分:2)
镜头样式uniplate让我们将使用整个数据结构的问题分解为一次只能在数据结构中的一个位置工作的片段。我们将单个节点上的操作应用于AST中的每个节点。
单个节点上的操作将提取任何页脚,我们将tell
添加到Writer
,并删除已删除页脚的return
修改后的节点。从你的问题来看,我假设你只想从DocBlock
删除页脚;你可以用同样的方式从其他节点中删除它们。其他节点将return
未经修改。
import qualified Data.DList as DList
import Control.Monad.Trans.Writer
extractNodeFooters :: Node -> Writer (DList.DList [Node]) Node
extractNodeFooters (DocBlock nodes) = do
let (footers, remainder) = extractFooters nodes
tell (DList.fromList footers)
return (DocBlock remainder)
extractNodeFooters node = return node
差异列表DList
避免了累积提取的页脚的二次性能。
extractFooters
从页脚开始拉出块,然后在下一个注释或列表末尾结束。它是根据从列表中提取块来编写的。这是一个解析问题;很奇怪我们需要将它应用于已经解析过的AST。
import Control.Applicative
isAnnotation :: Node -> Bool
isAnnotation x = case x of
PluginAnnotation _ _ -> True
FunctionAnnotation _ -> True
IndentAnnotation -> True
DedentAnnotation -> True
CommandAnnotation _ -> True
FooterAnnotation -> True
MappingsAnnotation -> True
MappingAnnotation _ -> True
OptionAnnotation _ _ _ -> True
HeadingAnnotation _ -> True
SubheadingAnnotation _ -> True
otherwise -> False
extractBlocks :: Alternative f => (a -> Maybe (a -> Bool)) -> [a] -> (f [a], [a])
extractBlocks start = go
where
go [] = (empty, [])
go (x:xs) = maybe no_extract extract (start x)
where
no_extract = (extracted, x:unextracted)
where
~(extracted, unextracted) = go xs
extract stop = (pure (x:block) <|> extracted, unextracted)
where
~(block, remainder) = break stop xs
~(extracted, unextracted) = go remainder
extractFooters :: Alternative f => [Node] -> (f [Node], [Node])
extractFooters = extractBlocks (\x -> if (x==FooterAnnotation) then Just isAnnotation else Nothing)
我们将在以下AST的每个节点上运行
example = Unit [
Code "Unit Code",
DocBlock [
Code "DocBlock Code",
DocBlock [
Code "DocBlock DocBlock Code",
FooterAnnotation,
Code "DocBlock DocBlock FooterAnnotation Code"
],
FooterAnnotation,
Code "DocBlock FooterAnnotation Code",
DocBlock [
Code "DocBlock FooterAnnotation DocBlock Code",
FooterAnnotation,
Code "DocBlock FooterAnnotation DocBlock FooterAnnotation Code"
]
],
FooterAnnotation,
Code "Unit FooterAnnotation Code"]
如果我们将extractNodeFooters
应用于example
,则无效,因为extractNodeFooters
仅更改DocBlock
个节点,而example
是根Unit
为具有Data
实例的类型派生的通用uniplate
遍历将操作应用于节点的每个直接后代。它不会递归地修改更深层的后代。如果我们将uniplate extractNodeFooters
应用于example
,则应从最外层的DocBlock
中删除页脚,Unit
是根DocBlock
的直接后代。它不会改变任何其他print . uniplate extractNodeFooters $ example
。这正是它的作用。
FooterAnnotation
仅移除DocBlock
Unit
后代Unit [
Code "Unit Code",
DocBlock [
Code "DocBlock Code",
DocBlock [
Code "DocBlock DocBlock Code",
FooterAnnotation,
Code "DocBlock DocBlock Footer Annotation Code"
]
],
FooterAnnotation,
Code "Unit FooterAnnotation Code"
]
中的[
[
FooterAnnotation,
Code "DocBlock FooterAnnotation Code",
DocBlock [
Code "DocBlock FooterAnnotation DocBlock Code",
FooterAnnotation,
Code "DocBlock FooterAnnotation DocBlock FooterAnnotation Code"
]
]
]
uniplate
记录删除的一个注释
import Control.Monad
postorder :: Monad m => ((a -> m c) -> (a -> m b)) -> (b -> m c) -> (a -> m c)
postorder t f = go
where
go = t go >=> f
preorder :: Monad m => ((a -> m c) -> (b -> m c)) -> (a -> m b) -> (a -> m c)
preorder t f = go
where
go = f >=> t go
要删除所有地方的注释,我们必须在每个后代节点上递归应用postorder
。我们有两个通用的选择。我们可以在将节点应用于所有后代之前将其应用于节点,或者之后我们可以执行此操作。这些被称为预订或后序遍历。在转换数据时,我们通常需要后序遍历,因为每当我们处理它们时,所有后代都将被转换。
print . postorder uniplate extractNodeFooters $ example
Unit [
Code "Unit Code",
DocBlock [
Code "DocBlock Code",
DocBlock [
Code "DocBlock DocBlock Code"
]
],
FooterAnnotation,
Code "Unit FooterAnnotation Code"
]
遍历将在从外部节点提取页脚之前从内部节点提取所有页脚。这意味着不仅每个页脚都会被提取,而且每个页脚中的每个页脚都将从页脚中提取出来。 [
[FooterAnnotation,Code "DocBlock DocBlock FooterAnnotation Code"],
[FooterAnnotation,Code "DocBlock FooterAnnotation DocBlock FooterAnnotation Code"],
[
FooterAnnotation,
Code "DocBlock FooterAnnotation Code",
DocBlock [
Code "DocBlock FooterAnnotation DocBlock Code"
]
]
]
删除每个页脚并分别记录每个页脚。
preorder
三个记录的页脚都不包含页脚。
print . preorder uniplate extractNodeFooters $ example
DocBlock
遍历将在从内部节点提取页脚之前从外部节点提取所有页脚。这意味着每个页脚都将完整提取。 Unit [
Code "Unit Code",
DocBlock [
Code "DocBlock Code",
DocBlock [
Code "DocBlock DocBlock Code"
]
],
FooterAnnotation,
Code "Unit FooterAnnotation Code"
]
删除每个页脚并将其记录完整。得到的AST与后序遍历相同;所有页脚都已从[
[
FooterAnnotation,
Code "DocBlock FooterAnnotation Code",
DocBlock [
Code "DocBlock FooterAnnotation DocBlock Code",
FooterAnnotation,
Code "DocBlock FooterAnnotation DocBlock FooterAnnotation Code"
]
],
[FooterAnnotation, Code "DocBlock DocBlock FooterAnnotation Code"]
]
中删除。
{{1}}
两个记录的页脚中的一个包含另一个未提取并单独记录的页脚。
{{1}}