人気ブログランキング |

Text.Parsec.Prim: <|> 演算子

Text.Parsec.Prim で <|> 演算子の定義は次のようになっている。

(<|>) :: (ParsecT s u m a) -> (ParsecT s u m a) -> (ParsecT s u m a)
p1 <|> p2 = mplus p1 p2

右辺の mplus は MonadPlus クラスの関数である。Parsec の mplus の定義は次のようになっている。

instance MonadPlus (ParsecT s u m) where
....mzero = parserZero
....mplus p1 p2 = parserPlus p1 p2

-- | @parserZero@ always fails without consuming any input. @parserZero@ is defined
-- equal to the 'mzero' member of the 'MonadPlus' class and to the 'Control.Applicative.empty' member
-- of the 'Control.Applicative.Alternative' class.

parserZero :: ParsecT s u m a
parserZero
....= ParsecT $ \s _ _ _ eerr ->
......eerr $ unknownError s

parserPlus :: ParsecT s u m a -> ParsecT s u m a -> ParsecT s u m a
{-# INLINE parserPlus #-}
parserPlus m n
....= ParsecT $ \s cok cerr eok eerr ->
......let
..........meerr err =
..............let
..................neok y s' err' = eok y s' (mergeError err err')
..................neerr err' = eerr $ mergeError err err'
..............in unParser n s cok cerr neok neerr
......in unParser m s cok cerr eok meerr

次の MonadPlus クラスのインスタンス宣言を見ると mplus 演算子の本体が parserZero と parserPlus であることが分かる。

instance MonadPlus (ParsecT s u m) where
....mzero = parserZero
....mplus p1 p2 = parserPlus p1 p2

parserZero の定義は次のようになっている。eerr で unkownError を返すだけの関数だ。

parserZero :: ParsecT s u m a
parserZero
....= ParsecT $ \s _ _ _ eerr ->
......eerr $ unknownError s

parserPlus の定義は次のようになる。

parserPlus :: ParsecT s u m a -> ParsecT s u m a -> ParsecT s u m a
{-# INLINE parserPlus #-}
parserPlus m n
....= ParsecT $ \s cok cerr eok eerr ->
......let
..........meerr err =
..............let
..................neok y s' err' = eok y s' (mergeError err err')
..................neerr err' = eerr $ mergeError err err'
..............in unParser n s cok cerr neok neerr
......in unParser m s cok cerr eok meerr

本体は in .. のところだから、m パーサ(の関数に)s, cok, cerr, eok, merr を渡す。runParsecT からもらうのは s, cok, cerr, eok, eerr だから eerr 継続のところだけが meerr に変わっている。つまり、消費が起きなくてエラーになったとき継続 meerr にerr が渡される。

unParser m s cok cerr eok meerr

merr の in .. の部分は次のようになっている。

unParser n s cok cerr neok neerr

unParser n だから、m がエラーになった場合 merr にのみ次のパーサ n のパターンマッチが行われる。パーサ n でのパターンマッチが成功するが消費が起きない場合 neok 継続が実行される。neok は次のようになっている。runParsecT の eok との違いはパーサ2つ分のエラーメッセージが作成されるところだ。

neok y s' err' = eok y s' (mergeError err err')

パーサ n のパターンマッチも失敗した場合、neerr 継続が実行される。neerr 継続は次のようになっている。

neerr err' = eerr $ mergeError err err'

これも eerr 継続にパーサ m とパーサ n のエラーメッセージをマージしたものを渡すだけだ。

まとめると m <|> n の動作はパーサ m のパターンマッチで消費の発生しない (Empty) エラーが起きた場合のみ次のパーサ n のパターンマッチが行われる。しかし、注意が必要なのはパーサ m のパターンマッチが Empty エラーのときのみ次のパーサ n のパターンマッチが実行されるということだ。"foo" と "baz" のいずれかにマッチさせようと思って次のようにすると成功する。

Prelude Text.Parsec> parseTest (string "foo" <|> string "bar") "bar"
"bar"

しかし、"foo" と "faz" にマッチさせようとするプログラムはエラーになる。

Prelude Text.Parsec> parseTest (string "foo" <|> string "faz") "faz"
parse error at (line 1, column 1):
unexpected "a"
expecting "foo"

これは string "foo" と "faz" のマッチングで cerr 継続が発生してしまうためだ。このようなときは try コンビネータを使って次のようにするとうまくいく。

Prelude Text.Parsec> parseTest (try (string "foo") <|> string "faz") "faz"
"faz"

これも eerr にだけ反応するという <|> の性質を知っていれば戸惑わない。

string "foo" の "faz" に対するパターンマッチが Consumed エラーを返すかどうかは確かめにくいが、次のように parse 関数を利用して parser state を取り出すと確かめることができる。

Prelude Text.Parsec> Right s = parse getParserState "SourceName" "faz"
Prelude Text.Parsec> res = runParsecT (string "foo") s
Prelude Text.Parsec> :t res
res :: Monad m => m (Consumed (m (Reply [Char] () String)))
Prelude Text.Parsec> do {Consumed x <- res; return x}
Prelude Text.Parsec> do {Empty x <- res; return x}
*** Exception: user error (Pattern match failure in do expression at <interactive>:13:5-11)

by tnomura9 | 2019-08-13 15:40 | Haskell
<< Text.Parsec.Pri... Text.Parsec.Pri... >>