Parsecにちょっと触ってみる(8): try はパースに失敗しても入力を消費してないように振る舞う。

「string "(a)" <|> string "(b)"」は"(a)" または "(b)" にマッチすることを期待したパーサ。
"(a)" にはマッチする

> :m Text.ParserCombinators.Parsec
> parseTest (string "(a)" <|> string "(b)") "(a)" -- > "(a)"

"(b)" のマッチには失敗する。

> parseTest (string "(a)" <|> string "(b)") "(b)"
-- >
parse error at (line 1, column 1):
unexpected "b"
expecting "(a)"

"abc" または "xyz" のときは問題ない。

> parseTest (string "abc" <|> string "xyz") "abc" -- > "abc"
> parseTest (string "abc" <|> string "xyz") "xyz" -- > "xyz"
  • 「"abc" と "axy"」のように一部同じ文字を含んでいると
    • "axy" を "abc" に一致させようとして失敗するが、 'a' を消費している。
    • 失敗したので "axy" とマッチさせようとするが残りは "xy" なので失敗する。
> parseTest (string "abc"  <|> string "axy") "axy"
parse error at (line 1, column 1):
unexpected "x"
expecting "abc"

(try p) というパーサを導入すると p が失敗したときには入力を全く消費していないように振る舞うので問題を回避できる。

> parseTest (try (string "abc") <|> string "axyz") "axyz" -- > "axyz"
> parseTest (try (string "(a)") <|> string "(b)") "(b)"   -- > "(b)"

(以下でも可能だけれどもこの場合分けが出来るとは限らない)

testOr1 :: Parser String
testOr1 = char '('                >>= \kakko -> 
          (char 'a' <|> char 'b') >>= \aORb ->
          char ')'                >>= \kokka -> 
          return [kakko,aORb,kokka]

> parseTest testOr1 "(a)" -- > "(a)"
> parseTest testOr1 "(b)" -- > "(b)"

下記のようにするとよりよいそうです。

> parseTest (do{ try (string "(a"); char ')'; return "(a)" } <|> string "(b)") "(a)"  -- > "(a)"
> parseTest (do{ try (string "(a"); char ')'; return "(a)" } <|> string "(b)") "(b)"  -- > "(b)"