2016-05-08 4 views
1

J'apprends à utiliser Parsec en analysant les lignes dans un fichier texte. Ce que j'est la suivante:Comment généraliser l'analyse répétitive avec 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) 

Je voudrais pouvoir généraliser ces fonctions d'analyse syntaxique car je pense qu'ils sont répétitifs. Je ne sais pas si c'est possible ou comment c'est possible.

Je voudrais aussi savoir s'il est possible d'avoir une fonction comme ceci:

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

Ainsi, lorsque parseAction reçoit une chaîne comme paramètre, il va essayer de l'analyser avec les différents parseurs. Je m'attends à ce qu'il revienne (Left ParseError) si aucun parseur n'a pu analyser l'entrée et (Action à droite) si un analyseur a réussi à analyser l'entrée.

Est-ce possible?

Répondre

3

En utilisant combinators Applicative vous pouvez écrire:

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

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

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

Pour votre deuxième question, il suffit d'utiliser <|> avec try

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

Remarque - try ne devrait pas être nécessaire sur le dernier analyseur, mais ça ne fait pas de mal de l'avoir. Aussi, si vous en savez assez sur le fonctionnement de vos parseurs, vous pourrez peut-être faire disparaître certains des try s.

+0

Cela le simplifie quelque peu mais je dois encore écrire n fois la partie '<*> num'. Y a-t-il un moyen de répéter automatiquement cela? – OneEyeQuestion

+1

@OneEyeQuestion Voyez si vous pouvez utiliser 'replicateM'. – MathematicalOrchid

+0

@MathematicalOrchid Comment utiliseriez-vous 'replicateM' puisqu'il renvoie une liste? Ou est-ce que je me trompe? – OneEyeQuestion