2014-06-10 4 views
5

Ich versuche, eine Zeichenfolge durch ",", ", and" und "and" zu trennen, und dann zurückgeben, was dazwischen war. Ein Beispiel dafür, was ich bisher habe, ist wie folgt:Verwenden von SepBy Zeichenfolge in Attoparsec

import Data.Attoparsec.Text 

sepTestParser = nameSep ((takeWhile1 $ inClass "-'a-zA-Z") <* space) 
nameSep p = p `sepBy` (string " and " <|> string ", and" <|> ", ") 

main = do 
    print $ parseOnly sepTestParser "This test and that test, this test particularly." 

würde ich die Ausgabe ["This test", "that test", "this test particularly."] sein mag. Ich habe ein vages Gefühl, dass das, was ich tue, falsch ist, aber ich kann nicht recht herausfinden warum.

+0

Warum nicht 'nameSep. takeWhile1 $ inClass "\ t-'a-zA-Z" '? - In deiner Ausgabe werden Räume offenbar nicht unterschiedlich behandelt. Warum sollten sie nicht in die Zeichenklasse aufgenommen werden? Wenn du 'space' anstelle von expliziten Zeichen wie' "\ t" 'etc magst, kannst du' nameSep 'verwenden. TakeWhile1 $ inClass "-'a-zA-Z" <|> Leerzeichen' – AndrewC

Antwort

4

Hinweis: Diese Antwort wurde geschrieben in literate Haskell. Speichern Sie es als Example.lhs und laden Sie es in GHCi oder ähnlichem.

Die Sache ist, sepBy implementiert als:

sepBy p s = liftA2 (:) p ((s *> sepBy1 p s) <|> pure []) <|> pure [] 

Dies bedeutet, dass der zweite Parser s wird nach der erste Parser gelungen bezeichnet werden. Dies bedeutet auch, dass, wenn Sie Leerzeichen in die Klasse der Zeichen Katalog auf, dass Sie mit

["This test and that test","this test particularly"] 

enden würde, da and jetzt parseable von p ist. Das ist nicht einfach zu beheben: Sie müssten nach vorne schauen, sobald Sie ein Leerzeichen drücken, und prüfen, ob nach einer beliebigen Anzahl von Leerzeichen ein "und" folgt, und wenn dies der Fall ist, hören Sie auf zu analysieren. Nur dann ein Parser geschrieben mit sepBy wird funktionieren.

lässt also einen Parser schreiben, die Worte stattdessen nimmt (der Rest dieser Antwort ist, lesen und schreiben Haskell):

> {-# LANGUAGE OverloadedStrings #-} 
> import Control.Applicative 
> import Data.Attoparsec.Text 
> import qualified Data.Text as T 
> import Control.Monad (mzero) 

> word = takeWhile1 . inClass $ "-'a-zA-Z" 
> 
> wordsP = fmap (T.intercalate " ") $ k `sepBy` many space 
> where k = do 
>   a <- word 
>   if (a == "and") then mzero 
>       else return a 

wordsP nun mehrere Wörter, bis es entweder trifft etwas nimmt, das ist nicht ein Wort oder ein Wort das entspricht "und". Die zurück mzero ein parsing failure zeigen, bei dem ein anderen Parser übernehmen kann:

> andP = many space *> "and" *> many1 space *> pure() 
> 
> limiter = choice [ 
>  "," *> andP, 
>  "," *> many1 space *> pure(), 
>  andP 
> ] 

limiter sind meist die gleichen Parser Sie bereits geschrieben haben, es ist das gleiche wie die Regex /,\s+and|,\s+|\s*and\s+/.

Jetzt können wir tatsächlich sepBy verwenden, da unser erster Parser mit dem zweiten überlappt nicht mehr:

> test = "This test and that test, this test particular, and even that test" 
> 
> main = print $ parseOnly (wordsP `sepBy` limiter) test 

Das Ergebnis ist ["This test","that test","this test particular","even that test"], wie wir wollten. Beachten Sie, dass dieser Parser keine Whitespaces speichert.

Wenn Sie also einen Parser mit sepBy erstellen möchten, stellen Sie sicher, dass beide Parser nicht überlappen.

+0

@atc: Dies ist buchstäblich Haskell. Daher sind die '>' am Anfang der Zeile signifikant. – Zeta

+0

Oh, ich habe etwas verpasst? Wie ist es wichtig? Sah für mich wie ein einfacher Quellcode aus und kopierte/paste-fähig in eine repl- oder Quelldatei. Die> machten das umständlich. Was macht '' 'hier? –

+1

Es kann nicht in eine REPL kopiert/eingefügt werden, da die meisten Funktionen in mehreren Zeilen definiert sind. [Ich habe eine Erklärung verlinkt] (https://wiki.haskell.org/Literate_programming), aber hier ist eine kurze Zusammenfassung: Sie können einfach __all__ der Antwort (einschließlich der Erklärung/Text) in eine '.lhs kopieren und einfügen 'Datei und laden Sie das in GHCi (oder kompilieren Sie es mit GHC). Beachten Sie, dass einige Zeilen nicht kompiliert werden sollen, z. die erste 'sepBy p ...' Erklärung oder die Top-Level-Liste; Sie würden nicht so oder so arbeiten. – Zeta

Verwandte Themen