Parsec 入門


Parsec 繰り返し

many

パターンの繰り返しを作るコンビネータには many がある。例えば letter (英字1文字)のパーサに many コンビネータを適用すると英単語にマッチする。
Prelude Text.Parsec> parseTest (many letter) "hello, world"
"hello"

したがって、many letter は複数の英字にマッチしてそのリスト(文字列)を返すパーサになる。

繰り返すのが英字ではなく文字列の場合、戻り値は文字列のリストになる。

Prelude Text.Parsec> parseTest (many $ string "foo") "foofoofoobar"
["foo","foo","foo"]

many は引数のパーサのパターンの繰り返しが0の場合でもマッチする。その場合は空のリストが戻り値になる。

Prelude Text.Parsec> parseTest (many $ string "foo") "bar buz"
[]

many1

many1 はパターンの繰り返しが1以上であれば many と同じ動作をするが、パターンが一つもないときはミスマッチエラーになる。

Prelude Text.Parsec> parseTest (many1 $ string "foo") "foofoobar"
["foo","foo"]

Prelude Text.Parsec> parseTest (many1 $ string "foo") "bar buz" parse error at (line 1, column 1): unexpected "b" expecting "foo"

skipMany

skipMany はパターンの繰り返しにマッチしてそれを読み捨てる。パターンの繰り返しが0回の場合空のリストを返す。

Prelude Text.Parsec> parseTest (skipMany $ char ' ') " hello, world"
()

これでは何のことをしているのかわからないので、先頭部分の空白を読み捨てたあと単語を拾い上げるようにしてみる。

Prelude Text.Parsec> parseTest (do skipMany $ char ' '; many letter) 
" hello, world"
"hello"

こうすれば上のパーサが行の先頭の空白を読み捨てて、最初の単語を拾ってくるのが分かる。

skipMany1

skipMany1 は skipMany と同じ動作をするが0回のパターンにはマッチしない。実行例は省略する。

many と skipMany コンビネータは Text.Parsec.Prim (Prim = primitive) モジュールで定義されている。しかし、many1 と skipMany1 は Text.Parsec.Combinator モジュールで定義されている。many と skipMany は基本的なコンビネータだからだろうか。

Prelude Text.Parsec> :i many
many :: ParsecT s u m a -> ParsecT s u m [a]
-- Defined in ‘Text.Parsec.Prim’
Prelude Text.Parsec> :i many1
many1 :: Stream s m t => ParsecT s u m a -> ParsecT s u m [a]
-- Defined in ‘Text.Parsec.Combinator’

Text.Parsec.Char の基本的なパーサと連結、選択、many、skipMany を組み合わせれば簡単なパーサなら記述可能だ。たとえば先頭が空白で演算子との間にも空白のある Int 型の数の足し算は次のようなパーサで実現できる。

Prelude Text.Parsec> parseTest (do spaces;x <- (many digit); spaces;
 char '+'; 
spaces; y <- (many digit); return ((read x :: Int) + (read y :: Int))) 
" 19 + 34 "
53

パーサコンビネータには many 以外にも複雑なパターンを作れるものが、Text.Parsec.Combinator モジュールに数多く定義されている。それは追々必要になったときに調べることにして、次回はパターンの再帰について調べてみる。

Text.Parsec.Combinatorの概要

Prelude Text.Parsec> :browse Text.Parsec.Combinator
anyToken :: (Stream s m t, Show t) => ParsecT s u m t
between :: Stream s m t => ParsecT s u m open -> ParsecT s u m close ->
 ParsecT s u m a -> ParsecT s u m a
chainl :: Stream s m t => ParsecT s u m a -> ParsecT s u m (a -> a -> a)
 -> a -> ParsecT s u m a
chainl1 :: Stream s m t => ParsecT s u m a -> ParsecT s u m (a -> a -> a)
 -> ParsecT s u m a
chainr :: Stream s m t => ParsecT s u m a -> ParsecT s u m (a -> a -> a)
 -> a -> ParsecT s u m a
chainr1 :: Stream s m t => ParsecT s u m a -> ParsecT s u m (a -> a -> a)
 -> ParsecT s u m a
choice :: Stream s m t => [ParsecT s u m a] -> ParsecT s u m a
count :: Stream s m t => Int -> ParsecT s u m a -> ParsecT s u m [a]
endBy :: Stream s m t => ParsecT s u m a -> ParsecT s u m sep ->
 ParsecT s u m [a]
endBy1 :: Stream s m t => ParsecT s u m a -> ParsecT s u m sep ->
 ParsecT s u m [a]
eof :: (Stream s m t, Show t) => ParsecT s u m ()
many1 :: Stream s m t => ParsecT s u m a -> ParsecT s u m [a]
manyTill :: Stream s m t => ParsecT s u m a -> ParsecT s u m end ->
 ParsecT s u m [a]
notFollowedBy ::
(Stream s m t, Show a) => ParsecT s u m a -> ParsecT s u m ()
option :: Stream s m t => a -> ParsecT s u m a -> ParsecT s u m a
optionMaybe :: Stream s m t => ParsecT s u m a -> ParsecT s u m (Maybe a)
optional :: Stream s m t => ParsecT s u m a -> ParsecT s u m ()
sepBy :: Stream s m t => ParsecT s u m a -> ParsecT s u m sep ->
 ParsecT s u m [a]
sepBy1 :: Stream s m t => ParsecT s u m a -> ParsecT s u m sep ->
 ParsecT s u m [a]
sepEndBy :: Stream s m t => ParsecT s u m a -> ParsecT s u m sep ->
 ParsecT s u m [a]
sepEndBy1 :: Stream s m t => ParsecT s u m a -> ParsecT s u m sep ->
 ParsecT s u m [a]
skipMany1 :: Stream s m t => ParsecT s u m a -> ParsecT s u m ()
lookAhead :: Stream s m t => ParsecT s u m a -> ParsecT s u m a