【问题标题】:Custom ADT vs. Tree for parser return value自定义 ADT 与解析器返回值的树
【发布时间】:2012-08-10 01:53:07
【问题描述】:

我正在使用 Parsec 构建一个简单的 Lisp 解析器。

对解析器类型使用自定义 ADT 与使用标准树(即Data.Tree)相比有哪些(不利)优势?

在尝试了两种方法之后,我想出了几点自定义ADT(即Parser ASTNode):

  • 似乎更清晰、更简单
  • 其他人已经这样做了(包括Tiger,它与 Parsec 捆绑在一起)

和一个反对(即Parser (Tree ASTNode):

  • Data.Tree已经有Functor、Monad等实例,对语义分析、评估、计算代码统计会有很大帮助

例如:

  1. 自定义 ADT

    import Text.ParserCombinators.Parsec
    
    
    data ASTNode 
      = Application ASTNode [ASTNode]
      | Symbol String
      | Number Float
      deriving (Show)
    
    
    int :: Parser ASTNode
    int = many1 digit >>= (return . Number . read)
    
    
    symbol :: Parser ASTNode
    symbol = many1 (oneOf ['a'..'z']) >>= (return . Symbol)
    
    
    whitespace :: Parser String
    whitespace = many1 (oneOf " \t\n\r\f")
    
    
    app :: Parser ASTNode
    app =
      char '(' >>
      sepBy1 expr whitespace >>= (\(e:es) ->
      char ')' >>
      (return $ Application e es))
    
    
    expr :: Parser ASTNode
    expr =  symbol  <|>  int  <|>  app
    

    示例使用:

    ghci> parse expr "" "(a 12 (b 13))"
    Right 
      (Application 
        (Symbol "a") 
        [Number 12.0, Application 
                        (Symbol "b") 
                        [Number 13.0]])
    
  2. Data.Tree

    import Text.ParserCombinators.Parsec
    import Data.Tree
    
    
    data ASTNode 
      = Application (Tree ASTNode)
      | Symbol String
      | Number Float
      deriving (Show)
    
    
    int :: Parser (Tree ASTNode)
    int = many1 digit >>= (\x -> return $ Node (Number $ read x) [])
    
    
    symbol :: Parser (Tree ASTNode)
    symbol = many1 (oneOf ['a' .. 'z']) >>= (\x -> return $ Node (Symbol x) [])
    
    
    whitespace :: Parser String
    whitespace = many1 (oneOf " \t\n\r\f")
    
    
    app :: Parser (Tree ASTNode)
    app =
      char '(' >>
      sepBy1 expr whitespace >>= (\(e:es) ->
      char ')' >>
      (return $ Node (Application e) es))
    
    
    expr :: Parser (Tree ASTNode)
    expr =  symbol  <|>  int  <|>  app
    

    示例用法:

    ghci> parse expr "" "(a 12 (b 13))"
    Right
     (Node
       (Application 
         (Node (Symbol "a") []))
       [Node (Number 12.0) [],
        Node 
          (Application 
            (Node (Symbol "b") []))
          [Node (Number 13.0) []]])
    

    (抱歉格式化——希望很清楚)

【问题讨论】:

  • 鉴于子树被表示为 AST,我不太明白“树 ASTNode”会给您带来什么。我原以为您真正拥有的是终端树(字符串或数字),其中应用程序由节点值后跟子列表指示。

标签: parsing haskell parsec


【解决方案1】:

我绝对会选择 AST,因为解释/编译/语言分析通常在很大程度上受您语言的结构驱动。 AST 将简单自然地表示并尊重该结构,而 Tree 两者都不会。

例如,语言实现技术的一种常见形式是通过翻译来实现一些复杂的功能:将涉及这些功能或结构的程序翻译成不使用它们的语言子集的程序(例如 Lisp 宏,都是关于这个的)。例如,如果您使用 AST,类型系统通常会禁止您生成非法翻译作为输出。而无法理解您的程序的 Tree 类型将无济于事。

您的 AST 看起来并不复杂,因此为其编写实用函数应该不难。以这个为例:

foldASTNode :: (r -> [r] -> r) -> (String -> r) -> (Float -> r) -> r
foldASTNode app sym num node = 
    case node of
      Application f args -> app (subfold f) (map subfold args)
      Symbol str         -> sym str
      Number n           -> num n
    where subfold = foldASTNode app sym num

无论如何,您希望在您的 AST 上使用哪种 Functor?上面没有类型参数...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-05-24
    • 1970-01-01
    • 2020-02-21
    • 1970-01-01
    • 2020-09-21
    • 2021-10-01
    • 1970-01-01
    相关资源
    最近更新 更多