東京Node学園 1時限目 メモ

東京Node学園 1時限目 : ATND

var memo = {
    opening: [ {
        speaker: '@meso',
        title: 'ご挨拶 / 5分でわかるNode.js',
        contents: [
            'http://tng1.mesolabs.com/',
            'キーワードはイベント駆動, 非同期(non blocking)',
            '簡単に早い非同期サーバが書けるよ!'
        ]
    } ],
    main: [ {
        speaker: 'masuidrive',
        title: 'ECMAScript5時代のJavaScript再入門',
        contents: [
            'JSON support / map, forEach / Objectの拡張 / strict mode など',
            'IE8 はECMA 5にだいぶ遅れてる 使えない子',
            'JSLintを使おう SVN, Gitコミット時などにチェック',
            {
                '非同期処理かきやすい': [
                    'インデント深くなるのがアレ',
                    'そこでDeferred',
                    'エラー処理もイイカンジで書ける'
                ]
            },
            {
                'ObjectのGetter, Setter': [
                    'プロパティへの代入、参照のときの処理を指定',
                    '内部で持つ値を整数値だけにしたりできるよね'
                ]
            },
            'freeze, seal アクセス権限の制御など',
            'Object.create() プロパティ設定してObject生成?',
            'その他 Arrayにイテレータ,Object.ke ys() など便利機能が多数',
            {
                'Test!': [
                    'HTTP経由でテストしてると時間かかる',
                    'Jenkins使ってるひと色々教えて!',
                ]
            }
        ]
    }, {
        speaker: '@koichik',
        title: '『非同期プログラミングの改善』のエッセンス',
        contents: [
            'Node本書いてます',
            {
                '非同期プログラミング': {
                    'イベントリスナ スタイル' : 'hoge.on("fuga", function())',
                    'コールバック スタイル'   : 'hoge.fuga("piyo", function(err, data))'
                }
            },
            {
                '問題': {
                    'コールバック'       : '深いネスト',
                    'エラーハンドリング' : '例外処理を各コールバック内でやると散在'
                }
            },
            'アクターとコールバックを分離',
            '無名関数とコールバックを分離し、コールバックは「次」の無名関数(アクター)を呼ぶ',
            'ライブラリ: "フロー制御モジュール"を用意しよう',
            'ライブラリ中でArrayで無名関数を保持、順番にメソッドを呼ぶ',
            'これでネストと無名関数の問題は解決',
            'エラー時のルーティング エラーが起きたら以降の処理を全部すっ飛ばせば良い',
            'さらに別のイディオムも。続きは書籍で!'
        ]
    }, {
        speaker: '@Jxck_',
        title: 'Test It!',
        contents: [
            {
                'nodeのtest': [
                    'assert: 失敗したらAssertionErrorを吐くよ',
                    'test framework: それぞれのtest成否を集計する',
                ]
            },
            'npm test: common.jsに沿ったtest framework',
            {
                'should': [
                    'いわゆるBBD style?',
                    'Objectのprototypeを利用',
                    'メソッドチェインでテストが書ける'
                ],
                'expresso': [
                    'パラレルでtest可能、シリアルでもok',
                    'coverageもとれる',
                    'httpサーバのテスト書きやすい'
                ],
                'nodeunit': [
                    '割と簡単に使えるunittest framework',
                    'setup, teardown可能',
                    '基本的には直列実行',
                    'Sandbox機能'
                ],
                'vows': [       // ばうず
                    'http://vowsjs.org/',
                    '非同期なものをテストするのに長けてる',
                    'topic, batch',
                    'event emitterをtriggerしてやる',
                    '@koichikの連載を読むべし'
                ],
                'tobi': [
                    'ブラウザシミュレート型',
                    'Test::WWW::Mechanize的な?',
                    'jQueryセレクタを使ってassertionしたり'
                ]
            },
            'その他 sinon, gjslint, node-dev とかもろもろ たくさん' // flymakeに使うと良いらしい
        ]
    } ],
    LT: [ {
        speaker: '@hakobera',
        title: 'Kinect + node.js + Audio Data API でテルミンみたいな楽器を作る',
        contents: [
            'kinnectから TCP/IP -> node server -> socket.ioでブラウザに',
            'kinnect.js',
            'libfreenectのJavaラッパーでRhinoを動かしてその上で実行',
            'device side javascript'
        ]
    }, {
        speaker: '@ndruger',
        title: 'node.jsによるマルチプレイヤーネットワークゲームの可能性',
        contents: [
            'いきなりデモ',
            'クライアント側をいじるだけで同じものが3Dでできたり',
            '簡単に作れる時代になってるよ!'
        ]
    } ]
};

