車輪の再発明のような気もするけれど。
次のようなWSFファイルを作成する。
<job id="sample">
<script language="JScript" src="pre_prototype.js"/>
<script language="JScript" src="prototype.js"/>
<script language="JScript" src="post_prototype.js"/>
<script language="JScript">
(3).times(function(i) {WScript.Echo(i)});
</script>
</job>
prototype.jsはブラウザで動かすことを前提とした作りになっているために、そのままではWSHでロードすることができない。 適当にダミーオブジェクトを定義する必要あり(pre_prototype.js)。
// For prototype.js version 1.6.0
var document = {
createElement: function() {return {appendChild: function(){}}},
createTextNode: function() {return {}},
createEvent: function() {return {__proto__: {}}},
write: function() {return {}},
getElementById: function() {return {}}
};
var window = {};
var navigator = {appVersion: ""};
var Element = {};
また、オートメーションオブジェクトにtoStringプロパティが定義されておらず Object.inspectの呼び出しに失敗するので、該当関数を再定義してみる(post_prototype.js)。
Object.extend(Object, {
inspect: function(object) {
try {
if (object === undefined) return 'undefined';
if (object === null) return 'null';
return object.inspect ? object.inspect() :
(object.toString ? object.toString() : "#<UnknownObject:" + object + ">");
} catch (e) {
if (e instanceof RangeError) return '...';
throw e;
}
}
});
ruby-breakpointはコード中に"breakpoint"と書いておくと実行時にその環境でreplに突入してくれるというもの。 オリジナルは複数行にわたる入力やdRubyを用いたリモートアクセスもサポートするが、そこまで考えなければ簡単に似たようなものが用意できる。
(function(i) {eval(breakpoint)})(0);
実行結果。
> i + 1 1
var breakpoint =
'for(;;) {' +
' WScript.StdOut.Write("> ");' +
' if (WScript.StdIn.AtEndOfStream) break;' +
' var expr = WScript.StdIn.ReadLine();' +
' try {' +
' WScript.StdOut.WriteLine(Object.inspect(eval(expr)));' +
' }' +
' catch (e) {' +
' WScript.StdOut.WriteLine("Error Code: " + e.number);' +
' WScript.StdOut.WriteLine(e.description);' +
' }' +
'}';
例外をキャッチしてスタックトレースを付与する関数でラップする。
function func1(i, j) {
(function () { func2("foo") })([]);
}
function func2(i, j) {
throw new Error("ERROR");
}
func2 = throwsInformationalError(func2);
var Test = Class.create({
func3: throwsInformationalError(function(i, j) {
this.func4("bar");
}, "func3"),
func4: throwsInformationalError(function(i, j) {
throw new Error("ERROR");
}, "func4")
});
それぞれ呼び出したときの実行結果。
func1();
test.wsf(149, 10) Microsoft JScript 実行時エラー: ERROR
--- Stack Trace ---
func2('foo')
[anonymous]([])
func1()
(new Test()).func3()
test.wsf(149, 10) Microsoft JScript 実行時エラー: ERROR
--- Stack Trace ---
func4('bar')
func3()
Object.extend(Function.prototype, {
getName: function () {
if (this.name) return this.name;
var regmatch = this.toString().match(/^function\s*(\w+)/);
return regmatch ? regmatch[1] : '[anonymous]';
}
});
function throwsInformationalError(fn, fn_name) {
function _throwsInformationalError() {
try {
return fn.apply(this, arguments);
}
catch (e) {
if (! e.informational) {
var stack = [];
for (var i = arguments.callee; i; i = i.caller) {
var args = [];
for (var j = 0; j < i.arguments.length; j++) {
args.push(Object.inspect(i.arguments[j]));
}
var _fn = (i == arguments.callee) ? fn : i;
if (_fn.getName() != "_throwsInformationalError") {
stack.push(_fn.getName() +"(" + args.join(", ") + ")");
}
}
e.description += "\n--- Stack Trace ---\n" + stack.join("\n");
e.informational = true;
}
throw e;
}
};
if (fn_name) {
fn.name = fn_name;
}
return _throwsInformationalError;
}
JScriptのfor...in文とVBScriptのFor Each...Next文は言語仕様的には似たようなものだけど、 その実装には決定的な違いがある。その差を埋めるためにEnumeratorが用意されてはいるけれど、 それでもなおカバーしきれないオブジェクトが存在する。ADSIによる属性アクセスなどがそれ。
> var o = GetObject("LDAP://DC=example,DC=com")
#<UnknownObject:>
> o.objectClass
[object Object]
> for (var i in o.objectClass) {WScript.Echo(i)}
undefined
> new Enumerator(o.objectClass)
オブジェクトがコレクションではありません。
ここで各要素にアクセスする方法を調べてみるも、MicrosoftのサンプルはVBScriptでFor Each...Nextしているものばかり。 しょうがないのでVBScript側でFor Each...Next文相当のプロシージャを用意しておいてWSFファイルで読み込んで利用する。 *1
Sub foreach(obj, fn)
Dim i
For Each i In obj
Call fn(i)
Next
End Sub
JScript側からはこのように。
> foreach(o.objectClass, function(i) {WScript.Echo(i)})
top
domain
domainDNS
undefined
*1 ただ、この例ならばo.objectClass.toArray()とやればアクセス可能。