ELVM を使った multiquine

言語間を自由に行き交うことができる Quine の集合のことを multiquine と言います。例えば mq.rb と mq.py が multiquine であるとは、

  • ruby mq.rb rb と実行すると mq.rb が出てくる(普通の Quine)
  • python mq.py py と実行すると mq.py が出てくる(普通の Quine)
  • ruby mq.rb py と実行すると mq.py が出てきて、逆に python mq.py rb と実行すると mq.rb が出てくる(相互に出力する)

という感じです。5 言語で普通に書かれた multiquine の図がわかりやすいでしょう。ぼくの本でもちょっと言及されています。

なお、拙作の Quine リレーウロボロス Quine とも)とは別のものです。Quine リレーは言語の進む方向が固定の輪ですが、multiquine は自由に行き来できるという自由度があります。

multiquine の難しさ

実のところ、「普通」の言語に限定すれば、multiquine はそんなに難しくありません(Quine リレーよりは難しいですが)。特に、「C 風の文字列リテラルがある言語」に限れば、極端に簡単になります(25 言語の例)。

しかし、この言語の中に Brainfuck や Lazy K が入ったとしたら、極端に難しくなります。Quine 自体が難しいわけではありません。Brainfuck で他 N-1 言語のコード生成器を書かなきゃならない、というところが辛いです。言語を増やしていくごとに、O(N^2) で実装タスクが増えていきます。

multiquine を効率的に実装する方法

これを解決する構想は昔からありました。こんなのです。

  1. 中間言語を設計する
  2. 中間言語で、Quine を書く
  3. 中間言語で、中間言語から各言語へのコード生成器を書く
  4. 各言語で、中間言語の処理系を書く

これらを組み合わせれば multiquine が作れます。2 で Quine (in 中間言語) を生成し、それを 3 で好きな言語に吐き出します。これらはすべて 4 の上で動かします。吐き出す先を実行時に変えれば、multiquine になります。

この方法の良い所は、言語を追加したいと思ったときに必要なのが

  • 中間言語でその言語向けのコード生成器を書き、
  • さらにその言語で中間言語の処理系を書く

だけとなることです。つまり、言語同士が独立になるので、N 言語 multiquine の実装量が O(N) になります。

ただ、中間言語の設計が辛くて実現してませんでした。中間言語を簡素にするとコード生成器の実装が辛くなり、中間言語を複雑にすると処理系の実装が辛くなるというバランスが取りきれなかった。

ELVM の登場

などとグダグダ言っていたら、shinh さんが ELVM とかいうものを爆誕させてくれました。わーい。上の 1 から 4 のタスクのうち 2 以外が黙っていたらできてしまった格好です。

ためしてみた

ということで、2 の部分だけ作って multiquine を作ってみました。

https://gist.github.com/mame/53fb5cf7b448b3249270ea771ef89655

詳しくはコメントを見てもらいたいですが、次のような感じで動きました。

$ ruby mq-gen.rb
$ gcc -o mq mq.c

$ echo c | ./mq > mq2.c
$ diff -s mq.c mq2.c
Files mq.c and mq2.c are identical

$ echo rb | ./mq > mq.rb
$ echo rb | ruby mq.rb > mq2.rb
$ diff -s mq.rb mq2.rb
Files mq.rb and mq2.rb are identical

$ echo c | ruby mq.rb > mq2.c
$ diff -s mq.c mq2.c
Files mq.c and mq2.c are identical

一応 bf も動くはずなのですが、とんでもなく遅いので動作は未確認。うーん、できたはできた(と思う)んだけど、現実的には Brainfuck と Lazy K の multiquine は無理そうだし(ていうかよく見たら ELVM が Lazy K をサポートしてなかった)、すっきりしないので未完。