JavaScriptで住所入力支援 その2

JavaScriptで住所入力支援 - すぎゃーんメモ にて、id:sun-basix氏から「google maps apiのGeocoderクラスを使った検索は?」とツッコミをいただきました。全然その発想がなかった… orz
というわけでgeocoderバージョンを作り直してみました。

forked from: 簡易住所入力補完 - jsdo.it - share JavaScript, HTML5 and CSS

入力された住所を元にgeocodeメソッドにより住所を検索、返ってきた結果の中から住所っぽいものだけを抽出、候補として表示(日本の住所だけには絞っていないので平仮名とかだと海外の住所もでてくる)。無駄なリクエストが飛ばないようにinput要素内が1秒以上更新されなかったら検索開始するようにしている。

けっこう便利かも。

JavaScriptで住所入力支援

「郵便番号から住所を補完する」的なJavaScriptはよくあるけれど、「郵便番号も分からない見知らぬ土地の住所を入力する」というときに簡単に入力できるものがあればいいな、と思ってちょっと作ってみた。
http://www1216u.sakura.ne.jp/address/
最初のロードにちょっと時間かかるかも…


郵便番号データを下記からダウンロードし、
郵便番号データダウンロード - 日本郵便
下記のようなコードでCSVファイルからjsonもしくはjsonp連想配列に変換した。

#!/usr/bin/env perl
use strict;
use warnings;
use utf8;

use Encode 'decode_utf8';
use JSON::XS 'encode_json';
use Unicode::Japanese;
use Text::CSV;

my $file = shift or die;

my $csv = Text::CSV->new;
open my $fh, "<:encoding(sjis)", $file or die "$file: $!";

my $dict = +{};
while (my $row = $csv->getline($fh)) {
    next if ($row->[8] eq '以下に掲載がない場合');
    $dict->{$row->[6]}{''}                       = decode_utf8(Unicode::Japanese->new($row->[3])->h2z->kata2hira->get);
    $dict->{$row->[6]}{$row->[7]}{''}            = decode_utf8(Unicode::Japanese->new($row->[4])->h2z->kata2hira->get);
    $dict->{$row->[6]}{$row->[7]}{$row->[8]}{''} = decode_utf8(Unicode::Japanese->new($row->[5])->h2z->kata2hira->get);
}
$csv->eof or $csv->error_diag();

my $json = encode_json($dict);
print "callback($json)";

各県ごとに作成したファイルを静的に配置し、クライアントJavaScriptでは最初にそれらを読み込む。入力があるごとに現在の入力とマッチするものを検索し候補をinput要素の下に出す。

上下キーで候補を選択し、右キーで確定(もしくはマウスクリック)、さらに先の候補があればそれを出す。半角スペースで 県/市/町 を区切る形になる仕様になってしまっている。。ひらがな対応しているので、上図の例のように読みを途中まで入力するだけでも絞り込める。
最初に挙げたURLでは東北6件のjsonデータをajaxで読み込んで使っている。最初の読み込みは遅いけど一度読み込めばキャッシュされるのかな? 結構はやくなる。
クロスドメインではどうだろう、と思ってjsdo.itでjsonpデータを取得して使うように作ってみたけど、正直いって最初の読み込みが遅すぎて使えない…。東京のデータだけで300KBとかになっちゃってるとは言え、転送が遅すぎるような…うーん なんでだろ

簡易住所入力補完 - jsdo.it - share JavaScript, HTML5 and CSS

instagram #prayforjapan map

続・ #prayforjapan を眺める - すぎゃーんメモにて、過去のものも取得できるようになったので、これだけの数があれば位置情報付きで投稿されているものも多いのでは、と思いGoogle Mapsマッシュアップしてみた。

instagram #prayforjapan photo map - jsdo.it - share JavaScript, HTML5 and CSS

フルスクリーン版はこちら

とにかくSocket.IOで http://sugyan.no.de/prayforjapan のサーバに繋ぐことでデータが取得できるので、そこから受け取ったものを舐めて緯度経度が含まれているものを探す。あれば画像をiconに使用してmarkingする、だけ。
重複を防ぐ仕組みとかはすごく適当に書いていたりするのでマシンの負荷は高いかも。iPhoneでも見られるかと思ったけど地図は表示されるものの写真は出てこず…処理が重いのかな?どうすればいいんだろう。

