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

Parsing 使用解析器组合器解析具有函数应用程序的表达式语法(左递归),parsing,haskell,parsec,recursive-descent,uu-parsinglib,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(

作为一个真实语言解析器的简化子问题,我试图为一种虚构语言的表达式实现一个解析器,该语言看起来类似于标准命令式语言(如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) ) )
我目前正在使用非常好的应用程序解析器组合器库

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

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

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

 {-# 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 ++ ")"
{-#语言灵活上下文}
{-#语言等级}
模块TestExprgramar
(
)在哪里
导入数据。可折叠(asum)
导入数据列表(插入)
导入Text.ParserCombinators.UU
导入Text.ParserCombinators.UU.Utils
导入Text.ParserCombinators.UU.BasicInstances
数据节点=
数字整数
|标识符字符串
|元组[节点]
|成员访问节点
|FunctionCall节点[节点]
|二进制操作字符串节点
parseFunctionCall::解析器节点
解析函数调用=
函数调用
parseIdentifier{-`parseExpr'将是正确的,但左递归-}
ParseBranchisedNodeList 0
运算符:[(字符,节点->节点->节点)]]
运算符=[[(“+”,二进制操作“+”)]
,[(“*”,二进制操作“*”)]
,[('.',成员访问权]
]
samePrio::[(字符,节点->节点->节点)]->解析器(节点->节点->节点)
samePrio ops=asum[op pListSep(pSymbol“,”)parsexpr
n->(:)
parseExpr
解析器[节点]
ParseBranchisedNodeList n=pPareRens(parseNodeList n)
解析器标识符::解析器节点
parseIdentifier=标识符pSome PLETER SHOW括号ISEDNODELIST ns
数字侧n->显示n
函数调用f args->show f++showCorrecardisedNodeList args
MemberAccess f g->show f++“+++show g
二进制操作op l r->“(“++show l++op++show r++”)

我不知道这个库,但可以告诉你如何删除左递归。标准的右递归表达式语法是

E -> T E'
E' -> + TE'  |  eps
T -> F T'
T' -> * FT'  |  eps
F -> NUMBER | ID | ( E ) 
若要添加函数应用程序,您必须确定其优先级。在我见过的大多数语言中,它是最高的。因此,您需要为函数应用程序添加另一层产品

E -> T E'
E' -> + TE'  |  eps
T -> AT'
T' -> * A T' |  eps
A -> F A'
A' -> ( E ) A' | eps
F -> NUMBER | ID | ( E ) 
是的,这是一个毛茸茸的语法,比左递归语法大。这是自上而下预测性语法分析的代价。如果你想要更简单的语法,请使用自下而上的语法生成器a la yacc。

简单查看一下
uu parsinglib
(我更熟悉
parsec
),我认为您可以通过折叠
pSome
组合器的结果来解决这个问题:

 parseFunctionCall :: Parser Node
 parseFunctionCall =
     foldl' FunctionCall <$>
         parseIdentifier {- `parseExpr' would be correct but left-recursive -}
         <*> pSome (parseParenthesisedNodeList 0)
parseFunctionCall::Parser节点
解析函数调用=
foldl函数调用
parseIdentifier{-`parseExpr'将是正确的,但左递归-}
pSome(ParseInsertisedNodeList 0)

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

谢谢@Gene!在我的问题中我实际上没有提到这一点,但我试图避开左因子分解,因为它不是很优雅,需要大量的工作,并且使构建AST变得更加困难。因此我非常高兴很高兴使用
pChainl
combinator解决了操作员的问题(但不是函数应用程序)。但可能实际上没有一个简单而优雅的解决方案,我不得不将整个语法因素留待考虑:-\。我也可以切换到类似yacc的
happy
解析器生成器。哦,是的,的确,这非常优雅、简单,工作很好!