很久以前写的诸个quine

原文写于2020年3月22日;C++实现写于28日


纯真灵魂的自产生程序

用不同语言写自产生程序,作为个人练习。太烧脑了

思路

自产生程序(Quine)即输出自身源代码的程序。既然要输出,必须有一个 输出语句 。但因为 输出语句 需要输出自己,还不能无限套娃,所以还需要一个 赋值语句 ,声明 输出语句 的源代码以便引用。

现在,自产生程序将有两条主要语句,分别是:

  1. 赋值语句 ,定义一个字符串变量s,使其包含 输出语句 的源代码;
  2. 输出语句 ,输出程序本身的源代码。

于是, 输出语句 所输出的内容就是:

  1. 赋值语句 ,把定义好的变量s转义,用引号包围;
  2. 输出语句 本身。

很不好理解,所以请看具体实现。

具体实现

Javascript

Javascript的实现最为简单,因为确实只需要上述的两行主要代码,并且在转义字符串时偷了点懒。

let s="console.log(\"let s=\"+JSON.stringify(s)+\";\\n\"+s);";
console.log("let 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?>";
?>

输出语句 输出的内容:

Python 2

现在我们要尝试不使用原生方法,自己转义字符串。 为了降低转义的复杂程度, 我们暂且把代码中需要的一个换行符也放在变量s ,用三引号声明变量s

s='''
print "s='\''"+s.replace("\\\\","\\\\\\\\").replace("'\''","'\\\\''")+"'\''"+s'''
print "s='''"+s.replace("\\","\\\\").replace("'''","'\\''")+"'''"+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}");
    }
}

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();
        }
    }
}

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;
}

定义了两个相同的字符串对象ss2,然后中间的那五行while循环把s2转义。

使用替代符垫一步是为了防止把已经转义好的反斜杠或双引号再转义一遍。我一开始没垫这一步,结果就死循环了,一直在反复转义第一个反斜杠(\\\\\\\\\\…)。