location情報はついているけど名前だけで緯度経度は無い、という画像も多く見られたので、Geocoding APIとかで地名から緯度経度を引けばもっと多くの写真が表示されるようになるかもしれない。

Shibuya.jsに行ってきた

Test.jsを聴きにいきました。
http://shibuyajs.org/articles/2011/02/28/test
Shibuya.js - Test.js : ATND
以下、自分メモ。敬称略です。

var memo = {
    main_talk: [{
        title: 'さいきんのCUIでのJavaScriptテスト',
        speaker: 'hotchpotch',
        contents: [ // 遅刻したので前半聴けず…,
            'Johnson: SpiderMonkeyをRubyから JSからRubyをつかえる',
            '応用 Envjs, JSDefferedのあわせ技',
            '思った以上に使える ふつうのJavaScriptなら動く',
            'エンドツーエンドテスト 全体の振る舞いをテストしたい',
            'HTMLの取得、Ajaxの実行',
            'Capybara: Ruby CUIからAjax混みのテストがかける',
            'rspecのフィルターで実現',
            '低コストが魅力, 完璧ではない を前提に絞る',
            {
                phantomjs: [
                    'QTWebKit',
                    'なんか速い',
                    'Jasmine, CUI Runner',
                    '起動中にアプローチできない',
                    'capybara-phantomjsができるようになるといいかも?',
                ]
            },
            'JS Test そこそこやりやすくなってる が 決定打はまだない',
            'どれだけ簡単にテストが書けるようになるか が鍵'
        ],
        url: 'http://d.hatena.ne.jp/secondlife/20110309/1299597318'
    }, {
        title: 'How I stopped worrying about and loved DumpRenderTree',
        speaker: 'omo',
        contents: [
            'webkitのテストについて',
            'DumpRenderTree: コマンドラインプログラム',
            '引数にHTMLを食わせるとRenderツリー(レイアウト情報)をDumpする',
            '各OSで期待値と比較',
            '昔はレンダリングが主だったかもしれないが 最近はできることが増えてる',
            ['読みにくい', '壊れやすい', 'メンテ大変'], 'なテストたち', {
                '例': [
                    'レイアウトと関係ないものもDump',
                    '少し手を入れると一気にテストが動かなくなる',
                    'overspec 本来比べる必要ないものまで比較している',
                    'いつも赤いとよくない 割れ窓理論',
                ]
            },
            'portごとに無視するテストの一覧を保持しておく',
            'Gardener for Chromium 当番制',
            'Reftests: Mozzila由来 ブラウザテストの本命?'
        ],
        url: 'http://www.slideshare.net/omododgsonorg/2011-0302drt'
    }, {
        title: 'サイボウズLiveの開発を支えるSelenium',
        speaker: 'kazfuku',
        contents: [
            'サイボウズLive',
            '構成', ['Apache', 'Tomcat', 'MySQL'],
            'Selenium', 'jUnit + Selenium RC',
            'Selenium IDEは保守がやばい',
            'Hudsonで自動化 1日2回動く',
            'JavaScriptのテストもできるよ',
            'Testにより工数は1.2~1.3倍, デグレ防げる',
            '1回のテストが4時間弱',
            'Firefoxの起動が7秒 -> テストスイートごとに使い回すように',
            'DBのロールバックが大変 test終了後に戻さないといかん',
            '最初にバックアップを取り 終了後にリストア', '-> 6時間…!',
            'マシンを替えて速くなったw',
        ] // なんにせよ、Testを継続的に行うのは大事だと思った
    }, {
        title: 'JS テスト放浪記',
        speaker: 't-wada',
        contents: [
            'テストフレームワーク遍歴', // すごい変更数
            '書法', ['xUnit', 'Test::Simple', 'Spec', 'expectations'],
            'JSpecはもはやJavaScriptじゃなくなったw',
            '出力形式', ['Web画面(QUnit)', 'progress', 'TAP', ''],
            't_wadaさんはTAP形式を選んだ',
            '実行母体', ['実ブラウザ', 'headless env'],
            'CUIつかいたいので 気合いで! phantomjs(NEW!)',
            ['書法は自分の好きなものを','実行母体は不安とスピードの綱引き', '依存は無い方が'],
            'TAPがUnixの流儀に倣っていたから 単機能、行指向、フィルタ',
            '変化に対応する ソフトウェアのてこの原理を理解する',
            'jscoverage --no-browserオプション',
            '通ってないところをEmacs上でハイライト'
        ]
    }],
    LT: [{
        speaker: 'piro_or',
        contents: [
            'Firefox4の変更点がテキストで30KB',
            'Jetpack Add-on SDK',
            '自動テストがかかせない',
            '色々いけてないけど 継続テストに向いているかも'
        ]
    }, {
        speaker: 'monjudoh',
        contents: [
            'テスターによるテストの話',
            '「何をやっているときに 何が起こったか」',
            '「操作は中断されました」タイミングによって起こったり起こらなかったり',
            'やってはいけないことを規定、自動検出する',
        ]
    }, {
        speaker: 'os0x',
        contents: [
            '予想外の縦書きプレゼン',
            'ブラウザを操作するタイプ、エミュレートするタイプ',
            'JSTestDriver: ブラウザをリモートで操作する',
            'TestSwarm: グリッドテスティング'
        ]
    }, {
        speaker: 'yukoba',
        contents: [
            '1%くらいしか話せないので ブログ読んでね!',
            'http://d.hatena.ne.jp/yukoba/20110308/p1',
            'DOMとXPathで!',
            'Firebugの中でしか動かないプログラムw'
        ]
    }, {
        speaker: 'Constellation',
        contents: [
            'strict modeの話',
            'early error ECMAScriptのparse時error',
            'ある種のsyntax analysis',
            'eval, argumentsはkeywordのようなもの',
        ] // 正直 内容がさっぱり理解できなかった… orz
    }]
};
  • JSのテスト 何となく名前を聞いたことがある、というもの以上に色んなものがあることを知った
  • まだろくに書いたことなかったけど、しっかり書けば面白そうだしちゃんとやってみようと思った
  • id:t-wadaさんの話が特に面白かった 色々ためしてみた過程 現在の方法に行き着いた考え方の背景など
  • ごはんおいしかった クックパッドさんすげー
  • 名前は知ってたけどお話したことなかった方など 色んな人とお話できて楽しかったです。ありがとうございました!

