2012年2月25日

JavaScriptコードをトークンに分解する正規表現

/(\/\*[\s\S]*?\*\/|\/{2,}[^\r\n]*(?:\r\n|\r|\n|)|"(?:\\[\s\S]|[^"\r\n\\])*"|'(?:\\[\s\S]|[^'\r\n\\])*'|(?:(?:\/(?!\*)(?:\\.|[^\/\r\n\\])+\/)(?:[gimy]{0,4}|\b)(?=\s*(?:(?!\s*[\/\\<>*+%`^"'\w$-])[^\/\\<>*+%`^'"@({[\w$-]|===?|!==?|\|\||[&][&]|\/[*\/]|[,.;:!?)}\]\r\n]|$)))|<([^\s>]*)[^>]*>[\s\S]*?<\/\2>|>>>=?|<<=|===|!==|>>=|\+\+(?=\+)|\-\-(?=\-)|[=!<>*+\/&|^-]=|[&][&]|\|\||\+\+|\-\-|<<|>>|0(?:[xX][0-9a-fA-F]+|[0-7]+)|\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|[1-9]\d*|[-+\/%*=&|^~<>!?:,;@()\\[\].{}]|(?![\r\n])\s+|(?:\r\n|\r|\n)|[^\s+\/%*=&|^~<>!?:,;@()\\[\].{}'"-]+)/g
これがパターンです。

