2013-04-16 8 views
6

Ich bin neu in Parsec (und zu Parsern im Allgemeinen), und ich habe einige Probleme mit diesem Parser schrieb ich:Schwierigkeit immer ein Parsec Parser Leerzeichen überspringen richtig

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

Die Idee ist, parsen Listen in diesem Format (ich arbeite an s-Ausdrücke oben):

(firstElement secondElement thirdElement and so on) 

ich diesen Code schrieb, es zu testen:

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 sein wird die Ausgabe erhalte ich:

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 

Wie Sie sehen können, scheitert es, wenn es Leerraum zwischen dem letzten Element der Liste ist, und die Schließung ). Ich verstehe nicht, warum der Leerraum nicht von der spaces verbraucht wird, die ich gerade vor <* char ')' eingegeben habe. Was für ein dummer Fehler habe ich gemacht?

Antwort

12

Das Problem ist, dass die endgültigen Räume, die durch die spaces im Argument zu many,

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

verbraucht werden und dann der Parser erwartet some letter aber findet eine schließende Klammer und damit versagt.

es zu lösen, verbrauchen Räume nur nach Token,

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

, die wie gewünscht funktioniert:

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

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

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

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

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

Testing "()": 
[] 
0

Es ist ein bisschen schwierig ist. Parser sind standardmäßig gierig. Was es in deinem Fall bedeutet? Wenn Sie versuchen, (hello world) zu analysieren, beginnen Sie mit der Analyse (, dann versuchen Sie, einige Leerzeichen und Bezeichner zu vergleichen. Also machen wir es. Es gibt keine Leerzeichen, aber es gibt einen Bezeichner. Wir sind fertig. Wir versuchen es erneut mit der Welt. Jetzt haben wir _) übrig. Sie versuchen Parser (spaces *> some letter). Es macht es gierig: so passen Sie Platz und jetzt erwarten Sie einen Brief, aber Sie erhalten stattdessen ). In diesem Moment scheitert der Parser, aber er hat bereits Platz verbraucht, also bist du verloren. Sie können diesen Parser mithilfe von try combinator do Rückzieher machen: try (many (spaces *> some letter))

3

Das Problem ist, dass, sobald der Parser many (spaces *> some letter) einen Raum sieht sie sich ein anderes Element zum Parsen verpflichtet, da Parsec standardmäßig nur ein Zeichen schaut nach vorn und Rückzieher nicht .

Die Vorschlaghammer Lösung ist try zu verwenden Rückzieher zu ermöglichen, aber Probleme wie diese am besten vermieden werden, indem einfach optional Leerzeichen Parsen nach jedes Token statt, wie in Daniel's answer gesehen.