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スクリプトを記号化できる、はず。たぶん。