如何使用镜头包从AST

时间:2016-04-13 02:18:34

标签: haskell lens

我正在探索使用the lens package来分析和转换this AST,但我不确定它是否适合这项任务。我认为它可能是,但它的表面积是如此之大和密集,我无法分辨。

我想要做的代表性操作如下。鉴于AST,我想从树中提取“页脚”部分:

  • 遍历它寻找FooterAnnotation个节点。
  • 找到这样的节点后,将所有以下节点累积到列表中,直到DocBlock或任何其他Annotation节点结束。
  • 理想情况下,我不仅将这些节点提取到列表中,而且还将它们从原始AST中删除,因此我可以将剩下的任何内容传递到下一阶段。

目前我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来指示我是否正在捕获“页脚”节点。我使用那些只更新状态的startstop函数打开和关闭捕获。捕获时,我将每个节点累积到列表中。

所以,这是有效的,但我显然以任何方式修改原始AST,这就是我认为镜头包可以派上用场,因为它提供了{{3}其中一些明确设计为与State monad一起使用。然而,由于我有限的福,我发现文档有点无法访问,我不知道从哪里开始。

此外,我还没有找到使用镜头库从结构中删除元素的任何示例。遍历,例如,a bunch of operators“留下相同数量的元素作为它开始的后续遍历的候选者”,所以我想知道是否需要用新的AST替换“已修剪”的节点{{1只填充它们所在的空隙的节点。这是对的吗?

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}}