|
|
@ -1,35 +1,68 @@ |
|
|
|
# PEGで言語をデザインしてみよう |
|
|
|
# はじめてのPEG |
|
|
|
|
|
|
|
|
|
|
|
PEGは「言語を定義するための言語」です。言語としてのPEGは,C++やJavaScriptなどの言語とはずいぶん毛色が違います。むしろ正規表現,XML Schemer,DBのテーブルスキーマのように「規則を定義」するタイプの言語です。 |
|
|
|
PEGは言語の文法を定義するための言語で、2004年に[Bryan Ford][Link_Ford]によって発表されました。記法はBNFに似ていますが、言語の構文を定義するだけでなく字句の定義も同一のファイルでに含めることができます。 |
|
|
|
|
|
|
|
|
|
|
|
PEGで文法を書き表すことによって,人間に読みやすくかつ厳密に文法を定義できます。さらにこの文法ファイルを使って,その言語のパーサーをプログラムに自動生成させることもできます。 |
|
|
|
PEGの文法ファイルは「構文規則名(N) <- 式(e)」で表される複数の構文規則で構成されます。例えば簡単な日付フォーマットはこんな感じで定義できるでしょう。 |
|
|
|
|
|
|
|
|
|
|
|
PEGの記法はとてもシンプルで習得しやすいものです。BNFや正規表現をご存知の方でしたら,ほんのわずかな時間で覚えられるでしょう。 |
|
|
|
``` |
|
|
|
|
|
|
|
# Ex) 12:34 p.m. |
|
|
|
|
|
|
|
DATE <- NUMBER ':' NUMBER ' ' SUFFIX |
|
|
|
|
|
|
|
NUMBER <- [0-9]+ |
|
|
|
|
|
|
|
SUFFIX <- 'a.m.' / 'p.m.' |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
PEGの理論的背景についてもっと知りたい方は,Bryan Fordさんの[2004年の論文][Link_Ford]を読みましょう。このTechnical Paperの2番めのセクションには,PEGをPEGで定義するとても興味深いコードが載せられています。(私もこれを参照しながらパーサジェネレータを実装しました。) |
|
|
|
PEGパーサーは,この文法ファイルの開始規則(DATE)に対して入力テキストがマッチするか判定します。式の中で他の規則が参照されている場合,再帰的にマッチを試みます。 |
|
|
|
|
|
|
|
|
|
|
|
「習うより慣れろ」と古くから言われるように,実際に手を動かしてコードを書くことは理解を深めるための早道です。その際[「PEG Playground」](http://yhirose.github.io/peglint/)を使ってみてください。このWebアプリは,PEGの文法定義コードと定義される言語のコードをリアルタイムにチェックしてくれます。 |
|
|
|
式には次のようなものがあります。 |
|
|
|
|
|
|
|
|
|
|
|
では始めましょう。最初にデザインするのはCSVフォーマットです。 |
|
|
|
* `N`(構文規則の参照) |
|
|
|
|
|
|
|
|
|
|
|
## CSVフォーマット |
|
|
|
* `'...'`または`"..."`(文字列) |
|
|
|
|
|
|
|
* `.`(任意の文字) |
|
|
|
|
|
|
|
* `[...]`(文字クラス。[a-zA-Z0-9-_]) |
|
|
|
|
|
|
|
|
|
|
|
おなじみカンマ区切りフォーマットの文法を定義してみましょう。CSVには色んな方言がありますが,以下の仕様を満たすものとします。 |
|
|
|
* `e1 e2`(連続) |
|
|
|
|
|
|
|
* `e1 / e2`(優先順位付き選択。`e1`が失敗した時のみ`e2`を試みる) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* `e*`(0回以上の繰り返し) |
|
|
|
|
|
|
|
* `e+`(1回以上の繰り返し) |
|
|
|
|
|
|
|
* `e?`(オプション) |
|
|
|
|
|
|
|
* `(e)`(グルーピング) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* `&e`(肯定先読み) |
|
|
|
|
|
|
|
* `!e`(否定先読み) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ではもう少し複雑な例を見てみましょう。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## CSVフォーマットの定義 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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] .)* |
|
|
|
NO_DQUOTE_FIELD <- (![,"\r\n] .)* |
|
|
|
|
|
|
|
|
|
|
|
この規則は「カンマ,ダブルクォート,改行以外の任意の文字の0回以上の繰り返し」を意味します。理解を深めるために,規則を構成する個々の要素を見てみましょう。 |
|
|
|
この規則は「カンマ,ダブルクォート,改行以外の任意の文字の0回以上の繰り返し」を意味します。規則を構成する個々の要素を見てみましょう。 |
|
|
|
|
|
|
|
|
|
|
|
`.`は任意の文にマッチします。`[,"\r\n]`はカンマ,ダブルクォート,改行文字のいずれかにマッチします。外側の`()*`は0回以上の繰り返しを意味します。どれも正規表現と全く同じですね。 |
|
|
|
`.`は任意の文字にマッチします。`[,"\r\n]`はカンマ,ダブルクォート,改行文字のいずれかにマッチします。外側の`()*`は0回以上の繰り返しを意味します。どれも正規表現と全く同じですね。 |
|
|
|
|
|
|
|
|
|
|
|
`!`は「否定先読み」演算子で,後続の`[,"\r\n]`の評価が偽であればマッチしたことになります。マッチしたとしても,現在処理している入力テキストの位置は進めません。 |
|
|
|
`!`は「否定先読み」演算子で,後続の`[,"\r\n]`がマッチしなければ,マッチしたことになります。またマッチしたとしても,現在処理している入力テキストの位置は進めません。 |
|
|
|
|
|
|
|
|
|
|
|
続いてダブルクォート有りのフィールドです。 |
|
|
|
続いてダブルクォート有りのフィールドです。 |
|
|
|
|
|
|
|
|
|
|
@ -41,9 +74,15 @@ PEGの理論的背景についてもっと知りたい方は,Bryan Fordさん |
|
|
|
|
|
|
|
|
|
|
|
FIELD <- DQ_FIELD / NO_DQUOTE_FIELD |
|
|
|
FIELD <- DQ_FIELD / NO_DQUOTE_FIELD |
|
|
|
|
|
|
|
|
|
|
|
これは「DQ_FIELDかNO_DQUOTE_FIELDにマッチ」を意味します。`/`は「Priority Choise」と呼ばれ,左から右に式の要素を評価します。評価が真になった時点でマッチに成功します。 |
|
|
|
これは「DQ_FIELDかNO_DQUOTE_FIELDにマッチ」を意味します。`/`は「優先順位付き選択(Priority Choise)」と呼ばれ,左の要素から順番にマッチを試みます。ある要素がマッチした時点で式の評価は終了します。 |
|
|
|
|
|
|
|
|
|
|
|
このDQ_FIELDとNO_DQUOTE_FIELDの順序はとても重要です。もしこの順序を逆にすると,ダブルクォート文字列はNO_DQUOTE_FIELDの規則で誤ってマッチしてしまい,DQ_FIELDとしては認識されなくなってしまいます。(ダブルクォート文字列は,最初のクオート無しのフィールド規則で長さ0のフィールドとしてマッチしてしまい,続くダブルクォート有りのフィールド規則にたどりつかないからです。) |
|
|
|
ですからDQ_FIELDとNO_DQUOTE_FIELDの順序はとても重要です。もしこの順序を逆にすると,ダブルクォート文字列はNO_DQUOTE_FIELDの規則で誤ってマッチしてしまい,DQ_FIELDとしては認識されなくなってしまいます。(ダブルクォート文字列は,最初のクオート無しのフィールド規則で長さ0のフィールドとしてマッチしてしまい,続くダブルクォート有りのフィールド規則はスキップされてしまいます。) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
次に改行文字を定義しましょう。 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NL <- '\r\n' / '\r' / '\n' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
この場合も要素の順序に気をつけてください。`\r\n`を`\r`の後に置くと,`\r\n`は決してマッチしなくなってしまいます。 |
|
|
|
|
|
|
|
|
|
|
|
続いてレコード規則を定義します。 |
|
|
|
続いてレコード規則を定義します。 |
|
|
|
|
|
|
|
|
|
|
@ -51,25 +90,19 @@ PEGの理論的背景についてもっと知りたい方は,Bryan Fordさん |
|
|
|
|
|
|
|
|
|
|
|
これは「1回以上のFIELDの繰り返し」を意味します。この形式は,「要素が最低1回以上出現するリスト」を表現するイディオムです。(「要素が0回以上出現するリスト」は`(ELEM (DELM ELEM)*)?`で表します。) |
|
|
|
これは「1回以上のFIELDの繰り返し」を意味します。この形式は,「要素が最低1回以上出現するリスト」を表現するイディオムです。(「要素が0回以上出現するリスト」は`(ELEM (DELM ELEM)*)?`で表します。) |
|
|
|
|
|
|
|
|
|
|
|
開始規則であるCSV規則は, |
|
|
|
最後に開始規則であるCSV規則を定義します。 |
|
|
|
|
|
|
|
|
|
|
|
CSV <- RECORD (NL RECORD)* NL? |
|
|
|
CSV <- RECORD (NL RECORD)* NL? |
|
|
|
|
|
|
|
|
|
|
|
と定義できます。改行文字を定義を忘れていましたね… |
|
|
|
無事CSVフォーマットの文法が完成です。BNFや正規表現に慣れている方は,親近感を感じたのではないでしょうか? |
|
|
|
|
|
|
|
|
|
|
|
NL <- '\r\n' / '\r' / '\n' |
|
|
|
ここでPEGで文法を定義する際によく直面する問題について考慮しましょう。これは構文解析と字句解析が一緒に行われるPEG特有の問題です。 |
|
|
|
|
|
|
|
|
|
|
|
この場合も要素の順序に気をつけてください。`\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 <- '"' (!["] . / '""')* '"' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
では,この文法をテストしてみましょう。 |
|
|
|
次章では,より複雑なスクリプト言語の文法デザインしてみましょう。 |
|
|
|
|
|
|
|
|
|
|
|
[Link_BasicGrammar]: http://kmizu.hatenablog.com/entry/20100203/1265183754 |
|
|
|
|
|
|
|
[Link_Ford]: http://pdos.csail.mit.edu/papers/parsing:popl04.pdf |
|
|
|
[Link_Ford]: http://pdos.csail.mit.edu/papers/parsing:popl04.pdf |
|
|
|