Hatena::ブログ(Diary)

Islands in the byte stream

2012-06-03

Unit tests for JSX

いずれJSX web siteにも載せますが、JSXには非同期テストにも対応したユニットテスト・フレームワーク、test-case.jsxが標準で添付されています。t/lib/*.jsxはこのtest-case.jsxでテストされています。

使い方は以下のようにTestCaseを継承して、"test"で始まるメソッドを並べるというJUnit風のものになっています。

非同期テストを行う場合は、TestCase#async()のコールバックの中でテストを行い、AsyncContext#done()でテストの終了を通知するというインターフェイスになっています。

import "test-case.jsx";
import "timer.jsx";

class _Test extends TestCase {

  // synchrounous tests

  function testClearTimeout() : void {
    var id = Timer.setTimeout(function() : void {
      this.fail("setTimeout called after clearTimeout");
    }, 1);
    Timer.clearTimeout(id);

    this.expect(id, "clearTimeout").toBe(id);
  }

  // asynchronous tests

  function testSetTimeout() : void {
    this.async(function(async : AsyncContext) : void {
      var to = 200;
      var t0 = Date.now();
      Timer.setTimeout(function() : void {
        var t1 = Date.now();

        this.expect(t1 - t0, "setTimeout 200 ms.").toBeGE(to - 50);

        async.done();
      }, to);
    }, 1000);
  }
}

このファイルをt/001.foo.jsxという名前で保存したとして、実行はjsx --testproveコマンドを使います。

prove --exec "jsx --test" --ext .jsx

このproveへの引数は.provercに書いてしまってもいいでしょう。jsx-random-generatorでは実際にそうやっており、proveコマンドを単体で起動するだけでテストを行えるようになっています。

このjsx --testはtest-case.jsxを前提としているわけではなく、他のテストフレームワークを使うこともできるのですが、その話はまた別のエントリで書こうと思います。

Enjoy testing!

2012-05-20

Uint8ArrayとUint8ClampedArrayの違い

W3Cによれば、この違いは値がclampされるかどうからしい。clampというのは、範囲の指定されたintにその範囲を超えた値を代入したとき、通常であればmoduloで処理するところを、範囲の上限の値を代わりに代入する、というものだ。

// NOTE: node.js doesn't support Uint8ClampedArray

"use strict";
// normal typed array
var a = new Uint8Array(1);
a[0] = 1000;
console.log(a[0]); // 1000 % 0xFF => 232

// clamped typed array
var a = new Uint8ClampedArray(1);
a[0] = 1000;
console.log(a[0]); // uint8 max => 255

どういういきさつで導入されたのかわからないが、不要に複雑にしすぎている気がする。

2012-05-13

Dart Hackathonに参加しました

Googleのオフィスで行われたGlobal Dart Hackathonにチューターとして参加して、ハッカソン開始前に軽くDart on Smartphoneについて解説&デモを行いました。

当日の様子はtogetterにまとめられています。

Hackathonの優勝者は、UUID生成器とMersenne Twister擬似乱数生成器とtesting frameworkを作成したチームで、短時間で役に立つものをつくりあげてとても素晴らしいと思います。

また、他の入賞作品としてはAndroid ADKとマッシュアップしてLEDを光らせたりWebRTCを使った画像認識ツールで会場をわかせたりしたものがありました。

以上。とても楽しいhackathonだったと思います。またやりたいですね。Happy Hacking!

2012-04-22

source-mapを可視化する

source-mapは非常に期待のできる技術だが、軽く仕様を読んでもどのように動くのかよくわからない。そこで、souce-mapを可視化するツールを作ってみた。source-mapの処理にはmozillaのJavaScriptによる実装を使った。

f:id:gfx:20120422145257p:image

左がオリジナルのソースコード、右がclosure compilerで最適化したコードである。具体的なコマンドラインオプションは以下のrepositoryのMakefile参照のこと。要素から要素への線はcanvasを利用した。ChromeFirefoxでのみ確認したので、他のブラウザではうまく表示できないかもしれない。

改良の余地はあるものの、とりあえずこれでデバッグできるようになった。

ブラウザにCommonJSを簡単に提供するスニペット

CommonJS (+node.js 独自拡張)なモジュールをブラウザで読み込むにはいくつかの方法がある。たとえばbrowserbuildはまとめて1ファイルにしてくれるので、プロダクションコード用としてはとてもよい。一方でbrowserbuildは痒いところに手が届かないしデバッグも結合後のソースで行わなければならず、開発用としてはあまりよくない。

単純にモジュールをロードしたいだけなので、そのためのスニペットを書いてみた。

https://github.com/gfx/require-simple.js

// require-simple.js
function require(name) {
	"use strict";
	if(!require.modules) {
		require.modules = {};
	}
	if(require.modules[name]) {
		if(require.debug) {
			console.log("require: \"" + name + "\" is already loaded");
		}
		return require.modules[name].exports;
	}

	function findModule(paths, name) {
		for(var i = 0; i < paths.length; ++i) {
			var url = paths[i]+ "/" + name + ".js";

			var xhr = new XMLHttpRequest();
			xhr.open("GET", url, false);
			xhr.send(null);
			
			if(require.debug) {
				console.log("require: " + xhr.status + " " + url);
			}

			if(xhr.status === 200) {
				return xhr.responseText;
			}

			if(xhr.status !== 404 || (i+1) === paths.length) {
				throw new Error("Failed to load \"" + name + "\": " +
								xhr.status + " " + xhr.responseText);
			}
		}
	}

	var src = findModule(require.paths, name);

	/*jslint evil: true */
	var f = new Function("module", "exports", src);

	var module = require.modules[name] = {
		exports: {}
	};
	f(module, module.exports);
	return module.exports;
}
require.debug = false;
require.paths = ["."];

