Haskell Parsec组合子顺序为何影响加法表达式解析结果?
加法表达式解析器的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关键字会强制每个<|>后的解析器重新开始解析。我计划扩展这个解析器,因此希望现在就能理解其中的原因。我的实际程序规模已更大,但该问题仍独立存在。
原因解析
Parsec的(<|>)组合子遵循优先尝试左侧解析器,仅当左侧解析失败且未消耗任何输入时,才会尝试右侧解析器的核心规则,这是问题的关键:
当
expr为try number <|> try add时:- 解析
"1+2"时,左侧的number会先匹配字符串开头的"1",解析成功并消耗了这部分输入,返回Number 1.0。 - 随后
p中的eof检查会发现还剩"+2"未解析,导致整个流程失败。 - 这里的
try number并未触发回退,因为number本身解析成功了——try仅在解析器自身失败时才会回退已消耗的输入,后续的eof失败并不会让<|>回退去尝试右侧的add。
- 解析
当
expr为try add <|> try number时:- 解析
"1+2"时,左侧的add会完整匹配整个加法表达式:先调用number解析"1",接着匹配"+",再调用number解析"2",全程消耗了所有输入,返回Add (Number 1.0) (Number 2.0)。 - 后续的
eof检查顺利通过,整个解析流程成功。
- 解析
你对try的理解存在偏差:try p的作用是当解析器p本身执行失败时,回退它已经消耗的输入,让后续解析器能从原始位置重新尝试。但如果p解析成功,无论后续步骤是否失败,try都不会触发回退操作。
内容的提问来源于stack exchange,提问作者Tristan
相关产品推荐
相关产品推荐

