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
オプションをつける必要があります.
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"