[英]Parsing expressions inside arithmetic expressions
我想解析算术表达式。
这是我当前的解析器:
data AExpr
= ExprAsAExpr Expr
| IntConst Integer
| Neg AExpr
| ABinary ABinOp AExpr AExpr
deriving (Show, Eq)
aExpr :: Parser AExpr
aExpr = makeExprParser aTerm aOperators
aTerm :: Parser AExpr
aTerm
= parens aExpr
<|> IntConst <$> integerParser
aOperators :: [[Operator Parser AExpr]]
aOperators =
[ [Prefix (Neg <$ symbol "-") ]
, [ InfixL (ABinary Multiply <$ symbol "*")
, InfixL (ABinary Divide <$ symbol "/") ]
, [ InfixL (ABinary Add <$ symbol "+")
, InfixL (ABinary Subtract <$ symbol "-") ]
]
使用这个我可以正确地解析这个:
1 + 2
生成这样的AST。
(ABinary Add (IntConst 1) (IntConst 2))
我可以解析的另一件事是通用表达式。 这些可以是变量,方法调用,三元等。
例如
身份标识:
varName
这将产生:
(Identifier (Name "varName"))
方法调用:
methodCall()
这将产生:
(MethodCall (Name "methodCall") (BlockExpr []))
这是解析通用表达式的示例。
expressionParser :: Parser Expr
expressionParser
= methodCallParser
<|> identifierParser
这工作正常,但我也想在此解析算术表达式。
expressionParser :: Parser Expr
expressionParser
= newClassInstanceParser
<|> methodCallParser
<|> AExprAsExpr <$> aExpr
<|> identifierParser
这意味着使用expressionParser
我现在可以解析所有不同的表达式,包括算术表达式。 如果碰巧是算术表达式,则将其包装在AExprAsExpr
。
问题
我想解析包含其他表达式的算术表达式。
例如
x + y
为此,我最初的想法是更改算术解析器,以便它也解析表达式。
data AExpr
= ExprAsAExpr Expr
| IntConst Integer
| Neg AExpr
| ABinary ABinOp AExpr AExpr
deriving (Show, Eq)
aExpr :: Parser AExpr
aExpr = makeExprParser aTerm aOperators
aTerm :: Parser AExpr
aTerm
= parens aExpr
<|> IntConst <$> integerParser
<|> ExprAsAExpr <$> expressionParser
aOperators :: [[Operator Parser AExpr]]
aOperators =
[ [Prefix (Neg <$ symbol "-") ]
, [ InfixL (ABinary Multiply <$ symbol "*")
, InfixL (ABinary Divide <$ symbol "/") ]
, [ InfixL (ABinary Add <$ symbol "+")
, InfixL (ABinary Subtract <$ symbol "-") ]
]
这个问题是当aTerm
调用表达式解析器,表达式解析器调用aExpr
存在递归循环。 这导致无限循环。 还有一个问题是,所有identifiers
现在都将被包装在AExprAsExpr
。
在算术表达式内部解析表达式的正确方法是什么?
编辑我现在才意识到您正在使用makeExpressionParser
而我的回答并不真正适用makeExpressionParser
。 无论如何,这个答案还是有帮助的?
Parsec是递归下降解析器的一种,这意味着您无法处理左递归。 您需要将其排除在外,如果语法与上下文无关,则可以始终这样做。 您看到此分解完成的一种方法是为每个优先级生成一个乘积。 这是简单算术的示例语法:
expr ::= addExpr
addExpr ::= mulExpr '+' addExpr
| mulExpr '-' addExpr
| mulExpr
mulExpr ::= term '*' mulExpr
| term '/' mulExpr
| term
term ::= '(' expr ')'
| number
注意模式:每个生产中的第一个符号将调出下一个更具体的符号。 然后,显式括号使我们可以重新输入顶级产品。 通常,这是在递归下降中表示运算符优先级的方式。
上面的语法只能产生右对齐的表达式。 尽管它将完全正确地接受字符串,但是当解释为左关联时,它不会正确解析。 尤其是,
1 - 2 - 3 - 4
将被解析为
1 - (2 - (3 - 4))
根据我们的惯例,这是不正确的。 在一般的递归下降解析器中,您必须做一些技巧才能在此处正确关联。 但是,在Parsec中,我们有many
组合器,可以利用many
组合器来发挥自己的优势。 例如,要解析左相关减法,我们可以说
subExpr = foldl1 (-) <$> many1 mulExpr
这里的下一个级别显然是chainl
组合器,它似乎是专门为此目的而设计的(尽管我现在才了解它-猜想我应该更仔细地阅读文档)。 使用此示例
addExpr = chainl1 mulExpr oper
where
oper = choice [ (+) <$ symbol '+'
, (-) <$ symbol '-'
]
我喜欢在Haskell中编写解析器。 祝好运!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.