Parsec --- 2.5 Sequences and separators (2)

many/many1はパースしたものを返しますが,パースしたものを返さないskipMany/skipMany1というパーサコンビネータも用意されています.

skipMany :: GenParser tok st a -> GenParser tok st ()
skipMany1 :: GenParser tok st a -> GenParser tok st ()
|haskell|<
という型です.パーサを引数として何も返さないパーサを返します.
>||
*Main> parseTest (skipMany1 letter >> many digit) "Haskell98"
"98"

*Main>  parseTest (skipMany letter >> many digit) "98"
"98"

*Main>  parseTest (skipMany1 letter >> many digit) "98"
parse error at (line 1, column 1):
unexpected "9"
expecting letter

letterをskipMany/skipMany1でとばした後,digitを0回以上パースして,それを返すわけですが,最初はHaskellをスキップして``98''を,つぎは,0回以上のletterをスキップなのでなくてもよくて``98''を返します.最後はskipMany1なので,letterがなくてエラーになります.

さらに,特定のもので区切られたものをパースするためにsepBy/SepBy1,区切られているばかりではなく,それで終わっているものをパースするためにsepEndBy/SepEndBy1というパーサコンビネータも用意されています.

sepBy :: GenParser tok st a -> GenParser tok st sep -> GenParser tok st [a]
sepBy1 :: GenParser tok st a -> GenParser tok st sep -> GenParser tok st [a]
sepEndBy :: GenParser tok st a -> GenParser tok st sep -> GenParser tok st [a]
sepEndBy1 :: GenParser tok st a -> GenParser tok st sep -> GenParser tok st [a]

「もの」をパースするパーサを第一引数に,「区切り」をパースするパーサを第二引数として,パースされた「もの」から構成されるリストを返すパーサを作ります.1がつついてるものは「ものが一個以上」,ついてないものは「ものが0個以上」を表します.

*Main> parseTest (sepBy (many1 letter) (char ',') ) "AB,CD"
["AB","CD"]

*Main> parseTest (sepBy1 (many1 letter) (char ',') ) "AB,CD"
["AB","CD"]

この例では「letterが一回以上」という「もの」が`,'で区切られているものを想定しています.返されるのは「letterが一回以上」の部分からなるリストです.

*Main>  parseTest (sepBy (many1 letter) (char ',') ) ""
[]
*Main>  parseTest (sepBy1 (many1 letter) (char ',') ) ""
parse error at (line 1, column 1):
unexpected end of input
expecting letter

空文字列に対しては,sepByでは0回以上なので,空リストが返されますが,sepBy1ではパースできないのでエラーになります.

sepEndBy/sepEndBy1はほとんどsepBy/sepBy1と同様ですが,「区切り」で終わる文字列に対する動作が異なります.sepEndBy/sepEndBy1は区切りで終わる文字列でもOKです.

*Main> parseTest (sepBy1 (many1 letter) (char ',') ) "ABCD,AAA,"
parse error at (line 1, column 10):
unexpected end of input
expecting letter
*Main> parseTest (sepEndBy1 (many1 letter) (char ',') ) "ABCD,AAA,"
["ABCD","AAA"]

最初の例は,sepBy1なので,`,'で終わっているにも関わらず,そのあとにmany1 letterを期待していますが,もう文字列の続きがないので,many1 letterがエラーを出しています(もし,many letterであったならばエラーはでず,["ABCD","AAA",""]が返されます).これをsepEndBy1に変えれば,``,''で終わっているのが認識されて,上のような結果になります(もし,many letterであったならば,終端とは認識されず,["ABCD","AAA",""]が返されます).したがって,つぎのようなことも起こります.

*Main> parseTest (sepEndBy (many1 letter) (char ',') ) "ABCD,AAA,98"
["ABCD","AAA"]

この場合,98の前の`,'でこのパーサがパースできる部分は終わっていて,98はそのまま残されていることになります.これから推測されるのは,sepEndBy p sepはsepByで動作していて,pが失敗したときには直前のsepを終端とみなしているということです.実際,

*Main> parseTest (sepEndBy (many1 letter) (char ',') >> many digit) "ABCD,AAA,98"
"98"

という動作が観察されます.