Top > Haskell_Parsec

Parsec

http://book.realworldhaskell.org/read/の16. The Parsec parsing libraryを読んで、ひたすら動作確認して慣れる。

module ParsecSample() where
import Text.ParserCombinators.Parsec
parseSample input = parse myParse "unknown" input

というのを用意して、myParseのParserをいろいろ作り変えて動作させてみる。

  • 一文字(char)
myParse = char 'a'
*ParsecSample> parseSample "aaaa"
Loading package parsec-2.1.0.1 ... linking ... done.
Right 'a'
  • たくさん(many)
myParse = many $ char 'a'
*ParsecSample> parseSample "aaaa"
Right "aaaa"
*ParsecSample> parseSample "aaaab"
Right "aaaa"
  • many + char
myParse = do
  many $ char 'a'
  char 'b'
*ParsecSample> parseSample "aaaab"
Right 'b'
  • 戻り値の操作
myParse = do
  ret <- many $ char 'a'
  char 'b'
  return ret
*ParsecSample> parseSample "aaaab"
Right "aaaa"
  • 以外(noneOf)

.または,の手前までの文字

myParse = do
  ret <- many ( noneOf ".,")
  oneOf ".,"
  return ret
*ParsecSample> parseSample "xyz."
Right "xyz"
*ParsecSample> parseSample "xy,z."
Right "xy"
  • endBy

通常の動作では、Parserは前から一致した分だけを返し残りの部分は気にしないが、endByを使うと第二引数の終了条件にマッチした場合に第一引数の結果を返すというのがきれいに書ける。

myParse = do
  endBy (many ( oneOf ['a'..'z'])) (string ":end")
*ParsecSample> parseSample "abc:end"
Right ["abc"]

*ParsecSample> parseSample "foobar:end"
Right ["foobar"]

*ParsecSample> parseSample "foobar:ends"
Left "unknown" (line 1, column 12):
unexpected end of input
expecting ":end"
  • sepBy

区切り文字による繰り返しを解析する。

myParse = do
  sepBy (many (oneOf ['a'..'z'])) (char ',')
*ParsecSample> parseSample "hoge,fuga"
Right ["hoge","fuga"]

*ParsecSample> parseSample "hoge,fuga,"
Right ["hoge","fuga",""]

*ParsecSample> parseSample "hoge , fuga,"
Right ["hoge"]
  • 文字列(string)
myParse = do
  string "hoge" <|> string "fuga"
*ParsecSample> parseSample "fuga"
Right "fuga"
*ParsecSample> parseSample "xhoge"
Left "unknown" (line 1, column 1):
unexpected "x"
expecting "hoge" or "fuga"
  • または(<|>)
  • try

どちらか一致した方を返す。orみたいなもの。

myParse = do
  string "hoge" <|> string "hoga"
*ParsecSample> parseSample "hoga"
Left "unknown" (line 1, column 1):
unexpected "a"
expecting "hoge"

だたし、一番目に失敗した場合、読み戻しをしないので、そのままでは期待した動作にならない。 下記のように、tryでくくることで、バックトラックするようにする。

myParse = do
  try(string "hoge") <|> try(string "hoga")
*ParsecSample> parseSample "hoga"
Right "hoga"
  • 失敗メッセージ(<?>)

マッチングの最後に<?>を用意することで、解析失敗時のメッセージを指定できる。

myParse = do
  try(string "hoge")
         <|> try(string "hoga")
         <?> "hoge or hoga"
*ParsecSample> parseSample "xxx"
Left "unknown" (line 1, column 1):
unexpected "x"
expecting hoge or hoga
  • あるかも(optionMaybe)
  • 結果を捨てて次のParser(>>)
myParse = do
  key <- many (oneOf ['a'..'z'])
  val <- optionMaybe (char '=' >> many (oneOf ['a'..'z']))
  return (key,val)