javascriptのquine

404 Blog Not Found:perl - Quine.pm で(ほぼ)あらゆるPerl Scriptをquineに
http://d.hatena.ne.jp/shinichiro_h/20081102#1225569359
こういう方法でquineを書けたのですね…。
SpiderMonkeyなら

eval(s="print('eval(s='+s.quote()+')')")

と書けるようだ。

$ cat quine.js
eval(s="print('eval(s='+s.quote()+')')")
$ js quine.js
eval(s="print('eval(s='+s.quote()+')')")
$ js quine.js | js
eval(s="print('eval(s='+s.quote()+')')")
$ js quine.js | js | js
eval(s="print('eval(s='+s.quote()+')')")
$ js quine.js | js | js | js
eval(s="print('eval(s='+s.quote()+')')")

とパイプで繋げていけるのが素敵。
node.jsの場合quote()が使えないようなので、以下のようにする必要がある…のかな?

eval(s="q=String.fromCharCode(34);process.stdout.write('eval(s='+q+s+q+')')")
$ cat quine.js
eval(s="q=String.fromCharCode(34);process.stdout.write('eval(s='+q+s+q+')')")
$ node quine.js
eval(s="q=String.fromCharCode(34);process.stdout.write('eval(s='+q+s+q+')')")

こちらは出力をパイプで繋げてnodeコマンドに流すとエラーになってしまう… ><
これを記号で… と思ったけどこれはevalの中で決められた形式そのまま出力するものなので先日のような方法では無理っぽい。残念。。

node.jsでsymbolic quine

node.jsにはfile systemを扱うライブラリがあるので、実行スクリプト自らのファイルを読み込むことでquine的なプログラムを簡単に書ける。

process.stdout.write(require('fs').readFileSync(__filename))
$ node -v
v0.4.2
$ cat quine.js
process.stdout.write(require('fs').readFileSync(__filename))
$ node quine.js
process.stdout.write(require('fs').readFileSync(__filename))

何となくコレを記号だけで書いてみようと思ったのだけど、どうも"require"や"__filename"はFunction()内で使えないらしい。

$ cat quine2.js
Function("process.stdout.write(require('fs').readFileSync(__filename))")()
$ node quine2.js

node.js:116
        throw e; // process.nextTick error, or 'error' event on first tick
        ^
