Updated tutorial.

pull/3/head
yhirose 9 years ago
parent 337741e17e
commit 51ac645969
  1. 64
      tutorial/chap_01.md
  2. 36
      tutorial/chap_02.md
  3. 0
      tutorial/chap_04.md
  4. 6
      tutorial/toc.md

@ -1,6 +1,6 @@
# まずは,Hello world # まずは"Hello world!"
はじめに取り組むプログラムは,もちろん**Hello world**ですよね。 はじめに取り組むプログラムは,もちろん**Hello world!**ですよね。
では「hello world!」と正しく入力できたら「OK」,そうでなければ「NG」と表示するプログラムを作ってみましょう!以下がソースコードです。 では「hello world!」と正しく入力できたら「OK」,そうでなければ「NG」と表示するプログラムを作ってみましょう!以下がソースコードです。
@ -8,47 +8,48 @@
// hello.cc // hello.cc
#include <iostream> #include <iostream>
#include "peglib.h" #include "peglib.h"
#include "linenoise.hpp"
using namespace std; using namespace std;
// 文法定義
const auto grammar = R"(
PROGRAM <- _ 'hello' _ 'world' '!' _
_ <- [ \t]*
)";
int main(void) int main(void)
{ {
// 文法を読み込んでパーサーを生成 // 文法を読み込んでパーサーを生成
peg::parser parser(grammar); peg::parser parser(R"(
PROGRAM <- _ HELLO _ WORLD '!' _
// 文法に誤りがあったかチェック HELLO <- [hH] 'ello'
if (!parser) { WORLD <- [wW] 'orld'
_ <- [ \t]*
)");
if (!parser) { // 文法に誤りがあったかチェック
cerr << "grammar error..." << endl; cerr << "grammar error..." << endl;
return -1; return -1;
} }
while (true) { while (true) {
// 文字列を一行読み込み auto line = linenoise::Readline("> "); // 文字列を一行読み込み
cout << "> ";
string line; if (line == "exit") { break; } // 終了
getline(cin, line);
// ユーザーからの入力をパース if (parser.parse(line.c_str())) { // ユーザーからの入力をパース
if (parser.parse(line.c_str())) {
cout << "OK" << endl; cout << "OK" << endl;
} else { } else {
cout << "NG" << endl; cout << "NG" << endl;
} }
linenoise::AddHistory(line.c_str()); // 入力履歴に追加
} }
return 0; return 0;
} }
``` ```
このコードを`hello.cc`に保存して,それからコンパイルしてみましょう。このコードはPEGパーサライブラリを使用するので,[ここから](https://raw.githubusercontent.com/yhirose/cpp-peglib/master/peglib.h)`peglib.h`ダウンロードして`hello.cc`があるディレクトリに保存してください。 このコードを`hello.cc`として保存してください。このコードは以下の2つのライブラリを使用するので,ダウンロードして`hello.cc`と同じディレクトリに保存してください。
* cpp-peglib C++ PEG parser library - [peblib.h](https://raw.githubusercontent.com/yhirose/cpp-peglib/master/peglib.h)
* cpp-linenoise C++ Readline library - [linenoise.hpp](https://raw.githubusercontent.com/yhirose/cpp-linenoise/master/linenoise.hpp)
コンパイル時には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
@ -73,7 +74,7 @@ OK
OK OK
``` ```
見事にPEG版Hello worldをクリアです!(プログラムを終了したい時は`Ctrl+C`を押してください。) 見事にPEG版Hello worldをクリアです!(プログラムを終了したい時は`exit`を入力してください。)
-- --
@ -87,21 +88,23 @@ OK
続いてPEGで文法を定義します。この文法は「hello world!」という文字列を受け付けるだけのとても簡単なものです。入力文字列の前後や「hello」と「world」の間には,任意の長さのスペースやタブを入れることができます。(ちなみに「world」と「!」の間には入れることができません。) 続いてPEGで文法を定義します。この文法は「hello world!」という文字列を受け付けるだけのとても簡単なものです。入力文字列の前後や「hello」と「world」の間には,任意の長さのスペースやタブを入れることができます。(ちなみに「world」と「!」の間には入れることができません。)
```cpp ```
const auto grammar = R"(
PROGRAM <- _ 'hello' _ 'world' '!' _ PROGRAM <- _ 'hello' _ 'world' '!' _
_ <- [ \t]* _ <- [ \t]*
)";
``` ```
この文法を理解するPEGパーサーを生成しましょう。`peglib::peg`がパーサーです。先ほどの定義した文法をコンストラクタに渡してパーサーを生成します。 この文法を理解するPEGパーサーを生成しましょう。`peglib::peg`がパーサーです。先ほどの定義した文法をコンストラクタに渡してパーサーを生成します。
```cpp ```cpp
// 文法を読み込んでパーサーを生成 // 文法を読み込んでパーサーを生成
peg::parser parser(grammar); peg::parser parser(R"(
PROGRAM <- _ HELLO _ WORLD '!' _
// 文法に誤りがあったかチェック HELLO <- [hH] 'ello'
if (!parser) { WORLD <- [wW] 'orld'
_ <- [ \t]*
)");
if (!parser) { // 文法に誤りがあったかチェック
cerr << "grammar error..." << endl; cerr << "grammar error..." << endl;
return -1; return -1;
} }
@ -112,8 +115,7 @@ const auto grammar = R"(
最後に`parse`メソッドを呼び、ユーザーの入力した文字列をパースします。成功すると`true`が返ります。 最後に`parse`メソッドを呼び、ユーザーの入力した文字列をパースします。成功すると`true`が返ります。
```cpp ```cpp
// ユーザーからの入力をパース if (parser.parse(line.c_str())) { // ユーザーからの入力をパース
if (parser.parse(line.c_str())) {
cout << "OK" << endl; cout << "OK" << endl;
} else { } else {
cout << "NG" << endl; cout << "NG" << endl;

@ -1,6 +1,6 @@
# はじめてのPEG # PEG記法を学ぼう
PEGは言語の文法を定義するための言語で、2004年に[Bryan Ford][Link_Ford]によって発表されました。記法はBNFに似ていますが、言語の構文を定義するだけでなく字句の定義も同一のファイルでに含めることができます。 PEGは言語の文法を定義するための言語で、2004年に[Bryan Ford][Link_Ford]によって発表されました。記法はEBNFに似ていますが、言語の構文を定義するだけでなく字句の定義も同一のファイルでに含めることができます。
PEGの文法ファイルは「構文規則名(N) <- (e)で表される複数の構文規則で構成されます例えば簡単な日付フォーマットはこんな感じで定義できるでしょう PEGの文法ファイルは「構文規則名(N) <- (e)で表される複数の構文規則で構成されます例えば簡単な日付フォーマットはこんな感じで定義できるでしょう
@ -32,9 +32,25 @@ PEGパーサーは,この文法ファイルの開始規則(DATE)に対し
* `&e`(肯定先読み) * `&e`(肯定先読み)
* `!e`(否定先読み) * `!e`(否定先読み)
ではもう少し複雑な例を見てみましょう。 幾つか短い例をみてみましょう。
## CSVフォーマットの定義 ```
# Ex) 大文字で始まる英単語
WORD <- [A-Z][A-Za-z-]+
```
```
# Ex) 数列 [1,2,3...]
NUMBER_LIST <- '[' (NUMBER (',' NUMBER)*)? ']'
NUMBER <- [0-9]+
```
正規表現ととても似ていますね。ではもう少し複雑な例として、CSVファイルための文法を定義してみましょう。
```
abc,"def ghi", jkl
...
```
CSVには様々な方言があるので,今回は以下の仕様を満たすものとします。 CSVには様々な方言があるので,今回は以下の仕様を満たすものとします。
@ -100,7 +116,7 @@ NL <- '\r\n' / '\r' / '\n'
## 空白の除去 ## 空白の除去
PEGの問題の中で一番よく知られているのは「空白」扱いです。Yaccなどでは,Lexなど外部の字句解析器がテキストのトークン分割を行う際に不必要な空白を除去してくれます。PEGでは構文解析と字句解析が明確に分離されていないため,空白除去の処理も通常の構文規則を用いて行う必要があります。 PEGの問題の中で一番よく知られているのは「空白」の除去です。Yaccなどでは,Lexなど外部の字句解析器がテキストのトークン分割を行う際に不必要な空白を除去してくれます。PEGでは構文解析と字句解析が明確に分離されていないため,空白除去の処理も通常の構文規則を用いて行う必要があります。
`[123,456,789]`のような数字の配列は,次のように定義できます。 `[123,456,789]`のような数字の配列は,次のように定義できます。
@ -127,11 +143,11 @@ PEGの問題の中で一番よく知られているのは「空白」扱いで
... ...
_ <- [ \t\r\n]* # 空白文字が0回以上 _ <- [ \t\r\n]* # 空白文字が0回以上
この文法で上の2つの例にマッチさせることができますが,`ifxtheny`にも誤ってマッチしてしまいます。トークン間に必ず空白が必要ということで'_'の規則を次のように変更してみます。 すこし複雑になりましたが、この文法で上の2つの例にマッチさせることができます。しかしこの文法には問題あり,`ifxtheny`にも誤ってマッチしてしまいます。トークン間に必ず空白が必要なので'_'の規則を次のように変更してみます。
_ <- [ \t\r\n]+ # 空白文字が1回以上 _ <- [ \t\r\n]+ # 空白文字が1回以上
今度は正しく`ifxtheny`がエラーとなります。しかし`if(x)then y`は空白を必要ないにもかかわらず,マッチしなくなってしまいました。こうした時は「否定先読み」を使用して,トークンの切れ目を明確にして解決することができます。 今度は正しく`ifxtheny`がエラーとなります。しかし今度は、`if(x)then y`は空白を必要ないにもかかわらず,マッチしなくなってしまいました。こうした時は「否定先読み」を使用して,トークンの切れ目を明確にして解決することができます。
IF <- 'if' __ ('(' _)? IDENT (')' _)? 'then' __ EXPR IF <- 'if' __ ('(' _)? IDENT (')' _)? 'then' __ EXPR
IDENT <- [a-zA-Z][a-zA-Z0-9_-]* __ IDENT <- [a-zA-Z][a-zA-Z0-9_-]* __
@ -143,12 +159,12 @@ PEGの問題の中で一番よく知られているのは「空白」扱いで
## Unicodeのサポート ## Unicodeのサポート
FordのPEGの論文ではUnicode文字については想定されていません。しかし多言語のテキストを扱うにはUnicodeのサポートが必須です。対応は処理系によってまちまちです。 FordのPEGの論文ではUnicodeについての言及はありません。しかし多言語のテキストを扱うにはUnicodeのサポートが必須です。Unicodeへの対応は、PEG処理系によってまちまちです。
この本で使用するPEGライブラリ(cpp-peglib)は正式にはUnicodeに未対応ですが,問題なくUTF8のテキストを扱うことができます。U+0800以上の全ての文字を規則名として使用することができます。 この本で使用するPEGライブラリ(cpp-peglib)は正式にはUnicodeに未対応ですが,問題なくUTF8のテキストを扱うことができます。U+0800以上の全ての文字を規則名として使用することができます。
このように幾つかの弱点はあるものの,PEGは十分に実用的な文法定義のための言語です。 このように幾つかの弱点はあるものの,PEGは十分に実用的な文法定義のための言語です。
次章では,ついにスクリプト言語の文法デザインに着手しましょう。 次章では,いよいよスクリプト言語のデザインに着手しましょう。
[Link_Ford]: http://pdos.csail.mit.edu/papers/parsing:popl04.pdf [Link_Ford]: http://www.brynosaurus.com/pub/lang/peg.pdf

@ -6,5 +6,7 @@
## 章 ## 章
1. [まずは,Hello world](chap_01.md) 1. [まずは"Hello world!"](chap_01.md)
2. [はじめてのPEG](chap_02.md) 2. [PEG記法を学ぼう](chap_02.md)
3. [言語"Elements"](chap_03.md)
4. [四則計算式](chap_04.md)

Loading…
Cancel
Save