2016-08-22 1 views
2

Je travaillais sur "Écris-toi un schéma en 48 heures" pour apprendre Haskell et j'ai rencontré un problème que je ne comprends pas vraiment. C'est pour la question 2 des exercices au bas de this section.Comment exprimer la logique d'analyse syntaxique dans Parsec ParserT monad

La tâche consiste à réécrire

import Text.ParserCombinators.Parsec 
parseString :: Parser LispVal 
parseString = do 
       char '"' 
       x <- many (noneOf "\"") 
       char '"' 
       return $ String x 

tels que des guillemets qui sont correctement saisies (par exemple dans « Cette phrase \ » est un non-sens ») être accepté par l'analyseur.

Dans un langage impératif Je pourrais écrire quelque chose comme ça (à peu près pythonique pseudocode):

def parseString(input): 
    if input[0] != "\"" or input[len(input)-1] != "\"": 
    return error 
    input = input[1:len(input) - 1] # slice off quotation marks 
    output = "" # This is the 'zero' that accumulates over the following loop 
    # If there is a '"' in our string we want to make sure the previous char 
    # was '\' 

    for n in range(len(input)): 
    if input[n] == "\"": 
     try: 
     if input[n - 1] != "\\": 
      return error 
     catch IndexOutOfBoundsError: 
     return error 
    output += input[n] 
    return output 

que je cherchais au docs for Parsec et je ne peux pas comprendre comment fonctionne cette comme une expression monadique.

Je suis arrivé à ceci:

parseString :: Parser LispVal 
parseString = do 
       char '"' 
       regular <- try $ many (noneOf "\"\\") 
       quote <- string "\\\"" 
       char '"' 
       return $ String $ regular ++ quote 

Mais cela ne fonctionne que pour un guillemet et il doit être à la fin de la chaîne - Je ne peux pas penser à une expression fonctionnelle qui fait la travailler que mes boucles et if-déclarations font dans le pseudocode impératif.

Je vous remercie de prendre votre temps pour lire ceci et me donner des conseils.

Répondre

3

Essayez quelque chose comme ceci:

dq :: Char 
dq = '"' 

parseString :: Parser Val 
parseString = do 
    _ <- char dq 
    x <- many ((char '\\' >> escapes) <|> noneOf [dq]) 
    _ <- char dq 
    return $ String x 
    where 
     escapes = dq <$ char dq 
      <|> '\n' <$ char 'n' 
      <|> '\r' <$ char 'r' 
      <|> '\t' <$ char 't' 
      <|> '\\' <$ char '\\' 
+1

Un côté: un 'x >> retour est' a' <$ x'. – user2407038

+0

Oh, merci. Je vais mettre à jour l'exemple! –

+0

Merci! J'ai travaillé à travers les signatures de type et la documentation pour voir pourquoi 'x >> renvoie a' est 'a <$ x'. Y a-t-il une raison pour laquelle cette dernière expression est préférée à la première ici? Et y a-t-il une raison pour laquelle '_ <- char dq' est préféré à' char dq'? (édité) – lachrimae

2

La solution est de définir un littéral de chaîne comme une citation de départ + de nombreux caractères valides + une citation de fin où un "caractère valide" est soit une séquence d'échappement ou non-devis.

Donc, il y a un changement d'une ligne à parseString:

parseString = do char '"' 
       x <- many validChar 
       char '"' 
       return $ String x 

et nous ajoutons les définitions:

validChar = try escapeSequence <|> satisfy (/= '"') 
escapeSequence = do { char '\\'; anyChar } 

escapeSequence peut être affinée pour permettre à un nombre limité de séquences d'échappement.