2008/09/11

はてな
ちょっと訳あって、WScriptからjQueryを呼び出す必要があり(嘘です)作ってみました。

これを使うと
$.each([1,2,3], function(index, item) {
    print("foo" + item);
});
$.ajax({
    type: "GET",
    url: "http://www.google.co.jp/",
    async: false,
    success: function(data) {
        print(data);
    }
});
こんなソースが実行出来ます。
$.eachなんかは目茶目茶便利なので、使わない手はありません。とりあえず、$.ajaxでgoogle.co.jpのソースが取得出来るくらいは動きます。
以下全体ソース。
// vim:fdm=marker fdl=0 fdc=0 fdo+=jump,search:
// vim:fdt=substitute(getline(v\:foldstart),'\\(.\*\\){\\{3}','\\1',''):
// {{{
(function(target) {
    target.window = {
        document : {
            defaultView : {}
        },
        navigator : {
            userAgent : "Windows Scripting Host"
        },
        location : {},
        XMLHttpRequest : function() {
            // copied from http://la.ma.la/misc/js/ie_xmlhttp.js
            var self = this;
            var props = "readyState,responseText,responseXML,status,statusText".split(",");
            this.readyState  = 0;
            this.__request__ = new ActiveXObject("Microsoft.XMLHTTP");
            this.__request__.onreadystatechange = function(){
                for(var i=0;i<props.length;i++){
                    try{
                        self[props[i]] = self.__request__[props[i]]
                    }catch(e){
                    }
                }
                self.onreadystatechange()
                if(self.readyState == 4) self.onload();
            }
            this.onreadystatechange = function(){};
        },
        setInterval : function(func, interval) {
            func(); // quickly f*ckin hack
        },
        clearInterval : function(timer) {
        },
        setTimeout : function(func, interval) {
            func(); // quickly f*ckin hack
        },
        clearTimeout : function(timer) {
        }
    };
    var methods = "open','abort','send','setRequestHeader','getResponseHeader','getAllResponseHeaders".split("','");
    var make_method = function(name){
        window.XMLHttpRequest.prototype[name] = function(){
            var params = new Array(arguments.length);
            for(var i=0;i<params.length;i++) params[i] = "_"+i;
            return Function(
                params.join(","),
                ["return this.__request__.",name,"(",params.join(","),")"].join("")
            ).apply(this,arguments);
        }
    };
    for (var i=0;i<methods.length;i++) make_method(methods[i]);
    for (var n in window) target[n] = window[n];
})(this);

function print(msg) {
    WScript.StdOut.WriteLine(String(msg));
}

