Hatena::ブログ(Diary)

Islands in the byte stream

2012-01-21

「PHPの最初のバージョンはPerlで書かれていた」は都市伝説?

[追記]

どうやら、最初の個人用のホームページ管理スクリプトPerlで書かれ、後にそれをCで書きなおしたものを"Personal Home Page Tools" 1.0として公開、というのが真相のようだ。

つまり、PHPの最初のバージョンがPerlで書かれていた、というのは都市伝説ではなく事実であるようだ。

PHP: PHP/FI Version 2.0 Documentation

PHP began life as a simple little cgi wrapper written in Perl. (snip) It was never intended to go beyond my own private use.

Announce: Personal Home Page Tools (PHP Tools)

Announcing the Personal Home Page Tools (PHP Tools) version 1.0.

These tools are a set of small tight cgi binaries written in C.

(snip)

情報提供をしてくださった方々に御礼申し上げます。

[/追記]

PHPの最初のバージョンはPerlで書かれていた、という話を聞いたことがあり、てっきりそうだと思い込んでいた。しかし、これは都市伝説かもしれない。

Wikipediaによれば、最初のバージョンはPersonal Home Page ToolsというPerlスクリプトで、次にCで書きなおしたものがPHP/FIである。

PHP - Wikipedia, the free encyclopedia

Danish/Greenlandic/Canadian programmer Rasmus Lerdorf initially created a set of Perl scripts he called "Personal Home Page Tools" to maintain his personal homepage.

(snip)

He rewrote these scripts as C programming language (snip) called this implementation 'Personal Home Page/Forms Interpreter' or PHP/FI.

しかし、公式ドキュメントには異なる歴史が書いてある。

PHP: History of PHP - Manual

PHP as it's known today is actually the successor to a product named PHP/FI. Created in 1994 by Rasmus Lerdorf, the very first incarnation of PHP was a simple set of Common Gateway Interface (CGI) binaries written in the C programming language.

(snip) he named the suite of scripts "Personal Home Page Tools," (snip)

In September of that year, Rasmus expanded upon PHP and - for a short time - actually dropped the PHP name. Now referring to the tools as FI (snip)

つまり最初のPersonal Home Page ToolsはCで書かれており、そののち機能が追加されてPHP/FIとなったとある。

これはどちらが真実なのだろうか。ソースコードが手に入れば一発で分かるのだが。

2012-01-12

node::console.logの実装

ブクマでも指摘がありますが、nodeのconsole.log()にはutil.format()が使われています。

このuti.format()はsprintf(3)風の機能も持っていますが、それ以上にオブジェクトのダンプ機能が嬉しいのではないかと思います。

ところで、consoleなどのnodeの組み込みオブジェクトの一部はJavaScriptで記述されているので簡単に読むことができます。

たとえばconsoleやutilの実装は以下にあります。

これによると、細かいオプションを渡してダンプしたいときは、util.inspect()を直接呼べばいいことがわかります。

#!/usr/bin/env node
"use strict";
var util = require('util');
var x = { first: { second: { third: { forth: 42 }}}};

console.log(x);
// -> { first: { second: { third: [Object] } } }

console.log("%s", util.inspect(x, true, 10));
// -> { first: { second: { third: { forth: 42 } } } }

nodeでコードを書いているときは、JSON.stringify()よりもこちらを使ったほうが正確な情報を得られます。

2012-01-10

JavaScriptでバイナリを読むjDataView

どうしたものかと思っていたところ、typed arrayの仕様の中にDataViewというものがあるようだ。

しかしポータビリティはよくないので、当面はpure JSによる実装であるjDataViewを使ったほうがいいのかもしれない。

こちらはオリジナルのDataViewを拡張しており、ストリームのように使える。また、ロードするとjQueryの$.get()でdataviewを使えるようになるのも嬉しい。

// from ArrayBuffer
var buffer = jDataView.createBuffer(1, 2, 3); // helper

var view = new jDataView(buffer);

console.log(view.getUint8()); // 1
console.log(view.getUint8()); // 2
console.log(view.getUint8()); // 3

// from jQuery
$.get(
  './hello.exe',
  function (view) {
    console.log( view.getString(2) ); // 'MZ'
  },
  'dataview'
);

2012-01-08

正月なので #発火村 に参加してきた

国境の長いトンネルを抜けると、そこは雪国だった。

正月発火村 #発火村 - Togetter

群馬の秘境、水上でハッカソンに参加してきた。

ハッカソンではClion.JSというECMA-335*1仮想マシンJavaScript実装を作りたかったのだけど、作業量的に一日二日でできるものでは到底なかったでので「実装を始めた」という程度でした。Clion.JSの開発は続けるつもりです。

さて、ハッカソン前半はずっとmonoを参考にしながらCLI executableのローダを書いていたのだけど、複雑すぎてまるで形になりませんでした*2。そこで急遽VMだけを先に作ることにし、最終的に発表したものが以下のスクリプトです。これにCLI executableを別途disassembleしたものを与えると実行します。

CLI VMは仕様を読むとスタックマシンのようですね。なのでスタックを用意して、disasmしたものからとってきた命令列を実行するだけです。もちろん完全な実装ではなく、文字列の出力と加減算くらいしかできませんが、100行程度のものなのでスタックマシンのVMの簡単なサンプルとしては丁度いいでしょう。

clion-vem.js:

#!/usr/bin/env node
"use strict";
var p = console.log;

var fs = require('fs');

var name  = process.argv[2] || '/dev/stdin';

