[英]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.