CodeIQのコーディングパズルで、同一のソースコードで複数の言語処理系で動作するプログラム(いわゆるpolyglot)として、標準入力からの2つの数を加算して出力するものを書くというお題"hybrid A+B problem"が出ていました。12言語(Bash, Brainfuck, C, CLIPS, INTERCAL, Perl, Perl 6, Python, Ruby, Pike, Prolog (swi), Whitespace)で動くものを書いてみましたので晒しておこうと思います。*1
まあ、マイナー言語で埋め込みが簡単なもの*2を多用していて、若干ずるい感じがしますが。。他の方の素晴らしい回答に埋め込んだら、きっと15言語とかにできるんじゃないかなということで晒す価値はあるかなと。
なお動作はideone.comで検証するということで、ideone.comのコンパイラ・インタープリタのバージョン向けでしか動かないものもあります*3。試したい時はコマンドでideone.comにソースを送信・実行 - Okiraku Programmingのツールなどを使うと良いと思います。
で、ソースはこちら。http://ideone.com/JmXx4l
行数を食っているINTERCAL*4とか適当に書いたせいで異様に文字数の多いBrainfuck*5が目立ちますが、一応少し説明をば。
まずあんまりエラーが出ないので楽に埋め込める系*6。
まず、CLIPSについては()で括られたキーワードが出てくるまでスキップする模様、そして(exit)行が出てくるとそれ以降パースしないみたいなので、比較的簡単に埋め込めます。
INTERCALも、最初の「DO」とか「PLEASE」が出てくるまで何を書いてもOKで、文法エラーがあってもその行に実行が移らなければエラーになりませんので、これも簡単です。
Prologもswiであれば、最初に「:-」が出てくるまでスキップしてくれるよう。最後に:- とか書いておけば、それ以後でシンタックスエラーがあると警告が出るものの、mainの実行はしてくれますから割とどこでもかけます。gnuだとこうは行きませんが。
Brainfuckは[]<>+-,.意外は無視される上、最後に0をポイントした状態で [ ... ] と書いておけば、...部分はスキップされますから楽勝ですね。
それからWhitespaceも、各行に最初に3文字以上スペースを入れていれば、push ダミーの数値(スタックに値を積む)と見なされますから、適当に空白を入れておいて最後にコードを置きました。まあさっさと終了(改行3連続)させてしまっても良いんですが。
さて、以上を除いた残りのbash, C, pike, perl, perl6, python, rubyについてです。
まず1行目。
#define x /* ++[...] [# Brainf**k
Cとpikeについてはプリプロセッサコメントで以降しばらく無視してもらいます。それ以外の言語にとっては#で始まるのでコメント扱いです。
# :-set ... :-main. :- # prolog
これもコメント扱い。
0 and nil and q <<2;
pythonにとっては、0 and <有効な式>(q <<2はシフト演算)に見え、and以降は評価しないので、qという変数が未定義だとかエラーになりません。
bashはこれを"0"というコマンドだと思って実行してしまうため、エラーが出ます。が実行は継続されます。今回の問題ではstdoutへの出力さえ正しければ、stderrは気にしなくても良いことになっているので放置(ひどい)。また、<<2をヒアドキュメントの開始と見なし、次の2まで読み飛ばします。
perlは、q<をクォートの開始だと解釈します。そして、<>の対応のネストを追いながら、>が閉じられるまでを文字列とみなします。
一方perl6は同じくクォートを開始しますが、 "<<" という括弧だと思い、">>" を探します……というのが今の仕様なんですが、ideone.comの古いバージョンだと動きが違い、ネストを無視して">"を探します。
rubyにとっては0は真なので、0 and nil and ... まで評価し、...部分は実行されません。q <<2については、qというメソッドにヒアドキュメントで次の"2"の行が出てくるまでをテキストとして渡す、という意味としてパースされます。
''' #'
pythonにとってはマルチラインコメント(というか文字列)の開始。次の'''までスキップします。他の言語はコメントとか文字列としてスキップ中。
2
eval 'read a;read b;echo $[a+b];exit;#' if !1; #bash
evalでbashのコードを実行し、終了させて以降のパースを止めさせます。rubyはif !1 によってこの行は評価しません。
puts gets.to_i+gets.to_i; #Ruby __END__
rubyのコード実行、__END__によってパースを止めさせます。
>;say get()+get();' #Perl 6
最初の">"を見つけたperl6が復帰。コードを実行した後、シングルクォートにより以降は文字列だと思わせます。
#>;print <>+<>,"\n"; __END__ #Perl
2個目の">"を見つけたperlが復帰。コードを実行し、__END__によって以降脱落します。
ここで、しばらくCLIPSやINTERCALのコードが続きます。
*/
が出てきたところでCとpikeがコメントを抜けます。この2つはプリプロセッサで切り分けてます。
#if __PIKE__ int main() {write((int)Stdio.stdin->gets()+(int)Stdio.stdin->gets()+"\n");} //pike #else int main(){int a,b;scanf("%d%d",&a,&b);printf("%d\n",a+b);return 0;} //C #endif
最後の
//'#]''';print int(raw_input())+int(raw_input()); #Python #Whitespace
は、C・pikeはコメント、Perl6の文字列終了+以降コメント、Brainfuckの最後の]、Python文字列の終了と、Pythonのコード*7が出てきます。そしてそれ以降にWhitespaceが入ってます。
多分並べ替えたりすればもうちょっと綺麗というか短くなるような気もするんですが、まあ面倒だし動くから良いやという。イマイチ感。
なんかがっかりな気分になったでしょうから、shinhさんの素晴らしいコードと解説サイトをご覧になって口直しされると良いと思います。
http://d.hatena.ne.jp/shinichiro_h/20140421
*1:ちなみに、C、C++、C++11、ObjectiveCは同じ言語クラスということで複数とカウントしないというルールでした。perlとperl6は全くといって良いほど互換性がないので別カウントです。
*2:CLIPS, INTERCAL, Prologあたり。Brainfuck, Whitespaceに、Cを入れたら使えるだろうpikeも入れたら約半分か…
*3:特に古めのperl6とか
*4:入力も出力も、1byteごとに前の文字とxorされたりビット列の前後がいれかわったりする狂気じみた仕様の言語なので、入出力処理がでかい…
*5:ヘッドをずらすようなループを書くのが面倒で桁数分コピペで繰り返すなど手抜き過ぎ
*6:ズルとも言う
*7:何でinput()じゃなくraw_input()にしたんだったかなあ…忘れた…