aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Makefile3
-rw-r--r--README.md95
-rw-r--r--assets/0.py.pngbin0 -> 440110 bytes
-rw-r--r--assets/1.py.pngbin0 -> 441740 bytes
-rw-r--r--assets/107.py.pngbin0 -> 331705 bytes
-rw-r--r--assets/108.php.pngbin0 -> 440815 bytes
-rw-r--r--tools/vhs/0.py.tape7
-rw-r--r--tools/vhs/1.py.tape7
-rw-r--r--tools/vhs/107.py.tape7
-rw-r--r--tools/vhs/108.php.tape7
-rw-r--r--tools/vhs/_config.tape4
11 files changed, 130 insertions, 0 deletions
diff --git a/Makefile b/Makefile
index ae9fa6e..775c718 100644
--- a/Makefile
+++ b/Makefile
@@ -3,3 +3,6 @@ q0.py:
python .107.unformatted.py > .108.php
php .108.php > 0.py
rm .107.unformatted.py .108.php
+
+assets/%.png: tools/vhs/%.tape tools/vhs/_config.tape
+ nix run 'nixpkgs#vhs' $<
diff --git a/README.md b/README.md
index 417e39e..7e8e391 100644
--- a/README.md
+++ b/README.md
@@ -15,3 +15,98 @@ $ diff 0.py 0-2.py
```
変則 Quine であり、最初の `0.py` は `gen.py` によって生成されます (`make` で出力可能)。
+
+
+# 基本動作
+
+`0.py` は「巳」の字の形をしています。これは、2025 年が巳年であることにちなんだものです。Python を使っているのも同様の理由です。
+
+![0.py](assets/0.py.png)
+
+`0.py` を Python で実行すると、次のような出力が得られます。
+
+![0.py output](assets/1.py.png)
+
+これは `0.py` と同じく Python コードであり、その大部分が `0.py` と共通しています。
+
+この出力を `1.py` として保存し、実行結果を `2.py` として保存するという動作を繰り返すと、108 回目に異なる動作となります (「108」という数字は、除夜の鐘から取ったものです)。
+
+```
+$ python 0.py > 1.py
+$ python 1.py > 2.py
+$ python 2.py > 3.py
+...
+$ python 107.py
+```
+
+![107.py output](assets/107.py.png)
+
+この実行結果は、「午」の字の形をしています。来年 2026 年は午年ですね!
+
+また、Python のソースコードではなく PHP のソースコードとなっています。PHP のマスコットは馬......であればよかったのですが残念ながら象でした。
+
+これを PHP インタプリタで実行すると、次のようになります。
+
+![108.php output](assets/108.php.png)
+
+これは、最初の `0.py` と完全に一致します。以降は同じ挙動を繰り返します。
+
+
+# 解説
+
+基本となるのは、mame 氏による「Quine リレー」の記事 (https://mametter.hatenablog.com/entry/20130715/p1) にて説明されているテクニックです。
+
+> 普通の Quine は、こんな感じの構造です。
+>
+> ```
+> s = (自分自身のソースコード文字列を得る)
+> puts s
+> ```
+>
+> 一方、例えば何かを出力する Perl プログラム、を出力する Ruby プログラムはこんな感じです。
+>
+> ```
+> puts "print(\"...\");"
+> ```
+>
+> これと Quine を組み合わせれば、Ruby と Perl を行き来する multi-Quine のできあがり。
+>
+> ```
+> s = (自分自身のソースコード文字列を得る)
+> puts "print(\"#{ s }\");"
+> ```
+
+実装に用いた言語こそ異なりますが、基本となるのはこのアイデアです。
+
+ここに、「巳」「午」の字形を出力したり、108 回のカウントをしたりする機構を加えると、今回のソースコードができます。
+
+`0.py` は `gen.py` というプログラムによって生成しており、その `template1` 変数に記述されたソースコードが動作の基盤となるものです。
+
+その大部分を占めている出力の形を整えるコード (`fmt`) を除くと、次のようになっています。
+
+```python
+from base64 import b64encode
+from zlib import compress, decompress
+j = b'107' # 108 回のカウント
+s = b"@@" # この変数にソースコードが埋め込まれる
+z = decompress(b64decode(s)).replace(b"j = b'%s'" % j, b"j = b'%03d'" % ((int(j)+1) % 108))
+t = z.replace(b"@"+b"@", b64encode(compress(z)))
+u = b64encode(t)
+
+def fmt(u, f, php): # 整形
+ ...
+
+F1 = ["巳の字形データ"]
+F2 = ["午の字形データ"]
+
+v = fmt(u, F1, False) # Python で「巳」に整形
+if j == b"107":
+ # 108 回目。PHP で「午」に整形
+ print(fmt(b64encode(compress(v)), F2, True).decode("utf-8"))
+else:
+ print(v.decode("utf-8"))
+```
+
+言語間での文字列エスケープを簡単にするため、Base64 エンコードを用いています。これによってソースコード中のクォートやバックスラッシュが消えるので、ソースコードを内部に埋め込む際のエスケープについて考える必要がなくなります。
+
+また、PHP コード側で整形処理を書く手間を減らすため、ソースコードの整形処理はすべて Python 側でおこなうようにしています。「午」の PHP コードは、内部に「巳」の形へ整形済みの Python コードを保持し、それを加工なしで出力するようになっています。
diff --git a/assets/0.py.png b/assets/0.py.png
new file mode 100644
index 0000000..67aa7d2
--- /dev/null
+++ b/assets/0.py.png
Binary files differ
diff --git a/assets/1.py.png b/assets/1.py.png
new file mode 100644
index 0000000..7680e51
--- /dev/null
+++ b/assets/1.py.png
Binary files differ
diff --git a/assets/107.py.png b/assets/107.py.png
new file mode 100644
index 0000000..6c38a72
--- /dev/null
+++ b/assets/107.py.png
Binary files differ
diff --git a/assets/108.php.png b/assets/108.php.png
new file mode 100644
index 0000000..e49d626
--- /dev/null
+++ b/assets/108.php.png
Binary files differ
diff --git a/tools/vhs/0.py.tape b/tools/vhs/0.py.tape
new file mode 100644
index 0000000..dd19867
--- /dev/null
+++ b/tools/vhs/0.py.tape
@@ -0,0 +1,7 @@
+Source "tools/vhs/_config.tape"
+
+Type "cat 0.py"
+Enter
+Sleep 1s
+
+Screenshot assets/0.py.png
diff --git a/tools/vhs/1.py.tape b/tools/vhs/1.py.tape
new file mode 100644
index 0000000..63391e1
--- /dev/null
+++ b/tools/vhs/1.py.tape
@@ -0,0 +1,7 @@
+Source "tools/vhs/_config.tape"
+
+Type "python 0.py"
+Enter
+Sleep 1s
+
+Screenshot assets/1.py.png
diff --git a/tools/vhs/107.py.tape b/tools/vhs/107.py.tape
new file mode 100644
index 0000000..5410f96
--- /dev/null
+++ b/tools/vhs/107.py.tape
@@ -0,0 +1,7 @@
+Source "tools/vhs/_config.tape"
+
+Type "python 107.py"
+Enter
+Sleep 1s
+
+Screenshot assets/107.py.png
diff --git a/tools/vhs/108.php.tape b/tools/vhs/108.php.tape
new file mode 100644
index 0000000..3fec172
--- /dev/null
+++ b/tools/vhs/108.php.tape
@@ -0,0 +1,7 @@
+Source "tools/vhs/_config.tape"
+
+Type "python 107.py | php"
+Enter
+Sleep 1s
+
+Screenshot assets/108.php.png
diff --git a/tools/vhs/_config.tape b/tools/vhs/_config.tape
new file mode 100644
index 0000000..0bf7842
--- /dev/null
+++ b/tools/vhs/_config.tape
@@ -0,0 +1,4 @@
+Set FontSize 8
+Set Width 950
+Set Height 950
+Set Theme "iceberg-dark"