很久以前写的诸个quine
原文写于2020年3月22日;C++实现写于28日
纯真灵魂的自产生程序
用不同语言写自产生程序,作为个人练习。太烧脑了
思路
自产生程序(Quine)即输出自身源代码的程序。既然要输出,必须有一个 输出语句 。但因为 输出语句 需要输出自己,还不能无限套娃,所以还需要一个 赋值语句 ,声明 输出语句 的源代码以便引用。
现在,自产生程序将有两条主要语句,分别是:
- 赋值语句 ,定义一个字符串变量s,使其包含 输出语句 的源代码;
- 输出语句 ,输出程序本身的源代码。
于是, 输出语句 所输出的内容就是:
- 赋值语句 ,把定义好的变量s转义,用引号包围;
- 输出语句 本身。
很不好理解,所以请看具体实现。
具体实现
Javascript
Javascript的实现最为简单,因为确实只需要上述的两行主要代码,并且在转义字符串时偷了点懒。
let s="console.log(\"let s=\"+JSON.stringify(s)+\";\\n\"+s);";
console.log("let s="+JSON.stringify(s)+";\n"+s);
- 第一行 赋值语句 声明了变量s,内容是 输出语句 的源代码。
- 第二行 输出语句 调用了输出方法
console.log
,输出的内容为: "let s="
, 赋值语句 的开头;JSON.stringify(s)
,借用JSON.stringify
方法将变量s转义;";\n"
, 赋值语句 结尾的分号,以及一个换行符;s
, 输出语句 本身。
以上程序在node.js中可以完美地输出自身的源代码。
PHP
PHP的实现也差不多一样简单,只是PHP代码需要用<?php ?>
包围。
<?php
$s='echo "<?php\\n\\$s=".var_export($s,true).";\\n".$s."\\n?>";';
echo "<?php\n\$s=".var_export($s,true).";\n".$s."\n?>";
?>
输出语句 输出的内容:
"<?php\n\$s="
,代码开头、一个换行符以及 赋值语句 的开头;var_export($s,true)
,导出变量s的源代码;";\n"
, 赋值语句 结尾的分号,以及一个换行符;$s
, 输出语句 本身;"\n?>"
,一个换行符和代码结尾。
Python 2
现在我们要尝试不使用原生方法,自己转义字符串。 为了降低转义的复杂程度, 我们暂且把代码中需要的一个换行符也放在变量s里 ,用三引号声明变量s。
s='''
print "s='\''"+s.replace("\\\\","\\\\\\\\").replace("'\''","'\\\\''")+"'\''"+s'''
print "s='''"+s.replace("\\","\\\\").replace("'''","'\\''")+"'''"+s
"s='''"
, 赋值语句 的开头和字符串前的三引号;s.replace("\\","\\\\").replace("'''","'\\''")
,转义变量s,- 一个反斜杠
"\\"
(\
)转义成两个反斜杠"\\\\"
(\\
), - 三引号
"'''"
('''
)转义成"'\\''"
('\''
); "'''"
,字符串后的三引号;s
,一个换行符和 输出语句 。
Java
Java就没有三引号这种偷懒语法了,并且代码前后有各有两行废话。
public class Main{
public static void main(String[]args){
String s="System.out.println(\"public class Main{\\n public static void main(String[]args){\\n String s=\\\"\"+s.replace(\"\\\\\",\"\\\\\\\\\").replace(\"\\n\",\"\\\\n\")+\"\\\";\\n \"+s+\"\\n }\\n}\");";
System.out.println("public class Main{\n public static void main(String[]args){\n String s=\""+s.replace("\\","\\\\").replace("\"","\\\"").replace("\n","\\n")+"\";\n "+s+"\n }\n}");
}
}
"public class Main{\n public static void main(String[]args){\n String s=\""
,代码开头的废话、 赋值语句 的开头和一个双引号;s.replace("\\","\\\\").replace("\"","\\\"").replace("\n","\\n")
,转义变量s,- 一个反斜杠
"\\"
(\
)转义成两个反斜杠"\\\\"
(\\
), - 一个双引号
"\""
("
)转义成反斜杠和双引号"\\\""
(\"
), - 一个换行符
"\n"
转义成反斜杠n"\\n"
(\n
); "\";\n "
, 赋值语句 结尾的双引号、分号,一个换行符以及下一行的缩进;s
, 输出语句 ;"\n }\n}"
,代码结尾的废话。
C#
代码好像长了不少,这只是因为“废话”比较多。
using System;
namespace QuineApplication
{
class Quine
{
static void Main(string[] args)
{
string s="Console.WriteLine(\"using System;\\nnamespace QuineApplication\\n{\\n class Quine\\n {\\n static void Main(string[] args)\\n {\\n string s=\\\"\"+s.Replace(\"\\\\\",\"\\\\\\\\\").Replace(\"\\\"\",\"\\\\\\\"\").Replace(\"\\n\",\"\\\\n\")+\"\\\";\\n \"+s+\"\\n Console.ReadKey();\\n }\\n }\\n}\");";
Console.WriteLine("using System;\nnamespace QuineApplication\n{\n class Quine\n {\n static void Main(string[] args)\n {\n string s=\""+s.Replace("\\","\\\\").Replace("\"","\\\"").Replace("\n","\\n")+"\";\n "+s+"\n Console.ReadKey();\n }\n }\n}");
Console.ReadKey();
}
}
}
"using System;\nnamespace QuineApplication\n{\n class Quine\n {\n static void Main(string[] args)\n {\n string s=\""
,代码开头的废话、 赋值语句 的开头和一个双引号;s.Replace("\\","\\\\").Replace("\"","\\\"").Replace("\n","\\n")
,转义变量s,三次字符串替换与Java的完全相同;"\";\n "
, 赋值语句 结尾的双引号、分号,一个换行符以及下一行的缩进;s
, 输出语句 ;"\n Console.ReadKey();\n }\n }\n}"
,代码结尾的废话。
C++
手动替换字符串。
#include <iostream>
using namespace std;
int main()
{
int pos;
string s=string("cout<<\"#include <iostream>\\nusing namespace std;\\nint main()\\n{\\n int pos;\\n string s=string(\\\"\"<<s2<<\"\\\"),s2=string(s);\\n while(pos=s2.find(\\\"\\\\\\\\\\\"),pos>=0)s2.replace(pos,1,\\\"\\\\x1a\\\");\\n while(pos=s2.find(\\\"\\\\x1a\\\"),pos>=0)s2.replace(pos,1,\\\"\\\\\\\\\\\\\\\\\\\");\\n while(pos=s2.find(\\\"\\\\\\\"\\\"),pos>=0)s2.replace(pos,1,\\\"\\\\x1a\\\");\\n while(pos=s2.find(\\\"\\\\x1a\\\"),pos>=0)s2.replace(pos,1,\\\"\\\\\\\\\\\\\\\"\\\");\\n while(pos=s2.find(\\\"\\\\n\\\"),pos>=0)s2.replace(pos,1,\\\"\\\\\\\\n\\\");\\n \"<<s<<\"\\n return 0;\\n}\";"),s2=string(s);
while(pos=s2.find("\\"),pos>=0)s2.replace(pos,1,"\x1a");
while(pos=s2.find("\x1a"),pos>=0)s2.replace(pos,1,"\\\\");
while(pos=s2.find("\""),pos>=0)s2.replace(pos,1,"\x1a");
while(pos=s2.find("\x1a"),pos>=0)s2.replace(pos,1,"\\\"");
while(pos=s2.find("\n"),pos>=0)s2.replace(pos,1,"\\n");
cout<<"#include <iostream>\nusing namespace std;\nint main()\n{\n int pos;\n string s=string(\""<<s2<<"\"),s2=string(s);\n while(pos=s2.find(\"\\\\\"),pos>=0)s2.replace(pos,1,\"\\x1a\");\n while(pos=s2.find(\"\\x1a\"),pos>=0)s2.replace(pos,1,\"\\\\\\\\\");\n while(pos=s2.find(\"\\\"\"),pos>=0)s2.replace(pos,1,\"\\x1a\");\n while(pos=s2.find(\"\\x1a\"),pos>=0)s2.replace(pos,1,\"\\\\\\\"\");\n while(pos=s2.find(\"\\n\"),pos>=0)s2.replace(pos,1,\"\\\\n\");\n "<<s<<"\n return 0;\n}";
return 0;
}
定义了两个相同的字符串对象s
、s2
,然后中间的那五行while循环把s2转义。
- 先把一个反斜杠
"\\"
(\
)替换成替代符"\x1a"
, - 然后把替代符
"\x1a"
替换成两个反斜杠"\\\\"
(\\
), - 然后把一个双引号
"\""
("
)替换成替代符"\x1a"
, - 然后把替代符
"\x1a"
替换成反斜杠和双引号"\\\""
(\"
), - 最后把一个换行符
"\n"
替换成反斜杠n"\\n"
(\n
)。
使用替代符垫一步是为了防止把已经转义好的反斜杠或双引号再转义一遍。我一开始没垫这一步,结果就死循环了,一直在反复转义第一个反斜杠(\
→\\
→\\\
→\\\\
…)。