Parasol チュートリアル 1

- パーザの拡張 -

ここでは,Parasol の標準パーザに,組み込みクラスや演算子,文などを追加して拡張する方法について説明します. ここで使われている例の完全なソースコードは,Parasol 配布パッケージの parasol/samples/tutorial 以下のディレクトリ yaciex に置かれています.

[Parasol ホーム]-[KiNOKO ホーム]

作成するパーザの仕様

以下の文法要素を Parasol 標準パーザ (YACI) に追加します.
・ 組み込みクラス BitSet
整数の 2 進数表記を用いて 2 値数の集合を表現するクラスです.このクラスの外部仕様を C++ のクラス宣言構文で表記すると,以下のようになります.
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);  // 文字列表記を返す
};

・ 組み込み関数 intToBitString() / bitStringToInt()
関数 string intToBitString(int n) は,整数 n の 2 進数表記を文字列に変換します.

関数 long bitStringToInt(string s) は,整数を 2 進表記した文字列 s を整数に変換します.文字列中の '0' は 0 に,それ以外の全ての文字は 1 に変換されます.

・ 文 repeat-until
repeat-until 文は,repeat statement until expression; の形で,文 statement の実行を 式 expression の値が true の間繰り返します.expression の評価は,statement の実行後に行われます.

・ 前置単項演算子 # と 2項演算子 ..
前置単項演算子 # は,整数に作用し,#n により n ビット目の立った整数を生成します.すなわち,#n == 0x0001 << n です.

2項演算子 .. も2つの整数に作用し,#n..#m により n ビット目から m ビット目が立った整数を返します(#3..#6 == 0x003c).この演算子は左から右に結合し,優先順位はビットアンドの1つ上になっています (ビットアンドより強く結合する).
以下は,作成するパーザが解釈できるスクリプトの例です.

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 クラスを継承して,独自のパーザクラスを作成します. ここで作成するパーザは,全ての文法要素を最初から定義したものではなく,Parasol 標準パーザの拡張なので,パーザクラスも TParaParser を直接継承するのではなく,標準パーザ TParaStandardParser を継承するようにします.

TParaParser の役割は,各文法要素を定義したオブジェクトを生成してパーザを構築することと,スクリプトを読んで解析し,実行するインターフェースを提供することです.TParaParser は,パーザ構築時に,各文法要素ごとに CreateXXXTable() メソッドを呼び出して,文法要素定義オブジェクトのテーブルを取得します.独自の文法要素を追加する場合は,このメソッドをオーバーライドし,その中でテーブルに文法要素を追加します.

作成するパーザのクラス名は TYachExParser としましょう.このクラスの定義は以下のようになります.


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);
};
中身は,コンストラクタとデストラクタ,および文法要素のテーブルを生成するメソッドのみです. 各メソッドの定義は以下のようになります.とりあえずは何もせず,CreateXXXTable() は親クラスである TParaStandardParser が生成したものをそのまま返すようにしましょう.


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 を継承したクラスを作成し,それを TParaObjectPrototypeTable に追加します. このクラスのインスタンスは,スクリプト中で対応するクラスのインスタンスが生成されるたびに一つずつ生成されます.スクリプト中でこのインスタンスのメソッドが呼ばれると,そのメソッド名とパラメータを引数にして,メソッド DispatchMessage() が呼ばれます. 必要なら,スクリプト中でインスタンスが生成・破棄された時に呼び出されるメソッド Construct()Destruct() も定義することができます.

パーザが必要な時にこのクラスのインスタンスを生成できるようにするために,TParaObjectPrototype は メソッド Clone() を持っています.ここでは,自分と同じ型のオブジェクトを一つ生成してそのポインタ返すようにしてください.

組み込みクラス BitSet を定義するクラス TBitSetObject の定義は以下のようになります.


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);

    //...
};
コンストラクタ,デストラクタは,必要がなければ特に何もしなくて構いません. 親クラス TParaObjectPrototype のコンストラクタは,引数に内部クラス名を取るので,これを渡してやります.内部クラス名は,特に指定が無ければ,そのままスクリプト中でのクラス名になります.Clone() では,自分と同じ型のオブジェクトを一つ生成して,そのポインタを返します.


TBitSetObject::TBitSetObject(void)
: TParaObjectPrototype("BitSet")
{
}

TBitSetObject::~TBitSetObject()
{
}

