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"]
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
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