Inside Parsec


Text.Prasec.Error

ParseError 例外

Text.Parsec.Error モジュールには Parsec でパターンマッチが失敗したときの ParseError 例外の関連のプログラムが記述されている。parseTest でパターンマッチが失敗すると例外が発生し、エラーの場所が表示される。
Prelude> :m + Text.Parsec Text.Parsec.Error Text.Parsec.Pos
Prelude Text.Parsec Text.Parsec.Error Text.Parsec.Pos> :set prompt
 "Parsec> "
Parsec> 
Parsec> parseTest letter "123"
parse error at (line 1, column 1):
unexpected "1"
expecting letter

このとき、これらの情報を保持した ParseError 例外が発生している。

ParseError の取り出し

そこで、ParseError 例外を取り出してみた。これは runParser 関数を利用すると取り出すことができる。runParser 関数の型は次のようになるので、(Left pe) のパターンで ParseError 型のデータを取り出せる。

Parsec> :t runParser
runParser
  :: Stream s Data.Functor.Identity.Identity t =>
     Parsec s u a -> u -> SourceName -> s -> Either ParseError a
Parsec> (Left pe) = runParser letter () "SourceName" "123"
Parsec> :t pe
pe :: ParseError

ParseError は Show クラスのインスタンスなので、上で得られた pe は show 関数で内容を見ることができる。したがって、ghci では pe と入力するだけで表示できる。

Parsec> pe
"SourceName" (line 1, column 1):
unexpected "1"
expecting letter

ParseError の実際のデータ型は :i コマンドで検索できる。

Parsec> :i ParseError
data ParseError = Text.Parsec.Error.ParseError !SourcePos [Message]
  	-- Defined in ‘Text.Parsec.Error’
instance [safe] Eq ParseError -- Defined in ‘Text.Parsec.Error’
instance [safe] Show ParseError -- Defined in ‘Text.Parsec.Error’

Text.Parsec.Error の概要

ここで、Text.Parsec.Error モジュールの概要を :browse コマンドで見てみる。

Parsec> :browse Text.Parsec.Error
data Message
  = SysUnExpect !String
  | UnExpect !String
  | Expect !String
  | Message !String
data ParseError = Text.Parsec.Error.ParseError !SourcePos [Message]
addErrorMessage :: Message -> ParseError -> ParseError
errorIsUnknown :: ParseError -> Bool
errorMessages :: ParseError -> [Message]
errorPos :: ParseError -> SourcePos
mergeError :: ParseError -> ParseError -> ParseError
messageString :: Message -> String
newErrorMessage :: Message -> SourcePos -> ParseError
newErrorUnknown :: SourcePos -> ParseError
setErrorMessage :: Message -> ParseError -> ParseError
setErrorPos :: SourcePos -> ParseError -> ParseError
showErrorMessages ::
  String
  -> String -> String -> String -> String -> [Message] -> String

ParseError 型

これを見ると ParseError のデータ構造が、

ParseError !SourcePos [Message]

なので、ParseError のフィールドにはパースエラーが発生したソースの位置 SourcePos とエラーメッセージのリスト [Message] が入っているのが分かる。

ParseError データコンストラクタはエクスポートされていないので、新しい ParseError を作るときは 、newErrorUnknoun 関数を使う。

Parsec> pe = newErrorUnknown (initialPos "foo")
Parsec> pe
"foo" (line 1, column 1):unknown parse error

Message 型

Message データ型はエラーメッセージであるが、エラーの種類によってデータコンストラクタが異なっている。このデータコンストラクタの違いによって、エラーメッセージを端末に表示するときにエラーの種類によって表現を変えることができる。

エラーメッセージはデータコンストラクタで作成できるが、Show クラスのインスタンスではないので ghci で表示できない。ParseError 関係の関数をテストするときにエラーメッセージが必要になるが、表示されなくても変数には束縛できるし、変数の型チェックをすることで、それがエラーメッセージであることが分かる。

Parsec> msg = Message "foo"
Parsec> msg
<interactive>:5:1: error:
    • No instance for (Show Message) arising from a use of ‘print’
    • In a stmt of an interactive GHCi command: print it
Parsec> :t msg
msg :: Message

addErrorMessage 関数

sddErrorMessage 関数は ParseError にメッセージを追加する。

Parsec> addErrorMessage (Message "bar") pe
"foo" (line 1, column 1):
bar

errorIsUnknown 関数

errorIsUnknown 関数は ParseError が Unknown エラーかどうかをチェックする。

Parsec> errorIsUnknown pe
True

errorMessages 関数

errorMessages 関数は ParseError からメッセージのリストを取り出す。Message 型は Show クラスのインスタンスではないので、ghci では表示できない。:t コマンドでタイプを表示する。

Parsec> :t errorMessages pem
errorMessages pem :: [Message]

errorPos 関数

errorPos 関数は ParseError から SourcePos ソース位置を取り出す。

Parsec> errorPos pe
"foo" (line 1, column 1)

mergeError 関数

mergeError 関数は2つの ParseError をマージする。

Parsec> mergeError pe pem
"foo" (line 1, column 1):
bar

messageString 関数

messageString 関数はメッセージを文字列に変換する。

Parsec> messageString (Message "bar")
"bar"

newErrorMessage 関数

newErrorMessage 関数 はメッセージと、SourcePos から新しい ParseError を作る。

Parsec> newErrorMessage (Message "bar") (initialPos "foo")
"foo" (line 1, column 1):
bar

setErrorMessages 関数

setErrorMessage 関数は SourcePos のメッセージを引数のメッセージと置き換える。

Parsec> setErrorMessage (Message "baz") pem
"foo" (line 1, column 1):
baz

setErrorPos 関数

setErrorPos 関数は Parse Error のパース位置を引数の SourcePos と置き換える。

Parsec> setErrorPos (newPos "baz" 3 4) pe
"baz" (line 3, column 4):unknown parse error

showErrorMessagews 関数

showErrorMessages はメッセージのリストを文字列に変換する。メッセージリスト意外に沢山の文字列の引数を必要としているが、メッセージの種類に応じて prefix になる。

Parsec> showErrorMessages "foo" "bar" "baz" "foobar" "foobaz" [Expect "qux"]
"\nbaz qux"

これらの実験で印象的だったのは showErrorMessages でメッセージのコンストラクタの違いによって表現を変えているところだ。showErrorMessages のソースがどうなっているかは興味深いが今は調べない。