ここでは,Parasol の標準パーザに,組み込みクラスや演算子,文などを追加して拡張する方法について説明します. ここで使われている例の完全なソースコードは,Parasol 配布パッケージの parasol/samples/tutorial 以下のディレクトリ yaciex に置かれています.
class BitSet { public: BitSet(void); // 空の集合で初期化 BitSet(long n); // 整数表記により初期化 BitSet(string s); // 文字列表記により初期化 void set(long n); // n bit 目をセットする void unset(long n); // n bit 目をクリアする long isSet(long n); // n bit 目がセットされているか long asInt(void); // 整数表記を返す string asString(int width = 0); // 文字列表記を返す };
int main() { int i = 0; int j = 31; repeat { BitSet bit_set(#i..#j); if (j - i > 10) { bit_set.unset(i + 5); bit_set.unset(j - 5); } println(bit_set.asString()); ++i; --j; } until (i > j); }
実行結果は以下のようになります.
% ./yaciex sample.yaci 11111011111111111111111111011111 01111101111111111111111110111110 00111110111111111111111101111100 00011111011111111111111011111000 00001111101111111111110111110000 00000111110111111111101111100000 00000011111011111111011111000000 00000001111101111110111110000000 00000000111110111101111100000000 00000000011111011011111000000000 00000000001111100111110000000000 00000000000111111111100000000000 00000000000011111111000000000000 00000000000001111110000000000000 00000000000000111100000000000000 00000000000000011000000000000000 %
TParaParser の役割は,各文法要素を定義したオブジェクトを生成してパーザを構築することと,スクリプトを読んで解析し,実行するインターフェースを提供することです.TParaParser は,パーザ構築時に,各文法要素ごとに CreateXXXTable() メソッドを呼び出して,文法要素定義オブジェクトのテーブルを取得します.独自の文法要素を追加する場合は,このメソッドをオーバーライドし,その中でテーブルに文法要素を追加します.
作成するパーザのクラス名は TYachExParser としましょう.このクラスの定義は以下のようになります.
中身は,コンストラクタとデストラクタ,および文法要素のテーブルを生成するメソッドのみです. 各メソッドの定義は以下のようになります.とりあえずは何もせず,CreateXXXTable() は親クラスである TParaStandardParser が生成したものをそのまま返すようにしましょう.
class TYaciExParser: public TParaStandardParser { public: TYaciExParser(int argc, char** argv); virtual ~TYaciExParser(); protected: virtual TParaTokenTable* CreateTokenTable(void); virtual TParaObjectPrototypeTable* CreateObjectPrototypeTable(void); virtual TParaBuiltinFunctionTable* CreateBuiltinFunctionTable(void); virtual TParaOperatorTable* CreateOperatorTable(void); virtual TParaStatementTable* CreateStatementTable(void); };
TYaciExParser::TYaciExParser(int argc, char** argv) : TParaStandardParser(argc, argv) { } TYaciExParser::~TYaciExParser() { } TParaTokenTable* TYaciExParser::CreateTokenTable(void) { TParaTokenTable* TokenTable; TokenTable = TParaStandardParser::CreateTokenTable(); // ここでパーザに追加するトークンを登録する return TokenTable; } TParaObjectPrototypeTable* TYaciExParser::CreateObjectPrototypeTable(void) { TParaObjectPrototypeTable* ObjectPrototypeTable; ObjectPrototypeTable = TParaStandardParser::CreateObjectPrototypeTable(); // ここでパーザに追加する組み込みクラスを登録する return ObjectPrototypeTable; } TParaBuiltinFunctionTable* TYaciExParser::CreateBuiltinFunctionTable(void) { TParaBuiltinFunctionTable* BuiltinFunctionTable; BuiltinFunctionTable = TParaStandardParser::CreateBuiltinFunctionTable(); // ここでパーザに追加する組み込み関数を登録する return BuiltinFunctionTable; } TParaOperatorTable* TYaciExParser::CreateOperatorTable(void) { TParaOperatorTable* OperatorTable; OperatorTable = TParaStandardParser::CreateOperatorTable(); // ここでパーザに追加する演算子を登録する return OperatorTable; } TParaStatementTable* TYaciExParser::CreateStatementTable(void) { TParaStatementTable* StatementTable; StatementTable = TParaStandardParser::CreateStatementTable(); // ここでパーザに追加する文を登録する return StatementTable; }
TParaParser の提供する解析・実行のインターフェースを main() 関数から呼び出せば,とりあえずパーザは完成です.
作成したパーザが実行できることを確認してみてください.
int main(int argc, char** argv) { if (argc < 2) { cerr << "Usage: " << argv[0] << " SourceFileName" << endl; return 0; } ifstream SourceFile(argv[1]); if (! SourceFile) { cerr << "ERROR: unable to open " << argv[1] << endl; return 0; } TYaciExParser Parser(argc, argv); try { Parser.Parse(SourceFile); } catch (TScriptException &e) { cerr << "ERROR: " << e << endl; return EXIT_FAILURE; } TParaValue Result; try { if (Parser.HasEntryOf("main")) { Result = Parser.Execute("main"); } else { Result = Parser.Execute(); } } catch (TScriptException &e) { cerr << "ERROR: " << e << endl; } return Result.AsLong(); }
パーザが必要な時にこのクラスのインスタンスを生成できるようにするために,TParaObjectPrototype は メソッド Clone() を持っています.ここでは,自分と同じ型のオブジェクトを一つ生成してそのポインタ返すようにしてください.
組み込みクラス BitSet を定義するクラス TBitSetObject の定義は以下のようになります.
コンストラクタ,デストラクタは,必要がなければ特に何もしなくて構いません. 親クラス TParaObjectPrototype のコンストラクタは,引数に内部クラス名を取るので,これを渡してやります.内部クラス名は,特に指定が無ければ,そのままスクリプト中でのクラス名になります.Clone() では,自分と同じ型のオブジェクトを一つ生成して,そのポインタを返します.
class TBitSetObject: public TParaObjectPrototype { public: TBitSetObject(void); virtual ~TBitSetObject(); virtual TParaObjectPrototype* Clone(void); virtual void Construct(std::vector<TParaValue*>& ArgumentList) throw(TScriptException); virtual int DispatchMessage(const std::string& Message, std::vector<TParaValue*>& ArgumentList, TParaValue& ReturnValue) throw(TScriptException); //... };
TBitSetObject::TBitSetObject(void) : TParaObjectPrototype("BitSet") { } TBitSetObject::~TBitSetObject() { } TParaObjectPrototype* TBitSetObject::Clone(void) { return new TBitSetObject(); }
Construct() メソッドは,スクリプト中でこのクラスのコンストラクタが呼ばれた時に呼び出されるメソッドです.スクリプト中で渡された引数が,TParaValue のベクタで渡されます.
もし,引数にエラーなどがあれば,例外 TScriptException を投げてください.スクリプト中で例外処理が行われていなければ(try ブロックの中で無ければ),TParaParser::Execute() が同じ例外を投げます.
void TBitSetObject::Construct(vector<TParaValue*>& ArgumentList) throw(TScriptException) { if (ArgumentList.size() == 0) { _Value = 0; } else if (ArgumentList[0]->IsLong()) { _Value = ArgumentList[0]->AsLong(); } else if (ArgumentList[0]->IsString()) { string StringValue = ArgumentList[0].AsString(); _Value = this->StringToInt(StringValue); } else { throw TScriptException("BitSet::BitSet(): invalid argument"); } }
TParaValue は,スクリプト中で使われる全ての値を保持するためのもので,文字,整数,実数,オブジェクトおよびポインタを保持でき,保持している値の型情報と,型変換のためのメソッドを提供します.これは ParaValue.hh で定義されていますが,とりあえずは以下の部分だけ知っていれば十分でしょう.
変換メソッド AsXXX() は,変換に失敗した場合,例外 TScriptException を投げます.
class TParaValue { public: // C++ プリミティブからの生成 explicit TParaValue(long LongValue = 0); explicit TParaValue(double DoubleValue); explicit TParaValue(string StringValue); explicit TParaValue(TParaObjectPrototype* ObjectValue); explicit TParaValue(TParaValue* PointerValue); // 型識別 virtual bool IsLong(void) const; virtual bool IsDouble(void) const; virtual bool IsString(void) const; virtual bool IsObject(void) const; virtual bool IsPointer(void) const; // プリミティブへの変換 virtual long AsLong(void) const throw(TScriptException); virtual double AsDouble(void) const throw(TScriptException); virtual string AsString(void) const throw(TScriptException); virtual TParaObjectPrototype* AsObject(void) const throw(TScriptException); virtual TParaValue* AsPointer(void) const throw(TScriptException); };
スクリプト中でメソッドが呼ばれた時に呼ばれるメソッド DispatchMessage() も同様に定義します.DispatchMessage() の引数には,スクリプト中で渡されたパラメータのリスト ArgumentList に加えて,メソッド名 Message (これは歴史的な理由で Message となっています) も渡されます.メソッドの戻り値は,第3引数の ReturnValue にセットしてください.
もし,Message に渡されたメソッド名が適切で無ければ,0 をリターンしてください.この場合も,スクリプト中でエラー処理が行われていない限り,TParaParser::Execute() が TScriptException を投げます.そうでなければ,1 をリターンしてください.
とりあえずここでは,メソッド名を見て,それぞれの処理メソッドに分岐するだけにします.
int TBitSetObject::DispatchMessage(const string& Message, vector<TParaValue*>& ArgumentList, TParaValue& ReturnValue) throw(TScriptException) { if (Message == "set") { ReturnValue = Set(ArgumentList); } else if (Message == "unset") { ReturnValue = Unset(ArgumentList); } else if (Message == "isset") { ReturnValue = IsSet(ArgumentList); } else if (Message == "asInt") { ReturnValue = AsInt(ArgumentList); } else if (Message == "asString") { ReturnValue = AsString(ArgumentList); } else { return 0; } return 1; }
各メソッド処理メソッドの実装例を以下に示します.基本的に,引数をチェックして,必要な処理を行い,結果を TParaValue 型のオブジェクトにして返すだけです.ここでも,引数にエラーがある場合は,TScriptException を投げてください.
TParaValue TBitSetObject::Set(vector<TParaValue*>& ArgumentList) throw(TScriptException) { if ((ArgumentList.size() < 1) || (! ArgumentList[0]->IsLong())) { throw TScriptException( "BitSet::set(long): invalid argument" ); } long Index = ArgumentList[0]->AsLong(); if ((Index < 0) || (Index >= _BitLength)) { throw TScriptException( "BitSet::set(long): argument out of range" ); } _Value |= (0x0001 << Index); return TParaValue((long) 0); } TParaValue TBitSetObject::IsSet(vector<TParaValue*>& ArgumentList) throw(TScriptException) { if ((ArgumentList.size() < 1) || (! ArgumentList[0]->IsLong())) { throw TScriptException( "BitSet::isSet(long): invalid argument" ); } long Index = ArgumentList[0]->AsLong(); if ((Index < 0) || (Index >= _BitLength)) { throw TScriptException( "BitSet::isSet(long): argument out of range" ); } long Result = (_Value >> Index) & 0x0001; return TParaValue(Result); }
最後に,作成したプロトタイプクラスを,TParaObjectPrototypeTable に登録します.これは,先に作成したパーザクラスの CreateObjectPrototypeTable() 内で行います.
TParaObjectPrototypeTable* TYaciExParser::CreateObjectPrototypeTable(void) { TParaObjectPrototypeTable* ObjectPrototypeTable; ObjectPrototypeTable = TParaStandardParser::CreateObjectPrototypeTable(); ObjectPrototypeTable->RegisterClass("BitSet", new TBitSetObject()); return ObjectPrototypeTable; }
テーブルにプロトタイプを追加するメソッドは,TParaObjectPrototypeTable::RegisterClass() です.第1引数にスクリプト中で使われるクラス名,第2引数にプロトタイプクラスのインスタンスを渡します.このプロトタイプオブジェクトはテーブルのデストラクタで削除されるので,new したものをそのまま渡してください.
まず,組み込みクラスと同様に,TParaObjectPrototype を継承して組み込み関数クラスを作成します.定義は以下のようになります.
class TBitFunction: public TParaObjectPrototype { public: TBitFunction(void); virtual ~TBitFunction(); virtual TParaObjectPrototype* Clone(void); virtual int DispatchMessage(const std::string& Message, std::vector<TParaValue*>& ArgumentList, TParaValue& ReturnValue) throw(TScriptException); //... };
組み込みクラスと全く同様に,コンストラクタ,デストラクタ,Clone() メソッド,DispatchMessage() メソッドを定義します.
TBitFunction::TBitFunction(void) : TParaObjectPrototype("BitFunction") { } TBitFunction::~TBitFunction() { } TParaObjectPrototype* TBitFunction::Clone(void) { return new TBitFunction(); } int TBitFunction::DispatchMessage(const string& Message, vector<TParaValue*>& ArgumentList, TParaValue& ReturnValue) throw(TScriptException) { if (Message == "bitStringToInt") { ReturnValue = BitStringToInt(ArgumentList); } else if (Message == "intToBitString") { ReturnValue = IntToBitString(ArgumentList); } else { return 0; } return 1; }
組み込み関数では,DispatchMessage() の第1引数 Message はスクリプト中での関数名になります.これらの実装は例えば以下のようにすれば良いでしょう.
TParaValue TBitFunction::BitStringToInt(vector<TParaValue*>& ArgumentList) throw(TScriptException) { if ((ArgumentList.size() < 1) || (! ArgumentList[0]->IsString())) { throw TScriptException( "bitStringToInt(string): invalid argument" ); } string StringValue = ArgumentList[0]->AsString(); long LongValue = TBitSetObject::StringToInt(StringValue); return TParaValue(LongValue); }
最後に,作成したプロトタイプクラスをパーザの BuiltinFunctionTable に追加します.
TParaBuiltinFunctionTable* TYaciExParser::CreateBuiltinFunctionTable(void) { TParaBuiltinFunctionTable* BuiltinFunctionTable; BuiltinFunctionTable = TParaStandardParser::CreateBuiltinFunctionTable(); BuiltinFunctionTable->RegisterAnonymousClass(new TBitFunction()); return BuiltinFunctionTable; }
以下は,repeat until 文のための文クラス TRepeatUntilStatement のクラス定義です.repeat until 文の文法は,
となっており,内部に一つの文(実行文)と式(条件式)を持っているため,対応する TRepeatUntilStatement クラスも,メンバに一つの文オブジェクトと式オブジェクトを保持しています.repeat 実行文 until (条件式);
Clone() は,組み込み関数のときと同様に,パーザがスクリプト中で新たな文に出会ったときに,パーザが新しい文クラスのインスタンスを生成するために呼ぶものです.
class TRepeatUntilStatement: public TParaStatement { public: TRepeatUntilStatement(void); virtual ~TRepeatUntilStatement(); virtual TParaStatement* Clone(void); virtual std::string FirstToken(void) const; virtual void Parse(TParaTokenizer* Tokenizer, TParaStatementParser* StatementParser, TParaSymbolTable* SymbolTable) throw(TScriptException); virtual TExecResult Execute(TParaSymbolTable* SymbolTable) const throw(TScriptException); protected: TParaExpression* _ConditionExpression; TParaStatement* _Statement; };
FirstToken() は,その文の開始語を返すメソッドで,パーザが文レベルを解析するときに,後に続くトークン列がどの文であるかを判別するのに使うものです.最初の一語のみを返すという仕様上,宣言文や式文などの特殊な文を除く全ての文は,ユニークなキーワードで始まっていなければなりません(このあたりの仕様は将来変更するかもしれません).
Parse() は,パーザがスクリプト中でその文に出会ったときに呼び出すメソッドです.この中でスクリプトのトークン列を解析し,その内容に従って文オブジェクトを構築します.このメソッドの中身は,文のシンタクスをそのまま実装したものになります.
Execute() は,実行時に呼ばれるメソッドで,パース時に構築したオブジェクトを評価し,適切な処理を行います.このメソッドは,文のセマンティクスを実装したものになります.
それでは,これらのメソッドの定義を順に見ていきます.まずはコンストラクタ,デストラクタ,Clone() メソッドです.これは特に問題はないでしょう.
FirstToken() メソッドは,以下のように文の開始語を返すだけです.
TRepeatUntilStatement::TRepeatUntilStatement(void) { _ConditionExpression = 0; _Statement = 0; } TRepeatUntilStatement::~TRepeatUntilStatement() { delete _ConditionExpression; delete _Statement; } TParaStatement* TRepeatUntilStatement::Clone(void) { return new TRepeatUntilStatement(); }
これによってパーザは文のパーズが必要になったときに,どの文オブジェクトを生成すればよいかが 分かります.そして,実際に文オブジェクトが生成されて呼ばれるのが,以下の Parse() メソッドです.
string TRepeatUntilStatement::FirstToken(void) const { return string("repeat"); }
スクリプトパーザは,内部に文パーザを持っており,これが第2引数に渡されます.また,文パーザは式パーザを持っていて,これは ExpressionParser() メソッドで取得できます.これらの文パーザ,式パーザを使いながら,文のシンタクス定義をそのままコーディングしていきます.文定義の中で再帰的に現れる文や式の部分で,文パーザや式パーザを利用していることに注意してください.
void TRepeatUntilStatement::Parse(TParaTokenizer* Tokenizer, TParaStatementParser* StatementParser, TParaSymbolTable* SymbolTable) throw(TScriptException) { TParaExpressionParser* ExpressionParser; ExpressionParser = StatementParser->ExpressionParser(); Tokenizer->Next().MustBe("repeat"); _Statement = StatementParser->Parse(Tokenizer, SymbolTable); Tokenizer->Next().MustBe("until"); Tokenizer->Next().MustBe("("); _ConditionExpression = ExpressionParser->Parse(Tokenizer, SymbolTable); Tokenizer->Next().MustBe(")"); Tokenizer->Next().MustBe(";"); }
これら以外の文法要素については,期待する文字列が期待する場所にあればいいので,一語ずつ読みだしながら,期待する文字列と一致しているかどうかを確認します.TParaTokenizer::Next() は,入力バッファから一語を取り出し,TParaToken オブジェクトとしてこれを返します.TParaToken::MustBe(const std::string& ExpectedString) は,トークンの内容と引数に渡された ExpectedString を比較して,もし一致しなければ TScriptException を投げます.
次に,文の実行時に呼ばれるメソッド Execute() を定義します.メソッド Execute() は,戻り値に,文の実行結果 TExecResult を返します.TExecResult は TParaStatement の中で定義されている構造体で,以下のように実行状態を表す TExecStatus と実行結果の値を保持する TParaValue のフィールドから構成されます.
ExecStatus は,enum TExecStatus の宣言にあるように,その文の実行後の制御を決定する状態を返します.例えば,もし esBreak が返されて,それがループの中ならば,次に実行される文はそのループの外側の次の文になります.もし,if 文や複合文などを実行中に,中の文が esBreak などを返したら,その文自体も同じ実行状態を返すべきだということに注意してください(そのようになっていないと,例えば while 文の中の if 文で break が呼ばれたときに,多くの人が期待するとおりには動かない).
class TParaStatement { public: enum TExecStatus { esNormal, esBreak, esContinue, esReturn, esExit, esError }; struct TExecResult { TExecResult(void); TExecResult(TExecStatus Status, const TParaValue& Value); TExecStatus ExecStatus; TParaValue ReturnValue; }; //... };
ReturnValue は,文の戻り値で,たとえば return 文などは,それが持っている式の値になります.ExecStatus と同様に,if 文や複合文などを実行中に中の文がある値を返したら,その文自体も同じ値を返すべきです.特に必要が無ければ 0 にしておいてかまいません.
さて,ここまで来たら,あとは文のセマンティクスにしたがって,コードを書いていくだけです. 以下のコードで,_Statement が repeat と until の間に書かれる文,_ConditionExpression が until の次に書かれる条件式です.これらに対して,Execute() や Evaluate が再帰的に使われています.文の実行状態の扱いに注意して読んでください.
これで,文クラスは完成です.これをパーザに登録します.組み込みクラスと同様,TParaParser の CreateStatementTable() をオーバーライドし,その中で文テーブルにこの文クラスのインスタンスを一つ登録します.
TParaStatement::TExecResult TRepeatUntilStatement::Execute(TParaSymbolTable* SymbolTable) throw(TScriptException) { TExecResult Result; do { Result = _Statement->Execute(SymbolTable); if (Result.ExecStatus == TParaStatement::esBreak) { // ここで break 状態は処理済みになるので,esNormal に戻す. Result.ExecStatus = esNormal; break; } if (Result.ExecStatus == TParaStatement::esContinue) { // ここで break 状態は処理済みになるので,esNormal に戻す. Result.ExecStatus = esNormal; continue; } if (Result.ExecStatus == TParaStatement::esReturn) { // ここでは return 状態は処理されない. break; } } while (! _ConditionExpression->Evaluate(SymbolTable).AsLong()); return Result; }
もし,文の追加によって新しいキーワードが増えたら,このキーワードもパーザのトークンテーブルに登録しなければなりません.特に,文の開始語は,キーワードとして登録されていないと,文の開始として認識されない場合があります.
TParaStatementTable* TYaciExParser::CreateStatementTable(void) { TParaStatementTable* StatementTable; StatementTable = TParaStandardParser::CreateStatementTable(); StatementTable->AddStatement(new TRepeatUntilStatement()); return StatementTable; }
TParaTokenTable* TYaciExParser::CreateTokenTable(void) { TParaTokenTable* TokenTable; TokenTable = TParaStandardParser::CreateTokenTable(); TokenTable->AddKeyword("repeat"); TokenTable->AddKeyword("until"); return TokenTable; }
Parasol では,結合性以外のシンタクス要素は,演算子オブジェクトを演算子テーブルに追加する際のパラメータとして指定し,演算子クラスは主に演算子の振舞いについて記述します.
以下は,単項演算子 # と2項演算子 .. のクラス定義です.ただし,クラス定義自体は演算子シンボルとの関連付けをしないので,ここではそれぞれビット生成演算子,ビット充填演算子と呼ぶことにします.
コンストラクタ,デストラクタ,Clone() メソッドは今までと同様です.その他に,実行時に演算子が評価されたときに呼ばれるメソッド Evaluate() を持っています.
class TOperatorBitGenerate: public TParaOperator { public: TOperatorBitGenerate(void); virtual ~TOperatorBitGenerate(); virtual TParaOperator* Clone(void) const; virtual TParaValue& Evaluate(TParaValue& Left, TParaValue& Right, TParaSymbolTable* SymbolTable) throw(TScriptException); }; class TOperatorBitFill: public TParaOperator { public: TOperatorBitFill(void); virtual ~TOperatorBitFill(); virtual TParaOperator* Clone(void) const; virtual TParaValue& Evaluate(TParaValue& Left, TParaValue& Right, TParaSymbolTable* SymbolTable) throw(TScriptException); };
一応コンストラクタ,デストラクタ,Clone() メソッドの定義を示しておきます.
次に Evaluate() メソッドの定義です.基本的に,組み込みクラスの DispatchMessage() と同様ですが,戻り値が参照であることに注意してください. これは,演算子の評価結果が左辺値の場合,その値に対する変更が正しく反映されるようにするためです. _Value は親クラス TParaOperator で宣言されています.
TOperatorBitGenerate::TOperatorBitGenerate(void) { } TOperatorBitGenerate::~TOperatorBitGenerate() { } TParaOperator* TOperatorBitGenerate::Clone(void) const { return new TOperatorBitGenerate(); } TOperatorBitFill::TOperatorBitFill(void) { } TOperatorBitFill::~TOperatorBitFill() { } TParaOperator* TOperatorBitFill::Clone(void) const { return new TOperatorBitFill(); }
引数に,演算子の右辺と左辺の値が渡されるので,計算をして結果を返します.前置単項演算子の場合は,右辺の値のみを使ってください.
作成した演算子クラスのインスタンスをパーザの演算子テーブルに追加します.前置単項演算子の追加は TParaOperatorTable::AddPrepositionalOperator() で,演算子のシンボルを指定して行います.
// 前置単項演算子 ビット生成演算子 の実行時評価メソッド TParaValue& TOperatorBitGenerate::Evaluate(TParaValue& Left, TParaValue& Right, TParaSymbolTable* SymbolTable) throw(TScriptException) { long BitPosition = Right.AsLong(); if (BitPosition < 0) { throw TScriptException("Natural number was expected."); } _Value = TParaValue((long) (0x0001ul << BitPosition)); return _Value; } // 2項演算子 ビット充填演算子 の実行時評価メソッド TParaValue& TOperatorBitFill::Evaluate(TParaValue& Left, TParaValue& Right, TParaSymbolTable* SymbolTable) throw(TScriptException) { unsigned long LongValue; if (Left.AsLong() == Right.AsLong()) { LongValue = (unsigned long) Left.AsLong(); } else { LongValue = (unsigned long) (Left.AsLong() | Right.AsLong()); bool IsToBeFilled = false; for (unsigned long Bit = 0x0001; Bit < LongValue; Bit <<= 1) { if (LongValue & Bit) { if (! IsToBeFilled) { IsToBeFilled = true; } else { break; } } if (IsToBeFilled) { LongValue |= Bit; } } } _Value = TParaValue((long) LongValue); return _Value; }
2項演算子は TParaOperatorTable::AddOperator() で,演算子のシンボル,結合順位を指定して行います.結合順位は整数を直接指定することもできますが,「追加」の場合は例のように別の演算子との相対値を使って指定したほうが簡単で便利です.ちなみに,優先順位は,数字が小さいほど強い結合になります.
最後に,追加した演算子のシンボルをトークンテーブルに演算子トークンとして登録します.
TParaOperatorTable* TYaciExParser::CreateOperatorTable(void) { TParaOperatorTable* OperatorTable; OperatorTable = TParaStandardParser::CreateOperatorTable(); OperatorTable->AddPrepositionalOperator("#", new TOperatorBitGenerate()); TParaOperatorPriority Priority("&", -1); OperatorTable->AddOperator("..", Priority, new TOperatorBitFill()); return OperatorTable; }
TParaTokenTable* TYaciExParser::CreateTokenTable(void) { TParaTokenTable* TokenTable; TokenTable = TParaStandardParser::CreateTokenTable(); TokenTable->AddOperator("#"); TokenTable->AddOperator(".."); return TokenTable; }