Je suis vraiment nouveau dans l'analyse syntaxique dans Haskell, mais c'est surtout logique.

Je travaille sur la construction d'un programme de création de modèles principalement pour apprendre à mieux analyser; les modèles peuvent interpoler des valeurs via la notation {{ value }}.

Voici mon analyseur actuel,

data Template a = Template [Either String a]
data Directive = Directive String

templateFromFile :: FilePath -> IO (Either ParseError (Template Directive))
templateFromFile = parseFromFile templateParser

templateParser :: Parser (Template Directive)
templateParser = do
  tmp <- template
  eof
  return tmp

template :: Parser (Template Directive)
template = Template <$> many (dir <|> txt)
    where
      dir = Right <$> directive
      txt = Left <$> many1 (anyChar <* notFollowedBy directive)

directive :: Parser Directive
directive = do
  _ <- string "{{"
  txt <- manyTill anyChar (string "}}")
  return $ Directive txt

Ensuite, je l'exécute sur un fichier comme celui-ci:

{{ value }}

This is normal Text

{{ expression }}

Lorsque j'exécute ceci en utilisant templateFromFile "./template.txt", j'obtiens l'erreur:

Left "./template.txt" (line 5, column 17):
unexpected Directive " expression "

Pourquoi cela se produit-il et comment puis-je le corriger?

Ma compréhension de base est que many1 (anyChar <* notFollowedBy directive) devrait saisir tous les caractères jusqu'au début de la prochaine directive, puis devrait échouer et renvoyer la liste des caractères jusqu'à ce point; puis il devrait revenir au many précédent et devrait essayer à nouveau d'analyser dir et devrait réussir; il se passe clairement quelque chose d'autre. je suis avoir du mal à comprendre comment analyser des éléments entre autres lorsque les analyseurs se chevauchent pour la plupart.

J'adorerais quelques conseils sur la façon de structurer tout cela de manière plus idiomatique, s'il vous plaît laissez-moi savoir si je fais quelque chose de manière idiote. À votre santé! Merci pour votre temps!

1
Chris Penner 20 avril 2017 à 23:09

3 réponses

Meilleure réponse

Vous avez quelques problèmes. Premièrement, dans Parsec, si un analyseur utilise une entrée puis échoue, c'est une erreur. Ainsi, lorsque l'analyseur:

anyChar <* notFollowedBy directive

Échoue (car le caractère est suivi d'une directive), il échoue après anyChar a consommé l'entrée, et cela génère une erreur immédiatement. Par conséquent, l'analyseur:

let p1 = many1 (anyChar <* notFollowedBy directive)

Ne réussira jamais s'il se heurte à une directive. Par exemple:

parse p1 "" "okay"   -- works
parse p1 "" "oops {{}}"  -- will fail after consuming "oops "

Vous pouvez résoudre ce problème en insérant une clause try:

let p2 = many1 (try (anyChar <* notFollowedBy directive))
parse p2 "" "okay {{}}"

Ce qui donne Right "okay" et révèle le deuxième problème. L'analyseur p2 ne consomme que des caractères qui ne sont pas suivis d'une directive, ce qui exclut l'espace immédiatement avant la directive, et vous n'avez aucun moyen dans votre analyseur de consommer un caractère qui est suivi par une directive, donc il reste bloqué.

Vous voulez en fait quelque chose comme:

let p3 = many1 (notFollowedBy directive *> anyChar)

Qui vérifie d'abord qu'à la position actuelle, on ne regarde pas une directive avant de saisir un caractère. Aucune clause try n'est nécessaire car si cela échoue, elle échoue sans consommer d'entrée. (notFollowedBy ne consomme jamais d'entrée, selon la documentation.)

parse p3 "" "okay" -- returns Right "okay"
parse p3 "" "okay {{}}" -- return Right "okay "
parse p3 "" "{{fails}}"  -- correctly fails w/o consuming input

Donc, en prenant votre exemple original avec:

txt = Left <$> many1 (notFollowedBy directive *> anyChar)

Devrait bien fonctionner.

3
K. A. Buhr 20 avril 2017 à 21:15

replace-megaparsec est une bibliothèque pour effectuer des recherches et des remplacements avec des analyseurs. le la fonction de recherche et de remplacement est streamEdit, qui peut trouver vos modèles {{ value }} puis les remplacer dans un autre texte.

streamEdit est construit à partir d'une version généralisée de votre fonction template appelée sepCap.

import Replace.Megaparsec
import Text.Megaparsec
import Text.Megaparsec.Char
import Data.Char

input = unlines
    [ "{{ value }}"
    , ""
    , "This is normal Text"
    , ""
    , "{{ expression }}"
    ]

directive :: Parsec Void String String
directive = do
    _ <- string "{{"
    txt <- manyTill anySingle (string "}}")
    return  txt

editor k = fmap toUpper k

streamEdit directive editor input
 VALUE 

This is normal Text

 EXPRESSION 
1
James Brock 31 août 2019 à 08:06
many1 (anyChar <* notFollowedBy directive)

Cela analyse uniquement les caractères non suivis d'une directive.

{{ value }}

This is normal Text

{{ expression }}

Lors de l'analyse du texte au milieu, il s'arrêtera au dernier t, laissant le retour à la ligne avant la directive non consommé (car c'est, bien, un caractère suivi d'une directive), donc la prochaine itération, vous essayez d'analyser une directive et vous échouez. Ensuite, vous réessayez txt sur cette nouvelle ligne, l'analyseur s'attend à ce qu'elle ne soit pas suivie d'une directive, mais il en trouve une, d'où l'erreur.

0
Li-yao Xia 20 avril 2017 à 20:57