简体   繁体   中英

How to generalize repetitive parsing with Parsec

I am learning to use Parsec by parsing lines in a text file. What I have is the following:

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)

I would like to be able to generalize these parsing functions since I feel that they are repetitive. I don't know if that is possible, or how it is possible.

I would also like to know if it is possible to have a function like this:

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

So when parseAction receives a string as parameter it will try to parse it with the different parsers. I expect it to return (Left ParseError) if no parser could parse the input and (Right Action) if a parser succeeded in parsing the input.

Is it possible?

Using Applicative combinators you can write:

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

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

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

For your second question, just use <|> with try

parseAction = try parseActionA <|> try parseActionB <|> try parseActionC

Note - try shouldn't be needed on the last parser, but it doesn't hurt to have it. Also, if you know enough about how your parsers work you may be able to do away with some of the try s.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM