diff --git a/tutorial/chap_01.md b/tutorial/chap_01.md index 83c095c..3c1dbff 100644 --- a/tutorial/chap_01.md +++ b/tutorial/chap_01.md @@ -1,9 +1,8 @@ -まずは,Hello World! -=================== +# まずは,Hello World! -プログラマーあれば,何はともあれ,まずは「Hello World」ですよね。それでは,「hello world!」と正しく入力できたら「OK」,そうでなければ「NG」と表示するプログラムを作ってみましょう! +はじめに取り組むプログラムは,もちろん「Hello World」ですよね。 -これがソースコードです。 +では「hello world!」と正しく入力できたら「OK」,そうでなければ「NG」と表示するプログラムを作ってみましょう!以下がソースコードです。 ```cpp // hello.cc @@ -53,52 +52,46 @@ int main(void) } ``` -このコードを`hello.cc`に保存して,それからコンパイルしてみましょう。PEG Parser Libraryを使用するので,[ここから](https://raw.githubusercontent.com/yhirose/cpp-peglib/master/peglib.h)`peglib.h`ダウンロードして,`hello.cc`があるディレクトリに保存してください。 +このコードを`hello.cc`に保存して,それからコンパイルしてみましょう。このコードはPEGパーサライブラリを使用するので,[ここから](https://raw.githubusercontent.com/yhirose/cpp-peglib/master/peglib.h)`peglib.h`ダウンロードして`hello.cc`があるディレクトリに保存してください。 -コンパイルにはC++11の機能を有効にする設定が必要です。わたしは普段`clang++`のパージョン3.5を使っているので,こんな感じになります。 +コンパイル時にはC++11の機能を有効にする必要があります。`clang++`のパージョン3.5ではこんな感じになります。 -``` -clang++ -std='c++11' -o hello hello.cc -``` + clang++ -std='c++11' -o hello hello.cc g++ 5.1でもほとんど同じで, -``` -g++ -std='c+11' -o hello hello.cc' -``` + g++ -std='c+11' -o hello hello.cc' Visual C++ 14 (Visual Studio 2015) では, -``` -cl -DUNICODE /EHsc hello.cc User32.lib -``` + cl -DUNICODE /EHsc hello.cc User32.lib -とするとうまくいきました。 +とするとコンパイルできました。 -では生成された実行ファイルを実行してみましょう。すると画面に`>`が表示され,ユーザーに入力を促します。文字列を入力してリターンキーを押してみましょう。正しく「hello world!」と入力すると「OK」と表示されます。間違うと「NG」です。ちなみに「 Hello World! 」もOKです。 +では生成された実行ファイルを実行してみましょう。画面に`>`が表示され,ユーザーに入力を促します。何か文字列を入力してリターンキーを押してみましょう。正確に「hello world!」と入力すると「OK」と表示されます。何かおかしな入力をすると「NG」になります。ちなみに「 Hello World! 」もOKです。 ``` > はろーわーるど! NG > hello world! OK -> Hello World! +> Hello World! OK ``` -どうでしょう,見事にPEG版Hello Worldが動きました!(プログラムを終了したい時は`quit`を入力するか,`Ctrl+C`を押してください。) +見事にPEG版Hello Worldをクリアです!(プログラムを終了したい時は`quit`を入力するか,`Ctrl+C`を押してください。) -- -では,このプログラムを順を追って見てみましょう。 +では,このプログラムのPEGに関する部分を順を追ってみましょう。 -まずはPEGライブラリを読み込みます。`peglib.h`はC++ header-only libraryですので、libファイルをリンクする必要はありません。 +まずはPEGライブラリを読み込みます。`peglib.h`はC++ header-only libraryですので,他にファイルは必要ありません。 ```cpp #include "peglib.h" ``` -続いてPEGで文法を定義します。今回は「Hello World!」という文字列を受け付けるとても簡単な文法です。言葉の先頭は大文字でも小文字でも受け入れてくれます。「_」は0以上長さのスペース文字やタブ文字,つまり「空白」を定義しています。入力文字列の前後や,「hello」と「world」の間には空白入れることができます。(ちなみに「world」と「!」の間には空白をいれることができません。) +続いてPEGで文法を定義します。この文法は「Hello World!」という文字列を受け付けるだけのとても簡単なものです。言葉の先頭は大文字でも小文字でも大丈夫です。入力文字列の前後や「hello」と「world」の間には,任意の長さのスペースやタブを入れることができます。(ちなみに「world」と「!」の間には入れることができません。) ```cpp const auto grammar = R"( @@ -109,24 +102,27 @@ const auto grammar = R"( )"; ``` -脇にそれますが,この文法には一つ大きなバグがあります。実は「helloworld!」のように言葉をくっつけて入力しても結果が「OK」になってしまいます。というのは「HELLO _ WORLD」の中の「_」は「0以上」を意味する「*」を使っているので,長さ0の空文字列にもマッチしてしまうからです。このバグの解決法は後ほど改めてしたいと思います。 +脇にそれますが,この文法には一つ大きなバグがあります。実は「helloworld!」のように言葉をくっつけて入力しても結果が「OK」になってしまいます。このバグの解決法は2章を読むとわかります。 この文法を理解するPEGパーサーを生成しましょう。`peglib::peg`がパーサーです。先ほどの定義した文法をコンストラクタに渡してパーサーを生成します。 ```cpp + // 文法を読み込んでパーサーを生成 peglib::peg parser(grammar); + // 文法に誤りがあったかチェック if (!parser) { cerr << "grammar error..." << endl; return -1; } ``` -文法にエラーがあると、上記のように`parser`オブジェクトの真偽値はfalseになります。 +文法にエラーがあると、上記のように`parser`オブジェクトの真偽値が'false'になります。 -最後に`parser.parse`メソッドを呼び出して、ユーザーの入力した文字列をパースします。そしてコードに構文エラーがあるかどうかを、メソッドの戻り値が教えてくれます。 +最後に`parser.parse`メソッドを呼び、ユーザーの入力した文字列をパースします。成功すると`true`が返ります。 ```cpp + // ユーザーからの入力をパース if (parser.parse(line.c_str())) { cout << "OK" << endl; } else { @@ -134,15 +130,15 @@ const auto grammar = R"( } ``` -ではこれを起点として,より実用的な言語を作っていきましょう。言語の作成には次のようなステップが必要です。 +これでおしまいです。ではこれを起点として,より実用的な言語を作っていきましょう。インタープリタ型言語の作成には,次のようなステップが必要です。 - 1. 文法を定義する(PEGの表記を使う。もちろん自分で書きます) - 2. パーサーを生成する(PEGライブラリを使う) - 3. ソースコードをパースして、構文木(AST―Abstract Syntax Tree)を生成する - 4. ASTを実行する(ここも自分でコードを書かないといけません) + 1. 言語の文法を定義する + 2. パーサーを生成する + 3. ソースコードをパースして、AST(Abstract Syntax Tree―構文木)を生成する + 4. ASTを実行するインタープリタを作成する -これからの章では,それぞれのステップを順を追って説明していきます。その際,実際にコードを動かしたり,自分で拡張していくなら,より一層理解が深まるに違いありません。Happy Hacking! +PEGパーサーライブラリはステップ2と3のみ扱います。それでステップ1と2は自分で扱わなければなりません。でもこの文法定義とインタープリタ作成の部分が一番面白いところで,言語に個性を与えるオリジナリティを出せるところだと思います。 --- +これからの章では,それぞれのステップを順を追って説明していきます。その際,実際にコードを動かしたり拡張していくなら,より一層理解が深まるに違いありません。 -次章では、PEGでどのように言語の文法をデザインしていくかを見てみましょう。 +Happy Hacking! diff --git a/tutorial/chap_02.md b/tutorial/chap_02.md new file mode 100644 index 0000000..f8e8735 --- /dev/null +++ b/tutorial/chap_02.md @@ -0,0 +1,81 @@ +# PEGで言語をデザインしてみよう + +PEGについて一言で説明しなさいと言われたら,「言語を定義するための言語」と答えるつもりです。パーサジェネレータは,このPEGを使って定義されたプログラミング言語の厳密な文法定義を読み込んで,パーサを生成することができます。 + +この「PEG言語」はC++やJavaScriptなどの処理手続きを記述する言語とはずいぶん毛色が違います。むしろ正規表現,XML Schemer,DBのテーブルスキーマのような「規則を定義」するタイプの言語に似ています。 + +この章では,次の「言語」をPEGを使って定義してみます。 + + * 日本語の「文」 + * カンマ区切り行 + * 括弧付き四則計算式 + * JSON + * JavaScriptの小さなサブセット + +下に行くほど文法定義が難しくなり,PEGのより多くの記法を使う必要が出てきます。これらの例が終わる頃には,簡単なスクリプト言語を自分でデザインできるようになるでしょう。(もちろん文法を定義しただけなので,コードを動きませんが。^^;) + +PEGの記法については,この章を読み進めるうちに自然に学べるように書いているつもりです。しかし時間があれば,kimizuさんの[「PEG基礎文法最速マスター」][Link_BasicGrammar]をご覧ください。PEGの記法について,とても簡潔にわかりやすくまとめられています。下記の例題を始める前に一読するなら,理解が確実に速まること間違いなしです。 + +「習うより慣れろ」と古くから言われるように,実際に手を動かしてコードを書くことは理解を深めるための早道です。その際[「PEG Playground」](http://yhirose.github.io/peglint/)ご活用ください。このWeb アプリは,PEGの文法定義コードと定義される言語のコードをリアルタイムに行ってくれます。 + +ではさっそく一番目の例から見てみましょう。 + +## 日本語の「文」を定義する + +文法と聞くと,遠い昔の国語の授業を懐かしく思い出します。ではPEGを使って,簡単な「文」のルールを作ってみましょう。こんな感じでどうでしょうか? + + 文 <- 主語 述語 '。' + +「文」は,連続する「主語」「述語」「'。'」で成り立っています。さらに「主語」と「述語」は, + + 主語 <- 名詞 助詞 + 述語 <- 動詞 助詞 + +と品詞を使って定義してみました。それぞれの品詞に実際の言葉を幾つか入れてみましょう。 + + 名詞 <- 'サーバー' / 'クライアント' + 動詞 <- '落ち' / '復旧し' + 助詞 <- 'が' / 'を' / 'た' / 'ます' + +ではこのルールにマッチする文を考えましょう。例えば, + + サーバーが落ちた。 + +悲しいですが,この文はルールにマッチします。 + + サーバーを復旧します。 + +希望が見えてきました。 + + プログラムが落ちた。 + +プログラムが「名詞」のリストにないので,マッチしません! + +文法を少し修正しましょう。 + + 文 <- 修飾語? 主語 述語 '。' + 名詞 <- 'サーバー' / 'クライアント' + 形容詞 <- '古い' / '新しい' + 動詞 <- '落ち' / '復旧し' + 助詞 <- 'が' / 'を' / 'た' / 'ます' / 'に' + +「文」ルールに「修飾語」を追加しました。`?`が付いていますが,これは「出現してもしなくてもよい」という意味です。また「形容詞」ルールも加わりました。これで次のような文全てがマッチします。 + + サーバーが落ちた。 + 古いサーバーが落ちた。 + サーバーを復旧します。 + 新しいサーバーに復旧します。 + +PEGの感覚がつかめてきましたか? + +さて,ここまでで使用した4つの記法についておさらいしましょう。 + +| 記法 | 意味 | | +|:-----|:---------------------------------------------| +| <- | ルールの定義 | 右側がルール名,左側はPEGの式 | +| / | 選択|右から左へ順番にマッチを試みる | +| ? | 0回または1回 | | +| '' | 文字列リテラル | | + + +[Link_BasicGrammar]: http://kmizu.hatenablog.com/entry/20100203/1265183754 diff --git a/tutorial/intro.md b/tutorial/intro.md index ee8aac2..a14b963 100644 --- a/tutorial/intro.md +++ b/tutorial/intro.md @@ -1,16 +1,30 @@ -前書き -===== +# 前書き -今この本をに目を通してくださっているみなさんは、「ぜひプログラミング言語を作ってみたい!」と考えている方のお一人でしょう。でも自分で言語を作ってみるなんて敷居が高過ぎるのでは?何を隠そう少し前の私も,『コンパイラ』と名の付く技術書をパラっと眺める度に,その内容の難しさに恐れを抱きました。orz... +今この文章をに目を通してくださっているみなさんは、「いつか自分でプログラミング言語を作ってみたい!」と思ったことがあるかもしれません。でもほとんどの人は「正直言って,言語を作るなんて自分には敷居が高過ぎる」とあきらめてしまいます。ご安心を! -しばらく前のこと,仕事で数十万行にも及ぶ大きなデータファイルを作成する必要がありました。エディタを使って手作業するなら軽く数ヶ月かかってしまいます。それでは大変なので,設定ファイルを定義してプログラムにデータを生成させようということになりました。数千行の設定ファイルを作れば,ものの数分で膨大なデータを生成してくれるのです。 +何を隠そうこれまでの私も,「コンパイラ」とか「インタープリタ」名の付く技術書を幾つも眺めてきました。「字句解析,構文解析,インタープリタ,VM…」など,本に書かれている概念自体はある程度理解できるものの,それを実装して実際に動くものを作るのは別問題です。 -はたして「プログラミング言語のようなもの」を作ることになりました。「さて,どう実装する? YACCを使う? パーサーを手書きで書く? でも面倒で難しそう...」と悩みつつWebで検索していたところ,偶然[『PEG』](https://ja.wikipedia.org/wiki/Parsing_Expression_Grammar)という可愛らしい言葉が目に入りました。PEGとは「Parsing Expression Grammar」の略号です。調べてみると,難しそうなYACCより簡単に文法が定義でき,かつPEGをサポートするライブラリがパーサーを生成してくれるのです。さらに調べてみると,PEGライブラリ自体が誰にでも比較的簡単に実装可能ということも発見しました! +でもしばらく前のこと,仕事で数十万行にも及ぶ大きなデータファイルを作成する必要がありました。エディタを使って手作業するなら軽く数ヶ月かかってしまいます。それでは大変なので,設定ファイルを定義してプログラムにデータを生成させようということになりました。数千行の特別な書式の設定ファイルを準備すれば,ものの数分で膨大かつ正確なデータを生成してくれるのです。 -それでWeb上の記事をいろいろ漁りながら,仕事で使っているC++用のPEGライブラリを見よう見まねで作り始めました。はい,コンピュータサイエンスを正式に学ぶことのなかった私でも、意外とあっさり実装できました。そのあと設定ファイルの文法をデザインし(これがまたとても面白い!),そのパーサーをPEGライブラリでいとも簡単に作ることができました。嬉しいことに、仕事のプロジェクトはとてもうまくいきました。 +はたして「プログラミング言語のようなもの」を作ることになりました。まずは設定ファイルをパースしなければなりません。「さて,パーサをどう実装する? Yaccのようなパーサージェネレータ使う? パーサーを手書きで書く? いずれにしても面倒で難しそう…」と悩みつつWebで検索していたところ,偶然[『PEG』][Link_PEG]という言葉を目にしました。PEGとは「Parsing Expression Grammar」の略号です。調べてみると,Yaccより簡単に扱えるパーサージェネレータだそうです。さらに調べていくとPEGパーサジェネレータ自体を実装する記事がWeb上に幾つもあり,誰でも比較的簡単に実装できるということがわかりました! -これが契機となって、「もう少し頑張れば、自分にもプログラミング言語を作ることができるかも」と思えるようになりました。文法定義とパーサーの生成がとても簡単になったので,言語作成の敷居がぐっと下がったように感じたのです。さらにこのPEGライブラリをもっと使いやすくなるよう改良を重ね,実際にミニプログラミング言語を作って動かすことができました。これがまたとても面白い! +さっそく日々の仕事で使っているC++で,PEGパーサジェネレータを作り始めました。はい,コンパイラ作成の授業など取ったことのなかった私でも,意外にあっさり実装できてしまいました。その後,先の設定ファイルの文法をPEGでデザインし(これがまたとても面白い!),そのパーサーをPEGライブラリでいとも簡単に作ることができました。嬉しいことに、仕事のプロジェクトはとても上手くいきました。 -恥ずかしい話ですが,今でも「字句解析,構文解析,構文木,意味解析,コード生成,最適化...」について、そこそこの深い知識しか持ち合わせていません。PEGの[オリジナルの論文](http://bford.info/pub/lang/peg)理論も完璧に理解しているわけではありません。それでも言語処理系を作ることの楽しさを十分経験でき,さらにこの分野をもっと勉強したいというモチベーションを得ることもできたことは大きな収穫でした。 +これが契機となって、「もう少し頑張れば、自分にもプログラミング言語を作ることができるかも」と思えるようになりました。文法定義とパーサーの生成がとても簡単になったので,言語作成の敷居がぐっと下がったように感じたのです。さらにこのPEGパーサジェネレータ改良を重ね,実際にシンプルな言語を作って動かすことができるようになりました! -皆さんにもこの同じ喜びを味わっていただきたと思い,この文章を書いています。必要なのはテキストエディタ(当然Vimですよね?)とC++11対応のコンパイラだけです。最新のClang,GCC,Visual C++をお持ちであれば,さっそく始めましょう! +正直なところ,今でも「コンパイラ技術」についての深い知識は持ち合わせていません。PEGの[オリジナルの論文][Link_Paper]に書かれている理論も完璧に理解しているわけではありません。それでも言語処理系を作ることの楽しさを十分味わい,この分野についてもっと勉強したいというモチベーションを得ることもできたことは素晴らしい経験でした。 + +皆さんにもこの同じ喜びを味わっていただきたと思い,この文章を書いています。想定している読者は,ある程度のプログラミングの経験を持っていて,自分で言語を創りあげたいとの熱意を持っている方です。 + +必要な道具はC++11対応のコンパイラだけです。最新の主要C++コンパイラは,Clang,GCC,Visual C++にかかわらず,全てC++11に対応しています。以下のおなじみHello Worldコードをコンパイル・実行できる環境があれば,準備は完了です。 + +```cpp +#include +int main(void) { + std::cout << "hello world!\n"; +}``` + +では,さっそく始めましょう! + +[Link_PEG]: https://ja.wikipedia.org/wiki/Parsing_Expression_Grammar +[Link_Paper]: http://bford.info/pub/lang/peg diff --git a/tutorial/toc.md b/tutorial/toc.md index 3754e09..66925b0 100644 --- a/tutorial/toc.md +++ b/tutorial/toc.md @@ -7,4 +7,4 @@ ## 章 1. [まずは,Hello World!](chap_01.md) - 2. [PEGで言語の文法をデザインする](chap_02.md) + 2. [PEGで言語をデザインしてみよう](chap_02.md)