2016-12-09 4 views
2

je l'ai écrit comme par exemple l'analyse syntaxique de permutation:permutation parsec analyse syntaxique

data Entry = Entry { 
    first_name :: String 
    , last_name :: String 
    , date_of_birth :: Maybe String 
    , nationality :: Maybe String 
    , parentage :: Maybe String 
} deriving (Show) 

nameParser :: Parser (String, String) 
nameParser = do 
    first_name <- many1 upper 
    endOfLine 
    last_name <- many1 letter 
    endOfLine 
    return $ (first_name, last_name) 

attributeParser :: String -> Parser String 
attributeParser field = do 
    string $ field ++ ": " 
    value <- many1 (noneOf "\n") 
    endOfLine 
    return value 

entryParser :: Parser Entry 
entryParser = do 
    (f, l) <- nameParser 
    (d, n, p) <- permute ((,,) 
    <$?> (Nothing, liftM Just (try $ attributeParser "Date of Birth")) 
    <|?> (Nothing, liftM Just (try $ attributeParser "Nationality")) 
    <|?> (Nothing, liftM Just (try $ attributeParser "Parentage")) 
    ) 
    return $ Entry f l d n p 

main = do 
    mapM_ putStrLn . map (show . parse entryParser "") $ goodTests 

goodTests = 
    "AAKVAAG\nTorvild\nDate of Birth: 1 July\nNationality: Norwegian\nParentage: business executive\n" : 
    "AAKVAAG\nTorvild\nNationality: Norwegian\nParentage: business executive\n" : 
    "AAKVAAG\nTorvild\nParentage: business executive\nNationality: Norwegian\n" : 
    "AAKVAAG\nTorvild\nParentage: business executive\n" : 
    "AAKVAAG\nTorvild\nNationality: Norwegian\n" : [] 

Il serait bon d'étendre Entry données avec de nouveaux champs à l'avenir, mais le faire, il faudra mettre un code encore plus répétitif dans entryParser fonction. Y at-il un moyen de rendre cette fonction accepter la liste des analyseurs?

J'ai commencé avec ceci:

attributeParsers = 
    map attributeParser ["Date of Birth", "Nationality", "Parentage"] 

permuteParams = 
    map (\p -> (Nothing, liftM Just (try p))) attributeParsers 

Mais ne pouvait pas venir de façon correcte avec de plier permuteParams avec l'opérateur <|?> (je suppose qu'il faudrait quelque chose de plus intelligent que (,,) constructeur de tuple alors).

Répondre

2

Dans un premier temps, vous pouvez abstrait les choses que vous faites pour chaque composant:

attr txt = (Nothing, liftM Just (try $ attributeParser txt)) 

Avec cela, vous pouvez aller à:

entryParser :: Parser Entry 
entryParser = do 
    (f, l) <- nameParser 
    (d, n, p) <- permute ((,,) 
    <$?> attr "Date of Birth" 
    <|?> attr "Nationality" 
    <|?> attr "Parentage" 
    ) 
    return $ Entry f l d n p 

Ensuite, si vous voulez, vous pouvez peigne ine les combinateurs infixes et les appels attr:

f .$ x = f <$?> attr x 
f .| x = f <|?> attr x 

infixl 2 .$ 
infixl 2 .| 

Cela vous donne:

entryParser :: Parser Entry 
entryParser = do 
    (f, l) <- nameParser 
    (d, n, p) <- permute ((,,) 
    .$ "Date of Birth" 
    .| "Nationality" 
    .| "Parentage" 
    ) 
    return $ Entry f l d n p 

Ensuite, vous pouvez simplifier encore en se débarrassant de la triple intermédiaire. Tout ce que vous faites est de construire et ensuite appliquer ses composants à Entry f l, afin que vous puissiez aussi bien appliquer le résultat de l'analyseur de permutation à Entry f l directement:

entryParser :: Parser Entry 
entryParser = do 
    (f, l) <- nameParser 
    permute (Entry f l 
    .$ "Date of Birth" 
    .| "Nationality" 
    .| "Parentage" 
    ) 

Je pense que cela est assez compact. Si vous voulez vraiment une sorte de pli, vous devrez soit introduire une liste intermédiaire et collecter les résultats de la permutation dans une liste. Ceci, cependant, fonctionne seulement tant que tous les attributs permutables sont du même type (ils sont actuellement), et n'est pas si gentil parce que vous ferez des suppositions au sujet du nombre d'éléments dans cette liste. Ou vous devrez utiliser une liste hétérogène/un type de magie de type, ce qui conduira à plus de complexité avec les types et, je pense, ne vaut pas la peine ici.

2

(<|?>) ne joue pas bien avec le pliage car le type du StreamPermParser que vous transmettez comme premier argument n'est pas le même que celui du résultat StreamPermParser. Pour un problème plus simple mais analogue, vous rencontreriez des problèmes similaires si vous essayiez d'utiliser (,,) avec (<$>) et (<*>) dans un style applicatif (par exemple (,,) <$> foo <*> bar <*> baz).

Si vous voulez réduire une partie de la répétition, ma suggestion prosaïque serait d'utiliser une définition locale:

entryParser :: Parser Entry 
entryParser = do 
    (f, l) <- nameParser 
    (d, n, p) <- permute ((,,) 
    <$?> optField "Date of Birth" 
    <|?> optField "Nationality" 
    <|?> optField "Parentage" 
    ) 
    return $ Entry f l d n p 
    where 
    optField fieldName = (Nothing, liftM Just (try $ attributeParser fieldName))