Parsec解析器公共前缀双分支场景下空格处理异常问题求解
问题根源
Parsec 的 <|> 选择运算符默认不支持消费输入后的自动回溯:只有当前一个分支完全没有消费任何输入就失败时,才会回滚输入指针尝试下一个分支;如果前一个分支已经消费了部分输入后失败,会直接抛出错误,不会进入后续分支。
你遇到的错误原因非常明确:
第一个分支首先执行 spaces 已经把输入开头的所有空格消费掉,输入指针移动到了第一个非空格字符的位置(也就是 " Bs" 中的大写 B)。接下来执行 lookAhead lower 匹配失败,此时第一个分支已经消费过输入,<|> 不会回滚指针,不会进入第二个分支,直接抛出匹配错误。哪怕你在第二个分支也加了 spaces 也没用,因为程序根本不会走到第二个分支的逻辑。
正确实现方案
方案1(推荐):公共前缀提取
前导空格是两个分支的公共匹配逻辑,直接提到选择分支外层统一处理,避免重复逻辑也不需要回溯:
testP :: Parser String testP = do -- 先统一消费所有前导空格 spaces -- 再在同一个输入位置执行两个分支的选择 (do lookAhead lower a1 <- lower a2 <- lower return [a1,a2] ) <|> (do a1 <- upper a2 <- lower return [a1,a2] )
这个方案性能最优,没有额外的回溯开销。
方案2:显式开启回溯
如果你出于某种原因需要把空格逻辑写在每个分支内,可以给第一个分支套上 try 函数,强制分支失败时回滚输入指针:
testP :: Parser String testP = (try $ do spaces lookAhead lower a1 <- lower a2 <- lower return [a1,a2] ) <|> (do spaces a1 <- upper a2 <- lower return [a1,a2] )
注意只有需要回溯的分支才需要加 try,过多使用 try 会降低解析器性能。
内容的提问来源于stack exchange,提问作者xsimplex
相关产品推荐
相关产品推荐