TParaObjectPrototype* TBitSetObject::Clone(void)
{
    return new TBitSetObject();
}

Construct() メソッドは,スクリプト中でこのクラスのコンストラクタが呼ばれた時に呼び出されるメソッドです.スクリプト中で渡された引数が,TParaValue のベクタで渡されます.


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");
    }
}
もし,引数にエラーなどがあれば,例外 TScriptException を投げてください.スクリプト中で例外処理が行われていなければ(try ブロックの中で無ければ),TParaParser::Execute() が同じ例外を投げます.

TParaValue は,スクリプト中で使われる全ての値を保持するためのもので,文字,整数,実数,オブジェクトおよびポインタを保持でき,保持している値の型情報と,型変換のためのメソッドを提供します.これは ParaValue.hh で定義されていますが,とりあえずは以下の部分だけ知っていれば十分でしょう.


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);
};
変換メソッド AsXXX() は,変換に失敗した場合,例外 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 したものをそのまま渡してください.

組み込み関数の追加

組み込み関数も,基本的に組み込みクラスと同じ手順でパーザに追加することができます.組み込みクラスとの違いは,作成したプロトタイプクラスを,PrototypeObjectTable ではなく BuiltinFunctionTable に追加することだけです.

まず,組み込みクラスと同様に,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;
}

文の追加

パーザに文を追加する方法も,組み込みクラスの追加と同様に,Parasol の TParaStatement を継承して独自の文クラスを定義し,それをパーザの StatementTable に登録することにより行います.ただし,文の場合は,文法定義(シンタクス)から指定しなければならないので,実行時に呼ばれるメソッドに加えて,構文解析時に呼ばれるメソッドも定義しなければなりません.

以下は,repeat until 文のための文クラス TRepeatUntilStatement のクラス定義です.repeat until 文の文法は,

repeat 実行文 until (条件式);
となっており,内部に一つの文(実行文)と式(条件式)を持っているため,対応する TRepeatUntilStatement クラスも,メンバに一つの文オブジェクトと式オブジェクトを保持しています.

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;
};
Clone() は,組み込み関数のときと同様に,パーザがスクリプト中で新たな文に出会ったときに,パーザが新しい文クラスのインスタンスを生成するために呼ぶものです.

FirstToken() は,その文の開始語を返すメソッドで,パーザが文レベルを解析するときに,後に続くトークン列がどの文であるかを判別するのに使うものです.最初の一語のみを返すという仕様上,宣言文や式文などの特殊な文を除く全ての文は,ユニークなキーワードで始まっていなければなりません(このあたりの仕様は将来変更するかもしれません).

Parse() は,パーザがスクリプト中でその文に出会ったときに呼び出すメソッドです.この中でスクリプトのトークン列を解析し,その内容に従って文オブジェクトを構築します.このメソッドの中身は,文のシンタクスをそのまま実装したものになります.

Execute() は,実行時に呼ばれるメソッドで,パース時に構築したオブジェクトを評価し,適切な処理を行います.このメソッドは,文のセマンティクスを実装したものになります.

それでは,これらのメソッドの定義を順に見ていきます.まずはコンストラクタ,デストラクタ,Clone() メソッドです.これは特に問題はないでしょう.


TRepeatUntilStatement::TRepeatUntilStatement(void)
{
    _ConditionExpression = 0;
    _Statement = 0;
}

TRepeatUntilStatement::~TRepeatUntilStatement()
{
    delete _ConditionExpression;
    delete _Statement;
}

TParaStatement* TRepeatUntilStatement::Clone(void)
{
    return new TRepeatUntilStatement();
}
FirstToken() メソッドは,以下のように文の開始語を返すだけです.

string TRepeatUntilStatement::FirstToken(void) const
{
    return string("repeat");
}
これによってパーザは文のパーズが必要になったときに,どの文オブジェクトを生成すればよいかが 分かります.そして,実際に文オブジェクトが生成されて呼ばれるのが,以下の Parse() メソッドです.

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(";");
}
スクリプトパーザは,内部に文パーザを持っており,これが第2引数に渡されます.また,文パーザは式パーザを持っていて,これは ExpressionParser() メソッドで取得できます.これらの文パーザ,式パーザを使いながら,文のシンタクス定義をそのままコーディングしていきます.文定義の中で再帰的に現れる文や式の部分で,文パーザや式パーザを利用していることに注意してください.