*ParsecSample> parseSample "xxx"
Right ("xxx",Nothing)
*ParsecSample> parseSample "key=val"
Right ("key",Just "val")
*ParsecSample> parseSample "key="
Right ("key",Just "")
  • Applicativeの利用

ここから下は、real world haskellにある下記の定義を利用。
ApplicativeとAlternativeのインスタンスにすることでParserが書きやすく、読みやすくなる。

module ParsecSample(
                    module Control.Applicative
                   , module Text.ParserCombinators.Parsec
                   ) where
import Control.Applicative
import Control.Monad (MonadPlus(..), ap)
import Text.ParserCombinators.Parsec hiding (many, optional, (<|>))
instance Applicative (GenParser s a) where
    pure  = return
    (<*>) = ap
instance Alternative (GenParser s a) where
    empty = mzero
    (<|>) = mplus
  • 左の結果を捨てる(*>)
myParse = do
  string "HT" *> (string "TP" <|> string "XX")
*ParsecSample> parseSample "HTTP"
Right "TP"
*ParsecSample> parseSample "HTXX"
Right "XX"
  • 右の結果を捨てる(<*) 上とは逆で、後ろに続くものを捨てている。
myParse = do
  string "HT" <* (string "TP" <|> string "XX")
*ParsecSample> parseSample "HTTP"
Right "HT"
  • 両方の結果を返す(<*>)
myParse = do
  (++) <$> string "HT" <*> (string "TP" <|> string "XX")
*ParsecSample> parseSample "HTTP"
Right "HTTP"
*ParsecSample> parseSample "HTXX"
Right "HTXX"

<$&>はfmapと同じ。

  • の間(between)
myParse = do
  between (char '<') (char '>') (many $ oneOf ['a'..'z'])
*ParsecSample> parseSample "<abc>"
Right "abc"
  • 上記の前後のスペースを無視するバージョン
myParse = do
  between (spaces *> char '<' <* spaces) (spaces *> char '>' <* spaces) (many $ oneOf ['a'..'z'])
*ParsecSample> parseSample "  < abc >"
Right "abc"

*ParsecSample> parseSample "  < abc >  a"
Right "abc"
  • between,sepByの例

全体に`sepBy`が掛かっているので、<と>に囲まれた要素の、カンマ区切りの繰り返し。

myParse = do
  between (spaces *> char '<' <* spaces) (spaces *> char '>' <* spaces) (many $ oneOf ['a'..'z']) `sepBy` (spaces *> char ',' <* spaces)
*ParsecSample> parseSample "  < abc,def >"
Left "unknown" (line 1, column 8):
unexpected ","
expecting white space or ">"

*ParsecSample> parseSample "  < abc >,  < abc >"
Right ["abc","abc"]

*ParsecSample> parseSample "  < abc >,  < def >   ,     < ghi >"
Right ["abc","def","ghi"]
  • choice

最初にマッチしたもの、という代替案の連続を配列で表すことができる。

myParse = do
  choice [char 'a',char 'b',char 'c']
*ParsecSample> parseSample "a"
Right 'a'

*ParsecSample> parseSample "c"
Right 'c'

*ParsecSample> parseSample "e"
Left "unknown" (line 1, column 1):
unexpected "e"
expecting "a", "b" or "c"
  • getInput,setInput

読み出して元に戻す。 notFollowedByというのもあるらしい。

  • おまけ
Prelude> :module +Control.Monad
Prelude Control.Monad> liftM2 (,) "a" "b"
[('a','b')]
Prelude Control.Monad> liftM2 (++) ["a"] ["b"]
["ab"]
myParse = do
  liftA2 (,) (many1 (oneOf ['a'..'z'])) (optionMaybe (char '=' *> many (oneOf ['a'..'z'])))

リロード   新規 編集 凍結解除 差分 添付 複製 名前変更   ホーム 一覧 単語検索 最終更新 バックアップ リンク元   ヘルプ   最終更新のRSS
Last-modified: 2009-05-24 (日) 21:42:48 (2958d)