// loader
var buffer = fs.readFileSync(name).toString().split(/\n+/);
var i;
for(i = 0; i < buffer.length; i++) {
    if(buffer[i].match(/\.entrypoint$/)) {
        break;
    }
}
var ops = [];
for(; i < buffer.length; i++) {
    var matched = buffer[i].match(/IL_(....):\s+(\w+)\s*(.*)$/);
    if(matched) {
        var label = matched[1];
        var name  = matched[2];
        var args  = matched[3];
        ops.push({ label: label, name: name, args: args });
    }
    else if(buffer[i].match(/^\s*\}/)) {
        break;
    }
}

// virtual machine
(function() {
    var stack = [];
    var registory = [];
    var method = {
        'System.Console::WriteLine': function(arg) {
            console.log("%s", arg);
        },
        'string::Concat': function(a, b) {
            return String(a) + String(b);
        },
    };
    var i, op;
    for(i = 0; i < ops.length; i++) {
        op = ops[i];
        switch(op.name) {
        case "box":
            // noop
            break;

        case "call":
            var m      = op.args.match(/(\w+(?:\.\w+)*::\w+)\((.*)\)/);
            var name   = m[1];
            var argc   = m[2].split(/,/).length;
            var args   = stack.splice( stack.length - argc );
            stack.push( method[name].apply(this, args) );
            break;

        case "add":
            var right = stack.pop();
            var left  = stack.pop();
            stack.push( (+left) + (+right) );
            break;
        case "sub":
            var right = stack.pop();
            var left  = stack.pop();
            stack.push( (+left) - (+right) );
            break;

        case "ldc":
            var m = op.args.match(/\w+\s*$/);
            var v = parseInt(m);
            stack.push(v);
            break;

        case "ldstr":
            stack.push( eval(op.args) );
            break;

        case "stloc":
            var idx = op.args.match(/\d+/)[0];
            registory[idx] = stack.pop();
            break;
        case "ldloc":
            var idx = op.args.match(/\d+/)[0];
            stack.push(registory[idx]);
            break;

        case "ret":
            return;
        default: throw Error("Not yet implemented: " + op.name);
        }
    }
})();

以下のようなC#コードが実行できます。

class HelloWorld {
    static void Main() {
       var x = "Hello, ";
       var y = "world!";
        System.Console.WriteLine(x + y);
    }
}

実行するにはmonoとnodeが必要で、逆にこれらがあればプラットフォームに関係なく実行できます。

$ mcs hello.cs # -> hello.exeができる
$ monodis hello.exe | ./clion-vem.js
Hello, world!

monodisするといろいろ出てきますが、見ているのは以下のところだけ。

$ monodis hello.exe
// (snip)
	IL_0000:  ldstr "Hello, "
	IL_0005:  stloc.0 
	IL_0006:  ldstr "world!"
	IL_000b:  stloc.1 
	IL_000c:  ldloc.0 
	IL_000d:  ldloc.1 
	IL_000e:  call string string::Concat(string, string)
	IL_0013:  call void class [mscorlib]System.Console::WriteLine(string)
	IL_0018:  ret 
//  (snip)

インストラクションの意味は以下のとおり。だいたい名前から推測できます。

  • ldstr 文字列定数をスタックにpush
  • stloc.x スタックから値をpopしてローカル変数xに保存
  • ldloc.x ローカル変数xから値を得てスタックにpush
  • call 関数の呼び出し
  • ret 関数からリターン

意味がわかればあとはコードに落としこむだけです。

感想

今回のような泊まりがけのハッカソンは初めて参加しました。家でコードを書くよりもずっと集中できたし、テンションを高いまま保っていられた*3

年に3回くらいはこういう泊まりがけのハッカソンがあってもいいかな、と思う。

ありがとうございました!

*1:事実上、C#処理系と考えてよい

*2:ヘッダの読み込みはどうやらできて、現在は中身の読み込みをしているところ。

*3:そのかわり、20時頃帰宅するなり眠り込んで朝3時に目が覚めた。

2012-01-02

ある値を非同期で取ってくる→その値を使ってさらに非同期処理というのをJSDeferredでする

ユースケースとしては、テストの最初にあるシステムにログインしてユーザIDなりセッションIDなりを得て、それでもって残りのテストを実行するみたいな処理です。

これ、素直にJSDeferredDeferred.next(...).next(...)と続けても、このnextのコールバックは最初にいきなり呼ばれたあと「待ち」に入るので、非同期で取ってきた値に依存した処理は書けないんですよね。

これは、Deferredオブジェクトを自分で作って制御すればいけるみたいです。

以下サンプルコードです。最初の100msかかる非同期処理で42という値を得て、それを次の処理に渡せることを確認します:

#!/usr/bin/env node
"use strict";

var Deferred = require('./jsdeferred.js').Deferred;

Deferred.
next(function() {
    console.log('# 1');

    var d = new Deferred;
    setTimeout(function() {
            console.log('# 2');
            d.call(42);
            console.log('# 4');
    }, 100);
    return d;
}).
next(function(foo) {
    console.log('# 3');
    console.log('got %s', foo);
    setTimeout(function() {
        console.log('# 5');
    }, 1);
}).error(function(e) {
    console.log(e);
});

console.log('# 0');

結果は#の番号通りになります:

# 0
# 1
# 2
# 3
got 42
# 4
# 5

このような連鎖がいくつもある処理をJSDeferredを使わずにやろうと思うと気が遠くなりますね。id:cho45++