日記帳

日記です。

プログラミング言語 BeanShell その4

http://d.hatena.ne.jp/sa-y/20060724 の続き.

callstack プロパティ

callstack プロパティは bsh.Callstack クラス*1インスタンスでそれまでのメソッド呼出しの経路を表わしています.中身は NameSpace オブジェクトの積まれたスタックになっており,メソッドが呼出されるとそのメソッド呼出し用の NameSpace オブジェクト(this.namespace)が push され,そのメソッドを抜けるとpopされるようなイメージです.

first()
{
	print(this.callstack.depth());                  // => 2
	print(this.callstack.get(0) == this.namespace); // => true
	print(this.callstack.get(1) == this.caller.namespace); // => true
	second();
}

second()
{
	print(this.callstack.depth());                  // => 3
	print(this.callstack.get(0) == this.namespace); // => true
	print(this.callstack.get(1) == this.caller.namespace); // => true
	print(this.callstack.get(2) == this.caller.caller.namespace); // => true
}

print(this.callstack.depth());                  // => 1
print(this.callstack.get(0) == this.namespace); // => true
first();

メソッドの呼出し経路順に NameSpace オブジェクトをたどっていくだけならば this.caller.caller... だけでも実現できます.callstack プロパティの本当に*えらい*所は以下のようなメソッドよにって callstack の内容を別の NameSpace オブジェクトに差し替えたりできるところです.

  • Callstack#swap(NameSpace newTop);
  • Callstack#set(int depth, NameSpace ns);
  • Callstack#push(NameSpace ns);
  • Callstack#pop();

例えば swap() では callstack のトップの NameSpace オブジェクトを変更できます.

var obj = object();
var a = 1;
print(a);              // => 1

this.callstack.swap(obj.namespace);

this.a = 2;
print(a);              // => 2

this.callstack.swap(global.namespace);

print(a);              // => 1
print(obj.a);          // => 2

2つの swap() メソッドの呼出しの間にだけ別の新しいスコープが導入されたように見えます.しかし実際には新しいスコープではなくて1行目で object() メソッドを実行したときに作られたスコープになっているだけです.

callstack プロパティを使うと Scripting Objects に後からメソッドを追加したりオーバーライドしたりすることも簡単にできます.

hoge(){
	void hello(){ print("Hello, World!"); }
	return this;
}

var obj = hoge();
obj.hello(); // => "Hello, World"

this.callstack.swap(obj.namespace);

void hello() { print("Goodbye, World!"); } // override method
void foo() { print("foo"); }               // append new method

this.callstack.swap(global.namespace);

obj.hello(); // => "Goodbye, World!"
obj.foo();   // => "foo"

あと callstack を変更すると実行コンテキストの this の示す内容が変ります.というか「this の示す内容は callstack の内容により決定する」という見方が正しいのでしょうけど.

var obj = object();
print(this == global); // => true

this.callstack.swap(obj.namespace);
print("this.callstack.swap(obj.namespace);");

print(this == global); // => false
print(this == obj);    // => true

this.callstack.swap(global.namespace);
print("this.callstack.swap(global.namespace);");

print(this == global); // => true
print(this == obj);    // => false

Callstack#set(int depth, NameSpace ns) を使えば現在のメソッドだけではなく,その呼出し元(やそのまた呼出し元)の名前空間を変更できます.標準でそれを利用した setNameSpace(NameSpace ns) という以下のようなコマンドが用意されています.

setNameSpace( ns ) 
{
	this.callstack.set( 1, ns );
}

標準でこういうコマンドが用意されているくらいなので BeanShell では callstack の内容を変更するようなプログラミングスタイルが推奨されているのでしょう♪(たぶん)

BeanShell の基本

制御文とかメソッド定義とかクロージャとかJavaオブジェクトを動かすとかeval()とかそういう基本的すぎることは置いておきます.

  • this.namespace プロパティで NameSpace オブジェクトを弄って遊ぶ
  • this.caller プロパティでソッドの呼出し元の環境を壊しながら遊ぶ
  • this.callstack プロパティを使って NameSpace オブジェクトを変更して遊ぶ

たぶんこの辺が BeanShell の基本なのだと思います.