function require(source, target) {
    target = target||this;
    var fso = new ActiveXObject('Scripting.FileSystemObject');
    var stm = fso.OpenTextFile(source, 1, false, -2);
    var text = stm.ReadAll();
    stm.Close();
    eval(text, target);
    for (var n in windowif (typeof target[n] === 'undefined') target[n] = window[n];
}
// }}}

require("jquery-latest.js");

$.each([1,2,3], function(index, item) {
    print("foo" + item);
});

$.ajax({
    type: "GET",
    url: "http://www.google.co.jp/",
    async: false,
    success: function(data) {
        print(data);
    }
});
setTimeoutとかsetIntervalはf*ckingはhackなので$.get(非同期)は動きません。直す余地ありです。
あと、XMLHttpRequestのbindはmalaさんのをパクってます。

はてな
tokuhiromさん作のfcgi-v8をCGIインタプリタとして、cho45さん作のblosxom.rhinoを改造してblosxom.v8というのを作ってみた。
v8にはunevalが無かったのでdankogaiさん作のuneval.jsを使わせて頂いた。
実行画面はblosxom.rhinoの書き換えなので、パクリ気味ですが...
blosxom-v8
fcgi-v8でファイル日付の取得が実装出来ていないので、今は日付は表示されていません。
blosxom.v8
よかったら遊んでみて下さい。

2008/09/05

はてな
shell.ccから改造したから分からなくなったんだよね。
v8::Objectに内部保持するnativeな値(例えばハンドルとか)を保持するには
FILE* fp = fopen(...);
v8::Local<v8::Object> obj = v8::Object::New();
obj->Set(v8::String::New("value"), v8::External::New((void*)fp));
という様に、v8::Externalでメンバを追加してやれば良い。
ただ分からなくなってた原因としては、shell.ccが持ってるPrintという関数が
v8::Handle<v8::Value> Print(const v8::Arguments& args) {
  bool first = true;
  for (int i = 0; i < args.Length(); i++) {
    v8::HandleScope handle_scope;
    if (first) {
      first = false;
    } else {
      printf(" ");
    }
    v8::String::AsciiValue str(args[i]);
    printf("%s", *str);
  }
  printf("\n");
  return v8::Undefined();
}
となってて、何でも文字列化してくれる様に思えてた。で実際
var obj = test();
print(obj.value); // ここでクラッシュ
こんな事やると落ちてしまうんだけど、良く考えたらExternalって言ってるんだから値の出しようもない。確かに以下の様なコードをshell.ccに含ませ
global->Set(v8::String::New("hoge"), v8::External::New(NULL));
javascriptから"print(hoge)"で例外を発生させた際のデバッグ出力で確かめたら
==== Stack trace ============================================

(No security context)
    1: DefaultString(this=00C104BD <JS Object>#0#,x=00E0068D <Proxy>#1#)
(No security context)
    2: ToString(this=00C104BD <JS Object>#0#,x=00E0068D <Proxy>#1#)
    6: arguments adaptor frame: 1->0
Security context: 00E006A1 <FixedArray[41]>#2#
    7: /* anonymous */(this=00C10361 <JS Global Object>#3#)

==== Details ================================================

(No security context)
[1]: DefaultString(this=00C104BD <JS Object>#0#,x=00E0068D <Proxy>#1#) {
  // stack-allocated locals
  var v = 01000135 <undefined>
  var s = 01000135 <undefined>
  // expression stack (top to bottom)
  [04] : 01000361 <String[8]: toString>
  [03] : 00E0068D <Proxy>#1#
  [02] : 00E0068D <Proxy>#1#
--------- s o u r c e   c o d e ---------
function DefaultString(x) {?  if ((typeof(x.toString) === 'function')) {?    var s = x.toString();?    if (%IsPrimitive(s)) return s;?  }?  if ((typeof(x.valueOf) === 'function')) {?    var v = x.valueOf();?    if (%IsPrimitive(v)) return v;?  }?  throw %MakeTypeError('cannot_convert_to_primitive', []);?}
-----------------------------------------
}

(No security context)
[2]: ToString(this=00C104BD <JS Object>#0#,x=00E0068D <Proxy>#1#) {
  // expression stack (top to bottom)
  [02] : 01002845 <String[13]: DefaultString>
  [01] : 00C104BD <JS Object>#0#
  [00] : 0100232D <String[8]: ToString>
--------- s o u r c e   c o d e ---------
function ToString(x) {?  if ((typeof(x) === 'string')) return x;?  if ((typeof(x) === 'number')) return %NumberToString(x);?  if ((typeof(x) === 'boolean')) return x ? 'true' : 'false';?  if ((typeof(x) === 'undefined')) return 'undefined';?  return ((x === null)) ? 'null' : %ToString(%DefaultString(x));?}
-----------------------------------------
とNULL参照している事が分かる。v8からすると"訳分かんない物をToStringして表示しようとしただけさ。"と言ったところか。
つまりはv8::Externalの使い方が悪いわけではなく、v8::Externalを表示しようとしてたPrint関数がマズイのだ。さらに元々Externalなんか扱ってなかったshell.ccにExternalを埋め込んだ犯人が悪い。そう...私。
言ってしまえばExternal組み込んだんだから、Printもちゃんと修正しろよって事ですね。
if (argv[0]->IsExternal()) {
    printf("[native code]");
    continue;
}
よくブラウザなんかで見かける"[native code]"という表示になる様にしました。
この修正は「Google の JavaScript エンジン v8 で FastCGI する - TokuLog 改めB日記」での成果物"fcgi-v8"にも組み込んであります。
ただ、Extenalに保持する様な値というのは、ハンドルとかメモリ確保した領域だとか、状態を保持する様な物のはずなので、実際には確保したポインタ等のリストをグローバルな領域にを持っておき、終了時に解放するかPersistentなObjectTemplateでインスタンスを作り、WeakReferenceCallbackにて解放するのがベターという事になる。
Externalを使う側からすると、そこに何が入っててもおかしい訳でもなく、v8として取り決めもない(引数の型はvoidポインタ)。よって複数の種類を格納したい場合は種類も判別出来る物、例えば
typedef enum _OBJECT_TYPE {
    OBJECT_TYPE_FILE_POINTER, // FILE*
    OBJECT_TYPE_WINDOW_HANDLE // ウィンドウハンドル
} OBJECT_TYPE;

typedef struct _OBJECT_VALUE {
    OBJECT_TYPE type;
    void* object;
} OBJECT_VALUE;

// 設定
OBJECT_VALUE* val = new OBJECT_VALUE;
val->type = OBJECT_TYPE_FILE_POINTER;
val->object = fopen("test.dat", "rb");
obj->Set(v8::String::New("value"), v8::External::New((void*)val));

// 取得
v8::Local<v8::Value> valueobj = obj->Get(v8::String::New("value"));
if (!valueobj->IsExternal()) return v8::ThrowException(v8::String::New("Exception: invalid object"));
OBJECT_VALUE* val = static_cast< OBJECT_VALUE* >(v8::Handle<v8::External>::Cast(valueobj)->Value());
if (val->type == OBJECT_TYPE_FILE_POINTER) {
    // FILE*な処理
} else
if (val->type == OBJECT_TYPE_WINDOW_HANDLE) {
    // ウィンドウハンドルな処理
}
こんな構造と設定および取得をすべき、という事になる。ここまで気付くのに結構時間を使ってしまった。

さてこのPrint関数ですが、↑のリンクにある"fcgi-v8"には「Big Sky :: Google Chromeが使っているjavascript v8エンジンにTwitterにアクセス出来るクラスを作ってポストする。」でご紹介したtwitter-v8とは違う方法でPrintを実装してあります。
twitter-v8の方はコンソールに出力する物なのでsetlocaleとワイドキャラクタ出力(%S)でOKですが、fcgi-v8の場合はUCSからUTF-8への変換を組み込んであります。一応v8のプロジェクトサイトでIssue 17 : 「Add Strign::WriteUtf8」としてリクエストされているので、もしかすると今後このコードは必要なくなるかもしれませんね。
v8エンジンは高速な上、今まで私が見た中で一番組み込みやすい代物になっているかと思います。皆さんもv8使った面白いもの作ってみませんか。

twitter-v8、fcgi-v8それぞれ参加者をお待ちしております。

2008/09/03

はてな
v8エンジン盛り上がってますね。
さて、今日はv8エンジンのshell.ccを弄って、シェル内部にTwitterオブジェクトを作ってみました。とはいっても、仕組みは簡単。FunctionTemplateから得たPrototypeTemplateにメソッドを追加、さらにInstanceTemplateにプロパティusernameとpasswordを足しているだけです。
  v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New();
  v8::Local<v8::Template> p = t->PrototypeTemplate();
  p->Set("friendsTimeline", v8::FunctionTemplate::New(TwitterFriendsTimeline));
  p->Set("updateStatus", v8::FunctionTemplate::New(TwitterUpdateStatus));
  v8::Local<v8::ObjectTemplate> i = t->InstanceTemplate();
  i->Set(v8::String::New("username"), v8::String::New(""));
  i->Set(v8::String::New("password"), v8::String::New(""));

  global->Set(v8::String::New("Twitter"), t);
このTwitterFriendsTimelineには以下の様なコードを...
v8::Handle<v8::Value> TwitterFriendsTimeline(const v8::Arguments& args) {
  v8::Handle<v8::Value> ret = v8::Undefined();
  v8::Local<v8::Object> This = args.This();
  v8::HandleScope handle_scope;
  v8::TryCatch try_catch;

  v8::String::AsciiValue username(This->Get(v8::String::New("username")));
  v8::String::AsciiValue password(This->Get(v8::String::New("password")));

  CURL* curl = NULL;
  CURLcode res = CURLE_OK;
  char *auth;

  response_data = NULL;
  response_size = 0;
  curl = curl_easy_init();
  if (!curl) return v8::ThrowException(v8::String::New("Error: unknown"));

  int n = strlen(*username) + strlen(*password);
  auth = (char*) malloc(n + 2);
  memset(auth, 0, n + 2);
  strcpy(auth, *username);
  strcat(auth, ":");
  strcat(auth, *password);

  curl_easy_setopt(curl, CURLOPT_URL, "http://twitter.com/statuses/friends_timeline.json");
  curl_easy_setopt(curl, CURLOPT_USERPWD, auth);
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, handle_returned_data);
  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
  res = curl_easy_perform(curl);
  curl_easy_cleanup(curl);

  free(auth);

  if (res == CURLE_OK) {
    char* json = NULL;
    json = (char*) malloc(response_size+1);
    memset(json, 0, response_size+1);
    memcpy(json, (char*) response_data, response_size);
    v8::Handle<v8::String> source = v8::String::New(json);
    free(json);

    v8::Handle<v8::Script> script = v8::Script::Compile(source);
    if (script.IsEmpty()) {
        ret = v8::ThrowException(v8::String::New("Error: unknown"));
        goto leave;
    }
    v8::Handle<v8::Value> result = script->Run();
    if (script.IsEmpty()) {
        ret = v8::ThrowException(v8::String::New("Error: unknown"));
        goto leave;
    }
    if (!result->IsArray()) {
        ret = v8::ThrowException(v8::String::New("Error: unknown"));
        goto leave;
    }
    ret = result;
  }

leave:
  if (response_data) free(response_data);
  response_data = NULL;
  return ret;
}
さらにTwitterUpdateStatusには
v8::Handle<v8::Value> TwitterUpdateStatus(const v8::Arguments& args) {
  v8::Handle<v8::Value> ret = v8::Undefined();
  v8::Local<v8::Object> This = args.This();
  v8::HandleScope handle_scope;
  v8::TryCatch try_catch;

  if (args.Length() != 1) return v8::ThrowException(v8::String::New("Error: usage(message)"));

  v8::String::AsciiValue username(This->Get(v8::String::New("username")));
  v8::String::AsciiValue password(This->Get(v8::String::New("password")));

  v8::Handle<v8::Object> global = v8::Context::GetCurrent()->Global();
  v8::Local<v8::Function> func = v8::Function::Cast(*global->Get(v8::String::New("encodeURIComponent")));
  v8::Local<v8::Value> funcargs[1];
  funcargs[0] = args[0]->ToString();
  v8::Local<v8::Value> result_val = func->Call(global, 1, funcargs);
  v8::String::AsciiValue escaped(result_val->ToString());

  char* status = url_encode_alloc(*escaped);

  CURL* curl = NULL;
  CURLcode res = CURLE_OK;
  char *auth;
  char url[2048];

  response_data = NULL;
  response_size = 0;
  curl = curl_easy_init();
  if (!curl) return v8::ThrowException(v8::String::New("Error: unknown"));

  memset(url, 0, sizeof(url));
  strncpy(url, "http://twitter.com/statuses/update.xml", sizeof(url)-1);
  strncat(url, "?status=", sizeof(url)-1);;
  strncat(url, status, sizeof(url)-1);
  free(status);

  int n = strlen(*username) + strlen(*password);
  auth = (char*) malloc(n + 2);
  memset(auth, 0, n + 2);
  strcpy(auth, *username);
  strcat(auth, ":");
  strcat(auth, *password);

  curl_easy_setopt(curl, CURLOPT_URL, url);
  curl_easy_setopt(curl, CURLOPT_USERPWD, auth);
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, handle_returned_data);
  curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
  curl_easy_setopt(curl, CURLOPT_POST, 1);
  curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "");
  res = curl_easy_perform(curl);
  curl_easy_cleanup(curl);

  free(auth);

  if (res != CURLE_OK) {
    ret = v8::ThrowException(v8::String::New("Error: unknown"));
  }
  if (response_data) free(response_data);
  response_data = NULL;
  return ret;
}
これだけ準備出来れば、あとは
var twitter = new Twitter()
twitter.username = "my-username"
twitter.password = "my-password"
var statuses = twitter.friendsTimeline();
for (var n = 0; n < statuses.length; n++) {
    print(statuses[n].user.screen_name, ":", statuses[n].text);
}
twitter.updateStatus("v8エンジン12気筒");
こんなスクリプト書いて
# shell twitter.js
とかやればfollowerのステータスが出た後でメッセージがポストされます。
簡単ですね。
ちなみに既存のPrintではAsciiValueを使いますが、AsciiValueではUTF-8が8bit落ちするのでsetlocaleを使ってワイドキャラでprintfする様修正しています。
コードはcodereposのこの辺に置いときます。汚いソースですが、よろしければ参考まで...

はてな
scons入れてwin32でビルドしてみたよ。ベンチマークも取ってみた。
環境はWindows XP、P4 3GHz CPU、1G memで。

まずmozillaのjsエンジン(ちょっと昔)
JavaScript-C 1.7.0 2007-10-03 (js.exe)
Richards: 87
DeltaBlue: 84
Crypto: 44
RayTrace: 78
EarleyBoyer: 87
----
Score: 74

次にrhino
Rhino 1.6 release 7 (win32 nativeビルド版)
Richards: 6
DeltaBlue: 5
Crypto: 4
RayTrace: 11
EarleyBoyer: 12
----
Score: 7

Rhino 1.7 release 7 2008 03 06(java版 js-14.jar)
Richards: 30
DeltaBlue: 30
Crypto: 24
RayTrace: 47
EarleyBoyer: 53
----
Score: 35

そしてv8エンジン
V8 version 0.2.5 (shell.exe)
Richards: 1020
DeltaBlue: 981
Crypto: 622
RayTrace: 580
EarleyBoyer: 1070
----
Score: 827
はぇーーーーーーーーーーーーーーー!(゚д゚;)

ベンチマークはbenckmarksフォルダにあるrun.jsで起動します。

追記
取り直したらこんな結果もでた
Richards: 1391
DeltaBlue: 1224
Crypto: 940
RayTrace: 774
EarleyBoyer: 1360
----
Score: 1110
もっと詳細が知りたい方は以下を参照。
Google V8 についてしらべてみました - TokuLog 改めB日記

2008/08/04

はてな
vimではコマンドモード時に、<c-x>で現在のディレクトリを補完するようにしているので、これがvimperatorでもやれると便利と思った。
twitter.jsでは「%URL%」とか「%TITLE%」で現在のURLが置き換えられる様になってるけど、他のコマンドでも使いたい場合もある。
例えば、そろそろ誰かが「mixiecho」ってコマンド作ったとして
:mixiecho ちょっwwwコレ http://example.jp/
とかやりたい時には、またtwitter.jsと同じ実装しなきゃいけない。
ま、こんなので困るのは私だけかも。
(function() {
    liberator.mappings.addUserMap([liberator.modes.COMMAND_LINE], ['<c-x>'],
        "insert current URL to command line",
        function () {
            var cmdline = liberator.CommandLine();
            var curcmd = ':' + cmdline.getCommand();
            if (!curcmd.match(/ $/)) curcmd += ' ';
            cmdline.open(curcmd, liberator.buffer.URL);
        }
    );
})();
コマンドラインのコントロール名は決まっているので、そこにkeyEventをdispatchEventしても良かったけど、まっいいや。

2008/07/28

はてな
不具合報告を頂きまた。自分でも動かなくなってたのに気付いてませんでした。
Big Sky :: XSLとjQuery/HTMLだけで作る、amazon最速検索
http://mattn.kaoriya.net/software/lang/javascript/20080605165643.htm
どうやら、jQueryがcacheを無効にする時に使用する特殊パラメータ"_"がAmazonで蹴られる様になったのが原因です。
結構ハマった(30分)割には、直し方は簡単。
$.ajaxSettings.cache = true;
まぁ、Amazon検索にはリアルタイムなキャッシュ無効は必要なさそうなので、これで良しとします。
一応、直ったのでこの記事にも実行例置いときます。コードは↑のリンク先を参照下さい。

続きを読む...


新規投稿