Updated tutorial.

This commit is contained in:
yhirose 2015-08-09 23:53:21 -04:00
parent 646eb7d5e9
commit a1704cad73
4 changed files with 58 additions and 76 deletions

View File

@ -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. ソースコードをパースして、ASTAbstract Syntax Tree―構文木)を生成する
3. ソースコードをパースして、AST抽象構文木)を生成する
4. ASTを実行するインタープリタを作成する
PEGパーサーライブラリはステップ2と3のみ扱います。それでステップ1と2は自分で扱わなければなりません。でもこの文法定義とインタープリタ作成の部分が一番面白いところで言語に個性を与えるオリジナリティを出せるところだと思います
PEGライブラリはステップ2と3のみ扱いステップ1と2は自分で扱わなければなりません。しかしこの文法定義とインタープリタ作成の部分が一番面白いところでデザインセンスと実装技術の見せ所です。
これからの章では,それぞれのステップを順を追って説明していきます。その際,実際にコードを動かしたり拡張していくなら,より一層理解が深まるに違いありません。

View File

@ -1,85 +1,75 @@
# PEGで言語をデザインしてみよう
PEGについて一言で説明しなさいと言われたら「言語を定義するための言語」と言えるかもしれません。パーサジェネレータはPEGを使って厳密に定義された言語の文法を読み込んでパーサを生成することができます。
PEGについて一言で説明としたら「言語を定義するための言語」です。言語としてのPEGはC++やJavaScriptなどの言語とはずいぶん毛色が違います。むしろ正規表現XML SchemerDBのテーブルスキーマのように「規則を定義」するタイプの言語です。
PEGC++やJavaScriptなどの処理手続きを記述する言語とはずいぶん毛色が違います。むしろ正規表現XML SchemerDBのテーブルスキーマのような「規則を定義」するタイプの言語に似ています。
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'
| 記法 | 意味 |
|:---------|:---------------|
| <- | 構文規則の定義 |
| &#2423; | 連接 |
| / | 選択 |
| ? | 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

View File

@ -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]に書かれている理論も完璧に理解しているわけではありません。それでも言語処理系を作ることの楽しさを十分味わい,この分野についてもっと勉強したいというモチベーションを得ることもできたことは素晴らしい経験でした。

View File

@ -6,5 +6,5 @@
## 章
1. [まずはHello World!](chap_01.md)
1. [まずはHello world](chap_01.md)
2. [PEGで言語をデザインしてみよう](chap_02.md)