cpp-peglib/tutorial/chap_02.md
2015-08-10 00:35:04 -04:00

5.4 KiB
Raw Blame History

PEGで言語をデザインしてみよう

PEGは「言語を定義するための言語」です。言語としてのPEGはC++やJavaScriptなどの言語とはずいぶん毛色が違います。むしろ正規表現XML SchemerDBのテーブルスキーマのように「規則を定義」するタイプの言語です。

PEGで文法を書き表すことによって人間に読みやすくかつ厳密に文法を定義できます。さらにこの文法ファイルを使ってその言語のパーサーをプログラムに自動生成させることもできます。

PEGの記法はとてもシンプルで習得しやすいものです。BNFや正規表現をご存知の方でしたらほんのわずかな時間で覚えられるでしょう。

PEGの理論的背景についてもっと知りたい方はBryan Fordさんの2004年の論文を読みましょう。このTechnical Paperの2番めのセクションにはPEGをPEGで定義するとても興味深いコードが載せられています。私もこれを参照しながらパーサジェネレータを実装しました。

「習うより慣れろ」と古くから言われるように,実際に手を動かしてコードを書くことは理解を深めるための早道です。その際「PEG Playground」を使ってみてください。このWebアプリはPEGの文法定義コードと定義される言語のコードをリアルタイムにチェックしてくれます。

では始めましょう。最初にデザインするのはCSVフォーマットです。

CSVフォーマット

おなじみカンマ区切りフォーマットの文法を定義してみましょう。CSVには色んな方言がありますが以下の仕様を満たすものとします。

  • CSVファイルは一つ以上フィールドでなる一つ以上の行の集まり
  • フィールドは,ダブルクォート無しと有りある
  • ダブルクォート無しのフィールドには,カンマとダブルクォートを含むことができない
  • ダブルクォート有りのフィールドには,ダブルクォート以外の全ての文字と,連続したダブルクォートを含むことができる

テストのしやすさを考慮して,規則をボトムアップで定義しましょう。まずはダブルクォート無しのフィールドです。

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のフィールドとしてマッチしてしまい続くダブルクォート有りのフィールド規則にたどりつかないからです。

続いてレコード規則を定義します。

RECORD <- FIELD (',' FIELD)*

これは「1回以上のFIELDの繰り返し」を意味します。この形式は「要素が最低1回以上出現するリスト」を表現するイディオムです。「要素が0回以上出現するリスト」は(ELEM (DELM ELEM)*)?で表します。)

開始規則であるCSV規則は

CSV <- RECORD (NL RECORD)* NL?

と定義できます。改行文字を定義を忘れていましたね…

NL <- '\r\n' / '\r' / '\n'

この場合も要素の順序に気をつけてください。\r\n\rの後に置くなら,\r\nは決してマッチしなくなります。

CSVの文法が定義できました

CSV             <- RECORD (NL RECORD)* NL?
RECORD          <- FIELD (',' FIELD)*
FIELD           <- DQ_FIELD / NO_DQUOTE_FIELD
NO_DQUOTE_FIELD <- (![,"\r\n] .)*
DQ_FIELD        <- '"' (!["] . / '""')* '"'

では,この文法をテストしてみましょう。