これら以外の文法要素については,期待する文字列が期待する場所にあればいいので,一語ずつ読みだしながら,期待する文字列と一致しているかどうかを確認します.TParaTokenizer::Next() は,入力バッファから一語を取り出し,TParaToken オブジェクトとしてこれを返します.TParaToken::MustBe(const std::string& ExpectedString) は,トークンの内容と引数に渡された ExpectedString を比較して,もし一致しなければ TScriptException を投げます.

次に,文の実行時に呼ばれるメソッド Execute() を定義します.メソッド Execute() は,戻り値に,文の実行結果 TExecResult を返します.TExecResultTParaStatement の中で定義されている構造体で,以下のように実行状態を表す TExecStatus と実行結果の値を保持する TParaValue のフィールドから構成されます.


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;
    };

    //...
};
ExecStatus は,enum TExecStatus の宣言にあるように,その文の実行後の制御を決定する状態を返します.例えば,もし esBreak が返されて,それがループの中ならば,次に実行される文はそのループの外側の次の文になります.もし,if 文や複合文などを実行中に,中の文が esBreak などを返したら,その文自体も同じ実行状態を返すべきだということに注意してください(そのようになっていないと,例えば while 文の中の if 文で break が呼ばれたときに,多くの人が期待するとおりには動かない).

ReturnValue は,文の戻り値で,たとえば return 文などは,それが持っている式の値になります.ExecStatus と同様に,if 文や複合文などを実行中に中の文がある値を返したら,その文自体も同じ値を返すべきです.特に必要が無ければ 0 にしておいてかまいません.

さて,ここまで来たら,あとは文のセマンティクスにしたがって,コードを書いていくだけです. 以下のコードで,_Statement が repeat と until の間に書かれる文,_ConditionExpression が until の次に書かれる条件式です.これらに対して,Execute()Evaluate が再帰的に使われています.文の実行状態の扱いに注意して読んでください.


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;
}
これで,文クラスは完成です.これをパーザに登録します.組み込みクラスと同様,TParaParserCreateStatementTable() をオーバーライドし,その中で文テーブルにこの文クラスのインスタンスを一つ登録します.

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;
}

演算子の追加

演算子も文と同様,シンタクスとセマンティクスがありますが,文の場合はシンタクスが文ごとにほぼ独立なのに対し,演算子の場合はシンタクスは式という文脈中での相互作用的なものになります.文の場合はシンタクスはトークンの配列の解析が主でしたが,演算子の場合は,式の中で演算子が相対的にどのように振舞うかの記述が主になります.具体的には,演算子のシンボルと,単項演算子か2項演算子か,結合の向き(結合性)はどちらか,単項なら前置か後置か,2項なら結合順位はいくつか,などを記述することになります(ただし,現在のバージョンの Parasol では,後置単項演算子はサポートしていません.また,3項以上の演算子もサポートされていません).

Parasol では,結合性以外のシンタクス要素は,演算子オブジェクトを演算子テーブルに追加する際のパラメータとして指定し,演算子クラスは主に演算子の振舞いについて記述します.

以下は,単項演算子 # と2項演算子 .. のクラス定義です.ただし,クラス定義自体は演算子シンボルとの関連付けをしないので,ここではそれぞれビット生成演算子,ビット充填演算子と呼ぶことにします.


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() を持っています.

一応コンストラクタ,デストラクタ,Clone() メソッドの定義を示しておきます.


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();
}
次に Evaluate() メソッドの定義です.基本的に,組み込みクラスの DispatchMessage() と同様ですが,戻り値が参照であることに注意してください. これは,演算子の評価結果が左辺値の場合,その値に対する変更が正しく反映されるようにするためです. _Value は親クラス TParaOperator で宣言されています.

引数に,演算子の右辺と左辺の値が渡されるので,計算をして結果を返します.前置単項演算子の場合は,右辺の値のみを使ってください.


// 前置単項演算子 ビット生成演算子 の実行時評価メソッド
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;
} 
作成した演算子クラスのインスタンスをパーザの演算子テーブルに追加します.前置単項演算子の追加は TParaOperatorTable::AddPrepositionalOperator() で,演算子のシンボルを指定して行います.

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;
}

実行エントリの追加


Edited by: Enomoto Sanshiro