ReferenceError: require is not defined
    at anonymous (eval at <anonymous> (/Users/sugyan/quine2.js:1:63))
    at Object.<anonymous> (/Users/sugyan/quine2.js:1:134)
    at Module._compile (module.js:383:26)
    at Object..js (module.js:389:10)
    at Module.load (module.js:315:31)
    at Function._load (module.js:276:12)
    at Array.<anonymous> (module.js:402:10)
    at EventEmitter._tickCallback (node.js:108:26)

まぁ__filenameはprocess.argv[1]から取れるからいいのだけど、requireはどうしようもない。

$ cat quine3.js
Function("process.stdout.write(arguments[0]('fs').readFileSync(process.argv[1]))")(require)
$ node quine3.js
Function("process.stdout.write(arguments[0]('fs').readFileSync(process.argv[1]))")(require)

のようにFunction()の引数にrequireを渡して呼び出してやればそれを使うことができるけど、このrequireはどうにも記号化できそうにない。
とは言え、使っているのはfs.jsのreadFileSync関数だけだし、それをそのまんま定義してしまえばいいのでは? ということでfs.jsから関連する部分を引っ張ってきてみる。色々冗長な部分削るとこんなカンジ。

var binding = process.binding('fs');
var constants = process.binding('constants');
var fs = {};

fs.readFileSync = function(path, encoding) {
  var fd = binding.open(path, constants.O_RDONLY, 0666);
  var buffer = new Buffer(4048);
  var buffers = [];
  var nread = 0;
  var lastRead = 0;

  do {
    if (lastRead) {
      buffer._bytesRead = lastRead;
      nread += lastRead;
      buffers.push(buffer);
    }
    var buffer = new Buffer(4048);
    lastRead = binding.read(fd, buffer, 0, buffer.length, null);
  } while (lastRead > 0);

  binding.close(fd);

  if (buffers.length > 1) {
    var offset = 0;
    var i;
    buffer = new Buffer(nread);
    buffers.forEach(function(i) {
      if (!i._bytesRead) return;
      i.copy(buffer, offset, 0, i._bytesRead);
      offset += i._bytesRead;
    });
  } else if (buffers.length) {
    buffer = buffers[0].slice(0, buffers[0]._bytesRead);
  } else {
    buffer = new Buffer(0);
  }

  if (encoding) buffer = buffer.toString(encoding);
  return buffer;
};

process.stdout.write(fs.readFileSync(process.argv[1]));

これでrequireの不要なquineが書けた。無理矢理簡略化すると、

var p=process;s=p.binding('fs'),d=s.open(p.argv[1],0,0),b=new Buffer(999),l=s.read(d,b,0,b.length);s.close(d);p.stdout.write(b.slice(0,l).toString('utf8'))

だいたいこうなる。やってることはほぼ同じのはず。

$ cat quine4.js
var p=process;s=p.binding('fs'),d=s.open(p.argv[1],0,0),b=new Buffer(999),l=s.read(d,b,0,b.length);s.close(d);p.stdout.write(b.slice(0,l).toString('utf8'))
$ node quine4.js
var p=process;s=p.binding('fs'),d=s.open(p.argv[1],0,0),b=new Buffer(999),l=s.read(d,b,0,b.length);s.close(d);p.stdout.write(b.slice(0,l).toString('utf8'))

これならFunction()内でも実行できる。

$ cat quine5.js
Function("var p=process;s=p.binding('fs'),d=s.open(p.argv[1],0,0),b=new Buffer(999),l=s.read(d,b,0,b.length);s.close(d);p.stdout.write(b.slice(0,l).toString('utf8'))")()
$ node quine5.js
Function("var p=process;s=p.binding('fs'),d=s.open(p.argv[1],0,0),b=new Buffer(999),l=s.read(d,b,0,b.length);s.close(d);p.stdout.write(b.slice(0,l).toString('utf8'))")()

あとは以下の記事を参考に記号化してゆく。
NetAgent Official Blog : ここまでできる! node.js に見る記号プログラミング このエントリーをはてなブックマークに追加
(ちなみに"i"については(false + undefined)[10]だと[NaN][10]になってしまいました。([] + false + undefined)[10]の間違いでしょうか?)


まずは文字列化

(0)["constructor"]["constructor"]("var p=process;s=p.binding('fs'),d=s.open(p.argv[1],0,0),b=new Buffer(999),l=s.read(d,b,0,b.length);s.close(d);p.stdout.write(b.slice(0,l).toString('utf8'))")()

ロジック部分を8進数表記に

