世の中はゴールデンウィーク。 このような心に余裕があるときにしかなかなか Haskell に取り組めない。 今日は、以前から使ってみたかった Parsec に入門した。 そのメモです。
環境
$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 8.8.4
Hello, World! という文字列をパースすることを考える。
まず letter というパーサーを使ってみる。
ghci で letter の型を見る。
$ ghci
Prelude> import Text.Parsec
Prelude Text.Parsec> :t letter
letter :: Stream s m Char => ParsecT s u m Char
よくわからないが使ってみる。
-- hello.hs
import Text.Parsec
text = "Hello, World!"
result = parse letter "(unknown)" text
main :: IO ()
main = do
putStrLn $ show result
実行します。
$ runghc hello.hs
Right 'H'
letter 任意の1文字をパースするパーサーのようだ。
ならば letter を使って2文字目までパースしてみる。
パーサー部分は Monad になっている(らしい)ので、 >> とか >>= が使える。
--result = parse letter "(unknown)" text
result = parse (letter >> letter) "(unknown)" text
実行する。
$ runghc hello.hs
Right 'e'
2文字目までパースしてその結果 Right 'e' が返った。
1文字目も結果として返してほしい。 ならば、次のようにしてみる。
result = parse (letter >>= \x-> letter >>= \y-> return (x,y)) "(unknown)" text
実行する。
$ runghc hello.hs
Right ('H','e')
できるにはできた。
parse 関数の最初の引数としてパーサーを渡して処理しているが、 パーサーが肥大化していくとコードが読み辛くなる。 ならば、rule としてパーサー部分は別定義とするか。
rule = (letter >>= \x-> letter >>= \y-> return (x,y))
result = parse rule "(unknown)" text
さらに do を使って記述する形に変更。(たしかに読みやすい。)
rule = do
x <- letter
y <- letter
return (x, y)
result = parse rule "(unknown)" text
実行する。
$ runghc hello.hs
Right ('H','e')
意図通り作動している。
次は many を使ってみる。
rule = many letter
実行。
$ runghc hello.hs
Right "Hello"
Hello までパースできた。 letter パーサーは , にマッチしないらしい。
ならば oneOf "," を使う。
rule = many (letter <|> (oneOf ","))
<|> を使うと or が表現できる。ここでは 1文字 or "," が many という意味になる。
実行。
$ runghc hello.hs
Right "Hello,"
カンマまできた。スペースにはマッチしない。 ならば space と oneOf "!" を使えば Hello, World! 全部をパースできるだろう。
rule = many (letter <|> (oneOf ",") <|> space <|> (oneOf "!"))
実行する。
$ runghc hello.hs
Right "Hello, World!"
できた。
Hello, World! 以外の文字列もパースしてみたい。 ならば、 result 関数に代えて parseText を定義してみる。
parseText :: String -> Either ParseError [Char]
parseText input = parse rule "(unknown)" input
型定義を明示的に記述しないとエラーになる。
これで parseText "FooBar" としてパースしたい任意の文字列を記述できるようになった。
完成したコード hello.hs:
-- hello.hs
import Text.Parsec
{--
rule = do
x <- letter
y <- letter
return (x, y)
--}
rule = many (letter <|> (oneOf ",") <|> space <|> (oneOf "!"))
parseText :: String -> Either ParseError [Char]
parseText input = parse rule "(unknown)" input
main :: IO ()
main = do
putStrLn $ show (parseText "Hello, Again!")
以上です。