車輪の再発明のような気もするけれど。
次のような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()とやればアクセス可能。