(0)["constructor"]["constructor"]("\166\141\162\040\160\075\160\162\157\143\145\163\163\073\163\075\160\056\142\151\156\144\151\156\147\050\042\146\163\042\051\054\144\075\163\056\157\160\145\156\050\160\056\141\162\147\166\133\061\135\054\060\054\060\051\054\142\075\156\145\167\040\102\165\146\146\145\162\050\071\071\071\051\054\154\075\163\056\162\145\141\144\050\144\054\142\054\060\054\142\056\154\145\156\147\164\150\051\073\163\056\143\154\157\163\145\050\144\051\073\160\056\163\164\144\157\165\164\056\167\162\151\164\145\050\142\056\163\154\151\143\145\050\060\054\154\051\056\164\157\123\164\162\151\156\147\050\042\165\164\146\070\042\051\051")()

"constructor"を変換

(0)["c"+"o"+"n"+"s"+"t"+"r"+"u"+"c"+"t"+"o"+"r"]["c"+"o"+"n"+"s"+"t"+"r"+"u"+"c"+"t"+"o"+"r"]("\166\141\162\040\160\075\160\162\157\143\145\163\163\073\163\075\160\056\142\151\156\144\151\156\147\050\042\146\163\042\051\054\144\075\163\056\157\160\145\156\050\160\056\141\162\147\166\133\061\135\054\060\054\060\051\054\142\075\156\145\167\040\102\165\146\146\145\162\050\071\071\071\051\054\154\075\163\056\162\145\141\144\050\144\054\142\054\060\054\142\056\154\145\156\147\164\150\051\073\163\056\143\154\157\163\145\050\144\051\073\160\056\163\164\144\157\165\164\056\167\162\151\164\145\050\142\056\163\154\151\143\145\050\060\054\154\051\056\164\157\123\164\162\151\156\147\050\042\165\164\146\070\042\051\051")()
(0)[([].filter+[])[3]+(true+[].filter+[])[10]+(undefined+[])[1]+(false+[])[3]+(true+[])[0]+(true+[])[1]+(undefined+[])[0]+([].filter+[])[3]+(true+[])[0]+(true+[].filter+[])[10]+(true+[])[1]][([].filter+[])[3]+(true+[].filter+[])[10]+(undefined+[])[1]+(false+[])[3]+(true+[])[0]+(true+[])[1]+(undefined+[])[0]+([].filter+[])[3]+(true+[])[0]+(true+[].filter+[])[10]+(true+[])[1]]("\166\141\162\040\160\075\160\162\157\143\145\163\163\073\163\075\160\056\142\151\156\144\151\156\147\050\042\146\163\042\051\054\144\075\163\056\157\160\145\156\050\160\056\141\162\147\166\133\061\135\054\060\054\060\051\054\142\075\156\145\167\040\102\165\146\146\145\162\050\071\071\071\071\051\054\154\075\163\056\162\145\141\144\050\144\054\142\054\060\054\142\056\154\145\156\147\164\150\051\073\163\056\143\154\157\163\145\050\144\051\073\160\056\163\164\144\157\165\164\056\167\162\151\164\145\050\142\056\163\154\151\143\145\050\060\054\154\051\056\164\157\123\164\162\151\156\147\050\042\165\164\146\070\042\051\051")()
(0)[([]["f"+"i"+"l"+"t"+"e"+"r"]+[])[3]+(true+[]["f"+"i"+"l"+"t"+"e"+"r"]+[])[10]+(undefined+[])[1]+(false+[])[3]+(true+[])[0]+(true+[])[1]+(undefined+[])[0]+([]["f"+"i"+"l"+"t"+"e"+"r"]+[])[3]+(true+[])[0]+(true+[]["f"+"i"+"l"+"t"+"e"+"r"]+[])[10]+(true+[])[1]][([]["f"+"i"+"l"+"t"+"e"+"r"]+[])[3]+(true+[]["f"+"i"+"l"+"t"+"e"+"r"]+[])[10]+(undefined+[])[1]+(false+[])[3]+(true+[])[0]+(true+[])[1]+(undefined+[])[0]+([]["f"+"i"+"l"+"t"+"e"+"r"]+[])[3]+(true+[])[0]+(true+[]["f"+"i"+"l"+"t"+"e"+"r"]+[])[10]+(true+[])[1]]("\166\141\162\040\160\075\160\162\157\143\145\163\163\073\163\075\160\056\142\151\156\144\151\156\147\050\042\146\163\042\051\054\144\075\163\056\157\160\145\156\050\160\056\141\162\147\166\133\061\135\054\060\054\060\051\054\142\075\156\145\167\040\102\165\146\146\145\162\050\071\071\071\071\051\054\154\075\163\056\162\145\141\144\050\144\054\142\054\060\054\142\056\154\145\156\147\164\150\051\073\163\056\143\154\157\163\145\050\144\051\073\160\056\163\164\144\157\165\164\056\167\162\151\164\145\050\142\056\163\154\151\143\145\050\060\054\154\051\056\164\157\123\164\162\151\156\147\050\042\165\164\146\070\042\051\051")()
(0)[([][(false+[])[0]+([]+false+undefined)[10]+(false+[])[2]+(true+[])[0]+(true+[])[3]+(true+[])[1]]+[])[3]+(true+[][(false+[])[0]+([]+false+undefined)[10]+(false+[])[2]+(true+[])[0]+(true+[])[3]+(true+[])[1]]+[])[10]+(undefined+[])[1]+(false+[])[3]+(true+[])[0]+(true+[])[1]+(undefined+[])[0]+([][(false+[])[0]+([]+false+undefined)[10]+(false+[])[2]+(true+[])[0]+(true+[])[3]+(true+[])[1]]+[])[3]+(true+[])[0]+(true+[][(false+[])[0]+([]+false+undefined)[10]+(false+[])[2]+(true+[])[0]+(true+[])[3]+(true+[])[1]]+[])[10]+(true+[])[1]][([][(false+[])[0]+([]+false+undefined)[10]+(false+[])[2]+(true+[])[0]+(true+[])[3]+(true+[])[1]]+[])[3]+(true+[][(false+[])[0]+([]+false+undefined)[10]+(false+[])[2]+(true+[])[0]+(true+[])[3]+(true+[])[1]]+[])[10]+(undefined+[])[1]+(false+[])[3]+(true+[])[0]+(true+[])[1]+(undefined+[])[0]+([][(false+[])[0]+([]+false+undefined)[10]+(false+[])[2]+(true+[])[0]+(true+[])[3]+(true+[])[1]]+[])[3]+(true+[])[0]+(true+[][(false+[])[0]+([]+false+undefined)[10]+(false+[])[2]+(true+[])[0]+(true+[])[3]+(true+[])[1]]+[])[10]+(true+[])[1]]("\166\141\162\040\160\075\160\162\157\143\145\163\163\073\163\075\160\056\142\151\156\144\151\156\147\050\042\146\163\042\051\054\144\075\163\056\157\160\145\156\050\160\056\141\162\147\166\133\061\135\054\060\054\060\051\054\142\075\156\145\167\040\102\165\146\146\145\162\050\071\071\071\071\051\054\154\075\163\056\162\145\141\144\050\144\054\142\054\060\054\142\056\154\145\156\147\164\150\051\073\163\056\143\154\157\163\145\050\144\051\073\160\056\163\164\144\157\165\164\056\167\162\151\164\145\050\142\056\163\154\151\143\145\050\060\054\154\051\056\164\157\123\164\162\151\156\147\050\042\165\164\146\070\042\051\051")()
(+[])[([][(![]+[])[+[]]+([]+![]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([]+![]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([]+![]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([]+![]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([]+![]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([]+![]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([]+![]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([]+![]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]("\166\141\162\040\160\075\160\162\157\143\145\163\163\073\163\075\160\056\142\151\156\144\151\156\147\050\042\146\163\042\051\054\144\075\163\056\157\160\145\156\050\160\056\141\162\147\166\133\061\135\054\060\054\060\051\054\142\075\156\145\167\040\102\165\146\146\145\162\050\071\071\071\071\051\054\154\075\163\056\162\145\141\144\050\144\054\142\054\060\054\142\056\154\145\156\147\164\150\051\073\163\056\143\154\157\163\145\050\144\051\073\160\056\163\164\144\157\165\164\056\167\162\151\164\145\050\142\056\163\154\151\143\145\050\060\054\154\051\056\164\157\123\164\162\151\156\147\050\042\165\164\146\070\042\051\051")()

で、これであとは任意のコードを8進表記表記したものを記号で表現できればいいのだけど、上記の記事では

うまい具合に、(consile.dir+)[97] と (console.dir+)[41] に「\」と「'」が存在していました。そして、「console」「dir」は次のように記号だけで書けます。

とさらっと書いてあるけど、実はconsole.dirを得るためには

console.dir
console["dir"]
Function("return console")()["dir"]
(0)["constructor"]["constructor"]("return console")()["dir"]

…と、また上記のような変換をかけていくことになり、「\」1文字を作るために2400文字ほど使うことになる。ほんとキモいw
そんなこんなでガーっと変換していった結果、symbolic quineは以下のような394125byteの大長編になってしまった。
http://lab.sugyan.com/misc/quine.js (ブラウザで開くと結構重いので注意)

$ wget http://lab.sugyan.com/misc/quine.js
$ node quine.js > result.js
$ diff quine.js result.js
$  

いちおうソースコードと実行結果は同じになってる。

感想

いやー初めて記号JavaScriptに挑戦してみたけど本当にキモいw
改めてid:hasegawayosukeさんのスゴさを思い知りました。

おまけ

もしかすると今後も「このnodeスクリプトを記号化したい!」ということがあるかもしれないと思い、任意のnodeスクリプトを記号化するスクリプトをnodeスクリプトで書いた。前述の理由でrequireや__filenameなどを含むものは変換しても動かないけど。

で、こいつをさらにrequire依存なくして簡略化した

var p=process,s=(function(){var b=p.binding,f=b("fs"),d=f.open(p.argv[2],b("constants").O_RDONLY,0666),u=new Buffer(512),s=[],n=0,l=0;do{if(l){u._=l;n+=l;s.push(u);}var u=new Buffer(512);l=f.read(d,u,0,u.length,null);}while(l>0);f.close(d);if(s.length>1){var i,offset=0;u=new Buffer(n);s.forEach(function(i){if(!i._bytesRead)return;i.copy(u,offset,0,i._);offset+=i._;});}else if(s.length){u=s[0].slice(0,s[0]._);}else{u=new Buffer(0);}return u.toString("utf8")})(),n=["[+[]]","[+!+[]]","[!+[]+!+[]]","[!+[]+!+[]+!+[]]","[!+[]+!+[]+!+[]+!+[]]","[!+[]+!+[]+!+[]+!+[]+!+[]]","[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]","[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]","[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]","[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]"];a={};if(!p.argv[2])p.exit(1);(function(){var _={"i":"+[]","j":"+!+[]","k":"!+[]+!+[]","l":"!+[]+!+[]+!+[]","x":"+!+[]+[+[]]","t":"!![]","f":"![]","u":"[][[]]"},d="("+_.u+"+[])["+_.k+"]",e="("+_.t+"+[])["+_.l+"]",f="("+_.f+"+[])["+_.i+"]",i="([]+"+_.f+"+"+_.u+")["+_.x+"]",l="("+_.f+"+[])["+_.k+"]",n="("+_.u+"+[])["+_.j+"]",r="("+_.t+"+[])["+_.j+"]",s="("+_.f+"+[])["+_.l+"]",t="("+_.t+"+[])["+_.i+"]",u="("+_.u+"+[])["+_.i+"]",w=[f,i,l,t,e,r].join("+"),c="([]["+w+"]+[])["+_.l+"]",o="("+_.t+"+[]["+w+"]+[])["+_.x+"]",x=[c,o,n,s,t,r,u,c,t,o,r].join("+"),y="(["+_.i+"]+["+_.i+"]+[]["+w+"])["+_.x+"]",z=[y,c,o,n,s,o,l,e].join("+");a.f="(+[])["+x+"]["+x+"]";a.r=[r,e,t,u,r,n].join("+");a.b="("+a.f+"("+a.r+"+"+z+")()["+[d,i,r].join("+")+"]+[])[["+[_.l,_.l,_.l].join("+")+"]+["+[_.k,_.k,_.l].join("+")+"]]";a.q="("+a.f+"("+a.r+"+"+z+")()["+[d,i,r].join("+")+"]+[])[["+[_.k,_.k].join("+")+"]+["+_.j+"]]";})();p.stdout.write(a.f+"("+a.f+"("+a.r+"+"+a.q);for(var i=0,l=s.length;i<l;i++){var c=s.charCodeAt(i);p.stdout.write("+"+a.b+"+"+(c<0100?"[+[]]+":""));p.stdout.write(Number(c).toString(8).split("").map(function(e){return n[e]}).join("+"));}p.stdout.write("+"+a.q+")())()")

を記号化してみたところ、約4.67MBの巨大JavaScriptファイルが出来上がった。圧縮したものを置いておくので欲しいヒトはこちらからどうぞ。。(2011/04/24 追記 アホらしいのでやめておきます。)
一応これを使っても任意のnodeスクリプトを記号化できる、はず。たぶん。