Parsecを使ってパーサを作ってみます.
C言語+Yaccでパーサを作る場合,Yaccの使い方を新しく覚えたり
YaccとCの混ざった変なソースコードを延々と書き続けなければなりませんが
パーサージェネレータParsec+Haskellでは,単なるHaskellの関数として動作するParsecの関数を使うだけで
簡単にパーサを作る事ができます.
正規表現のように,新しく文法を覚える必要はなく,多くのエラーはコンパイル時に見つける事もできます.

インストール

GHCには標準で付属します.

使い方

パーサーコンビネータでは,小さなパーサを組み合わせて,大きなパーサを作っていく事になります.

import Text.ParserCombinators.Parsec

float  :: Parser Float
float   = do a <- number
             char '.'
             b <- number
             return $ fromIntegral a + pnt (fromIntegral b)
    where
      pnt n = if n<1 then n
              else pnt (n/10)
number :: Parser Int
number  = do n<-many1 digit
             return $ read n

ここで,numberは普通の10進数 "123"等をパースするパーサです.
floatは

<数> . <数>

という構成になっている,小数点付きの値をパースするパーサです.
このように,Parsecでは単純なパーサを組み合わせて,複雑なパーサを作る事になります.
BNFからパーサを作る事も簡単にできます.

コンパイル

ghcでコンパイルには,

-package parsec

オプションをつける必要があります.

URL解析

http://www.flightless-wing.com/hoge.html
のようなURLをホストの部分とファイル名の部分に分けたくなる事がたまにあります.
試しに作ってみました.

import Text.ParserCombinators.Parsec

run :: Show a => Parser a -> String -> IO ()
run p input
        = case (parse p "" input) of
            Left err -> do{ putStr "parse error at "
                          ; print err
                          }
            Right x  -> print x

word = do c <-letter
          cs<-many (letter <|> digit)
          return (c:cs)

file = do f<-fileUnit
          f2<-file
          return (f++f2)
       <|> return ""
    where
      fileUnit = word
                 <|> do c<-oneOf "-_./~"
                        return [c]

host = do u<-hostUnit
          h<-host
          return (u++h)
       <|> return ""
    where
      hostUnit = word
                 <|> do c<-oneOf "-_."
                        return [c]

uri = do string "http://"
         h <- host
         f <- file
         return (h,f)

かなり怪しい実装な気がします...

実行結果

ghciでテストしました.

*Main> run uri "http://www.flightless-wing.com/hoge.html"
("www.flightless-wing.com","/hoge.html"

HTTPリクエストの解析

  • Haskell版httpd
    Haskellでウェブサーバを書いてみました.
    リクエストの解釈にParsecを使っています.

参考