Inside Parsec


Text.Parsec.Prim: ParsecT

ParsecT

Text.Parsec.Prim にはパーサの本体の ParsecT のデータ宣言がされている。
newtype ParsecT s u m a
   = ParsecT {unParser :: forall b .
     State s u
     -> (a -> State s u -> ParseError -> m b) -- consumed ok
     -> (ParseError -> m b) -- consumed err
     -> (a -> State s u -> ParseError -> m b) -- empty ok
     -> (ParseError -> m b) -- empty err
     -> m b
     }

ParsecT データ型には唯一つの名付きフィールド unParser があり、そこには、5個の引数を持ち、戻り値が m b 型の関数が収められている。この関数はパーサごとに動作が異なり、文字列のパース機能を発揮する。

unParser フィールドの無名関数の第1引数は State s u 型のパーサ状態であり、パースする入力文字やソースの位置、ユーザ状態が納められている。

あとの4つは引数の種類は異なるが、いずれも m b 型を返す関数だ。それぞれ、役割があり、マッチが成功して文字列の消費が起きる場合 (consumed ok, cok) 、マッチが失敗するが文字列の消費が起きる場合 (comsumed err, cerr) 、マッチが成功するが文字列の消費が起こらない場合 (empty ok, eok) 、マッチが失敗して文字列の消費も起こらない場合 (empty err, eerr) に対応している。

関数型の引数

これらの関数型の引数がどのように使われるかは、実際のパーサを作る関数を見れば分かる。たとえば unexpected msg 関数は文字列 msg を引数にとり、ParsecT s u m a 型のパーサ・モナドを返す関数だ。

unexpected msg
   = ParsecT $ \s _ _ _ eerr ->
   eerr $ newErrorMessage (UnExpect msg) (statePos s)
 

このパーサモナドの unParser フィールドには無名関数が納められているが、これは、Sutate s u モナドと、4個の引数をとり、次の関数、

eerr $ newErrorMessage (UnExpect msg) (statePos s)

で m a 型の値を返す関数が納められる。unParser フィールドの関数の引数が謎だったが、unexpected msg では、ParsecT の unParser フィールドの関数の値を計算するのに第5引数の eerr 関数が使われている。

この ParsecT モナドが、Parsec のパーサ・モナドの実体だ。unParser アクセサはパーサの unParser フィールドの無名関数を取り出す働きがある。この関数に State s u 状態を渡すことで、State s u 状態に保持されている入力文字列のパースが行われる。

また、パースの結果は、上に述べたようにパースの結果で consumed ok、consumed err、empty ok、empty err の4つの場合が想定できるが、その個別の処理は第2〜第5の4つの関数型引数がそれぞれ担当する。

この方法は関数の引数で、関数から戻される値のコントロールができるが、継続渡しプログラミングと呼ばれている。

runParsecT

runParsecT 関数はパーサ・モナド ParsecT s u m a と状態 State s u を引数にとりパースを行う。runParsecT 関数の定義は次のようになっている。unParser アクセサで ParsecT s u m a パーサ・モナドのフィールドの無名関数を取り出し利用している。

runParsecT :: Monad m => ParsecT s u m a -> State s u -> m (Consumed
 (m (Reply s u a)))
runParsecT p s = unParser p s cok cerr eok eerr
   where cok a s' err = return . Consumed . return $ Ok a s' err
     cerr err = return . Consumed . return $ Error err
     eok a s' err = return . Empty . return $ Ok a s' err
     eerr err = return . Empty . return $ Error err

ParsecT s u m a モナドの unParser フィールドの無名関数の第2〜第5引数については、runParsecT 関数が実引数の関数を提供する。

runPrasecT で処理できるのは単一のパーサだけだが、パーサがモナドなので、複数のパーサを bind 演算子で結合したものの全体は一個のパーサ・モナドとみなすことができる。つまり連結されたパーサ・モナドを処理する関数も runParsecT 関数で処理することができる。

まとめると、runParsecT 関数は Parsec のパースを行う Parsec の心臓部の関数だ。パーサモナド ParsetT s u m a と状態 State s u を引数にとり、 m (Consume (m (Reply s u a)) 型の値を戻り値として返す。

runParserT

runParserT 関数は Parsec でパースを行うときのユーザ・インターフェースを提供する。runParserT 関数は、runPT 関数の別名だ。runPT 関数は ParsecT s u m a パーサ・モナド、ユーザ状態 u、String 型のソース名 SourceName、入力文字列の s を引数にとり、m (Either ParseError a) 型の値を返す。

runPT 関数は、 runParsecT p s 関数を内部的に利用している。

runParserT :: (Stream s m t)
   => ParsecT s u m a -> u -> SourceName -> s -> m (Either ParseError a)
runParserT = runPT

runPT :: (Stream s m t)
   => ParsecT s u m a -> u -> SourceName -> s -> m (Either ParseError a)
runPT p u name s
   = do res <- runParsecT p (State s (initialPos name) u)
     r <- parserReply res
     case r of
       Ok x _ _ -> return (Right x)
       Error err -> return (Left err)
   where
     parserReply res
       = case res of
         Consumed r -> r
         Empty r -> r

これだけの解析では実際のパーサのマッチの仕組みはまだわからないが、大まかな動作の流れが分かる。

実は、これは継続渡しプログラミングとの初遭遇だったのだが、全体像が分かってからでないと継続渡しプログラムの意味がわからないので、このままにしておく。