自己生成プログラム

実行すると自分自身のコードを吐き出すプログラム。こういうのってなんていうんだっけ。自己複製?
コード→文字列の変換のエスケープが不要になるrubyが一番作りやすくて、しくみも理解しやすいかも。

注意したところは、改行文字を直接使わないようにしたこと。そうすることで改行が機種ごとに違っても二段階以降は同じものが生成されるようになるから。あと、コードを無用に切り詰めないこと。

ruby

lines = %{
print "lines = %{"
print lines
print "}"
print lines
}
print "lines = %{"
print lines
print "}"
print lines

(要最後の空行)

もしくは

lines = %{
puts "lines = %{"
puts lines.strip
puts "}"
puts lines.strip
}
puts "lines = %{"
puts lines.strip
puts "}"
puts lines.strip

python

lines = r"""
print("lines = r\"\"\"")
print(lines.strip())
print("\"\"\"")
print(lines.strip())
"""
print("lines = r\"\"\"")
print(lines.strip())
print("\"\"\"")
print(lines.strip())

もしくは

lines = [
'print("lines = [")',
'for line in lines: print("%s," % repr(line))',
'print("]")',
'for line in lines: print(line)',
]
print("lines = [")
for line in lines: print("%s," % repr(line))
print("]")
for line in lines: print(line)

haskell

(ここからはコードをエスケープして文字列リテラルに埋め込んでますが、下部のコードと同じ内容である点は一緒です)

ls = [
 "main = do",
 "  putStrLn \"ls = [\"",
 "  mapM_ (putStrLn.stringline) contents",
 "  putStrLn \" \\\"\\\"]\"",
 "  mapM_ putStrLn contents",
 "    where",
 "      contents = reverse.tail.reverse $ ls",
 "      stringline line = \" \" ++ show line ++ \",\"",
 ""]
main = do
  putStrLn "ls = ["
  mapM_ (putStrLn.stringline) contents
  putStrLn " \"\"]"
  mapM_ putStrLn contents
    where
      contents = reverse.tail.reverse $ ls
      stringline line = " " ++ show line ++ ","

c

static const char * const lines[] = {
  "int putchar(int);",
  "int puts(const char *);",
  "void eputs(const char *str) {",
  "  putchar(\' \'); putchar(\' \'); putchar(\'\\\"\');",
  "  for (; *str; ++str) {",
  "    char ch = *str;",
  "    if (ch == \'\\\\\' || ch == \'\\\'\' || ch == \'\\\"\') putchar(\'\\\\\');",
  "    putchar(ch);",
  "  }",
  "  puts(\"\\\",\");",
  "}",
  "int main() {",
  "  const char * const *cursor;",
  "  puts(\"static const char * const lines[] = {\");",
  "  for (cursor = lines; *cursor; ++cursor) eputs(*cursor);",
  "  puts(\"  0};\");",
  "  for (cursor = lines; *cursor; ++cursor) puts(*cursor);",
  "  return 0;",
  "}",
  0};
int putchar(int);
int puts(const char *);
void eputs(const char *str) {
  putchar(' '); putchar(' '); putchar('\"');
  for (; *str; ++str) {
    char ch = *str;
    if (ch == '\\' || ch == '\'' || ch == '\"') putchar('\\');
    putchar(ch);
  }
  puts("\",");
}
int main() {
  const char * const *cursor;
  puts("static const char * const lines[] = {");
  for (cursor = lines; *cursor; ++cursor) eputs(*cursor);
  puts("  0};");
  for (cursor = lines; *cursor; ++cursor) puts(*cursor);
  return 0;
}

java

class Self {
  private static final String[] lines = {
    "  public static void main(String[] args) {",
    "    System.out.println(\"class Self {\");",
    "    System.out.println(\"  private static final String[] lines = {\");",
    "    for (int i = 0; i < lines.length; i += 1) printStr(lines[i]);",
    "    System.out.println(\"  };\");",
    "    for (int i = 0; i < lines.length; i += 1) System.out.println(lines[i]);",
    "    System.out.println(\"}\");",
    "  }",
    "  private static void printStr(String line) {",
    "    System.out.print(\"    \\\"\");",
    "    for (int i = 0; i < line.length(); i += 1) {",
    "      char ch = line.charAt(i);",
    "      if (ch == \'\\\'\' || ch == \'\\\"\' || ch == \'\\\\\') System.out.print(\'\\\\\');",
    "      System.out.print(ch);",
    "    }",
    "    System.out.println(\"\\\",\");",
    "  };",
  };
  public static void main(String[] args) {
    System.out.println("class Self {");
    System.out.println("  private static final String[] lines = {");
    for (int i = 0; i < lines.length; i += 1) printStr(lines[i]);
    System.out.println("  };");
    for (int i = 0; i < lines.length; i += 1) System.out.println(lines[i]);
    System.out.println("}");
  }
  private static void printStr(String line) {
    System.out.print("    \"");
    for (int i = 0; i < line.length(); i += 1) {
      char ch = line.charAt(i);
      if (ch == '\'' || ch == '\"' || ch == '\\') System.out.print('\\');
      System.out.print(ch);
    }
    System.out.println("\",");
  };
}

しくみ

以下のような構成でつくると自己生成するコードになる

コードの前半部
コード後半部埋め込みリテラル
コード後半部
  mainコード
    前半部を出力
    後半部リテラル内容をリテラル形式に変換して出力
    後半部をリテラルから出力
  ユーティリティコード
    リテラル形式変換
    ...

作り方は以下のような感じ

  • リテラル部分だけ空けておき、リテラル化関数とメイン出力を書く
  • 後半部分をコピペし、リテラル化する
    • 後半部の出力で、そのまま後半部コードが出るようにする
    • 文字列などでエスケープが必要なところはエスケープする
  • 実行結果とソースをdiff等で比較し、空行、インデント、改行文字等を調整する


本質は、コードをリテラルにして、実行でそれを二つ出力させること。一つはコード本体として、もう一つは出力させるリテラルデータとして。そしてそのために、そのリテラル自体をリテラル形式に変換すること。

このタイプで有名なコードはprintfなどで、format文字列を使うタイプで、format文字列自体を適用させて使うものです。以下は、フォーマットを使うruby版です:

s = %{s = %%{%s};puts sprintf(s, s)};puts sprintf(s, s)

そのPython版:

s = 's = %s;print(s %% repr(s))';print(s % repr(s))

ダブルクオートのASCIIコードを利用した場合:

s = "s = %c%s%c;print(s %% (34, s, 34))";print(s % (34, s, 34))

これを省略を多用したcにした場合(以下のコードはEOFまで改行なしにする)

main(){char*s="main(){char*s=%c%s%c;printf(s,34,s,34);}";printf(s,34,s,34);}

これに似たコードが良く見かけるタイプだと思います。(出る警告は、mainの戻り値が暗黙int、printfが暗黙、mainのreturnがない、改行なしでEOF)



コードの入ったformat文字にそのformatを埋め込むことで、コード部分を二つ出力させていることになっているんですね。