Nota: questa risposta è scritta in literate Haskell. Salvalo come Example.lhs
e caricalo in GHCi o simile.
Il fatto è che, sepBy
è implementato come:
sepBy p s = liftA2 (:) p ((s *> sepBy1 p s) <|> pure []) <|> pure []
Ciò significa che la seconda parser s
sarà chiamato dopo la prima parser è riuscita. Questo significa anche, che se si dovesse aggiungere spazio bianco alla classe di personaggi, che, ne risulterebbe con
["This test and that test","this test particularly"]
dal and
è ora leggibile dalla p
. Questo non è facile da risolvere: è necessario guardare avanti non appena si colpisce uno spazio, e controllare se dopo un numero arbitrario di spazi un "e" segue, e se lo fa, smettere di analizzare. Solo quindi funzionerà un parser scritto con sepBy
.
così lascia scrivere un parser che prende le parole invece (il resto di questa risposta è alfabetizzata 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
ora prende più parole fino a quando non sia colpisce qualcosa, che non è una parola, o una parola quello è uguale a "e". La tornato mzero
indica un parsing failure, in cui un altro parser può assumere:
> andP = many space *> "and" *> many1 space *> pure()
>
> limiter = choice [
> "," *> andP,
> "," *> many1 space *> pure(),
> andP
> ]
limiter
è in gran parte lo stesso parser che hai già scritto, è lo stesso del regex /,\s+and|,\s+|\s*and\s+/
.
Ora possiamo effettivamente utilizzare sepBy
, dal momento che il nostro primo parser non si sovrappone con la seconda più:
> test = "This test and that test, this test particular, and even that test"
>
> main = print $ parseOnly (wordsP `sepBy` limiter) test
Il risultato è ["This test","that test","this test particular","even that test"]
, proprio come volevamo. Nota che questo particolare parser non conserva gli spazi bianchi.
Quindi, ogni volta che si desidera creare un parser con sepBy
, assicurarsi che entrambi i parser non si sovrappongano.
Perché non 'nameSep. takeWhile1 $ inClass "\ t-'a-zA-Z" '? - Il tuo output chiaramente non tratta gli spazi in modo diverso, perché non includerli nella classe di caratteri? Se ti piace 'space' invece di caratteri espliciti come' "\ t" 'ecc, puoi usare' nameSep. takeWhile1 $ inClass "-'a-zA-Z" <|> spazio' – AndrewC