为什么这些Haskell Parsec组合子的顺序很重要?



我想做一个简单的解析器来解析加法表达式。下面是我的代码:

import Text.Parsec.Char
import Text.Parsec.String
import Text.ParserCombinators.Parsec
data Expr = Number Float |
Add Expr Expr |
number :: Parser Expr
number = do
n <- try $ many1 digit
return $ Number $ read n
add :: Parser Expr
add = do
e1 <- number
char '+'
e2 <- number
return $ Add e1 e2
expr :: Parser Expr
expr =  try number <|> try add

p :: String -> Either ParseError Expr
p = parse (do{e <- expr; eof; return e}) "error"

但是这里是输出

ghci> parse add "err" "1+2"
Right (Add (Number 1.0) (Number 2.0))
ghci> p "1"
Right (Number 1.0)
ghci> p "1+2"
Left "error" (line 1, column 2):
unexpected '+'
expecting digit or end of input

但是如果我改变expr组合子的顺序为

expr :: Parser Expr
expr =  try add <|> try number

然后输出变为

ghci> p "1+2"
Right (Add (Number 1.0) (Number 2.0))

为什么会发生这种情况?我认为try关键字强制我正在组合的解析器在每个<|>之后重新启动。

我打算把它做得更大,所以我想确定我理解为什么现在会发生这种情况。

我的实际程序已经更大了,但这仍然独立地引起问题。

您面临的问题是,当字符串"1+2"number解析时,它接替(不可否认,有一些未解析的字符)。try的使用只有在失败时才有意义。

也许另一种显示这一点的方法是考虑示例try (string "a") <|> try (string "ab")。这将成功匹配任何以字符a开头的字符串,但它永远不会匹配以字符"ab"开头的字符串。

如果你试过了

exprAll :: Parser Expr
exprAll =  try (number <* eof) <|> try (add <* eof)

那么你可能会得到你想要的行为。在这种情况下,"try"d解析器在到达文件结束字符之前不会成功,所以当遇到+时,number <* eof的解析尝试失败,然后使用add <* eof重新开始解析。

最新更新