Inside Parsec


Text.Parsec.Pos

Text.Prasec.Pos では入力文字列が Parsec でパースされるときの位置情報をあらわすデータ型 SourcePos が定義されている。SourcePos に関する関数はすべて Text.Parsec.Pos モジュールで完結しているので、このモジュールをインポートするだけで ghci で試してみることができる。
Prelude> import Text.Parsec.Pos
Prelude Text.Parsec.Pos> :set prompt "Parsec> "
Parsec> 

Text.Parsec.Pos モジュールの概要

Text.Source.Pos モジュールの概要は :browse コマンドで得られる。

Parsec> :browse Text.Parsec.Pos
type Column = Int
type Line = Int
type SourceName = String
data SourcePos
  = Text.Parsec.Pos.SourcePos SourceName
                              {-# UNPACK #-}Line
                              {-# UNPACK #-}Column
incSourceColumn :: SourcePos -> Column -> SourcePos
incSourceLine :: SourcePos -> Line -> SourcePos
initialPos :: SourceName -> SourcePos
newPos :: SourceName -> Line -> Column -> SourcePos
setSourceColumn :: SourcePos -> Column -> SourcePos
setSourceLine :: SourcePos -> Line -> SourcePos
setSourceName :: SourcePos -> SourceName -> SourcePos
sourceColumn :: SourcePos -> Column
sourceLine :: SourcePos -> Line
sourceName :: SourcePos -> SourceName
updatePosChar :: SourcePos -> Char -> SourcePos
updatePosString :: SourcePos -> String -> SourcePos

SourcePos のデータ構造

SourcePos のデータ構造は、

SourcePos SourceName Line Column

である。SourceName は String 型の別名で、Line と Column は Int 型の別名である。いずれも type 宣言で定義されている。

SourcePosの作成

SourcePos の作成は newPos 関数で行える。引数には、ソース名、Line 番号、Column 番号を与える。SourcePos データ型は Show クラスのインスタンスなので、ghci で表示できる。

Parsec> pos = newPos "foo" 1 2
Parsec> pos
"foo" (line 1, column 2)

SourcePos の初期値は initialPos 関数で作れる。

Parsec> initialPos "bar"
"bar" (line 1, column 1)

SourcePos を操作する関数

SourcePos を操作する関数には次のようなものがある。

incSourceColumn 関数は Column 番号を引数の番号分増やす。

Parsec> incSourceColumn pos 3
"foo" (line 1, column 5)

incSourceLine 関数は Line 番号を引数の番号分増やす。

Parsec> incSourceLine pos 3
"foo" (line 4, column 2)

setSourceColumn 関数は Coulumn 番号を引数の番号で置き換える。

Parsec> setSourceColumn pos 10
"foo" (line 1, column 10)

setSourceLine 関数は Line 番号を引数の番号で置き換える。

Parsec> setSourceLine pos 10
"foo" (line 10, column 2)

setSourceName 関数は SourceName を引数の文字列で置き換える。

Parsec> setSourceName pos "bar"
"bar" (line 1, column 2)

sourceColunm 関数は SourcePos の Column 番号を得る。

Parsec> sourceColumn pos
2

sourceLine 関数は SourcePos の Line 番号を得る。

Parsec> sourceLine pos
1

sourceName 関数は SourcePos の SourceName を得る。

Parsec> sourceName pos
"foo"

updatePosChar 関数は Char の分だけ Column 番号を増やす。

Parsec> updatePosChar pos 'a'
"foo" (line 1, column 3)

updatePosString 関数は String の分だけ Column 番号を増やす。

Parsec> updatePosString pos "baz"
"foo" (line 1, column 5)

ParseError 中の SourcePos

SourcePos は ParseError 例外のフィールドに入っており、errorPos で取り出すことができる。

Parsec> :m + Text.Parsec Text.Parsec.Error
Parsec> (Left pe) = runParser letter () "SourceName" "123"
Parsec> pe
"SourceName" (line 1, column 1):
unexpected "1"
expecting letter
Parsec> errorPos pe
"SourceName" (line 1, column 1)

SourcePos は入力文字列のどの位置で Parsec によるパースが行われるかを示す。これはパースエラーのときの ParseError 例外にも含まれていて、パースエラーがどこで起こったかを示している。

SoucePos のような意味的にまとまったデータは代数的データ型にしておくと可読性も保守性もいいようだ。また、ネット上の Hackage は関数の説明から簡単にソースにアクセスできるのでデータ構造の詳細を知るのに便利だ。