2014-06-10 11 views
5

Próbuję oddzielić ciąg znaków od ",", ", and" i "and", a następnie zwracać to, co było pomiędzy. Przykładem tego, co mam tak daleko jest w następujący sposób:Używanie łańcucha sepBy w 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." 

chciałbym wyjście być ["This test", "that test", "this test particularly."]. Mam niejasne poczucie, że to, co robię, jest złe, ale nie jestem w stanie zrozumieć dlaczego.

+0

Dlaczego nie 'nameSep. takeWhile1 $ inClass "\ t-'a-zA-Z" '? - twoje wyniki wyraźnie nie traktują przestrzeni inaczej, dlaczego nie uwzględnić ich w klasie znaków? Jeśli podoba ci się "spacja" zamiast jawnych znaków takich jak "\ t" itp., Możesz użyć 'nameSep. takeWhile1 $ inClass "-'a-zA-Z" <|> space' – AndrewC

Odpowiedz

4

Uwaga: tę odpowiedź zapisano w literate Haskell. Zapisz go jako Example.lhs i załaduj go do GHCi lub podobnego.

Chodzi o to, sepBy jest zaimplementowany jako:

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

Oznacza to, że drugi parser s zostanie wywołana po pierwszy parser udało. Oznacza to również, że jeśli były, aby dodać spacje do klasy znaków, że chcesz skończyć z

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

od and jest teraz parsowalnym przez p. Nie jest to łatwe do naprawienia: będziesz musiał patrzeć w przyszłość, gdy tylko uderzysz w spację, i sprawdzić, czy po dowolnej liczbie spacji pojawia się znak "i", a jeśli tak, przestań parsować. Tylko , a następnie parser napisany z sepBy będzie działać.

Więc pozwala napisać parser który zaczyna rozmowę zamiast (reszta tej odpowiedzi jest literat 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 teraz trwa kilka słów aż do niego albo uderza coś, że nie jest to słowo lub słowo to równa "i". Zwracany mzero wskaże parsing failure, w których inny parser może przejąć:

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

limiter jest w większości takie same parser już napisany, jest taka sama jak regex /,\s+and|,\s+|\s*and\s+/.

Teraz możemy faktycznie korzysta sepBy, ponieważ nasz pierwszy parser nie pokrywają się z drugim już:

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

Rezultatem jest ["This test","that test","this test particular","even that test"], tak jak chcieliśmy. Zauważ, że ten konkretny analizator składni nie zachowuje białych znaków.

Zawsze, gdy chcesz utworzyć analizator składni z sepBy, upewnij się, że oba parsery się nie nakładają.

+0

@atc: To jest dosłowny Haskell. Dlatego '>' na początku linii są znaczące. – Zeta

+0

Och, coś przeoczyłem? Jak to jest ważne? Wygląda mi to jak zwykły kod źródłowy, a tym samym możliwość kopiowania/wklejania do pliku replik lub źródła. The> zrobił to niewygodne. Co robi tutaj '>'? –

+1

Nie można kopiować/wklejać do REPL w żaden sposób, ponieważ większość funkcji jest definiowana na wielu liniach. [Podłączyłem wyjaśnienie] (https://wiki.haskell.org/Literate_programming), ale oto krótkie podsumowanie: po prostu można skopiować i wkleić __all__ odpowiedzi (łącznie z wyjaśnieniem/tekstem) do '.lhs 'plik i ładuj, który w GHCi (lub skompiluj go z GHC). Zauważ, że niektóre wiersze nie mają być kompilowane, np. pierwsze wyjaśnienie 'sepBy p ...' lub lista najwyższego poziomu; nie działałyby tak czy owak. – Zeta

Powiązane problemy