使い方:

<script src="require-simple.js"></script>
<script>
require.paths.unshift("assets/js");
var module = require("module");
</script>

node.jsの最新のパス指定(module.paths)には対応していないがとりあえずこれで十分だ。

なお、負荷のかかるサイトではこのスニペットを使うべきではない。browserbuildなどで結合したものを使ったほうがよい。

2012-04-14

円マークキーでバックスラッシュを入力する

正規表現リテラル /foo\/bar/ が構文エラーで弾かれてなぜだろうと思っていたら円マークが原因だった。これだから日本語キーボードは困る。

カッとなってtextareaで円マークをバックスラッシュに置き換えるようにした。ついでにTABキーで¥tを入力するようにした。

var input = document.getElementByName("edit");

function inputStr(event, input, str) {
	event.stopPropagation();
	event.preventDefault();

	var s = input.selectionStart;
	var c = input.value;

	input.value = c.substring(0, s) +
				  str +
				  c.substring(s, c.length);
	s += str.length;
	input.setSelectionRange(s, s);
}

input.addEventListener('keydown', function(event) {
	if(event.keyCode === "\t".charCodeAt(0)) {
		inputStr(event, input, "\t");
	}
	else if(event.keyIdentifier === "U+00A5" /* yen mark */) {
		inputStr(event, input, "\u005C" /* backslash */);
	}
});

C++でJavaScriptのString.prototype.replaceを実現できるか

ECMA ScriptのString.prototype.replaceメソッドは第二引数が関数だった場合、パターンマッチでキャプチャされる部分が可変長でしかもこの可変長引数が固定引数に挟まれているという不思議仕様です。

使用例:

#!/usr/bin/env node
console.log( "(f)ooba(r)".replace(function(part, p1, p2, pos, src) {
    console.log( Array.prototype.slice.call(arguments) );
});
// 結果:
// [ 'oba', 'o', 'a', 2, 'foobar' ]

なぜString.prototype.matchが返すようなオブジェクトにしなかったのかと問い詰めたくなります。おそらく、パフォーマンスのためにオブジェクト構築をやめ、使用順に引数を並べたらこうなったということなのでしょう。

さて、これを静的型付け言語であるC++で表現できるか試してみました。結論から言うとC++11のvariadic templateを使えばできます。以下のコードはclang 3.0 (Apple ver.)で確認しました。

// 可変長引数replacerのスタブ例

#include <iostream>
#include <string>

using namespace std;

template <typename ...Args>
const string vreplace_impl(string src, std::string pattern,
        const string replacer(const string, int pos, const string)) {

    replacer("vreplace with 0 capture", 0, src);
    return "";
}

template <typename ...Args>
const string vreplace_impl(string src, std::string pattern,
        const string replacer(const string, const string p1, int pos, const string)) {

    replacer("vreplace with 1 capture", "p1", 0, src);
    return "";
}

template <typename ...Args>
const string vreplace(string src, std::string pattern,
        const string replacer(const string, Args...)) {
    vreplace_impl(src, pattern, replacer);
    return "";
}


const string r0(const string part, int pos, const string src) {
    cout << "r0 " << part << "," << pos << "," << src << std::endl;
    return "";
}

const string r1(const string part, const std::string p1, int pos, const string src) {
    cout << "r1 " << part << "," << pos << "," << src << std::endl;
    return "";
}

int main() {

    vreplace("foobar", "foo",   r0); // capureが含まれないのでr0
    vreplace("foobar", "f(oo)", r1); // captureが一つ含まれるのでr1

    return 0;
}

// 結果:
// r0 vreplace with 0 capture,0,foobar
// r1 vreplace with 1 capture,0,foobar

ただ、引数の数の決定の仕方が両言語では異なります。JS版ではパターンに含まれるキャプチャに依存してreplacerの引数を決定するのに対し、C++版ではreplaceに与えたreplacerのシグネチャに依存して、パターンのなかにキャプチャがいくつあるべきかを決定するのです。パターンの中のキャプチャの数がシグネチャと一致しない場合、おそらくランタイムエラーを投げることになるでしょう。

まあとにかく、実装はできそうです。もちろん、C++でこの種の処理を実装したいときはMatchedクラスを定義し、可変長部分はコンテナにするのがベストプラクティスだと思いますが。

参考文献:

15.5.4.11 String.prototype.replace (searchValue, replaceValue)

...

If replaceValue is a function, then for each matched substring, call the function with the following m + 3

arguments. Argument 1 is the substring that matched. If searchValue is a regular expression, the next m

arguments are all of the captures in the MatchResult (see 15.10.2.1). Argument m + 2 is the offset within string

where the match occurred, and argument m + 3 is string. The result is a String value derived from the original

input by replacing each matched substring with the corresponding return value of the function call, converted

to a String if need be.