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