Parsec makeTokenParser を使う。
Parsecで実際にパーサを作るときは、Text.Parsec.Token モジュールの makeTokenParser を使ってトークンパーサを自動作成したほうがいい。プログラム言語のトークンは共通しているものが多いし、makeTokenParser を使って作成したトークンは空白処理など行き届いた動作をするからだ。Prelude> import Text.Parsec.Token Prelude Text.Parsec.Token> :t makeTokenParser makeTokenParser :: Text.Parsec.Prim.Stream s m Char => GenLanguageDef s u m -> GenTokenParser s u m
makeTokenParser
makeTokenParser は GenLanguage データ型の値を引数にとり、GenTokenParser 型のデータを返す。GenLanguage 型も GenTokenParser 型も複数のフィールドをもつ単純なレコードだ。したがって、フィールド名のアクセサで fieldname data とすればフィールドの値を取り出すことができる。
GenLanguageDef
GenLanguageDef 型と GenTokenParser 型のフィールド名はそれぞれ次のようになる。GenLnaguageDef レコードのフィールドには、GenTokenParser を設定するためのパラメータを記述する。それを元に makeTokenParser 関数で GenTokenParser 型のデータを得るのが目的だ。
data GenLanguageDef s u m = LanguageDef { commentStart :: String, commentEnd :: String, commentLine :: String, nestedComments :: Bool, identStart :: ParsecT s u m Char, identLetter :: ParsecT s u m Char, opStart :: ParsecT s u m Char, opLetter :: ParsecT s u m Char, reservedNames :: [String], reservedOpNames:: [String], caseSensitive :: Bool }data GenTokenParser s u m = TokenParser { identifier :: ParsecT s u m String, reserved :: String -> ParsecT s u m (), operator :: ParsecT s u m String, reservedOp :: String -> ParsecT s u m (), charLiteral :: ParsecT s u m Char, stringLiteral :: ParsecT s u m String, natural :: ParsecT s u m Integer, integer :: ParsecT s u m Integer, float :: ParsecT s u m Double, naturalOrFloat :: ParsecT s u m (Either Integer Double), decimal :: ParsecT s u m Integer, hexadecimal :: ParsecT s u m Integer, octal :: ParsecT s u m Integer, symbol :: String -> ParsecT s u m String, lexeme :: forall a. ParsecT s u m a -> ParsecT s u m a, whiteSpace :: ParsecT s u m (), parens :: forall a. ParsecT s u m a -> ParsecT s u m a, braces :: forall a. ParsecT s u m a -> ParsecT s u m a, angles :: forall a. ParsecT s u m a -> ParsecT s u m a, brackets :: forall a. ParsecT s u m a -> ParsecT s u m a, squares :: forall a. ParsecT s u m a -> ParsecT s u m a, semi :: ParsecT s u m String, comma :: ParsecT s u m String, colon :: ParsecT s u m String, dot :: ParsecT s u m String, semiSep :: forall a . ParsecT s u m a -> ParsecT s u m [a], semiSep1 :: forall a . ParsecT s u m a -> ParsecT s u m [a], commaSep :: forall a . ParsecT s u m a -> ParsecT s u m [a], commaSep1 :: forall a . ParsecT s u m a -> ParsecT s u m [a] }
GenTokenParser 型のデータのフィールドには、それぞれ、フィールド名に対応するパーサが格納されている。例えば integer フィールドには整数をパースするパーサ、parens フィールドには括弧の間の部分を取り出すパーサといった具合だ。
したがって、自分のプログラム言語用のトークンパーサを作りたかったら、まず GenLanguageDef レコードを作成し、makeTokenParser でトークンの詰まった GenTokenParser 型のデータを作成する。個々のパーサは、それぞれのパーサのフィルド名で取り出して使うという手順になる。
GenTokenDef
ところで、GenTokenDef レコードは0から作成する必要はない。Text.Parsec.Language モジュールにはデフォールトの GenLanguageDef 型のレコードがいくつか準備されておりそれをカスタマイズするだけでトークンパーサを作ることができるような環境になっている。
emptyDef
デフォールトの GenLanguageDef 設定レコードの中で、最もシンプルなものは emptyDef だ。これを使ってどんなパーサが作れるかを実験してみる。
Text.Parsec.Token は qualified インポートで別名をつける。GenTokenParser のフィールド名と同じパーサを作りたいからだ。その意味は次の実行例を見れば分かる。
Prelude> import Text.Parsec Prelude Text.Parsec> import qualified Text.Parsec.Token as P Prelude Text.Parsec P> import Text.Parsec.Language (emptyDef) Prelude Text.Parsec P Text.Parsec.Language> lexer = P.makeTokenParser emptyDef
lexer
この lexer の中に色々なパーサが含まれている。どんなパーサが含まれているかは、上のフィールド名のリストをみれば分かる。
それでは整数をパースする integer パーサを作ってみよう。
Prelude Text.Parsec P Text.Parsec.Language> integer = P.integer lexer Prelude Text.Parsec P Text.Parsec.Language> parseTest integer "123" 123
文字列 "123" が integer でパースされて整数の 123 が返されるのが分かる。それでは '+' というシンボルをパースするパーサを作ってみよう。
Prelude Text.Parsec P Text.Parsec.Language> plus = (P.symbol lexer) "+" Prelude Text.Parsec P Text.Parsec.Language> parseTest plus "+" "+"
それでは整数と整数の足し算をするパーサ add を定義して実行してみる。
Prelude Text.Parsec P Text.Parsec.Language> add = (+) <$> integer <*> (plus >> integer) Prelude Text.Parsec P Text.Parsec.Language> parseTest add "123 + 456" 579
注目すべきは integer と plus のパーサがちゃんとトークン間の空白の処理をしてくれていることである。
このように、自分のプログラム用の言語処理のパーサを作るときは makeTokenParser 関数を積極的に利用して省力化したほうがいい。makeTokenParser を使いこなせるようになって初めて Parsec 使いと言えるのではないだろうか。