mirror of
https://github.com/yhirose/cpp-peglib.git
synced 2024-12-22 20:05:31 +00:00
Updated tutorial.
This commit is contained in:
parent
646eb7d5e9
commit
a1704cad73
@ -1,6 +1,6 @@
|
||||
# まずは,Hello World!
|
||||
# まずは,Hello world
|
||||
|
||||
はじめに取り組むプログラムは,もちろん「Hello World」ですよね。
|
||||
はじめに取り組むプログラムは,もちろん**hello world**ですよね。
|
||||
|
||||
では「hello world!」と正しく入力できたら「OK」,そうでなければ「NG」と表示するプログラムを作ってみましょう!以下がソースコードです。
|
||||
|
||||
@ -13,9 +13,7 @@ using namespace std;
|
||||
|
||||
// 文法定義
|
||||
const auto grammar = R"(
|
||||
PROGRAM <- _ HELLO _ WORLD '!' _
|
||||
HELLO <- [hH] 'ello'
|
||||
WORLD <- [wW] 'orld'
|
||||
PROGRAM <- _ 'hello' _ 'world' '!' _
|
||||
_ <- [ \t]*
|
||||
)";
|
||||
|
||||
@ -36,10 +34,6 @@ int main(void)
|
||||
string line;
|
||||
getline(cin, line);
|
||||
|
||||
if (line == "quit") {
|
||||
break;
|
||||
}
|
||||
|
||||
// ユーザーからの入力をパース
|
||||
if (parser.parse(line.c_str())) {
|
||||
cout << "OK" << endl;
|
||||
@ -68,18 +62,18 @@ Visual C++ 14 (Visual Studio 2015) では,
|
||||
|
||||
とするとコンパイルできました。
|
||||
|
||||
では生成された実行ファイルを実行してみましょう。画面に`>`が表示され,ユーザーに入力を促します。何か文字列を入力してリターンキーを押してみましょう。正確に「hello world!」と入力すると「OK」と表示されます。何かおかしな入力をすると「NG」になります。ちなみに「 Hello World! 」もOKです。
|
||||
では生成された実行ファイルを実行してみましょう。画面に`>`が表示され,ユーザーに入力を促します。何か文字列を入力してリターンキーを押してみましょう。正確に「hello world!」と入力すると「OK」と表示されます。何かおかしな入力をすると「NG」になります。
|
||||
|
||||
```
|
||||
> はろーわーるど!
|
||||
NG
|
||||
> hello world!
|
||||
OK
|
||||
> Hello World!
|
||||
> hello world!
|
||||
OK
|
||||
```
|
||||
|
||||
見事にPEG版Hello Worldをクリアです!(プログラムを終了したい時は`quit`を入力するか,`Ctrl+C`を押してください。)
|
||||
見事にPEG版Hello worldをクリアです!(プログラムを終了したい時は`Ctrl+C`を押してください。)
|
||||
|
||||
--
|
||||
|
||||
@ -91,18 +85,16 @@ OK
|
||||
#include "peglib.h"
|
||||
```
|
||||
|
||||
続いてPEGで文法を定義します。この文法は「Hello World!」という文字列を受け付けるだけのとても簡単なものです。言葉の先頭は大文字でも小文字でも大丈夫です。入力文字列の前後や「hello」と「world」の間には,任意の長さのスペースやタブを入れることができます。(ちなみに「world」と「!」の間には入れることができません。)
|
||||
続いてPEGで文法を定義します。この文法は「hello world!」という文字列を受け付けるだけのとても簡単なものです。入力文字列の前後や「hello」と「world」の間には,任意の長さのスペースやタブを入れることができます。(ちなみに「world」と「!」の間には入れることができません。)
|
||||
|
||||
```cpp
|
||||
const auto grammar = R"(
|
||||
PROGRAM <- _ HELLO _ WORLD '!' _
|
||||
HELLO <- [hH] 'ello'
|
||||
WORLD <- [wW] 'orld'
|
||||
PROGRAM <- _ 'hello' _ 'world' '!' _
|
||||
_ <- [ \t]*
|
||||
)";
|
||||
```
|
||||
|
||||
脇にそれますが,この文法には一つ大きなバグがあります。実は「helloworld!」のように言葉をくっつけて入力しても結果が「OK」になってしまいます。このバグの解決法は2章を読むとわかります。
|
||||
脇にそれますが,この文法には一つ大きなバグがあります。実は「helloworld!」のように言葉をくっつけて入力しても結果が「OK」になってしまいます。このバグの解決法は後の章で説明します。
|
||||
|
||||
この文法を理解するPEGパーサーを生成しましょう。`peglib::peg`がパーサーです。先ほどの定義した文法をコンストラクタに渡してパーサーを生成します。
|
||||
|
||||
@ -130,14 +122,14 @@ const auto grammar = R"(
|
||||
}
|
||||
```
|
||||
|
||||
これでおしまいです。ではこれを起点として,より実用的な言語を作っていきましょう。インタープリタ型言語の作成には,次のようなステップが必要です。
|
||||
このように,パーサジェネレータを使うと簡単に構文解析パーサを生成ことができます。もちろん実用的な言語の文法はもっと複雑ですし,パーサは構文チェックをするだけでコードを実行することできません。実際に動作するインタープリタ型言語の作成には,次のようなステップが必要です。
|
||||
|
||||
1. 言語の文法を定義する
|
||||
2. パーサーを生成する
|
||||
3. ソースコードをパースして、AST(Abstract Syntax Tree―構文木)を生成する
|
||||
3. ソースコードをパースして、AST(抽象構文木)を生成する
|
||||
4. ASTを実行するインタープリタを作成する
|
||||
|
||||
PEGパーサーライブラリはステップ2と3のみ扱います。それでステップ1と2は自分で扱わなければなりません。でもこの文法定義とインタープリタ作成の部分が一番面白いところで,言語に個性を与えるオリジナリティを出せるところだと思います。
|
||||
PEGライブラリはステップ2と3のみ扱い,ステップ1と2は自分で扱わなければなりません。しかし,この文法定義とインタープリタ作成の部分が一番面白いところで,デザインセンスと実装技術の見せ所です。。
|
||||
|
||||
これからの章では,それぞれのステップを順を追って説明していきます。その際,実際にコードを動かしたり拡張していくなら,より一層理解が深まるに違いありません。
|
||||
|
||||
|
@ -1,85 +1,75 @@
|
||||
# PEGで言語をデザインしてみよう
|
||||
|
||||
PEGについて一言で説明しなさいと言われたら,「言語を定義するための言語」と言えるかもしれません。パーサジェネレータは,PEGを使って厳密に定義された言語の文法を読み込んで,パーサを生成することができます。
|
||||
PEGについて一言で説明としたら,「言語を定義するための言語」です。言語としてのPEGは,C++やJavaScriptなどの言語とはずいぶん毛色が違います。むしろ正規表現,XML Schemer,DBのテーブルスキーマのように「規則を定義」するタイプの言語です。
|
||||
|
||||
PEGは,C++やJavaScriptなどの処理手続きを記述する言語とはずいぶん毛色が違います。むしろ正規表現,XML Schemer,DBのテーブルスキーマのような「規則を定義」するタイプの言語に似ています。
|
||||
PEGで文法を書き表すことによって,人間に読みやすくかつ厳密に文法を定義できます。さらにこの文法ファイルを使って,その言語のパーサーをプログラムに自動生成させることもできます。
|
||||
|
||||
この章では,次の「言語」をPEGを使って定義してみます。
|
||||
PEGの記法はとてもシンプルで習得しやすいものです。BNFや正規表現をご存知の方でしたら,ほんのわずかな時間で覚えられるでしょう。
|
||||
|
||||
* 日本語の「文」
|
||||
* カンマ区切り行
|
||||
* 括弧付き四則計算式
|
||||
* JSON
|
||||
* JavaScriptの小さなサブセット
|
||||
PEGの理論的背景についてもっと知りたい方は,Bryan Fordさんの[2004年の論文[Link_Ford]を読みましょう。このTechnical Paperの2番めのセクションには,PEGをPEGで定義するとても興味深いコードが載せられています。(私もこれを参照しながらパーサジェネレータを実装しました。)
|
||||
|
||||
下に行くほど文法定義が難しくなり,PEGのより多くの記法を使う必要が出てきます。これらの例が終わる頃には,きっと簡単なスクリプト言語を自分でデザインできるようになるでしょう。(もちろん文法を定義しただけなので,コードを動きませんが。^^;)
|
||||
「習うより慣れろ」と古くから言われるように,実際に手を動かしてコードを書くことは理解を深めるための早道です。その際[「PEG Playground」](http://yhirose.github.io/peglint/)を使ってみてください。このWebアプリは,PEGの文法定義コードと定義される言語のコードをリアルタイムにチェックしてくれます。
|
||||
|
||||
PEGの記法については,この章を読み進めるうちに自然に学べるようになっています。しかし時間が許すなら,是非kimizuさんの[「PEG基礎文法最速マスター」][Link_BasicGrammar]をご覧ください。PEGの記法について,とても簡潔にわかりやすくまとめられています。
|
||||
では始めましょう。最初にデザインするのはCSVフォーマットです。
|
||||
|
||||
「習うより慣れろ」と古くから言われるように,実際に手を動かしてコードを書くことは理解を深めるための早道です。その際[「PEG Playground」](http://yhirose.github.io/peglint/)ご活用ください。このWeb アプリは,PEGの文法定義コードと定義される言語のコードをリアルタイムにチェックしてくれます。
|
||||
## 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?
|
||||
|
||||
PEGの感覚がつかめてきましたか?
|
||||
と定義できます。改行文字を定義を忘れていましたね…
|
||||
|
||||
さて,ここまでで使用した4つの記法についておさらいしましょう。
|
||||
NL <- '\r\n' / '\r' / '\n'
|
||||
|
||||
| 記法 | 意味 |
|
||||
|:---------|:---------------|
|
||||
| <- | 構文規則の定義 |
|
||||
| ॷ | 連接 |
|
||||
| / | 選択 |
|
||||
| ? | 0回または1回 |
|
||||
| 'string' | 文字列リテラル |
|
||||
この場合も要素の順序に気をつけてください。`\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
|
||||
|
@ -6,11 +6,11 @@
|
||||
|
||||
でもしばらく前のこと,仕事で数十万行にも及ぶ大きなデータファイルを作成する必要がありました。エディタを使って手作業するなら軽く数ヶ月かかってしまいます。それでは大変なので,設定ファイルを定義してプログラムにデータを生成させようということになりました。数千行の特別な書式の設定ファイルを準備すれば,ものの数分で膨大かつ正確なデータを生成してくれるのです。
|
||||
|
||||
はたして「プログラミング言語のようなもの」を作ることになりました。まずは設定ファイルをパースしなければなりません。「さて,パーサをどう実装する? Yaccのようなパーサージェネレータ使う? パーサーを手書きで書く? いずれにしても面倒で難しそう…」と悩みつつWebで検索していたところ,偶然[『PEG』][Link_PEG]という言葉を目にしました。PEGとは「Parsing Expression Grammar」の略号です。調べてみると,Yaccより簡単に扱えるパーサージェネレータだそうです。さらに調べていくとPEGパーサジェネレータ自体を実装する記事がWeb上に幾つもあり,誰でも比較的簡単に実装できるということがわかりました!
|
||||
はからずも「プログラミング言語のようなもの」を作ることになりました。まずは設定ファイルをパースしなければなりません。「さて,パーサをどう実装する? Lex+Yaccを使う? パーサーを手書きで書く? いずれにしても面倒で難しそう…」と悩みつつWebで検索していたところ,偶然[『PEG』][Link_PEG]という言葉を目にしました。PEGとは「Parsing Expression Grammar」の略号です。調べてみると,PEGの文法ファイル一つで,字句解析と構文解析を行えること,普段良く使っている正規表現に似ていること,自分でもPEGパーサコンビネータやジェネレータを作成できそうなことがわかりました。
|
||||
|
||||
さっそく日々の仕事で使っているC++で,PEGパーサジェネレータを作り始めました。はい,コンパイラ作成の授業など取ったことのなかった私でも,意外にあっさり実装できてしまいました。その後,先の設定ファイルの文法をPEGでデザインし(これがまたとても面白い!),そのパーサーをPEGライブラリでいとも簡単に作ることができました。嬉しいことに、仕事のプロジェクトはとても上手くいきました。
|
||||
さっそく日々の仕事で使っているC++で,PEGコンビネータとパーサジェネレータを作り始めました。はい,コンパイラ作成の経験などなかった私でも,意外にあっさり実装できてしまいました。その後,先の設定ファイルの文法をPEGでデザインし(これがまたとても面白い!),パーサジェネレータでいとも簡単にパーサを生成することができました。このプロジェクトはとても上手くいきました。
|
||||
|
||||
これが契機となって、「もう少し頑張れば、自分にもプログラミング言語を作ることができるかも」と思えるようになりました。文法定義とパーサーの生成がとても簡単になったので,言語作成の敷居がぐっと下がったように感じたのです。さらにこのPEGパーサジェネレータ改良を重ね,実際にシンプルな言語を作って動かすことができるようになりました!
|
||||
これが契機となって、「もう少し頑張れば、自分にもプログラミング言語を作ることができるかも」と思えるようになりました。文法定義とパーサーの生成がとても簡単になったので,言語作成の敷居がぐっと下がったように感じたのです。さらにこのPEGライブラリに改良を重ね,実際にプログラミング言語を作って動かすことができるようになりました!
|
||||
|
||||
正直なところ,今でも「コンパイラ技術」についての深い知識は持ち合わせていません。PEGの[オリジナルの論文][Link_Paper]に書かれている理論も完璧に理解しているわけではありません。それでも言語処理系を作ることの楽しさを十分味わい,この分野についてもっと勉強したいというモチベーションを得ることもできたことは素晴らしい経験でした。
|
||||
|
||||
|
@ -6,5 +6,5 @@
|
||||
|
||||
## 章
|
||||
|
||||
1. [まずは,Hello World!](chap_01.md)
|
||||
1. [まずは,Hello world](chap_01.md)
|
||||
2. [PEGで言語をデザインしてみよう](chap_02.md)
|
||||
|
Loading…
Reference in New Issue
Block a user