2017-04-20 2 views
0

Ich bin wirklich neu in Haskell zu analysieren, aber es macht meistens Sinn.Parsec: Überlappende Parser behandeln

Ich arbeite am Aufbau eines Templating-Programms, um das Parsen besser zu lernen; Vorlagen können Werte in {{ value }} Notation interpolieren.

Hier ist meine aktuellen Parser,

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 

Dann habe ich es auf eine Datei in etwa so aus:

{{ value }} 

This is normal Text 

{{ expression }} 

wenn ich laufe diese templateFromFile "./template.txt" mit erhalte ich die Fehlermeldung:

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

Warum passiert das und wie kann ich es beheben?

Mein grundlegendes Verständnis ist, dass many1 (anyChar <* notFollowedBy directive) alle Zeichen bis zum Beginn der nächsten Direktive greifen sollte, dann fehlschlagen und die Liste der Zeichen bis zu diesem Punkt zurückgeben sollte; dann sollte es zurückfallen auf die vorherige many und sollte versuchen, dir erneut zu analysieren und sollte erfolgreich sein; Es passiert jedoch offensichtlich etwas anderes. Ich bin Probleme haben herauszufinden, wie man Dinge zu analysieren zwischen andere Dinge, wenn die Parser meist überlappen.

Ich hätte gerne ein paar Tipps, wie man das alles idiomatisch strukturieren kann, bitte lassen Sie mich wissen, wenn ich etwas auf eine alberne Art mache. Prost! Vielen Dank für Ihre Zeit!

Antwort

2

Sie haben ein paar Probleme. Wenn in Parsc ein Parser irgendeine Eingabe verbraucht und dann fehlschlägt, ist das ein Fehler. Also, wenn der Parser:

anyChar <* notFollowedBy directive 

fehlschlägt (weil der Charakter durch eine Richtlinie gefolgt ist), scheitert es nachanyChar Eingang verbraucht hat, und das erzeugt sofort einen Fehler. Daher wird der Parser:

let p1 = many1 (anyChar <* notFollowedBy directive) 

nie erfolgreich sein, wenn es in eine Direktive auftritt.Zum Beispiel:

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

Sie können dieses Problem beheben, indem Sie eine try Klausel eingefügt:

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

die Right "okay" und zeigt das zweite Problem ergibt. Parser p2 verbraucht nur Zeichen, denen keine Direktive folgt, so dass das Leerzeichen unmittelbar vor der Direktive ausgeschlossen wird und Sie in Ihrem Parser kein Zeichen verwenden können, das gefolgt von einer Direktive enthält, so dass es blockiert.

Sie wollen eigentlich so etwas wie:

let p3 = many1 (notFollowedBy directive *> anyChar) 

die ersten Prüfungen, die an der aktuellen Position, wir sind nicht auf eine Richtlinie suchen, bevor ein Zeichen greifen. Es wird keine try-Klausel benötigt, denn wenn dies fehlschlägt, schlägt sie fehl, ohne Eingabe zu verbrauchen. (notFollowedBy verbraucht nie Eingang, gemäß der Dokumentation.)

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

So, Ihr ursprüngliches am Beispiel mit:

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

sollte gut funktionieren.

0
many1 (anyChar <* notFollowedBy directive) 

Dies analysiert nur Zeichen, denen keine Direktive folgt.

{{ value }} 

This is normal Text 

{{ expression }} 

Wenn Sie den Text in der Mitte Parsen, wird es in der letzten t, stoppen Sie die Newline vor der Richtlinie unverbrauchten verlassen (weil es ist, nun ja, ein durch eine Richtlinie gefolgt Zeichen), so dass die nächste Iteration Sie Versuchen Sie, eine Direktive zu parsen, und Sie scheitern. Dann versuchen Sie txt auf diesem Newline, der Parser erwartet, dass nicht von einer Direktive gefolgt wird, aber es findet einen, daher der Fehler.

+0

Sinn ergibt; Wie kann ich es am besten reparieren, um das zu tun, was ich erwarte? –

+0

'txt = many1 (notFollowedBy directive *> anyChar)' ist die einfachste Lösung, obwohl es 'directive' zweimal pro Direktive ausführt. –

Verwandte Themen