使用解析器组合器解析具有函数应用的表达式语法(左递归)

标签 parsing haskell parsec recursive-descent uu-parsinglib

作为真实语言解析器的简化子问题,我正在尝试为虚构语言的表达式实现一个解析器,该语言看起来类似于标准命令式语言(例如 Python、JavaScript 等)。其语法具有以下结构:

  • 整数
  • 标识符 ([a-zA-Z]+)
  • 带有 +* 以及括号的算术表达式
  • 使用.进行结构访问(例如foo.bar.buz)
  • 元组(例如 (1, foo, bar.buz))(为了消除歧义,一元组写为 (x,))
  • 函数应用(例如foo(1, bar, buz()))
  • 函数是第一类,因此它们也可以从其他函数返回并直接应用(例如,foo()()是合法的,因为foo()可能返回一个函数)

因此,用这种语言编写的一个相当复杂的程序是

(1+2*3, f(4,5,6)(bar) + qux.quux()().quuux)

关联性应该是

( (1+(2*3)), ( ((f(4,5,6))(bar)) + ((((qux.quux)())()).quuux) ) )

我目前正在使用非常好的uu-parsinglib一个应用解析器组合器库。

第一个问题显然是直观的表达式语法 (expr ->identifier | number | expr * expr | expr + expr | (expr) 是左递归的。但我可以解决这个问题使用 pChainl 组合器(请参阅下面示例中的 parseExpr)。

剩下的问题(因此是这个问题)是函数应用程序与从其他函数返回的函数(f()())。同样,语法是左递归 expr -> fun-call | ...; fun-call -> expr (参数列表)。有什么想法可以使用uu-parsinglib优雅地解决这个问题吗? (我猜这个问题应该直接适用于 parsecattoparsec 和其他解析器组合器)。

请参阅下面我当前的程序版本。它运行良好,但函数应用程序仅处理标识符以删除左递归:

 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE RankNTypes #-}

 module TestExprGrammar
     (
     ) where

 import Data.Foldable (asum)
 import Data.List (intercalate)
 import Text.ParserCombinators.UU
 import Text.ParserCombinators.UU.Utils
 import Text.ParserCombinators.UU.BasicInstances

 data Node =
     NumberLiteral Integer
     | Identifier String
     | Tuple [Node]
     | MemberAccess Node Node
     | FunctionCall Node [Node]
     | BinaryOperation String Node Node

 parseFunctionCall :: Parser Node
 parseFunctionCall =
     FunctionCall <$>
         parseIdentifier {- `parseExpr' would be correct but left-recursive -}
         <*> parseParenthesisedNodeList 0

 operators :: [[(Char, Node -> Node -> Node)]]
 operators = [ [('+', BinaryOperation "+")]
             , [('*' , BinaryOperation "*")]
             , [('.', MemberAccess)]
             ]

 samePrio :: [(Char, Node -> Node -> Node)] -> Parser (Node -> Node -> Node)
 samePrio ops = asum [op <$ pSym c <* pSpaces | (c, op) <- ops]

 parseExpr :: Parser Node
 parseExpr =
     foldr pChainl
           (parseIdentifier
           <|> parseNumber
           <|> parseTuple
           <|> parseFunctionCall
           <|> pParens parseExpr
           )
           (map samePrio operators)

 parseNodeList :: Int -> Parser [Node]
 parseNodeList n =
     case n of
       _ | n < 0 -> parseNodeList 0
       0 -> pListSep (pSymbol ",") parseExpr
       n -> (:) <$>
           parseExpr
           <* pSymbol ","
           <*> parseNodeList (n-1)

 parseParenthesisedNodeList :: Int -> Parser [Node]
 parseParenthesisedNodeList n = pParens (parseNodeList n)

 parseIdentifier :: Parser Node
 parseIdentifier = Identifier <$> pSome pLetter <* pSpaces

 parseNumber :: Parser Node
 parseNumber = NumberLiteral <$> pNatural

 parseTuple :: Parser Node
 parseTuple =
     Tuple <$> parseParenthesisedNodeList 1
     <|> Tuple [] <$ pSymbol "()"

 instance Show Node where
     show n =
         let showNodeList ns = intercalate ", " (map show ns)
             showParenthesisedNodeList ns = "(" ++ showNodeList ns ++ ")"
         in case n of
              Identifier i -> i
              Tuple ns -> showParenthesisedNodeList ns
              NumberLiteral n -> show n
              FunctionCall f args -> show f ++ showParenthesisedNodeList args
              MemberAccess f g -> show f ++ "." ++ show g
              BinaryOperation op l r -> "(" ++ show l ++ op ++ show r ++ ")"

最佳答案

简要查看the list-like combinators对于uu-parsinglib(我更熟悉parsec),我认为你可以通过折叠pSome组合器的结果来解决这个问题:

 parseFunctionCall :: Parser Node
 parseFunctionCall =
     foldl' FunctionCall <$>
         parseIdentifier {- `parseExpr' would be correct but left-recursive -}
         <*> pSome (parseParenthesisedNodeList 0)

这也相当于替代方案 some组合器,它确实应该适用于您提到的其他解析库。

关于使用解析器组合器解析具有函数应用的表达式语法(左递归),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26198078/

相关文章:

c++ - 如何解析函数及其参数

Haskell列表理解,从数字列表中删除整数

haskell - Haskell 中匿名函数的真值表

haskell - Parsec:消耗所有输入

xml - 如何在 Perl 中使用 XML::LibXML 访问节点

Java XML 解析器执行每个节点两次

parsing - Haskell 和秒差距 : Parsing two separated lists of numbers

haskell - Haskell 中的递归解析器

parsing - 需要获取自动化ffmpeg或ffprobe的音频视频结构

haskell - 中缀运算符自动提升为一元中缀运算符