注: この回答はリテラシーな Haskellで書かれています。名前を付けて保存しExample.lhs
、GHCi などにロードします。
問題は、次のようsepBy
に実装されていることです。
sepBy p s = liftA2 (:) p ((s *> sepBy1 p s) <|> pure []) <|> pure []
これは、最初のパーサーが成功した後s
に 2 番目のパーサーが呼び出されることを意味します。これは、文字のクラスに空白を追加すると、最終的には次のようになることも意味します。
["This test and that test","this test particularly"]
sinceand
は で解析できるようになりましたp
。これを修正するのは簡単ではありません: スペースにヒットしたらすぐに先を見て、任意の数のスペースの後に「and」が続くかどうかを確認し、そうであれば解析を停止する必要があります。そうして初めて、で書かれたパーサーが機能しsepBy
ます。
それでは、代わりに単語を取るパーサーを書きましょう (この回答の残りの部分は読みやすい 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
何かにヒットするか、それが単語ではないか、または「and」に等しい単語になるまで、複数の単語を使用するようになりました。返された は、別のパーサーが引き継ぐことができる解析失敗mzero
を示します。
> andP = many space *> "and" *> many1 space *> pure()
>
> limiter = choice [
> "," *> andP,
> "," *> many1 space *> pure (),
> andP
> ]
limiter
ほとんどはあなたがすでに書いたパーサーと同じです。それは regex と同じ/,\s+and|,\s+|\s*and\s+/
です。
sepBy
最初のパーサーが 2 番目のパーサーとオーバーラップしないため、実際に を使用できるようになりました。
> test = "This test and that test, this test particular, and even that test"
>
> main = print $ parseOnly (wordsP `sepBy` limiter) test
結果は["This test","that test","this test particular","even that test"]
、希望どおりです。この特定のパーサーは空白を保持しないことに注意してください。
したがって、 でパーサーを作成する場合は常にsepBy
、両方のパーサーが重複しないようにしてください。