UOJ Logo r_64的博客

博客

干了一件有趣的事情

2017-08-12 22:22:28 By r_64

听说有一个东西叫做Polyglot,就是写一份能由多种语言的编译器编译运行成功的程序。

然后我花了两个多小时摸索出了一个Pascal,C,Python3的Polyglot→_→(以UOJ上编译器为准)

(*c),d=2//len('''
;/*)var a,b:longint;begin read(a,b);writeln(a+b) end.*/
#include <stdio.h>
a,b;main(){scanf("%d%d",&a,&b);printf("%d",a+b);return 0;}
/*'''),3
print(sum(map(int,input().split())))
#*/

见我的submission。CPascalPython

先解释一下这玩意怎么work的吧。首先考虑C语言:

(*c),d=2//len('''
;/*)var a,b:longint;begin read(a,b);writeln(a+b) end.*/
#include <stdio.h>
a,b;main(){scanf("%d%d",&a,&b);printf("%d",a+b);return 0;}
/*'''),3
print(sum(map(int,input().split())))
#*/

C语言中是可以声明不带类型(默认int)的变量的,我试了一下(*c),d=2这种,发现照样可以。剩下的显然是个A+B,对吧?

然后考虑Pascal语言,长这样:

(*c),d=2//len('''
;/*)var a,b:longint;begin read(a,b);writeln(a+b) end.*/
#include <stdio.h>
a,b;main(){scanf("%d%d",&a,&b);printf("%d",a+b);return 0;}
/*'''),3
print(sum(map(int,input().split())))
#*/

注意到pascal处理器会忽略end.后面的所有内容,这给我们写polyglot带来了一点小便利。然后再就是Pascal的注释有三种写法:(*comment*){comment}//comment

接下来看Python3:

(*c),d=2//len('''
;/*)var a,b:longint;begin read(a,b);writeln(a+b) end.*/
#include <stdio.h>
a,b;main(){scanf("%d%d",&a,&b);printf("%d",a+b);return 0;}
/*'''),3
print(sum(map(int,input().split())))
#*/

这里用到了python3的一个语法:*操作符可以解包一个iterable object。例如a,*b,c=1,2,3,4,5会进行如下的赋值:a=1,b=(2,3,4),c=5。然后再注意到//是整除运算符,而且又是C里面的注释。

有了这个模板之后,就可以轻易地写出任意功能的C+Python3+Pascal的Polyglot啦!只要把A+B Problem的部分替换掉即可。

话说我不知道这个模板是不是还算短的。。毕竟Polyglot界人才辈出,我又只是个新手。。。

以及,我充分利用了这三个语言的语法特性,所以这个玩意是当不了C++和Python2的Polyglot的。如果谁构造出了C++和Python2的Polyglot请告知。(呃可能这些事情早已有人做过了吧,有的网站上甚至可以找到100个语言的Polyglot。。不过自己创造出一个Polyglot还是蛮爽的


下面是我的心路历程。。

首先,我在某地看到了一个C和Python3的Polyglot:

#include <stdio.h>/*
print(sum(map(int,input().split())))
'''}*/
a,b;main(){scanf("%d%d",&a,&b);printf("%d",a+b);return 0;}//'''
#include <stdio.h>/*
print(sum(map(int,input().split())))
'''}*/
a,b;main(){scanf("%d%d",&a,&b);printf("%d",a+b);return 0;}//'''

这个程序厉害在哪呢?它的第一行是#include,以#开头,而这个符号是Python中的注释符。那么我们可以在第一行的行末放一个/*,然后第二行就归Python管了。Python将它的事情干完之后,又可以将控制权交给C。

(然而这个程序的idea并没有在我的成品中体现)

接下来要将Pascal加入这个Polyglot。简单起见,我们先考虑C与Pascal的Polyglot(扔掉Python)。这个时候麻烦就来了:第一行是#include吗?如果是的话Pascal编译器肯定GG,如果不是的话是什么呢?program a_plus_b或者var a,b:longint之类的东西,C++编译器就GG了。想要两个编译器都不GG,我们显然需要一个注释。然而又不能用两种语言共有的注释//。gcc看到文件的第一个字符是{的时候会报错,所以也不能用{comment}。fpc看到/*也会报错,那么最后的答案只有一个:(*

因为(*是Pascal的注释,所以接下来要考虑怎么伺候gcc。接下来显然是一个(*pts)一样的指针,又发现gcc中可以用(*pts)这样的语句定义变量,第一步就完成了:(*c)

接下来把控制权转交Pascal:(*c)/*)PAS,其中PAS是Pascal的A+B代码。

因为fpc在扫描到end.之后就退了,所以直接将控制权转给C即可。(*c)/*)PAS*/C,其中C是C的A+B代码。

(注:我Python3是今天现学的。。若下面理解有误请指出。。)

下一步就是加入Python啦。然而Pascal的到来使得#include不能作为开头了,我们需要另寻他法。幸运的是,Python3中也有以(*开头的语法:*后面接一个iterable(tuple/list之类的东西)表示将它“unpack”,例如print(*[2,3,4])相当于print(2,3,4)。但是这个语法不太好用。。因为必须以(*c开头(而不是(*[c),而*后面只能接iterable不能接int。不过我还是想到了(*c*[list])的方法,只不过(*c*[list])貌似在C中会CE。这很好办,我们用(*c//2*[list])即可。

现在的代码:(*c//2*[list])/*)PAS\n*/);C,然而Python方面还是过不了,原因是(*[list])不能被赋值,也不能参与加减乘除之类的运算,也不具有方法(总之就是不能写在表达式最左边)。我查了一会资料(事实上中间还走了若干弯路),发现有a,*b,c=1,2,3,4,5这种语法,最终调了一会才得出这样的第一行: (*c),d=2//len('''

这样有一个好处就是,第二行控制权给了C,在第二行开头写;/*)就可以把控制权交给Pascal,剩下的就很简单了:(*c),d=2//len('''\n;/*)PAS*/C/*'''),3,3;PY#*/即可,其中PY是Python3的A+B代码。

最终的成品就如博客开头所示。

花了两个小时。。这玩意还是挺有趣的。


另外我今天下午也在浪浪浪,打广告

评论

AnzheWang
66666,前排膜
laofu
有趣
vegchicken
orz
WrongAnswer
以前听说有给代码判断语言的题(?) 看来根据代码判断语言是不存在的事情。。。
mcfxmcfx
这个代码如果交到能自动识别语言的oj上会被识别成啥?。。
WuHongxun
666
leapfrog
神仙
DPair
Polyglots, what are they and who can be one?
riteme
我见过一个项目叫 cosmopolitan,不是把多个编程语言揉在一起,好像是把多个平台的可执行文件格式合并到一块(

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。