D-JS をもうちょっと実験

2009/09/01 | めも

引き続き弄ったが気力がなかったので C++ 側ではなく D 側でしこしこ.

先日の D 文字列をばんばん eval して使うという怪しげな使い方をもうちょっと進めて,違和感なく D 関数として js を実行できるようにしてみる.js 関数のインポート,とは微妙に違うけどノリと実現できることは大体そんな感じ.

発想としては js コードを引数にとって D 関数を返すような関数を作ればいい.こういう lambda っぽいことは当の js さんの方が当然得意で,js 書けば

function hoge(code)
{
    return function(){return eval(code);};
}

とかこういうの.D 言語でも言語レベルでデリゲート使った動的クロージャ利用ができるので似たような書き方でいける.

typedef string delegate() JS; // 強い型付け
 
JS jsFunction(string code)
{
    return cast(JS){return v8d.eval(code);};
}
 
JS hello = jsFunction("['H', 'e', 'l', 'l', 'o', "!"].join('');");
hello(); // => "Hello!"

関数リテラルで無名デリゲート書けるので本当に同じノリ.

んでさらに,引数を取れるようにしたい.

事前にソースコードを与えて関数(デリゲート)を作成してるので,その呼び出し時に引数を事前に与えた js ソースの適切な場所に埋め込んでから V8 に食わせるという形.

書式付き文字列ですっきり実装できるんじゃない? とやってみる. js ソースとして

"%s".substr(%d, %d);

とかを与えておいて,

JSsubstr("hoge", 1, 2);

と呼ぶとその内部で

string formatted = std.string.format(`"%s".substr(%d, %d);`, "hoge", 1, 2);

な形で呼び出されて,formatted すなわちここでは

"hoge".substr(1, 2);

が評価されて,めでたく “og” が返ってくるという感じ.

引数の型がまちまちなんでテンプレートかなと若干憂鬱になるが,D にはテンプレートを使わない組み込みの可変個引数関数があるよってことで使ってみる.

typedef string delegate(...) JS; // 強い型付け
 
JS jsFunction(string code)
{
    return cast(JS)(...){
        return v8d.eval(code);
    };
}

ところがこう可変個引数関数へのデリゲートに変更した途端にコンパイルが通らないというか dmd コンパイラが落ちた.v2.014 を使ってて若干古い気もしたので最新の v2.031 にすると通った.

ところがこの可変個引数が割と扱いにくい代物で,各引数にアクセスするにもポインタ経由でお世辞にもエレガントじゃないし,そのままの引数全体をさらに別の可変個引数関数に渡す簡単な方法なんかもない.タプル使えば色々できるのかな,と思うんだけどそうするとテンプレート使っちゃう.

具体的にどう問題になるかというと,スコープのひとつ外にある code を第一引数に,可変個引数デリゲートが受け取った引数全体を第二引数以降の書式指定のパラメータにして std.string.format (sprintf みたいなもん)やらを呼びたいんだけど,うまいこと呼ぶ方法はないもんか,と.

あれこれ試行錯誤して,結局美しい方法が見つからなかったので,気分の悪いローレベルなコードを書いて動かした.

同じような情報を必要としている人が万一いることも考えて詳細を書いておく.

可変個引数関数は,その中で特殊ローカル変数 _arguments と _argptr が使える.前者は型情報を保持するクラス TypeInfo の配列で,後者は引数リストの先頭の void* ポインタ(まあ C の可変個引数関数と似たようなもんではある).

TypeInfo から型のサイズが取れる (tsize()) ので,ポインタ操作してキャストして,これらから各引数を取り出して使うことはできる.が,可変個引数関数にその全てをつっこみ直す簡単な方法は(わから)ない.

じゃあ std.string.format はどう動いてるのかと dmd/src/phobos/std/string.d を見てみると,std.format.doFormat を呼んでいる.そしてその doFormat の引数には _arguments と _argptr がそのまま使えることがわかる.今は書式化ができればいいので,この doFormat を直接呼べばいい.

自分の望む可変個引数の並びをメモリ上に構成し,_arguments と _argptr をそれぞれ更新し,doFormat に渡せば,任意の可変個引数がうまいこと渡せる.

より具体的には,_arguments を走査して現在の可変個引数のメモリ上のサイズを計算し,前時代的な malloc でそのサイズプラス追加したい引数のサイズのメモリを確保,memcpy なりバイト毎に代入なりで現在の可変個引数を新しいメモリ領域にコピー,空いてる領域に望みの追加したい引数を配置,_arguments にも追加した引数の TypeInfo( typeid(型) で取れる)を位置を合わせ正しく追加,doFormat を _arguments と malloc した領域の先頭のポインタで呼び出し,malloc したとこ free,という流れ.

JS jsFunction(string code)
{
    return cast(JS)(...){
        int size = 0;
        foreach (arg; _arguments){
            size += ((arg.tsize() + size_t.sizeof - 1) & ~(size_t.sizeof - 1));
        }
 
        void* argptr = malloc(size + string.sizeof);
        *cast(string*)(argptr) = "{" ~ code ~ "}";
 
        byte* fp = cast(byte*)_argptr;
        byte* tp = cast(byte*)argptr;
        for(int i = 0; i < size; i++){
            tp[i + string.sizeof] = fp[i];
        }
 
        _arguments = typeid(string) ~ _arguments;
 
        string formatted;
        std.format.doFormat((dchar c){formatted ~= cast(char)c;}, _arguments, argptr);
        free(argptr);
        return v8d.eval(formatted);
    };
}

とこれで

JS init = jsFunction("var ts = new Tsuikyo();");
JS add = jsFunction("var w = ts.add('%s');");
JS stroke = jsFunction("w.stroke('%c');");
JS printTimes = jsFunction("for(var i = 0; i < %d; i++) print(w.get%sString());");
 
init();
add("ほげ");
stroke('h');
stroke('o');
printTimes(3, "InputKey"); // => "hohoho"

みたいなことは実際にできるようになった.

引数の数や型の簡単なチェックを入れるとか,細かいことはまだ色々.

D から js を利用する……というよりはメタプロの魔の森に足を踏み入れかけているだけのような気はすごくする.

Trackback URL

この記事にはまだコメントがついていません。

コメントをどうぞ