Home About Contact
Haskell , Text Processing

Haskell / Parsec メモ

世の中はゴールデンウィーク。 このような心に余裕があるときにしかなかなか 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,"

カンマまできた。スペースにはマッチしない。 ならば spaceoneOf "!" を使えば 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!")

以上です。