日記帳

日記です。

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

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

正直 Java のコードの実行は普通にコンパイルすればいいので BeanShell らしい使い方を考えてみる.

BeanShell の BeanShell らしい所というと以下の3つくらいでしょうか?

  • namespace プロパティ
  • caller プロパティ
  • callstack プロパティ

namespace プロパティ

namespace プロパティは現在の名前空間となるオブジェクトであり, bsh.NameSpace クラス(http://www.beanshell.org/javadoc/bsh/NameSpace.html)のインスタンスです.

以下のようなメソッドがあるところを見ると bsh.NameSpace クラスは BeanShell という言語にとってかなり肝になるクラス のようです.

  • String[] getMethodNames()
  • BshMethod[] getMethods()
  • Object invokeMethod(String methodName, Object[] args, Interpreter interpreter)
  • String[] getVariableNames()
  • Object getVariable(String name)
  • void setVariable(String name, Object value, boolean strictJava)
  • ...

これらのメソッドの提供する機能の多くは言語に組込まれているので明示的に呼ぶ必要はありません.なので普通こういうオブジェクトは言語内から見えないようにしそうなものですが BeanShell はこのあたり剥き出しで素敵です♪

以下のようにすれば"test"で始まる名前のメソッドを全て呼べたりします.

void testHoge() { print("Hoge"); }
void testFuga() { print("Fuga"); }
void hello() { print("Hello, World!"); }

for (var method : this.namespace.getMethods()) {
	if (method.getName().startsWith("test")) {
		method.invoke(new Object[0], this.interpreter);
	}
}
// =>"Hoge"
// =>"Fuga"

まぁこんなの今時のスクリプト言語ならみんなできるでしょうけど…;

caller プロパティ

caller プロパティは現在の実行コンテキストを呼び出し元の実行コンテキストを取得できます.実行コンテキストが取得できるということは呼出し元の変数を書き換えたりメソッドを定義したりも出来てもうやりたい放題ということです.

メソッドの呼び出し元への副作用のあるメソッドが簡単に実装できます.

void changeX(){
	this.caller.x = 100;
}

int x = 20;
print(x); // => 20
changeX();
print(x); // => 100

void hoge(){
	int x = 30;
	print(x); // => 30
	changeX();
	print(x); // => 100
}
hoge();

BeanShell はクロージャをサポートしており静的スコープなメソッドが使えますが caller プロパティがあるおかげで動的スコープも使えるわけです.

以下d:keyword:クロージャのサンプルの BeanShell 版です.

makeCounterStatic () {
	var count = 0;
	int up () {
		return count++;
	}
	return this;
}

makeCounterDynamic () {
	int up () {
		return this.caller.count++;
	}
	return this;
}

var count = 10;

var counter = makeCounterStatic();
print(counter.up()); // => 0
print(counter.up()); // => 1
print(counter.up()); // => 2

counter = makeCounterDynamic();
print(counter.up()); // => 10
print(counter.up()); // => 11
print(counter.up()); // => 12


this.caller.caller... とかやっていくと何個でも戻れますし,this.caller.namespace とすれば NameSpace オブジェクトが取得できるので… まぁ何でもありですね.

例えばメソッドの別名を定義するメソッドとかも簡単そうです.

void alias(String aliasName, String methodName, Class[] signature)
{
	bsh.BshMethod method = this.caller.namespace.getMethod(methodName, signature);
	if(method != null)
		this.caller.namespace.setMethod(aliasName, method);
}

void hello() { print("Hello"); }
alias("greeting", "hello", new Class[0]);

hello(); // => "Hello"
greeting(); // => "Hello"

第三引数に Class[] signature が必要なのは BeanShell がメソッドのオーバーロードをサポートしている言語だからです.

callstack プロパティ

疲れた.続きはまた来世…