2013-04-16 1 views
6

Je suis nouveau à parsec (et parseurs en général), et je vais avoir des problèmes avec cet analyseur je l'ai écrit:Difficulté à obtenir un analyseur parsec pour sauter des espaces correctement

list = char '(' *> many (spaces *> some letter) <* spaces <* char ')' 

L'idée est de analyser les listes dans ce format (je travaille à s-expressions):

(firstElement secondElement thirdElement and so on) 

j'ai écrit ce code pour le tester:

import Control.Applicative 
import Text.ParserCombinators.Parsec hiding (many) 

list = char '(' *> many (spaces *> some letter) <* spaces <* char ')' 

test s = do 
    putStrLn $ "Testing " ++ show s ++ ":" 
    parseTest list s 
    putStrLn "" 

main = do 
    test "()" 
    test "(hello)" 
    test "(hello world)" 
    test "(hello world)" 
    test "(hello world)" 
    test "()" 

T son est la sortie I get:

Testing "()": 
[] 

Testing "(hello)": 
["hello"] 

Testing "(hello world)": 
["hello","world"] 

Testing "(hello world)": 
["hello","world"] 

Testing "(hello world)": 
parse error at (line 1, column 14): 
unexpected ")" 
expecting space or letter 

Testing "()": 
parse error at (line 1, column 3): 
unexpected ")" 
expecting space or letter 

Comme vous pouvez le voir, il échoue quand il y a un espace blanc entre le dernier élément de la liste, et la fermeture ). Je ne comprends pas pourquoi l'espace blanc n'est pas consommé par le spaces que j'ai mis juste avant <* char ')'. Quelle erreur idiote ai-je faite?

Répondre

12

Le problème est que les espaces finaux sont consommés par le spaces dans l'argument de many,

list = char '(' *> many (spaces *> some letter) <* spaces <* char ')' 
        -- ^^^^^^ that one 

et l'analyseur attend some letter mais trouve une parenthèse fermante et échoue donc.

Pour le résoudre, les espaces ne consomment après jetons,

list = char '(' *> spaces *> many (some letter <* spaces) <* char ')' 

qui fonctionne comme vous le souhaitez:

$ runghc lisplists.hs 
Testing "()": 
[] 

Testing "(hello)": 
["hello"] 

Testing "(hello world)": 
["hello","world"] 

Testing "(hello world)": 
["hello","world"] 

Testing "(hello world)": 
["hello","world"] 

Testing "()": 
[] 
0

Il est un peu difficile. Les analyseurs par défaut sont gourmands. Qu'est-ce que cela signifie dans votre cas? Lorsque vous essayez d'analyser (hello world), vous commencez par analyser (, puis vous essayez de faire correspondre des espaces et des identifiants. Donc nous le faisons. Il n'y a pas d'espaces, mais il y a un identifiant. Nous avons fini. Nous essayons à nouveau avec le monde. Nous avons maintenant _) restant. Vous essayez parser (spaces *> some letter). Cela le rend gourmand: donc vous faites correspondre l'espace et maintenant vous attendez une lettre, mais vous obtenez ) à la place. En ce moment l'analyseur échoue, mais il a déjà consommé de l'espace, alors vous êtes condamné. Vous pouvez faire cet analyseur ne en utilisant try retours en arrière Combinator: try (many (spaces *> some letter))

3

Le problème est qu'une fois que l'analyseur many (spaces *> some letter) voit un espace, il s'engage à l'analyse syntaxique un autre élément, car parsec par défaut ne regarde que l'avance d'un caractère et ne pas faire marche arrière .

La solution de marteau de forgeron est d'utiliser try pour permettre retours en arrière, mais des problèmes comme celui-ci sont à éviter en analysant simplement en option des espaces après chaque jeton au lieu, comme on le voit dans Daniel's answer.

Questions connexes