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 使いと言えるのではないだろうか。