
Here's what I eventually came up with. I decided to make a function parseArg that can parse either "v" args or "c" args, and use sepBy parseArg space to parse all the args on the command line. I created the algebraic type Arg to express both kinds of args, which I call Verts and Chans, so that parseArg can have the signature parseArg :: Parser Arg and parsing the whole command line: parseArgs :: Parser [Arg] Then I grab the first Verts and first Chans from the resulting list, and ignore the other ones (if any others are present). I use fromMaybe to supply a default value. data Command = Forward Int | Backward Int | Jump Int | Play Int [Int] | Quit deriving (Show) data Arg = Verts Int | Chans [Int] deriving (Show) integer :: Parser Int integer = do ds <- many1 digit return (read ds) digitList :: Parser [Int] digitList = do d <- digit remainder <- digitList return $ read [d] : remainder <|> return [] -- Parse an argument for the p command. -- Note that so-called "v" args are now just bare integers. -- "c" args are still prefaced with a "c" parseArg :: Parser Arg parseArg = do fmap Verts integer <|> do char 'c' fmap Chans digitList parseArgs :: Parser [Arg] parseArgs = sepBy parseArg space parseP :: Parser Command parseP = (do many space -- Zero or more arguments may be present. -- We will consider at most one "v" argument -- and at most one "c" argument. There are -- default values for the "V" and "C" arguments -- when none are present. args <- parseArgs let vArgs = [i | Verts i <- args] cArgs = [c | Chans c <- args] singleV = fromMaybe 1 (listToMaybe vArgs) singleC = fromMaybe [] (listToMaybe cArgs) return $ Play singleV singleC) <|> (return $ Play 1 []) parseCommand :: Parser Command parseCommand = do char 'j' fmap Jump integer <|> do char 'f' fmap Forward (option 1 integer) <|> do char 'b' fmap Backward (option 1 integer) <|> do char 'p' parseP <|> do char 'q' return Quit runParse :: String -> Either String Command runParse s = case parse parseCommand "" s of Left err -> Left $ show err Right x -> Right x