tutorial 0 -- hello world α
このチュートリアルは、yaccなどでBNFを扱ったことがある人を対象にしています。
まずはhello worldです。
入力文法ファイル
以下のような入力文法ファイルを作成し、hello0.cpgというファイル名で保存します。入力文法ファイル1つがパーサひとつに対応します。
%token Hello World; %namespace hello_world; %dont_use_stl; HelloWorld<int> : [] Hello World;
1行目
この文法で作るパーサで使うトークンを定義しています。この宣言からは、
enum Token {
token_Hello,
token_World,
};
というenum宣言が出力され、トークン種別として扱われます。*1
2行目
namespace名を宣言しています。出力されるヘッダ全体がこのnamespaceで囲まれます。指定しない場合、"caper_parser"というnamespace名が用いられます。
3行目
パーサの内部スタックにSTLを用いない(配列を用いる)ことを宣言します。配列を使う場合、メモリの動的な確保がなくなる代わりに、スタック長に制限がかかります。
5行目
BNFによる文法定義です。
文法は文法規則の集合で、文法規則は以下のような形で指定します。
非終端記号名<非終端記号の型> : [セマンティックアクション名]
| [セマンティックアクション名] 項
| [セマンティックアクション名] 項 項
...
;
記号類は多少異なりますが、意味は通常のBNFと同様です。caper独特な部分については後のチュートリアルで説明します。
今回の文法は、'hello' 'world'の順でトークンが来ると受理する単純なものです。
先頭の文法がルート文法になります。
*1 %external_token;という宣言を別の行で与えると、このenum定義は出力されず、後で説明するParserクラスがToken型をテンプレートパラメータとして必要とするようになります。Token型を別のファイルで定義したいときに使ってください。
caperでパーサを生成
上記の文法からパーサ(.hppファイル)を生成するには、コマンドラインから
% caper hello0.cpg hello0.hpp
とします。
すると、
#ifndef HELLO_WORLD_HPP
#define HELLO_WORLD_HPP
namespace hello_world {
enum Token {
token_eof,
token_Hello,
token_World,
};
template < class Value, class SemanticAction >
class Parser {
public:
typedef Token token_type;
typedef Value value_type;
public:
Parser( SemanticAction& sa );
void reset();
bool post( token_type token, const value_type& value );
bool accept( value_type& v );
bool error();
};
} // namespace hello_world
#endif // #ifndef HELLO_WORLD_HPP
だいたいこのようなパーサが出力されます。(見易さのため実装は省略)
パーサを自分のプログラムから使う
上で出力されたパーサをメインプログラムから使うコードの例です。
#include <iostream>
#include "hello0.hpp"
struct SemanticAction {
void syntax_error(){}
void stack_overflow(){}
};
int main( int, char** )
{
SemanticAction sa;
hello_world::Parser< int, SemanticAction > parser( sa );
parser.post( hello_world::token_Hello, 0 );
parser.post( hello_world::token_World, 0 );
parser.post( hello_world::token_eof, 0 );
return 0;
}
2行目
生成したパーサをincludeしています。
4~7行目
セマンティックアクションハンドラを定義しています。この構造体は、テンプレートパラメータの形でパーサに渡されます。classでも構いません。今回は文法ファイルでセマンティックアクションを定義しませんでしたが、その場合でも文法エラーハンドラは必要なので、void syntax_error(){}の定義は必要になります。syntax_errorの内部では必要がなければ特に何もしなくても構いません。stack_overflowも同様に必要です。
11行目
パーサインスタンスを生成しています。トークンに関連付けられる値の型は、第1テンプレート引数の"int"です。コンストラクト時に、10行目で生成したセマンティックアクションハンドラを引数として渡しています。
ここでは、Parserクラステンプレートの第3引数'StackSize'を省略しています。第3引数は整数で、デフォルト値は「STL使用」なら0、「STL不使用」なら'適当な数値'(1024)になっています。「STL使用」のときに'StackSize'を0にすると、決してオーバーフローしません(メモリがなくなった時点でメモリ割り当て失敗の例外が発生するでしょう)。
12~14行目
パーサにトークンを与えています。14行目が終了した時点で受理が完了しますが、今回は受理しても何もしないので何もおこりません。
実行
というわけで、今回は実行してもただ受理するだけで何も出力しません。
以上がcaperの出力するパーサの基本的な機能です。