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!
3 réponses
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.
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
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.
De nouvelles questions
parsing
L'analyse se réfère à la rupture d'un artefact en ses éléments constitutifs et à la capture de la relation entre ces éléments. Cette balise n'est pas destinée aux questions sur la plate-forme d'analyse auto-hébergée (utilisez la balise [parse-platform]) ou les erreurs d'analyse dans un langage de programmation particulier (utilisez plutôt la balise de langage appropriée).