解析器中的 Parsec <|> 选择,错误抛出但不转到下一个解析器

Parsec <|> choice in parser, Error throws but does not go to next parser

我正在学习 haskell Write yourself a scheme

我目前正在尝试在方案中实施 char 识别。一个字符是 #\<character>#\<character-name> 就像 #\a#\#\space.

所以我写了下面的代码:

-- .. some code ..
data LispVal = Atom String
             | List [LispVal]
             | DottedList [LispVal] LispVal
             | String String
             | Number Integer
             | Bool Bool
             | Char Char deriving Show
-- .... More code ...
parseChar :: Parser LispVal
parseChar = liftM Char (parseSingleChar <|> parseSpecialCharNotation)

parseSingleChar :: Parser Char
parseSingleChar = do string "#\"
                     x <- letter
                     return x

parseSpecialCharNotation :: Parser Char
parseSpecialCharNotation = do string "#\"
                              x <- (parseSpace <|> parseNewline)
                              return x

parseSpace :: Parser Char
parseSpace = do char 's'
                char 'p'
                char 'a'
                char 'c'
                char 'e'
                return ' '

parseNewline :: Parser Char
parseNewline = do char 'n'
                  char 'e'
                  char 'w'
                  char 'l'
                  char 'i'
                  char 'n'
                  char 'e'
                  return '\n'

-- .. some more code...

readExpr :: String -> String
readExpr input = case parse parseExpr "lisp" input of
                 Left err -> "Parse Error: " ++ show err
                 Right val -> "Found value: " ++ show val

此刻,我还不知道 Parsec 中的 string 解析器。

问题是我认识到,#\a#\space 被视为 s

*Main> readExpr "#\space"
"Found value: Char 's'"

为了解决这个问题,我把parseChar改成了

parseChar :: Parser LispVal
parseChar = liftM Char (parseSpecialCharNotation <|> parseSingleChar)

但是之前的问题已经解决了,但现在它给我的错误是正常字符,如 -

*Main> readExpr "#\s"
"Parse Error: \"lisp\" (line 1, column 4):\nunexpected end of input\nexpecting \"p\""

为什么会这样?它不应该因为 parseSpecialCharNotation 失败而移至 parseSingleChar 吗?

完整代码位于:Gist

来自 documentation for <|>:

The parser is called predictive since q is only tried when parser p didn't consume any input (i.e.. the look ahead is 1).

在您的情况下,两个解析都在失败之前消耗 "#\",因此无法评估另一个替代方案。您可以使用 try 来确保回溯按预期工作:

The parser try p behaves like parser p, except that it pretends that it hasn't consumed any input when an error occurs.

类似于下一个:

try parseSpecialCharNotation <|> parseSingleChar

旁注:从解析器中提取 "#\" 是否更好,否则您将做同样的工作两次。类似下一个:

do
  string "#\"
  try parseSpecialCharNotation <|> parseSingleChar

此外,您可以使用 string 组合器代替一系列 char 解析器。