diff --git a/tutorial/chap_01.md b/tutorial/chap_01.md index 6f90b01..83c095c 100644 --- a/tutorial/chap_01.md +++ b/tutorial/chap_01.md @@ -1,87 +1,148 @@ -はろーわーるど! -============== +まずは,Hello World! +=================== -まずはRubyでお馴染みの`irb`のようなREPL(Read-Eval-Print-Loop)環境を実現します。以下のソースコードを`repl.cc`に保存します。 +プログラマーあれば,何はともあれ,まずは「Hello World」ですよね。それでは,「hello world!」と正しく入力できたら「OK」,そうでなければ「NG」と表示するプログラムを作ってみましょう! + +これがソースコードです。 ```cpp +// hello.cc #include -#include "linenoise.hpp" +#include "peglib.h" using namespace std; +// 文法定義 +const auto grammar = R"( + PROGRAM <- _ HELLO _ WORLD '!' _ + HELLO <- [hH] 'ello' + WORLD <- [wW] 'orld' + _ <- [ \t]* +)"; + int main(void) { - while (true) { - // 一行入力 - auto line = linenoise::Readline("> "); - - // 空行ならスキップ - if (line.empty()) { - break; - } - - // 入力テキストをそのまま出力 - cout << "echo: '" << line << "'" << endl; - - // 履歴に追加 - linenoise::AddHistory(line.c_str()); - } - return 0; + // 文法を読み込んでパーサーを生成 + peglib::peg parser(grammar); + + // 文法に誤りがあったかチェック + if (!parser) { + cerr << "grammar error..." << endl; + return -1; + } + + while (true) { + // 文字列を一行読み込み + cout << "> "; + string line; + getline(cin, line); + + if (line == "quit") { + break; + } + + // ユーザーからの入力をパース + if (parser.parse(line.c_str())) { + cout << "OK" << endl; + } else { + cout << "NG" << endl; + } + } + + return 0; } ``` -次に`linenoise.hpp`を[ここから](https://raw.githubusercontent.com/yhirose/cpp-linenoise/master/linenoise.hpp)ダウンロードして,`repl.cc`と同じディレクトリに保存します。私はこんな感じで`wget`を使いました。 - -``` -wget https://raw.githubusercontent.com/yhirose/cpp-linenoise/master/linenoise.hpp -``` +このコードを`hello.cc`に保存して,それからコンパイルしてみましょう。PEG Parser Libraryを使用するので,[ここから](https://raw.githubusercontent.com/yhirose/cpp-peglib/master/peglib.h)`peglib.h`ダウンロードして,`hello.cc`があるディレクトリに保存してください。 -続いてコンパイルです。わたしは`clang++`のパージョン3.5を使っています。 +コンパイルにはC++11の機能を有効にする設定が必要です。わたしは普段`clang++`のパージョン3.5を使っているので,こんな感じになります。 ``` -clang++ -std='c++11' -o repl repl.cc +clang++ -std='c++11' -o hello hello.cc ``` -g++ 5.1では, +g++ 5.1でもほとんど同じで, ``` -g++ -std='c+11 -o repl repl.cc' +g++ -std='c+11' -o hello hello.cc' ``` Visual C++ 14 (Visual Studio 2015) では, ``` -cl -DUNICODE /EHsc repl.cc User32.lib +cl -DUNICODE /EHsc hello.cc User32.lib ``` とするとうまくいきました。 -では生成された実行ファイル`repl`を実行してみましょう。 +では生成された実行ファイルを実行してみましょう。すると画面に`>`が表示され,ユーザーに入力を促します。文字列を入力してリターンキーを押してみましょう。正しく「hello world!」と入力すると「OK」と表示されます。間違うと「NG」です。ちなみに「 Hello World! 」もOKです。 ``` -./repl +> はろーわーるど! +NG +> hello world! +OK +> Hello World! +OK ``` -REPLのプロンプト`>`が表示されるので,何か入力してリターンキーを押してみましょう。 +どうでしょう,見事にPEG版Hello Worldが動きました!(プログラムを終了したい時は`quit`を入力するか,`Ctrl+C`を押してください。) +-- + +では,このプログラムを順を追って見てみましょう。 + +まずはPEGライブラリを読み込みます。`peglib.h`はC++ header-only libraryですので、libファイルをリンクする必要はありません。 + +```cpp +#include "peglib.h" ``` -> はろーわーるど! -echo: 'はろーわーるど!' -> _ + +続いてPEGで文法を定義します。今回は「Hello World!」という文字列を受け付けるとても簡単な文法です。言葉の先頭は大文字でも小文字でも受け入れてくれます。「_」は0以上長さのスペース文字やタブ文字,つまり「空白」を定義しています。入力文字列の前後や,「hello」と「world」の間には空白入れることができます。(ちなみに「world」と「!」の間には空白をいれることができません。) + +```cpp +const auto grammar = R"( + PROGRAM <- _ HELLO _ WORLD '!' _ + HELLO <- [hH] 'ello' + WORLD <- [wW] 'orld' + _ <- [ \t]* +)"; ``` -プログラミング入門の定番「はろーわーるど!」が表示されました! +脇にそれますが,この文法には一つ大きなバグがあります。実は「helloworld!」のように言葉をくっつけて入力しても結果が「OK」になってしまいます。というのは「HELLO _ WORLD」の中の「_」は「0以上」を意味する「*」を使っているので,長さ0の空文字列にもマッチしてしまうからです。このバグの解決法は後ほど改めてしたいと思います。 -実は入力したテキストは,履歴リストに追加されています。`Ctrl+P`か`↑`キーを押してみてください。 +この文法を理解するPEGパーサーを生成しましょう。`peglib::peg`がパーサーです。先ほどの定義した文法をコンストラクタに渡してパーサーを生成します。 + +```cpp + peglib::peg parser(grammar); + if (!parser) { + cerr << "grammar error..." << endl; + return -1; + } ``` -> はろーわーるど!_ + +文法にエラーがあると、上記のように`parser`オブジェクトの真偽値はfalseになります。 + +最後に`parser.parse`メソッドを呼び出して、ユーザーの入力した文字列をパースします。そしてコードに構文エラーがあるかどうかを、メソッドの戻り値が教えてくれます。 + +```cpp + if (parser.parse(line.c_str())) { + cout << "OK" << endl; + } else { + cout << "NG" << endl; + } ``` -このように`linenoise`ライブラリを使うと,以前に入力したテキストを呼び出すことができます。長い文字列を再度入力しなければならない時などはとても便利です。(`linenoise`に関する情報は[Githubのプロジェクトページ](https://github.com/yhirose/cpp-linenoise)をご覧ください。) +ではこれを起点として,より実用的な言語を作っていきましょう。言語の作成には次のようなステップが必要です。 + + 1. 文法を定義する(PEGの表記を使う。もちろん自分で書きます) + 2. パーサーを生成する(PEGライブラリを使う) + 3. ソースコードをパースして、構文木(AST―Abstract Syntax Tree)を生成する + 4. ASTを実行する(ここも自分でコードを書かないといけません) -`Ctrl+C`を押すとREPLから抜けることができます。 +これからの章では,それぞれのステップを順を追って説明していきます。その際,実際にコードを動かしたり,自分で拡張していくなら,より一層理解が深まるに違いありません。Happy Hacking! -まだPEGについて何も出てきませんね...とりあえずPEGライブラリを[ここから](https://raw.githubusercontent.com/yhirose/cpp-peglib/master/peglib.h)ダウンロードして,後の章のために準備しておきましょう。このファイル`peglib.h`も同じディレクトリに保存してください。 +-- -これで必要な準備が終わりました! +次章では、PEGでどのように言語の文法をデザインしていくかを見てみましょう。 diff --git a/tutorial/code/repl.cc b/tutorial/code/repl.cc deleted file mode 100644 index cf876cc..0000000 --- a/tutorial/code/repl.cc +++ /dev/null @@ -1,24 +0,0 @@ -#include -#include "linenoise.hpp" - -using namespace std; - -int main(void) -{ - while (true) { - // テキスト入力 - auto line = linenoise::Readline("> "); - - // 空行ならスキップ - if (line.empty()) { - break; - } - - // 入力テキストをそのまま出力 - cout << "echo: '" << line << "'" << endl; - - // 履歴に追加 - linenoise::AddHistory(line.c_str()); - } - return 0; -} diff --git a/tutorial/intro.md b/tutorial/intro.md index 71a2ab4..ee8aac2 100644 --- a/tutorial/intro.md +++ b/tutorial/intro.md @@ -1,16 +1,16 @@ 前書き ===== -今この本を手にとっているみなさんは「ぜひプログラミング言語を作ってみたい! 」と考えている方のお一人かもしれません。でも自分で言語を作ってみるなんて敷居が高過ぎる? 何を隠そう少し前の私も,『コンパイラ』と名の付く技術書をパラっと眺める度に,その理論の難解さに恐れを抱きました。Orz... +今この本をに目を通してくださっているみなさんは、「ぜひプログラミング言語を作ってみたい!」と考えている方のお一人でしょう。でも自分で言語を作ってみるなんて敷居が高過ぎるのでは?何を隠そう少し前の私も,『コンパイラ』と名の付く技術書をパラっと眺める度に,その内容の難しさに恐れを抱きました。orz... -少し前,仕事で数十万行にも及ぶ大きなデータファイルを作成する必要がありました。エディタを使って手作業するなら軽く数ヶ月かかってしまいます。それでは大変なので,設定ファイルを定義してプログラムにデータを生成させようということになりました。数千行の設定ファイルを作れば,ものの数分で膨大なデータを生成してくれるのです。 +しばらく前のこと,仕事で数十万行にも及ぶ大きなデータファイルを作成する必要がありました。エディタを使って手作業するなら軽く数ヶ月かかってしまいます。それでは大変なので,設定ファイルを定義してプログラムにデータを生成させようということになりました。数千行の設定ファイルを作れば,ものの数分で膨大なデータを生成してくれるのです。 -はたして「プログラミング言語のようなもの」を作ることになりました。「さて,どう実装する? YACCを使う? パーサーを手書きで書く? でも面倒で難しそう...」と悩みつつWebで検索していたところ,偶然[『PEG』](https://ja.wikipedia.org/wiki/Parsing_Expression_Grammar)という可愛らしい言葉が目に入りました。PEGとは「Parsing Expression Grammar」の略号です。調べてみると,難しそうなYACCより簡単に文法が定義でき,かつPEGをサポートするライブラリがパーサーを生成してくれるのです。さらに調べてみると,PEGライブラリ自体が比較的簡単に実装可能ということも発見しました! +はたして「プログラミング言語のようなもの」を作ることになりました。「さて,どう実装する? YACCを使う? パーサーを手書きで書く? でも面倒で難しそう...」と悩みつつWebで検索していたところ,偶然[『PEG』](https://ja.wikipedia.org/wiki/Parsing_Expression_Grammar)という可愛らしい言葉が目に入りました。PEGとは「Parsing Expression Grammar」の略号です。調べてみると,難しそうなYACCより簡単に文法が定義でき,かつPEGをサポートするライブラリがパーサーを生成してくれるのです。さらに調べてみると,PEGライブラリ自体が誰にでも比較的簡単に実装可能ということも発見しました! -とりあえずいろんなWebの記事を漁りながら,仕事で使っているC++用のPEGライブラリを見よう見まねで作り始めました。はい,意外とあっさり実装できました。そのあと,設定ファイルの文法をデザインし(これがとても面白い!),そのパーサーをPEGライブラリでいとも簡単に作ることができました。 +それでWeb上の記事をいろいろ漁りながら,仕事で使っているC++用のPEGライブラリを見よう見まねで作り始めました。はい,コンピュータサイエンスを正式に学ぶことのなかった私でも、意外とあっさり実装できました。そのあと設定ファイルの文法をデザインし(これがまたとても面白い!),そのパーサーをPEGライブラリでいとも簡単に作ることができました。嬉しいことに、仕事のプロジェクトはとてもうまくいきました。 -これが契機となって「自分にもプログラミング言語を作ることができるかも」と思えるようになりました。文法定義とパーサーの生成がとても簡単になったので,言語作成の敷居がぐっと下がったように感じたのです。さらにこのPEGライブラリをもっと使いやすくなるよう改良を重ね,実際に簡単な言語を作ってみました。これがとても面白い! +これが契機となって、「もう少し頑張れば、自分にもプログラミング言語を作ることができるかも」と思えるようになりました。文法定義とパーサーの生成がとても簡単になったので,言語作成の敷居がぐっと下がったように感じたのです。さらにこのPEGライブラリをもっと使いやすくなるよう改良を重ね,実際にミニプログラミング言語を作って動かすことができました。これがまたとても面白い! -恥ずかし話ですが,「字句解析,構文解析,構文木,意味解析,コード生成,最適化...」などの言語処理系の深い専門知識は今もって持ち合わせていません。PEGの[オリジナルの論文](http://bford.info/pub/lang/peg)も完璧に理解しているわけではありません。それでも言語処理系を作ることの楽しさを十分経験でき,さらにこの分野をもっと勉強したいというモチベーションを得ることもできました。 +恥ずかしい話ですが,今でも「字句解析,構文解析,構文木,意味解析,コード生成,最適化...」について、そこそこの深い知識しか持ち合わせていません。PEGの[オリジナルの論文](http://bford.info/pub/lang/peg)理論も完璧に理解しているわけではありません。それでも言語処理系を作ることの楽しさを十分経験でき,さらにこの分野をもっと勉強したいというモチベーションを得ることもできたことは大きな収穫でした。 皆さんにもこの同じ喜びを味わっていただきたと思い,この文章を書いています。必要なのはテキストエディタ(当然Vimですよね?)とC++11対応のコンパイラだけです。最新のClang,GCC,Visual C++をお持ちであれば,さっそく始めましょう! diff --git a/tutorial/toc.md b/tutorial/toc.md index baf6693..3754e09 100644 --- a/tutorial/toc.md +++ b/tutorial/toc.md @@ -4,7 +4,7 @@ * [序文](intro.md) * [目次](toc.md) -章 --- +## 章 - 1. [はろーわーるど!](chap_01.md) + 1. [まずは,Hello World!](chap_01.md) + 2. [PEGで言語の文法をデザインする](chap_02.md)