Quine 自产生程序 — Java

Quine 以哲学家奎恩(Willard Van Orman Quine)命名,指的是输出结果为程序自身源码的程序,也称为 自产生程序。有点类似于代码中的不动点,举一个例子

quine.py
1
exec(s:='print("exec(s:=%r)"%s)')

那么以下命令全部输出 exec(s:='print("exec(s:=%r)"%s)')

1
2
3
python quine.py
python -c $(python quine.py)
python -c $(python -c $(python quine.py))

理论上图灵完备的语言都可以构造出 Quine,那么如何在 Java 中构造呢?

维基百科的例子

下面的例子来自维基百科 https://en.wikipedia.org/wiki/Quine_(computing)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Quine
{
public static void main(String[] args)
{
char q = 34; // Quotation mark character
String[] l = { // Array of source code
"public class Quine",
"{",
" public static void main(String[] args)",
" {",
" char q = 34; // Quotation mark character",
" String[] l = { // Array of source code",
" ",
" };",
" for(int i = 0; i < 6; i++) // Print opening code",
" System.out.println(l[i]);",
" for(int i = 0; i < l.length; i++) // Print string array",
" System.out.println(l[6] + q + l[i] + q + ',');",
" for(int i = 7; i < l.length; i++) // Print this code",
" System.out.println(l[i]);",
" }",
"}",
};
for(int i = 0; i < 6; i++) // Print opening code
System.out.println(l[i]);
for(int i = 0; i < l.length; i++) // Print string array
System.out.println(l[6] + q + l[i] + q + ',');
for(int i = 7; i < l.length; i++) // Print this code
System.out.println(l[i]);
}
}

