JavaScriptにsleepを実装しようとして気づいたら新言語を作ってた話
あのですね、JavaScriptにsleep()とかwait()みたいなのってほしいじゃないですか。で、調べてみると標準にはなくて作れるには作れるけどブラウザ依存だったりビジーループだったりJava Applet併用だったりしてがっかりするじゃないですか。
ぼくは1年半くらい前に「竹内関数で音楽生成」っていうエントリーを書いたんですが、当然これをJavaScriptで計算しながら音を鳴らすページを作りたくなりますよね。ですよね。こう見えても昔はN88-BASICでCMD PLAYとかばりばり使ってたくちなんで、そのくらい簡単だと思うじゃないですか。そしたらもう当時はAudio APIも出たばかりで情報もすくなくてブラウザ依存で、しかもJavaScriptでsleepできないしふんだりけったりで最初の一歩も踏み出せない状態。
そんなわけでなんとか手はないかとこの1年以上ずっと試行錯誤してきてようやく完成したと、まあそういうわけです。
http://aikelab.net/tarai/
これは[Start/Pause]ボタンを押すと、竹内関数(たらいまわし関数)を再帰的にどんどん呼び出して、関数を呼び出すたびに1小節分の音を鳴らして、鳴り終わるまで待って、音が鳴りおわったら次の関数を再帰して、ということをくりかえしいく、そんなページ。ということはですよ、sleep処理が実現できてるんですよ、奥さん。しかもソースコード上はsetTimeout地獄とかになってないんですよ。
で、まあ、どうやったかというと、タイトルのとおりJavaScriptで「sleepできる別の言語」を作ったわけです。CoffeeScriptやJSXのようなJavaScriptに変換するやつじゃなくってJavaScriptで書かれたインタプリタです。
この言語、ぼくがいうのもなんですが小さいわりにちゃんとしてて、現在の実行アドレスを格納するProgram Counter(PC)レジスタ、クロックごとにPCレジスタの命令を実行するプロセッサ、メモリ領域のスタック、ローカル変数(v1〜v4)、引数(a1〜a3)付の関数定義、簡易的な四則演算、if文による分岐なんかを実装してたりします。
関数呼び出しのときは、退避すべき変数と呼び出し元アドレスと引数をスタックに積んで、returnするときはそれらを戻しつつ戻り値をスタックに積んで返します。こうすることで竹内関数のような深い再帰呼び出しも問題なく実行できます。
それで、実行するスクリプトはこんな感じ。playのところで音を鳴らしつつ一定時間スリープします。
var script = [ '#----------------------', '# tarai function script', '#----------------------', 'call tarai 10 5 0', 'end', '', 'def tarai', ' play a1 a2 a3', ' if a1 <= a2', ' return a2', ' v1 = a1 - 1', ' call tarai v1 a2 a3', ' v2 = ret', ' v1 = a2 - 1', ' call tarai v1 a3 a1', ' v3 = ret', ' v1 = a3 - 1', ' call tarai v1 a1 a2', ' v4 = ret', ' call tarai v2 v3 v4', ' return ret', 'defend' ];
JavaScriptのライブラリとしてはちょっとつかいみちが思いつかない感じかもしれないけど、プログラミング言語実装の参考にどうぞ。