秒差距尝试左边的替代方案<|>
在它尝试正确的方法之前。如果左边的选择成功了,那么它就不会再打扰右边的选择了。所以在这种情况下,当输入输入时5*5
,Parsec 的过程如下所示:
- Try
V <$> strParser
. strParser
开始于tok "\""
,但输入字符串不以'"'
so strParser
fails.
- Try
N <$> numParser
. numParser
成功解析数字 5,因此 Parsec 只是返回N 5
.
- 完毕!无需尝试第三种选择。
所以我们可以尝试通过移动来修补这个解析器Mult
选项到顶部,包裹在try
这样它就可以回溯并尝试numParser
or strParser
如果输入结果不是乘法。
arithParser :: Parser Aexp
arithParser = try (Mult <$> arithParser <* tok "*" <*> arithParser)
<|> N <$> numParser
<|> V <$> strParser
这个解析器还有另一个更微妙的问题。让我们逐步完成上述步骤。
- Try
try (Mult <$> arithParser <* tok "*" <*> arithParser)
。这个解析器开始于arithParser
,所以递归调用arithParser
.
- Try
try (Mult <$> arithParser <* tok "*" <*> arithParser)
。这个解析器开始于arithParser
,所以递归调用arithParser
.
- Try
try (Mult <$> arithParser <* tok "*" <*> arithParser)
。这个解析器开始于arithParser
,所以递归调用arithParser
.
- ...
这是一个无限循环。 Parsec 无法处理左递归语法。您必须设计您的解析器,以便它在递归调用之前至少消耗一个标记。一种常见的方法是“扁平化”你的语法:
expr, term :: Parser AExp
expr = do
n <- term
rest <- optional $ tok "*" *> expr
return $ maybe n (Mult n) rest
term = N <$> numParser
<|> V <$> strParser
<|> parenthesised expr
parenthesised = between (char '(') (char ')')
在这里,我将解析器分成一个解析任意的expr
- a term
可选地后跟乘法符号和被乘数expr
- 以及一个解析单个的term
s - 数字、字符串和括号表达式。递归调用expr
现在还好 - 里面的那个expr
仅在您解析之后才会发生term
(总是消耗输入)和里面的term
仅在解析左括号后才会发生。
注意expr
具有类似列表的结构:它解析一个事物,可能后面跟着很多事物。一般来说,您应该考虑解析器消耗输入标记的线性输入流,因此列表形解析器往往比树形解析器更有效也就不足为奇了。
The Control.Monad.Combinators.Expr https://hackage.haskell.org/package/parser-combinators-1.3.0/docs/Control-Monad-Combinators-Expr.html模块包含打包此模式并以任意优先级和固定规则解析表达式的函数。
expr = makeExprParser term [[InfixR $ tok "*" $> Mult]]