プログラミング言語 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 の内容を変更するようなプログラミングスタイルが推奨されているのでしょう♪(たぶん)