2017-05-30 10 views
1

je besoin d'un combinateur comme p1 << p2, mais p2 devrait fonctionner que si p1 a réussi et consommé une entrée.Dans Parsec, comment exécuter un second analyseur, uniquement si le premier analyseur a consommé une entrée?

Si p1 a réussi sans consommer d'entrée, p2 ne doit pas s'exécuter.

Si p1 a échoué, alors p2 est également ignoré?

Résultat global est r1 « s résultat

+0

Parsec vous permet d'accéder à l'emplacement de la source en cours, pour que vous puissiez mesurer avant et après 'p1' et voir si elle est la même – luqui

+0

Sinon, il semble être un refactor de votre gramm gramm Soyez en ordre. Divisez donc 'p1' en' p1Empty' et 'p1'', et utilisez' p1 '<|> (p1Empty << p2) ', si possible. – luqui

Répondre

0

Je ne pense pas que vous pouvez faire pour parseurs arbitraires p1 et p2: vous avez besoin de communiquer en quelque sorte. Si vous pouviez le faire, il me semble que vous briseriez la transparence référentielle.

Par exemple, considérons l'analyse de la chaîne d'entrée repeat 'x': que ce soit un caractère p1 consomme ou non, p2 verra la chaîne comme une mer sans fin de x caractères. S'il n'a pas communiqué avec p1 d'une façon ou d'une autre (par exemple en modifiant quelque chose dans l'état de l'analyseur), alors vous ne pouvez pas savoir si un caractère a été consommé; Si votre combinateur était en quelque sorte capable de traiter ces deux cas différemment, ce serait enfreindre les règles.

+1

Je pensais que les parseurs Parsec, étant des Monades, permettaient une telle "communication" – dmzkrsk

+0

Oui, les parseurs peuvent communiquer entre eux s'ils le souhaitent. Mais votre combinateur ne peut pas atteindre un autre analyseur arbitraire et le forcer à communiquer, et il ne peut pas observer de l'extérieur si l'analyseur a consommé quoi que ce soit. – amalloy

1

Avec une implémentation de l'analyseur naïf, vous devriez être en mesure de le faire:

(<<) p1 p2 = P $ \inp -> case parse p1 inp of 
    ErrorResult e -> ErrorResult e 
    SuccessResult (rem, res) -> if rem == inp 
    then SuccessResult (rem, res) 
    else parse p2 rem 

Bien que parsec est plus avancé, vous pourriez probablement rouler votre propre là-bas aussi.

2

Les primitives de type Parsec font une distinction interne entre un analyseur qui réussit après avoir consommé une entrée et un analyseur qui réussit après avoir consommé aucune entrée que vous devriez pouvoir exploiter. En particulier, les éléments suivants devraient travailler pour analyser p puis - conditionné sur p entrée avec succès la consommation - parse q et jeter ses résultats:

ifConsumed :: Monad m => ParsecT s u m a -> ParsecT s u m b -> ParsecT s u m a 
ifConsumed p q = mkPT k 
    where -- k :: State s u -> m (Consumed (m (Reply s u a))) 
     k s = do cons <- runParsecT p s 
       case cons of 
        Consumed mrep -> do 
        rep <- mrep 
        case rep of 
         Ok x s' err -> runParsecT (fmap (const x) q) s' 
         Error err -> return . Consumed . return $ Error err 
        Empty mrep -> do 
        rep <- mrep 
        case rep of 
         Ok x s' err -> return . Empty . return $ Ok x s' err 
         Error err -> return . Empty . return $ Error err 

Il est laid parce parsec ne pas exposer directement le constructeur , donc vous devez utiliser les intermédiaires mkPt et runParsecT qui ajoutent beaucoup de passe-partout.

En un mot, il exécute l'analyseur syntaxique p. Si cela réussit avec l'entrée consommée (la branche Consumed -> Ok), il exécute l'analyseur q modifié via fmap pour renvoyer la valeur analysée par p. D'autre part, si p réussit sans aucune entrée consommée (la branche Empty -> Ok), il renvoie simplement le succès sans exécuter l'analyseur q. Le seul inconvénient est que je ne suis pas sûr à 100% comment, dans la bibliothèque Parsec elle-même, l'invariant est préservé par lequel la branche Consumed -> Ok n'est appelée que lorsque l'entrée a été consommée, donc je ne sais pas si c'est vraiment fiable. Vous aurez envie de le tester avec soin dans votre cas d'utilisation particulier.

Pour l'analyseur suivant qui analyse une liste d'un ou de plusieurs éléments séparées par des virgules où chaque élément est composé de zéro ou plusieurs chiffres, alors deux points d'exclamation seulement si l'analyseur précédent a consommé une entrée, puis un point-virgule. - il semble fonctionner:

p :: Parser [String] 
p = ifConsumed (sepBy1 (many digit) (char ',')) (char '!' >> char '!') <* char ';' 

runp :: String -> Either ParseError [String] 
runp = parse p "" 

Certains tests:

runp ""   -- fails, expecting semicolon 
runp ";"  -- returns [""] 
runp "!!;"  -- fails, "!!" w/ no preceding content 
runp ",;"  -- fails, missing "!!" 
runp ",!!;"  -- returns ["",""] 
runp ",!;"  -- fails, expecting second "!" 
runp ",1,23;" -- fails, missing "!!" 
runp ",1,23!!;" -- returns ["","1","23"]