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

Parsec 連接と選択

個々のパーサは単独では余り利用価値がない。パーサを組み合わせることで多様なパターンに対応できる。組み合わせかたの「くりかえし」については前回に述べた。この記事では、パターンの連接と選択について調べてみる。

Parsec で使われる要素的なパーサは Parsec モナドの関数だ。IOモナドの時と同じように、モナドの関数を合成してもそれはやはりそのモナドの関数になる。つまり、Parsec モナドの関数であるパーサを合成してもやはり、Parsec モナドの関数つまりパーサになるということだ。

たとえば、(many letter) という関数はアルファベットの文字列のパーサだ。また、(many digit) というのは、数字の列のパーサだ。この2つを使って、文字列の次に数字の列が来るようなパターンにマッチするパーサを組み立てたいときはどうすればいいだろうか。このようなパターンの連接(並び)を作るには、モナドの do 記法を使う。そこで、次のような alnum という新しいパーサを定義してみる。

Prelude> :m Text.ParserCombinators.Parsec
Prelude Text.ParserCombinators.Parsec> let alnum = do many letter; many digit
Prelude Text.ParserCombinators.Parsec> parseTest alnum "hello123 world"
"123"

上の例では確かにマッチしているが、戻値は後半のマッチだけだ。マッチの全体を戻値として得るためには、alnum を次のように定義すればいい。

Prelude Text.ParserCombinators.Parsec> let alnum = do a <- many letter; b <- many digit; return (a ++ b)
Prelude Text.ParserCombinators.Parsec> parseTest alnum "hello123 world"
"hello123"

Parsec のパターンマッチのうれしいところは、Perl では正規表現が言語内言語として使用されているのに対し、あくまでも Haskell で記述できるということだ。上のような細かい調整が、Haskell で記述できるので新しい言語を覚える必要がない。

それでは、(many letter) または (many digit) にマッチするパーサを作ってみよう。

Prelude Text.ParserCombinators.Parsec> let alOrNum = many1 letter <|> many1 digit
Prelude Text.ParserCombinators.Parsec> parseTest alOrNum "123hello"
"123"

パターンの選択の場合はパーサを <|> 演算子で結合するとよいことが分かる。

これで、Parsec を使ってパターンマッチを使うプログラムの作り方に関する記事は終わりだ。

最後に、Parsec によるパターンマッチを使ったサンプルプログラム match_test を紹介する。*.hs ファイルの中から = 記号を見つけて関数の名前をリストアップするプログラムだ。以前に紹介した Match.hs を利用する。コンパイルは ghc --make mathc_test.hs -o match_test で行う。

実行例:
>match_test Match.hs
match expr line =
getMatch expr line =

ソースファイルは次のようになる。

ファイル名: match_test.hs

module Main where

import Text.ParserCombinators.Parsec
import Match
import System.Environment (getArgs)

concatFile :: [FilePath] -> IO [Char]
concatFile [] = return []
concatFile (f:fs) = do
  xs <- readFile f
  rest <- concatFile fs
  return $ xs ++ rest

func_def = manyTill anyChar (char '=')

main = do
  args <- getArgs
  cs <- if null args
    then getContents
    else concatFile args
  putStrLn $ unlines $ filter (match func_def) $ lines cs
by tnomura9 | 2011-11-27 19:27 | Haskell | Comments(0)
<< Parsec いろいろなパターン Parsec パーサコンビネー... >>