5.6 KiB
はじめてのPEG
PEGは言語の文法を定義するための言語で、2004年にBryan Fordによって発表されました。記法はBNFに似ていますが、言語の構文を定義するだけでなく字句の定義も同一のファイルでに含めることができます。
PEGの文法ファイルは「構文規則名(N) <- 式(e)」で表される複数の構文規則で構成されます。例えば簡単な日付フォーマットはこんな感じで定義できるでしょう。
# Ex) 12:34 p.m.
DATE <- NUMBER ':' NUMBER ' ' SUFFIX
NUMBER <- [0-9]+
SUFFIX <- 'a.m.' / 'p.m.'
PEGパーサーは,この文法ファイルの開始規則(DATE)に対して入力テキストがマッチするか判定します。式の中で他の規則が参照されている場合,再帰的にマッチを試みます。
式には次のようなものがあります。
-
N
(構文規則の参照) -
'...'
または"..."
(文字列) -
.
(任意の文字) -
[...]
(文字クラス。[a-zA-Z0-9-_]) -
e1 e2
(連続) -
e1 / e2
(優先順位付き選択。e1
が失敗した時のみe2
を試みる) -
e*
(0回以上の繰り返し) -
e+
(1回以上の繰り返し) -
e?
(オプション) -
(e)
(グルーピング) -
&e
(肯定先読み) -
!e
(否定先読み)
ではもう少し複雑な例を見てみましょう。
CSVフォーマットの定義
CSVには様々な方言があるので,今回は以下の仕様を満たすものとします。
- CSVファイルは,一つ以上フィールドでなる,一つ以上の行の集まり
- フィールドは,ダブルクォート無しと有りある
- ダブルクォート無しのフィールドには,カンマとダブルクォートを含むことができない
- ダブルクォート有りのフィールドには,ダブルクォート以外の全ての文字と,連続したダブルクォートを含むことができる
文法は以下のようになります。
CSV <- RECORD (NL RECORD)* NL?
RECORD <- FIELD (',' FIELD)*
FIELD <- DQ_FIELD / NO_DQUOTE_FIELD
NO_DQUOTE_FIELD <- (![,"\r\n] .)*
DQ_FIELD <- '"' (!["] . / '""')* '"'
NL <- '\r\n' / '\r' / '\n'
個々の規則をボトムアップで見ていきましょう。最初はダブルクォート無しのフィールドです。
NO_DQUOTE_FIELD <- (![,"\r\n] .)*
この規則は「カンマ,ダブルクォート,改行以外の任意の文字の0回以上の繰り返し」を意味します。規則を構成する個々の要素を見てみましょう。
.
は任意の文字にマッチします。[,"\r\n]
はカンマ,ダブルクォート,改行文字のいずれかにマッチします。外側の()*
は0回以上の繰り返しを意味します。どれも正規表現と全く同じですね。
!
は「否定先読み」演算子で,後続の[,"\r\n]
がマッチしなければ,マッチしたことになります。またマッチしたとしても,現在処理している入力テキストの位置は進めません。
続いてダブルクォート有りのフィールドです。
DQ_FIELD <- '"' (!["] . / '""')* '"'
この規則は「両端にダブルクォートがあって,その間はダブルクォートでない任意の文字か2連続するダブルクォートの0以上の繰り返し」という意味です。
ではこの2つの規則をまとめましょう。
FIELD <- DQ_FIELD / NO_DQUOTE_FIELD
これは「DQ_FIELDかNO_DQUOTE_FIELDにマッチ」を意味します。/
は「優先順位付き選択(Priority Choise)」と呼ばれ,左の要素から順番にマッチを試みます。ある要素がマッチした時点で式の評価は終了します。
ですからDQ_FIELDとNO_DQUOTE_FIELDの順序はとても重要です。もしこの順序を逆にすると,ダブルクォート文字列はNO_DQUOTE_FIELDの規則で誤ってマッチしてしまい,DQ_FIELDとしては認識されなくなってしまいます。(ダブルクォート文字列は,最初のクオート無しのフィールド規則で長さ0のフィールドとしてマッチしてしまい,続くダブルクォート有りのフィールド規則はスキップされてしまいます。)
次に改行文字を定義しましょう。
NL <- '\r\n' / '\r' / '\n'
この場合も要素の順序に気をつけてください。\r\n
を\r
の後に置くと,\r\n
は決してマッチしなくなってしまいます。
続いてレコード規則を定義します。
RECORD <- FIELD (',' FIELD)*
これは「1回以上のFIELDの繰り返し」を意味します。この形式は,「要素が最低1回以上出現するリスト」を表現するイディオムです。(「要素が0回以上出現するリスト」は(ELEM (DELM ELEM)*)?
で表します。)
最後に開始規則であるCSV規則を定義します。
CSV <- RECORD (NL RECORD)* NL?
無事CSVフォーマットの文法が完成です。BNFや正規表現に慣れている方は,親近感を感じたのではないでしょうか?
ここでPEGで文法を定義する際によく直面する問題について考慮しましょう。これは構文解析と字句解析が一緒に行われるPEG特有の問題です。
空白の除去
キーワードの処理
次章では,より複雑なスクリプト言語の文法デザインしてみましょう。