Updated tutorial.

This commit is contained in:
yhirose 2015-08-07 23:50:27 -04:00
parent b035706a0b
commit 7b0ad8a747
4 changed files with 115 additions and 78 deletions

View File

@ -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 <iostream>
#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("> ");
// 文法を読み込んでパーサーを生成
peglib::peg parser(grammar);
// 空行ならスキップ
if (line.empty()) {
// 文法に誤りがあったかチェック
if (!parser) {
cerr << "grammar error..." << endl;
return -1;
}
while (true) {
// 文字列を一行読み込み
cout << "> ";
string line;
getline(cin, line);
if (line == "quit") {
break;
}
// 入力テキストをそのまま出力
cout << "echo: '" << line << "'" << endl;
// 履歴に追加
linenoise::AddHistory(line.c_str());
// ユーザーからの入力をパース
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`を使いました。
このコードを`hello.cc`に保存してそれからコンパイルしてみましょう。PEG Parser Libraryを使用するので[ここから](https://raw.githubusercontent.com/yhirose/cpp-peglib/master/peglib.h)`peglib.h`ダウンロードして,`hello.cc`があるディレクトリに保存してください。
コンパイルにはC++11の機能を有効にする設定が必要です。わたしは普段`clang++`のパージョン3.5を使っているので,こんな感じになります。
```
wget https://raw.githubusercontent.com/yhirose/cpp-linenoise/master/linenoise.hpp
clang++ -std='c++11' -o hello hello.cc
```
続いてコンパイルです。わたしは`clang++`のパージョン3.5を使っています。
g++ 5.1でもほとんど同じで,
```
clang++ -std='c++11' -o repl repl.cc
```
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`を実行してみましょう。
```
./repl
```
REPLのプロンプト`>`が表示されるので,何か入力してリターンキーを押してみましょう。
では生成された実行ファイルを実行してみましょう。すると画面に`>`が表示されユーザーに入力を促します。文字列を入力してリターンキーを押してみましょう。正しく「hello world!」と入力すると「OK」と表示されます。間違うと「NG」です。ちなみに「 Hello World! 」もOKです。
```
> はろーわーるど!
echo: 'はろーわーるど!'
> _
NG
> hello world!
OK
> Hello World!
OK
```
プログラミング入門の定番「はろーわーるど!」が表示されました!
どうでしょう見事にPEG版Hello Worldが動きましたプログラムを終了したい時は`quit`を入力するか,`Ctrl+C`を押してください。)
実は入力したテキストは,履歴リストに追加されています。`Ctrl+P`か`↑`キーを押してみてください。
--
```
> はろーわーるど_
では,このプログラムを順を追って見てみましょう。
まずはPEGライブラリを読み込みます。`peglib.h`はC++ header-only libraryですので、libファイルをリンクする必要はありません。
```cpp
#include "peglib.h"
```
このように`linenoise`ライブラリを使うと,以前に入力したテキストを呼び出すことができます。長い文字列を再度入力しなければならない時などはとても便利です。(`linenoise`に関する情報は[Githubのプロジェクトページ](https://github.com/yhirose/cpp-linenoise)をご覧ください。)
続いてPEGで文法を定義します。今回は「Hello World!」という文字列を受け付けるとても簡単な文法です。言葉の先頭は大文字でも小文字でも受け入れてくれます。「_」は0以上長さのスペース文字やタブ文字つまり「空白」を定義しています。入力文字列の前後や「hello」と「world」の間には空白入れることができます。(ちなみに「world」と「!」の間には空白をいれることができません。)
`Ctrl+C`を押すとREPLから抜けることができます。
```cpp
const auto grammar = R"(
PROGRAM <- _ HELLO _ WORLD '!' _
HELLO <- [hH] 'ello'
WORLD <- [wW] 'orld'
_ <- [ \t]*
)";
```
まだPEGについて何も出てきませんねとりあえずPEGライブラリを[ここから](https://raw.githubusercontent.com/yhirose/cpp-peglib/master/peglib.h)ダウンロードして,後の章のために準備しておきましょう。このファイル`peglib.h`も同じディレクトリに保存してください。
脇にそれますがこの文法には一つ大きなバグがあります。実は「helloworld!」のように言葉をくっつけて入力しても結果が「OK」になってしまいます。というのは「HELLO _ WORLD」の中の「_」は「0以上」を意味する「*」を使っているので長さ0の空文字列にもマッチしてしまうからです。このバグの解決法は後ほど改めてしたいと思います
これで必要な準備が終わりました!
この文法を理解する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;
}
```
ではこれを起点として,より実用的な言語を作っていきましょう。言語の作成には次のようなステップが必要です。
1. 文法を定義するPEGの表記を使う。もちろん自分で書きます
2. パーサーを生成するPEGライブラリを使う
3. ソースコードをパースして、構文木AST―Abstract Syntax Treeを生成する
4. ASTを実行するここも自分でコードを書かないといけません
これからの章ではそれぞれのステップを順を追って説明していきます。その際実際にコードを動かしたり自分で拡張していくならより一層理解が深まるに違いありません。Happy Hacking!
--
次章では、PEGでどのように言語の文法をデザインしていくかを見てみましょう。

View File

@ -1,24 +0,0 @@
#include <iostream>
#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;
}

View File

@ -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対応のコンパイラだけです。最新のClangGCCVisual C++をお持ちであれば,さっそく始めましょう!

View File

@ -4,7 +4,7 @@
* [序文](intro.md)
* [目次](toc.md)
--
## 章
1. [はろーわーるど!](chap_01.md)
1. [まずはHello World!](chap_01.md)
2. [PEGで言語の文法をデザインする](chap_02.md)