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

时间:2015-06-02 10:30:22

标签: parsing haskell parsec

我正在使用Write yourself a scheme学习haskell。

我目前正在尝试在计划中实施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

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

问题在于我认识到,#\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\""

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

完整代码:Gist

1 个答案:

答案 0 :(得分:6)

来自<|>的{​​{3}}:

  

解析器被称为预测,因为只有在解析器p没有消耗任何输入时才会尝试q(即前瞻是1)。

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

  

解析器try p的行为类似于解析器p,除了假装它在发生错误时没有消耗任何输入。

类似下一个:

try parseSpecialCharNotation <|> parseSingleChar

旁注:从解析器中提取"#\\"是否更好,因为否则您将执行两次相同的工作。像下一个:

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

此外,您可以使用documentation组合器而不是一系列char解析器。