人気ブログランキング | 話題のタグを見る

Text.Parsec.Prim: tokens

Text.Parsec.Char モジュールには Parsec の基本的な(主に1文字の)パーサが記述されているが、驚いたことに string パーサ以外は全て satisfy コンビネータを用いて定義されている。例えば letter パーサの定義は次のようになる。

letter :: (Stream s m Char) => ParsecT s u m Char
letter = satisfy isAlpha <?> "letter"

satisfy コンビネータ自体もこのモジュールで定義されており、定義は次のようになっている。これには、Text.Parsec.Prim モジュールの tokenPrim コンビネータを利用している。

satisfy :: (Stream s m Char) => (Char -> Bool) -> ParsecT s u m Char
satisfy f = tokenPrim (\c -> show [c])
................................(\pos c _cs -> updatePosChar pos c)
................................(\c -> if f c then Just c else Nothing)

tokenPrim の引数は全て関数で、第1引数はトークンの表示のための関数、第2引数はソース位置の計算、第3引数はパターンマッチのためのテスト関数である。1文字のパーサを作るための基本関数だが、satisfy を定義した後は1文字のパーサは全て satisfy 関数で定義できる。

唯一の例外は文字列のパターンマッチを行う string パーサで Text.Parsec.Char モジュールの定義では次のようになっている。Text.Parsec.Prim モジュールの tokens 関数を利用している。また show は文字列変換のための関数 updatePosString はパーサがマッチして消費が起きたときのソース位置 source position を計算するための関数である。updataPosString 関数は Text.Parsec.Pos モジュールで定義されている。

string :: (Stream s m Char) => String -> ParsecT s u m String
string s = tokens show updatePosString s

Text.Parsec.Prim モジュールの tokens 関数の定義は次のようになっている。

tokens showTokens nextposs tts@(tok:toks)
....= ParsecT $ \(State input pos u) cok cerr _eok eerr ->
....let
........errEof = (setErrorMessage (Expect (showTokens tts))
..................(newErrorMessage (SysUnExpect "") pos))

........errExpect x = (setErrorMessage (Expect (showTokens tts))
...................... (newErrorMessage (SysUnExpect (showTokens [x])) pos))

........walk [].... rs = ok rs
........walk (t:ts) rs = do
..........sr <- uncons rs
..........case sr of
............Nothing................ -> cerr $ errEof
............Just (x,xs) | t == x....-> walk ts xs
........................| otherwise -> cerr $ errExpect x

........ok rs = let pos' = nextposs pos tts
....................s' = State rs pos' u
................in cok tts s' (newErrorUnknown pos')
....in do
........sr <- uncons input
........case sr of
............Nothing........ -> eerr $ errEof
............Just (x,xs)
................| tok == x..-> walk toks xs
................| otherwise -> eerr $ errExpect x

tokens はパーサを定義するための関数なので ParsecT $ \s cok cerr eok eerr -> funciton body で ParsecT モナドを作る。モナドのフィールドの関数の本体は in 以下で定義されている。let 句のヘルパー関数の定義を省略すると tokens で作成されるパーサは次のようになる。

ParsecT $\s cok cerr eok eerr ->
....in do
........sr <- uncons input
........case sr of
............Nothing........ -> eerr $ errEof
............Just (x,xs)
................| tok == x..-> walk toks xs
................| otherwise -> eerr $ errExpect x

冒頭で input 文字列から1文字を取り出す。つぎに、取り出した文字 sr について case 分岐し、さらに Just (x,xs) 以下のガードの分岐の3分岐している。

case 分岐の Nothing は input 文字列が空の場合でエラー終了 eerr $ errEof する。Just (x, xs) のときは input 文字列から1文字 x と残りの文字列 xs が取り出される。tok は let のヘルパー関数で取り出されたトークンの文字列の先頭の文字だ。

x の値によってガードで分岐するが tok == x のときはヘルパー関数の walk 関数で残りの文字列のマッチを繰り返す。otherwise では異常終了なのでエラー処理をする。

let 句のヘルパー関数についても簡単に眺めてみる。

errEof, errExpect x はエラー情報 ParseError を定義している。

........errEof = (setErrorMessage (Expect (showTokens tts))
..................(newErrorMessage (SysUnExpect "") pos))

........errExpect x = (setErrorMessage (Expect (showTokens tts))
...................... (newErrorMessage (SysUnExpect (showTokens [x])) pos))

walk (t:ts) rs 関数はパーサの文字列 (t:ts) と input 文字列 rs のマッチングを行う関数だ。再帰的に文字列の先頭の文字を比較する作業を繰り返している。

.......walk [].... rs = ok rs
........walk (t:ts) rs = do
..........sr <- uncons rs
..........case sr of
............Nothing................ -> cerr $ errEof
............Just (x,xs) | t == x....-> walk ts xs
........................| otherwise -> cerr $ errExpect x

ok 関数はパターンマッチが成功したときの処理をする関数である。関数内でパーサ位置とパーサ状態を変更している。

........ok rs = let pos' = nextposs pos tts
....................s' = State rs pos' u
................in cok tts s' (newErrorUnknown pos')

コードを読んだ限りでは token 関数は汎用と言うよりは string パーサを生成するための専用関数のように見える。

by tnomura9 | 2019-08-12 09:00 | Haskell | Comments(0)
<< Text.Parsec.Pri... Text.Parsec.Err... >>