如何用Parsec推广重复解析

时间:2016-05-08 15:17:03

标签: haskell parsec

我正在学习通过解析文本文件中的行来使用Parsec。我所拥有的是以下内容:

import Text.Parsec (ParseError, parse)
import Text.Parsec.String (Parser)
import Text.Parsec.Char (anyChar, digit, char, oneOf)
import Control.Monad (void)
import qualified Text.Parsec.Combinator as C

data Action =
  ActionA Int Int
  | ActionB Int Int Int Int Int
  | ActionC Int Int Int
  deriving (Show)

parseWithEof :: Parser a -> String -> Either ParseError a
parseWithEof p = parse (p <* C.eof) ""

parseActionA :: Parser Action
parseActionA = do
  char 'A'
  void $ oneOf " "
  a <- C.many1 digit
  void $ oneOf " "
  b <- C.many1 digit
  return $ ActionA (read a) (read b)

parseActionB :: Parser Action
parseActionB = do
  char 'B'
  void $ oneOf " "
  a <- C.many1 digit
  void $ oneOf " "
  b <- C.many1 digit
  void $ oneOf " "
  c <- C.many1 digit
  void $ oneOf " "
  d <- C.many1 digit
  void $ oneOf " "
  e <- C.many1 digit
  return $ ActionB (read a) (read b) (read c) (read d) (read e)

parseActionC :: Parser Action
parseActionC = do
  char 'C'
  void $ oneOf " "
  a <- C.many1 digit
  void $ oneOf " "
  b <- C.many1 digit
  void $ oneOf " "
  c <- C.many1 digit
  return $ ActionC (read a) (read b) (read c)

我希望能够概括这些解析函数,因为我觉得它们是重复的。我不知道这是否可能,或者它是如何可能的。

我还想知道是否可以使用这样的函数:

parseAction :: String -> Either ParseError Action
parseAction input = 
    parseWithEof parseActionA input
    <some operator|combinator> parseWithEof parseActionB input
    <some operator|combinator> parseWithEof parseActionC input

因此当parseAction收到一个字符串作为参数时,它会尝试用不同的解析器解析它。如果解析器没有解析输入,我希望它返回(Left ParseError),如果解析器成功解析输入,则返回(Right Action)。

有可能吗?

1 个答案:

答案 0 :(得分:3)

使用Applicative组合器,你可以写:

num = do oneOf " "; fmap read (C.many1 digit)

parseActionA = ActionA <$> (char 'A' >> num) <*> num

parseActionB = ActionB <$> (char 'B' >> num) <*> num <*> num <*> num <*> num

关于第二个问题,只需将<|>try

一起使用即可
parseAction = try parseActionA <|> try parseActionB <|> try parseActionC

注意 - 在最后一个解析器上不需要try,但拥有它并不会有什么坏处。此外,如果您对解析器的工作方式有足够的了解,则可以取消部分try