大概的原理是(是一个通用的模式)

  1. 在代码中预留一个字符串数组,每个元素代表一行代码
  2. 先输出代码到数组之前 String[] l = { 的行
  3. 再输出数组(需要处理缩进)
  4. 再输出剩下的行
  5. 最后把这个代码粘贴到数组中

还有一个 Java 15 及以上(text blocks)的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Quine {
public static void main(String[] args) {
String textBlockQuotes = new String(new char[]{'"', '"', '"'});
char newLine = 10;
String source = """
public class Quine {
public static void main(String[] args) {
String textBlockQuotes = new String(new char[]{'"', '"', '"'});
char newLine = 10;
String source = %s;
System.out.print(source.formatted(textBlockQuotes + newLine + source + textBlockQuotes));
}
}
""";
System.out.print(source.formatted(textBlockQuotes + newLine + source + textBlockQuotes));
}
}

转义字符

上面的例子是通过 %s 替换自身代码来完成的,之前的 Java 版本中需要对换行和双引号进行转义。按照这个思路可以替换成如下实现(通过 char 跳过转义)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Quine {
public static void main(String[] args) {
char quote = 34;
char[] replace = {92,'n',34,10,32,32,32,32,32,32,32,32,32,32,32,32,'+',32,34};
String source = "public class Quine {\n"
+ " public static void main(String[] args) {\n"
+ " char quote = 34;\n"
+ " char[] replace = {92,'n',34,10,32,32,32,32,32,32,32,32,32,32,32,32,'+',32,34};\n"
+ " String source = %s;\n"
+ " String self = source.replace(String.valueOf((char)10), new String(replace));\n"
+ " System.out.print(String.format(source, quote + self + quote));\n"
+ " }\n"
+ "}";
String self = source.replace(String.valueOf((char)10), new String(replace));
System.out.print(String.format(source, quote + self + quote));
}
}

代码会有点麻烦(为了保留格式),上面 replace 代码相当于

1
source.replace("\n", "\\n\"\n            + \"")

如果不考虑格式和换行的话(只能写在一行了)

1
class Q{public static void main(String[]a){String s="class Q{public static void main(String[]a){String s=%c%s%1$c;System.out.printf(s,34,s);}}";System.out.printf(s,34,s);}}

这里还有更多的例子 http://www.nyx.net/~gthompso/self_java.txt

还有一种更加通用的方案,通过编码的方式绕过转义字符的影响,参考 https://www.zhihu.com/question/22006572/answer/47923822

先写一个通用的代码

1
2
3
4
5
6
7
public class Quine {
public static void main(String[] args) {
String s = "__";
// 通过 URLDecoder
System.out.println(java.net.URLDecoder.decode(s).replaceFirst("__", s));
}
}

然后把上面的代码通过 URLEncoder 编码,然后替换引号里面

1
2
3
4
5
6
7
public class Quine {
public static void main(String[] args) {
String s = "public+class+Quine+%7B%0A++++public+static+void+main%28String%5B%5D+args%29+%7B%0A++++++++String+s+%3D+%22__%22%3B%0A++++++++%2F%2F+%E9%80%9A%E8%BF%87+URLDecoder%0A++++++++System.out.println%28java.net.URLDecoder.decode%28s%29.replaceFirst%28%22__%22%2C+s%29%29%3B%0A++++%7D%0A%7D";
// 通过 URLDecoder
System.out.println(java.net.URLDecoder.decode(s).replaceFirst("__", s));
}
}

同理

1
2
3
4
5
6
7
public class Quine {
public static void main(String[] args) {
String s = "cHVibGljIGNsYXNzIFF1aW5lIHsKICAgIHB1YmxpYyBzdGF0aWMgdm9pZCBtYWluKFN0cmluZ1tdIGFyZ3MpIHsKICAgICAgICBTdHJpbmcgcyA9ICJfXyI7CiAgICAgICAgLy8g6YCa6L+HIEJhc2U2NAogICAgICAgIFN5c3RlbS5vdXQucHJpbnRsbihuZXcgU3RyaW5nKGphdmEudXRpbC5CYXNlNjQuZ2V0RGVjb2RlcigpLmRlY29kZShzKSkucmVwbGFjZUZpcnN0KCJfXyIsIHMpKTsKICAgIH0KfQ==";
// 通过 Base64
System.out.println(new String(java.util.Base64.getDecoder().decode(s)).replaceFirst("__", s));
}
}

Ouroboros programs

public class Quine
{
public static void main(String[] args)
{
char q = 34;
String[] l = {
" ",
"=============<<<<<<<< C++ Code >>>>>>>>=============",
"#include <iostream>",
"#include <string>",
"using namespace std;",
"",
"int main(int argc, char* argv[])",
"{",
" char q = 34;",
" string l[] = {",
" };",
" for(int i = 20; i <= 25; i++)",
" cout << l[i] << endl;",
" for(int i = 0; i <= 34; i++)",
" cout << l[0] + q + l[i] + q + ',' << endl;",
" for(int i = 26; i <= 34; i++)",
" cout << l[i] << endl;",
" return 0;",
"}",
"=============<<<<<<<< Java Code >>>>>>>>==========",
"public class Quine",
"{",
" public static void main( String[] args )",
" {",
" char q = 34;",
" String[] l = {",
" };",
" for(int i = 2; i <= 9; i++)",
" System.out.println(l[i]);",
" for(int i = 0; i < l.length; i++)",
" System.out.println( l[0] + q + l[i] + q + ',' );",
" for(int i = 10; i <= 18; i++))",
" System.out.println(l[i]);",
" }",
"}",
};
for(int i = 2; i <= 9; i++)
System.out.println(l[i]);
for(int i = 0; i < l.length; i++)
System.out.println( l[0] + q + l[i] + q + ',' );
for(int i = 10; i <= 18; i++)
System.out.println(l[i]);

}
}
#include <iostream>
#include <string>
using namespace std;

int main(int argc, char* argv[])
{
char q = 34;
string l[] = {
" ",
"=============<<<<<<<< C++ Code >>>>>>>>=============",
"#include <iostream>",
"#include <string>",
"using namespace std;",
"",
"int main(int argc, char* argv[])",
"{",
" char q = 34;",
" string l[] = {",
" };",
" for(int i = 20; i <= 25; i++)",
" cout << l[i] << endl;",
" for(int i = 0; i <= 34; i++)",
" cout << l[0] + q + l[i] + q + ',' << endl;",
" for(int i = 26; i <= 34; i++)",
" cout << l[i] << endl;",
" return 0;",
"}",
"=============<<<<<<<< Java Code >>>>>>>>=============",
"public class Quine",
"{",
" public static void main(String[] args)",
" {",
" char q = 34;",
" String[] l = {",
" };",
" for(int i = 2; i <= 9; i++)",
" System.out.println( l[i] );",
" for(int i = 0; i < l.length; i++)",
" System.out.println(l[0] + q + l[i] + q + ',');",
" for(int i = 10; i <= 18; i++)",
" System.out.println(l[i]);",
" }",
"}",
};
for(int i = 20; i <= 25; i++)
cout << l[i] << endl;
for(int i = 0; i <= 34; i++)
cout << l[0] + q + l[i] + q + ',' << endl;
for(int i = 26; i <= 34; i++)
cout << l[i] << endl;
return 0;
}

这个例子来自维基百科 https://en.wikipedia.org/wiki/Quine_(computing)

两段代码结构上非常相似,而且跟第一个例子也很像。Java 的代码输出 C++ 的代码,C++ 的代码输出 Java 的代码,循环生成。

也可以用上面占位符的思路,这里演示多个 Java 代码相互生成的例子,class A -> class B -> class C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class A {
public static void main(String[] args) {
String s = "public+class A+%7B%0A++++public+static+void+main%28String%5B%5D+args%29+%7B%0A++++++++String+s+%3D+%22__%22%3B%0A++++++++String+code+%3D+java.net.URLDecoder.decode%28s%29.replaceFirst%28%22__%22%2C+s%29%3B%0A++++++++String+a+%3D+%22class+%22+%2B+%22A%22%3B%0A++++++++String+b+%3D+%22class+%22+%2B+%22B%22%3B%0A++++++++String+c+%3D+%22class+%22+%2B+%22C%22%3B%0A++++++++if+%28code.contains%28a%29%29+%7B%0A++++++++++++code+%3D+code.replaceAll%28a%2C+b%29%3B%0A++++++++%7D+else+if+%28code.contains%28b%29%29+%7B%0A++++++++++++code+%3D+code.replaceAll%28b%2C+c%29%3B%0A++++++++%7D%0A++++++++System.out.println%28code%29%3B%0A++++%7D%0A%7D";
String code = java.net.URLDecoder.decode(s).replaceFirst("__", s);
String a = "class " + "A";
String b = "class " + "B";
String c = "class " + "C";
if (code.contains(a)) {
code = code.replaceAll(a, b);
} else if (code.contains(b)) {
code = code.replaceAll(b, c);
}
System.out.println(code);
}
}

其他语言的例子

下面的例子来自 https://rosettacode.org/wiki/Quine

C
1
2
#include <stdio.h>
int main(){char*c="#include <stdio.h>%cint main(){char*c=%c%s%c;printf(c,10,34,c,34,10);return 0;}%c";printf(c,10,34,c,34,10);return 0;}
Go
1
2
3
4
5
6
7
8
package main

import "fmt"

func main() {
a := "package main\n\nimport \"fmt\"\n\nfunc main() {\n\ta := %q\n\tfmt.Printf(a, a)\n}\n"
fmt.Printf(a, a)
}
JavaScript
1
(f=_=>`(f=${f})()`)()

Quine Relay

https://github.com/mame/quine-relay

This is a Ruby program that generates Rust program that generates Scala program that generates …(through 128 languages in total)… REXX program that generates the original Ruby code again.

多种语言的 Quine 循环, Ruby -> Rust -> Scala -> ... -> Ruby 最终生成跟原来相同的 Ruby 程序,一共 128 种语言。原理可以参考 https://www.zhihu.com/question/21568155

参考

https://en.wikipedia.org/wiki/Quine_(computing)
http://www.nyx.net/~gthompso/quine.htm
https://www.zhihu.com/question/22006572
https://github.com/mame/quine-relay
https://rosettacode.org/wiki/Quine

Quine 自产生程序 — Java

https://www.alphalxy.com/2021/09/quine-java/

Author

Xinyu Liu

Posted on

2021-09-09

Updated on

2021-09-10


Comments