例えば、
/* comment */
var regex = /[\/\\"']*/g;
var str = "hoge\"fuga'\
piyo";
// 'comment'
var arr = [
    0x0FFF, 1e8, 0, 12345,
    3.524603890386048e+24,
    0.0006215, 0666 // (classic mode)
];
var i = 0;
var e4x = <>{{///*E4X*///}}</>;
var xml = <root><hoge fuga="piyo" /></root>;
var ほげ = '/*"ほ//げ"*/';
var $var = re.test(str) && arr[2] || (i+++i) === 1;
このコードを文字列として code.match(pattern) すると、以下の要素を持った配列が得られます。
0: /* comment */
1: 

2: var
3:  
4: regex
5:  
6: =
7:  
8: /[\/\\"']*/g
9: ;
10: 

11: var
12:  
13: str
14:  
15: =
16:  
17: "hoge\"fuga'\
piyo"
18: ;
19: 

20: // 'comment'

21: var
22:  
23: arr
24:  
25: =
26:  
27: [
28: 

29:   
30: 0x0FFF
31: ,
32:  
33: 1e8
34: ,
35:  
36: 0
37: ,
38:  
39: 12345
40: ,
41: 

42:   
43: 3.524603890386048e+24
44: ,
45: 

46:   
47: 0.0006215
48: ,
49:  
50: 0666
51: 

52: ]
53: ;
54: 

55: var
56:  
57: i
58:  
59: =
60:  
61: 0
62: ;
63: 

64: var
65:  
66: e4x
67:  
68: =
69:  
70: <>{{///*E4X*///}}</>
71: ;
72: 

73: var
74:  
75: xml
76:  
77: =
78:  
79: <root><hoge fuga="piyo" /></root>
80: ;
81: 

82: var
83:  
84: ほげ
85:  
86: =
87:  
88: '/*"ほ//げ"*/'
89: ;
90: 

91: var
92:  
93: $var
94:  
95: =
96:  
97: re
98: .
99: test
100: (
101: str
102: )
103:  
104: &&
105:  
106: arr
107: [
108: 2
109: ]
110:  
111: ||
112:  
113: (
114: i
115: ++
116: +
117: i
118: )
119:  
120: ===
121:  
122: 1
123: ;
そもそもシンタックスハイライトのほうが崩れてる…。

jsFiddle でテストできます。

文字列は、改行の前にバックスラッシュ (\) がある場合も対応してみました。
セミコロン挿入もあって、スペースと改行は区別されます。

E4X はシンプルなのだけ対応してます。
var x = <root<div id={(function() {
return '</root>' ? 'hoge' : 'fuga';
}())}></root>
こういうのは無理っぽい。あとネストとか。


match() に g つけて match(/(...)/g) がこんな便利だなんて…。


2012年2月24日

同期処理を非同期に変換-JavaScriptライブラリPot.js+PotLite.jsリリースノート






Pot.js 1.14PotLite.js 1.31 リリースしました。



その前に Pot.js + PotLite.js リファレンス が完成しました (ということにしたい) ので、
お時間あるときにでも覗いてみてください。

結局 /test-for-new-design/ に置いてあるのはファイル消すのも嫌だし
全部リダイレクトさせるようにしました。



Pot.js 1.14 と PotLite.js 1.31 では、Pot.deferreed() ていう関数つくりました。

deferreed は、関数の中の for, for-in, for-of, do, while 等の同期ループを
Pot.Deferred.forEach() などの非同期イテレータに置き換えて関数を再定義します。

前の記事「JavaScript whileループとPot.Deferred.forEverイテレータでCPU使用率を比較」 でも書いてますが、
Pot.js ライブラリのイテレータは CPU の負荷なく実行できます。

ループを置き換えることで、巨大な文字列を扱ったり、ループ回数が未知な処理をする際に
重さについて考えなくて済むようにと、作ってみまみた。

もともとは deferrize() という関数から派生しています。

関数内のコードをトークン単位に分解して、
単純にトークンの数が多いループブロックをメインループとして解析して、
その文に見合った Pot.Deferred イテレータを割り当ててるんですが
ぜんぜん思ったよりたいへんで まだ複雑なものは対応できてないです。
とくに三項演算子 (?:) と function のコンボとか、E4X とかまでは今後の課題。。 にしたいけど、
そこまでしてたらライブラリの方向性違うんじゃないのって思ってしかたないです。

deferreed の 1 コールで zip.deflate 圧縮とかファイル規模への md5 とか非同期化できたら楽そう。

例えば以下のような関数が

// 文字列を charCode からなる配列にして返す関数
var toCharCode = function(string) {
    var result = [];
    for (var i = 0; i < string.length; i++) {
        result.push(string.charCodeAt(i));
    }
    return result;
};

// deferreed により for 文を非同期イテレータにした関数を生成
var toCharCodeDefer = Pot.deferreed(toCharCode);

// 例えば toCharCodeDefer は内部で以下のような感じに変換される
//
//  function(string) {
//      var result = [];
//      return Pot.Deferred.repeat(string.length, function(i) {
//          result.push(string.charCodeAt(i));
//      }).then(function() {
//          return result;
//      });
//  };
//

// Pot.Deferred インスタンスが返るため then() などで繋げられる
toCharCodeDefer('abc').then(function(res) {
    Pot.debug(res); // [97, 98, 99]
});

// この変化により、巨大な文字列でも負荷を分散させ実行できる
var largeString = new Array(100000).join('abcdef');

// 'slow' などの指定が可能
toCharCodeDefer.slow(largeString).then(function(res) {
    Pot.debug(res.length); // 599994
});

あと、プラグインみたいなことしたくて Pot.Plugin ての作ったんですが
なにか違うような気がしてならないです。

プラグイン = ただのオブジェクト
になってしまった。

Pot.addPlugin() という関数で任意の関数とかオブジェクトを登録。
Pot.removePlugin() で削除。
Pot.hasPlugin() で確認、Pot.listPlugin() で列挙。

addPlugin した関数は Pot.xxx でアクセスできる。
・・・それって Pot.myFunc = function() {}; でいいじゃん! てなったので、
以下のようにしてみました。
var string = '\t abc\n \t ';

// オリジナルの Pot.trim()
debug(Pot.trim(string)); // 'abc'

// プラグイン関数の登録により Pot.trim() を上書き
Pot.addPlugin('trim', function(s) {
    return s.replace(/^ +| +$/g, ''); // スペース (U+0020) だけ削除するようにする
});

// 登録した Pot.trim()
debug(Pot.trim(string)); // '\t abc\n \t'

// プラグインの trim を削除
Pot.removePlugin('trim');

// 元に戻る
debug(Pot.trim(string)); // 'abc'
元に戻せる (むしろ消えない) ので、
一時的に挙動を変えたい時とか便利かもしれないです。
あと、Pot.globalize() したときに プラグイン関数もグローバル化します。
Pot.globalize() しなくても with (Pot) {...} でもいい気がしてきました。

プラグインの詳細は
Pot.Plugin リファレンス あたりから参照ください。

マニュアルは英語版も含めてちょっとずつ進めています。


以下はレポジトリとかのリンクなど。

Pot.js / PotLite.js

Pot.js は CPU に負荷をかけることなく JavaScript の実行を可能とするユーティリティライブラリです。
PotLite.js は Pot.js の非同期な部分だけを抽出したライトバージョンです。

ダウンロード

マニュアル

その他の情報についてはマニュアル/マニュアルからのリンク から参照ください。



レポジトリ



その他、なにか問題・バグ・感想・指摘などあれば、
コメントやメールまたは @polygon_planet まで送っていただけるとうれしいです。


2012年2月18日

Tumblrのリブログ/ポストのリミット残数が確認できるTomblooパッチ

Firefox アドオン Tombloo のパッチです。

機能概要

Tumblr でリブログ/ポスト できる残りリミット数を表示する Tombloo パッチです。


前々から作ろうとしてて、でも正確に計算できないと思って困ってましたが
べつに ちょっとくらい違ってもいいやって作りました。

リセットされる正確な時間がずっとわからずに
16:00 なのか 14:00 なのか 14:15 なのか
いろんな違う情報があって曖昧です。
先日、このパッチのテストのため リミットまでリブログして
次の日の 14:00 過ぎにポストしたらリセットされてました。
タンブリストが 14:00 だよ! て言ってくれたので間違いないと思います。

それと、タイムゾーンで考えると 14 が自然な数値だともわかります。
Tumblr, Inc. はニューヨークにあります。
ニューヨークのタイムゾーンは EST (Eastern Standard Time) で、
GMT -5 時間になり、 日本との時差は -14 時間です。
人間が考えそうなリセット時間はおそらく 0:00 ですので、
日本ではちょうど 14:00 にリセットされることになります。

そんなわけでパッチでは 14:00 にリセットされる設定になってます。

このパッチはテストに日数がかかるので、まだ完全な状態じゃないかもしれません。
そもそも Tombloo 以外からリブログしたら、その数だけ狂っていきます。
とは言っても、http://www.tumblr.com/new/photo を見ているので
ある程度リミットに近くなったら整合性はとれるはずです。


機能:

「ポスト完了時に通知メッセージを表示するパッチ」と連携 は、
Tumblr にリブログ/ポストすると、以下のような PostReblog の表示が付くようになります。


上のメッセージを表示させるパッチは、
「ポスト完了時に通知メッセージを表示するパッチ」 からインストールできます。


修正や改善策などあれば地味にアップデートしていきます。


問題等あればパッチ削除してください。

パッチの削除がわからなかったら、
パッチ一括アップデートぱっち で削除もできます。

パッチ (Download or Update):

インストール/アップデート:

上のパッチリンクを
右クリック→「Tombloo」→「Tomblooパッチのインストール」でインストール/アップデート。


※直接右クリックからインストールできない場合:

できない場合は

「Tomblooパッチのインストールに失敗しなくなるパッチ」 のパッチ/記事をインストールもしくは参照ください。

レポジトリ



2012年2月11日

JavaScript whileループとPot.Deferred.forEverイテレータでCPU使用率を比較-Pot.js+PotLite.jsリリースノート

Pot.js 1.13PotLite.js 1.30 リリースしました。

Pot.js 1.13 と PotLite.js 1.30 では、主に内部ループ処理を大幅に高速化しました。
(そろそろ ChangeLog 作らないとまずいかも…)

あとは、変数宣言とか 例の (function(){}()) とか (結局直してる)
細かい修正などです。

先日、Hacker NewsEcho JS で Pot.js が紹介されました (ありがとうございます)。
でもアクセスが今までの 1000 倍くらいになって、すごいことになってちょっとビビった。
(API サーバのほうは適当に調節しておいたので大丈夫だった。よかった。。)


それで Pot.js の本来の存在意義ですが、

実行環境の CPU に負荷をかけることなく JavaScript が実行できる。

といったことが本来の目的であり、常に追求している目標でもあります。
Deferred が重点にとらわれがちですが、Pot.Deferred はそれの足がかりであり、
Pot.Deferred だけがライブラリの中核ではないので、いろいろ使ってみてください。

新しいデザインのリファレンス

現在、新しいデザインでリファレンスを書き直しています。


前は 全部で 1 ファイルになっちゃってて、どんどん重くなるし更新もやり難くて
どうしようもなかったのですが、今回は 各ページを非同期読み込みにして
英語版と日本語版で見れるようにして、戻るボタンとかも再現したり、がんばってます。

でもまだ作成途中です (2012-02-11 現在)。

ある程度完成したら 本来の /index.html に移動させようと思ってるんですが、
先ほどの Echo JS などが /test-for-new-design/ にリンクしちゃってるもんだから
どうしようかと考え中。(たぶん リダイレクトか、もうこのままでいいか…)。

CPU 比較を実行

jsFiddle のほうで
JavaScript の while ループと、Pot.js の非同期イテレータとで CPU 使用率の比較を書いてみました。
前に書いた Pot.js イテレータと jQuery.each と for ループの CPU 使用率をグラフで比較
と同じ事ですが、実際に実行できます (ただし負荷テストなのでブラウザクラッシュに気をつけてください)。
もっとも最近のブラウザはループでクラッシュしないと思いますが。。

CPU 使用率は、Web ブラウザからの JavaScript では取得できないと思うので、
CPU モニタリングするアプリとか、
なければ Win ならタスクマネージャから「パフォーマンス」→ 「CPU 使用率」
を見ながら実行してみるとわかりやすいと思います。


今回は while 文と Pot.Deferred.forEver の比較ですが、
Pot.js 非同期イテレータは CPU 負荷が一定量に抑えられたループが可能になっていると思います。
ただし、この例のような処理だと実行時間はある程度伸びてしまいます。

実行時間に関しては 速度調整 が可能なので、処理に応じたスピードを選ぶこともできます。
(このへんは今後改善したいです)。

サンプルのソースコード

jsFiddle でも確認できますが、今回使用したサンプルのソースコードです。
ループ処理の部分だけ抽出しています。

JavaScript while ループを使ったソースコード:
// while で同期ループして圧縮
compressSync : function(s) {
    var a = 53300, b, c, d, e, f, g = -1,
        h, i, r = [], x = String.fromCharCode;

    s = new Array(a--).join(' ') + s;
    while ((b = s.substr(a, 256))) {
        for (c = 2, i = b.length; c <= i; ++c) {
            d = s.substring(
                a - 52275,
                a + c - 1
            ).lastIndexOf(b.substring(0, c));
            if (!~d) {
                break;
            }
            e = d;
        }
        if (c === 2 || c === 3 && f === g) {
            f = g;
            h = s.charCodeAt(a++);
            r.push(
                x(h >> 8 & 255),
                x(h & 255)
            );
        } else {
            r.push(
                x((e >> 8 & 255) | 65280),
                x(e & 255),
                x(c - 3)
            );
            a += c - 1;
        }
    }
    return r.join('');
}

Pot.js 非同期イテレータ (今回は Pot.Deferred.forEver) を使ったソースコード:
// Pot.js 非同期イテレータで圧縮
compressAsync : function(s) {
    var a = 53300, b, c, d, e, f, g = -1,
        h, i, r = [], x = String.fromCharCode;

    var deferred = new Pot.Deferred();

    s = new Array(a--).join(' ') + s;

    // whileループを forEver に置き換え
    Pot.Deferred.forEver[SPEED](function() {

        b = s.substr(a, 256);
        if (!b) {
            throw Pot.StopIteration;
        }

        for (c = 2, i = b.length; c <= i; ++c) {
            d = s.substring(
                a - 52275,
                a + c - 1
            ).lastIndexOf(b.substring(0, c));
            if (!~d) {
                break;
            }
            e = d;
        }
        if (c === 2 || c === 3 && f === g) {
            f = g;
            h = s.charCodeAt(a++);
            r.push(
                x(h >> 8 & 255),
                x(h & 255)
            );
        } else {
            r.push(
                x((e >> 8 & 255) | 65280),
                x(e & 255),
                x(c - 3)
            );
            a += c - 1;
        }
    }).then(function() {
        deferred.begin(r.join(''));
    }, function(err) {
        deferred.raise(err);
    });
    return deferred;
}

同期か非同期かの違いがありますが、
単に、メインの while 文を Pot.Deferred.forEver に変えてるだけです。
forEver は、StopIteration が throw されるまで、永久にループする関数です。

あと、この関数は LZ77アルゴリズムによる圧縮関数をJavaScript最短コードで | 圧縮電子精神音楽浮遊構造体 (見れないかも)
で作ったソースコードをちょっとだけ手直ししたものです。
文字列を圧縮解凍します。

Pot.js / PotLite.js

Pot.js は CPU に負荷をかけることなく JavaScript の実行を可能とするユーティリティライブラリです。

PotLite.js は Pot.js の非同期な部分だけを抽出したライトバージョンです。

ダウンロード

マニュアル

マニュアルは上に書いたように 2012-02-11 現在、まだすべてのオブジェクトの解説ができてません。。

その他の情報についてはマニュアル/マニュアルからのリンク から参照ください。

レポジトリ




その他、なにか問題・バグ・感想・指摘などあれば、
コメントやメールまたは @polygon_planet まで送っていただけるとうれしいです。