はじめてのQuineリレー

概要

昨日、自分自身を出力するHaskellプログラムを生成するOCamlプログラムを生成するSchemeプログラムを生成するRubyプログラムを生成するJavaScriptプログラムを生成するCプログラムを書いた。

#include<stdio.h>
int main(){char*g="var p=print;p('puts %%^(format #t!Printf.printf~s~s!(x->string`main=putStr%%S)^.tr *?!...?#;p <<_;$><<?)');p('#include<stdio.h>');print(%cint main(){char*g=%c%s%c;printf(g,47,34,g,34,47);}%c.source);p('_')";printf(g,47,34,g,34,47);}

以下のように動作する。Makefileは後述。

$ make diff
clang -std=c99 -Wall -W -Werror -pedantic -o quine-relay-c start.c && ./quine-relay-c > quine-relay.js
v8 --use_strict quine-relay.js > quine-relay.rb
ruby quine-relay.rb > quine-relay.scm
gosh quine-relay.scm > quine-relay.ml
ocaml quine-relay.ml > quine-relay.hs
runghc quine-relay.hs > goal.c
diff -u start.c goal.c
$ wc start.c quine-relay.*
       2       9     270 start.c
       0       8     287 quine-relay.hs
       0      13     402 quine-relay.js
       0       8     304 quine-relay.ml
       4      15     360 quine-relay.rb
       2      11     333 quine-relay.scm
       8      64    1956 total

動機

SchemeRubyが得意でHaskellOCamlも書けます!」と堂々と言えるようになりたい。ならばそれらの言語でQuineリレーをしよう。思い付いてしまったからには、書かねばなるまい。そう思っていた時期もあった。

かのように決心して書き始めたのだが、存外に難しくはなく、その日のうちにできてしまった。

構想

コンセプト。Quineリレー自体は偉大なる先人が凄いのを成し遂げていらっしゃるし、自分はQuineリレーには初挑戦なので、今回は得意言語のアッピルに重点を置いて、とりあえず高級言語数個での完成を目指すことにした。次に順序が問題となる。Brainf**kやBefungeを入れるでもないのに順序が任意では趣に欠けるというもの。これはなんとなくCを起点にして触った順にした。結果、自分のプログラミングの学びの歴史を辿り最後は初心に帰るという個人的に気持ち悪い大層趣のあるプログラムができあがった。あと、今回の主旨に鑑みて、特定の言語で何重にもエスケープして残りの言語は文字列を出力するだけ、みたいなヒキョウなことはなるべくしないようにした。

もう一つ、追加の制約としてCコンパイラのオプションに -std=c99 -Wall -W -Werror -pedantic を付けることにした。これでこのプログラムがコンパイラも黙る程の真っ当なプログラムであることは確定的に明らか。奇妙な挙動や拡張に依存していたら、コンパイラによってバラバラに引き裂かれることになる。

ついでにちょっと縮めた。

感想

  • 難しいというより面倒臭い
  • 改行のために\nを書くとバックスラッシュで面倒が増えるのでJSのprintを活用
  • JSなら正規表現リテラルが使える→正規表現リテラル内では括弧の対応やらメタ文字が合法じゃないとだめ→エスケープのためにバックスラッシュを入れると面倒が増える→いくえ不明
  • バックスラッシュは排除
  • 多様なリテラルがある言語はこういう時強い
  • raw文字列リテラルがある言語は意外と少ないけど、あるといいなあ
  • 複数行の文字列リテラルが書けない言語は(省略されました)
  • みんな printf のことが好きなんだなあ( ꒪⌓꒪)

付録

Makefile

.PHONY: all clean diff
CC = clang
CFLAGS = -std=c99 -Wall -W -Werror -pedantic
JavaScript = v8 --use_strict
Ruby = ruby
Scheme = gosh
OCaml = ocaml
Haskell = runghc

DIFF = diff -u

START = start.c
GOAL = goal.c
Q = quine-relay
C_EXE = $(Q)-c

all: $(GOAL)

diff: all
	$(DIFF) $(START) $(GOAL)

$(Q).js: $(START)
	$(CC) $(CFLAGS) -o $(C_EXE) $< && ./$(C_EXE) > $@

$(Q).rb: $(Q).js
	$(JavaScript) $< > $@

$(Q).scm: $(Q).rb
	$(Ruby) $< > $@

$(Q).ml: $(Q).scm
	$(Scheme) $< > $@

$(Q).hs: $(Q).ml
	$(OCaml) $< > $@

$(GOAL): $(Q).hs
	$(Haskell) $< > $@

clean:
	rm -f $(GOAL) $(C_EXE) $(Q).js $(Q).rb $(Q).scm $(Q).ml $(Q).hs

あと各処理系のバージョン。Gaucheは開発版の何か。

$ clang -v
clang version 3.5.0 (tags/RELEASE_350/final)
Target: x86_64-apple-darwin13.4.0
Thread model: posix
$ echo -n | v8
V8 version 3.25.30 [sample shell]
>
$ ruby -v
ruby 2.2.0p0 (2014-12-25 revision 49005) [x86_64-darwin13]
$ gosh -V
Gauche scheme shell, version 0.9.5_pre1 [utf-8,pthreads], x86_64-apple-darwin13.4.0
$ ocaml -version
The OCaml toplevel, version 4.02.0
$ ghc -V
The Glorious Glasgow Haskell Compilation System, version 7.8.3