TeX言語でドドスコ（ラブ注入）してみた話

:::note info
これは「[TeX & LaTeX Advent Caleandar 2022](https://adventar.org/calendars/7868)」の1日目の記事です。
（2日目は [h20y6m さん](https://h20y6m.github.io/posts/2022-12-02-texadvent2022/)です。）
:::

例によって、今年も[「TeX & LaTeX Advent Calendar」][texadvent]の季節が到来しました。素敵な記事が毎日（日付通りに:wink:）届けられるといいですね！

さて、今年の重点テーマは（昨年の続きということで）**​「やっぱりTeX言語（とか）しましょう」​**です。というわけで、初日となる今日は、**​「自作のTeX言語プログラムの動作を解説する」​**というテーマで話をします。

対象となるTeX言語プログラムは……**これです。**

https://twitter.com/zr_tex8r/status/1558023016541331461

はい……「TeX & LaTeX Advent Calendar」の大ファンの方なら、こういう**​[チョ][finale2012]​[ット][finale2014]​[読み][finale2015]​[にくい][finale2018]​[プロ][finale2020]​[グラム][finale2021]​**はお馴染みでしょう（**クリスマス🎄の日**によく見かけますね:upside_down_face:）。読みにくいプログラムを頑張って “解読” してみましょう:smiley:

## 前提知識

* **フツーの**TeX言語の知識。

  この記事ではplain TeXを扱いますが、plain特有の知識は前提としないので「LaTeX上のTeX言語の知識」でかまいません。`\expandafter`が絡んだトークン列の展開が解っていれば十分です。（**フツー**ですね:upside_down_face::upside_down_face::upside_down_face:）


:::note info
`\expandafter`に自信がないという人は、例の[「\\expandafter のキホン」の記事][expandafter]を読んで復習しておきましょう。
:::

## 「ドドスコ」(ラブ注入) を説明してみる話

「ドドスコ」というのは、今年（2022年）の夏に発生した「プログラミング大喜利」のお題の一つで、以下の仕様を満たすプログラムをつくることを求めています。

https://twitter.com/Sheeeeepla/status/1554028833942441984

> 【問題】配列{"ドド","スコ"}からランダムに要素を標準出力し続け、『その並びが「ドドスコスコスコ」を3回繰り返したもの』に一致したときに「ラブ注入♡」と標準出力して終了するプログラムを作成せよ

この “お題” に寄せられた回答を集めた[「Awesome-DDSK」][awesome-ddsk]というGitHubレポジトリも作られています。

https://github.com/Comamoca/Awesome-DDSK

本記事ではこの問題のことを**​「ドドスコ問題」​**と呼ぶことにします。

## とにかく「ドドスコ」を実行してみる話

先ほどのTeX言語のソースファイルは以下のGistで公開しています。

* [TeX言語でラブ注入♡（ドドスコプログラム）][ddsk-gist]（Gist）

プログラムの実行方法は`00README.md`ファイルの説明の通りですが、改めて説明します。

* （チョット読みにくい）TeX言語ソースファイル `ddsk.tex`
* 謎のファイル `ddsk.tcx`

この2つのファイルを同じディレクトリに置いた上で、そのディレクトリで次のように`pdftex`（plain pdfTeXの起動コマンド）を実行すると、「ドドスコ問題」の出力が書かれたPDFファイル`ddsk.pdf`が出力されます。

```bash
pdftex -translate-file=./ddsk ddsk.tex
```

「ドドスコ問題」の出力は乱数の出方によって毎回異なりますが、例えば以下のような出力が得られます。

![image-ddskout-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14550/2a538e17-d771-c085-ccb5-42fe9fdf8113.png)

何度か実行してみると、確かに「ドドスコ問題」の条件を満たす出力が得られていることがわかります。

## まずはファイルを正しく“読んで”みる話

プログラムの動作は解ったので、改めて（チョット読みにくい）ソースを見てみましょう。

```text:ddsk.tex
ド♡！ㇻゴタド入♡！ド！ド入♡！ド♡！ㇻゴタド！ド♡ド♡！ド♡ㇻゴタッド♡
ゴㇲプズブダズポタゴルド！ド！ド♡ッ♡♡ド♡ゴㇲプズブダズポタゴルド！ド！
ド♡ゴㇲプズブダズポタゴルド！ド♡ㇻゴタッド♡♡ド！ド♡ッ♡！！ド♡♡ド！
ド♡ㇻゴタド！ド♡♡ド♡！！ド♡♡ド♡♡！ド♡ズダボズブットゴッド！ド♡♡
ド♡！！ド♡♡ド♡♡♡ド♡ㇻトトラダゴド！ド♡♡ド♡！！ド♡♡ド♡！♡ド♡
トズタトラダゴド！ド♡ブゴゾトラグブタッド！ド！！ド！！スダド♡♡！ド！！
コストド♡トガズルダゴポッド！ド！♡ド！！ド♡！♡スボド！♡ド♡♡！ド！！
ド！！ド♡！♡スゾド！！ド♡ダゴポド！ド！♡♡ボド！！ド♡♡！ド！！ド！♡
ド♡♡♡ゾド！！スㇲド♡♡！ド！！コスズド♡♡！ド！！ド！♡ド♡♡！ド！！
ド！！ド♡♡♡スガド！♡♡スクド！♡♡スㇻド！♡♡スブド！♡♡スラド！♡♡
スコッド！♡♡スタッド！♡♡ススド！♡♡スプッド！♡♡スルド！！ド！！スズ
ド♡♡！ド！！ド！♡ド♡♡♡スソド！！ボッド！！スズッド♡♡！ド！！スズッ
ド♡♡！ド！！コスポド♡！♡スグド♡！♡ド！！ド♡！♡ド！！ド♡！♡ススッ
グ♡♡♡スドグ♡♡♡スタグ♡ㇻラゾッゴルトズソッゴッグ！ボグ♡ㇲダゴポグ！
グ！♡♡ボッコトドブドクスラドブドポドラドコドガコトスドブクブクコトドブド
クスラドブドポドラドコドガコトススブクブクㇻガコトドゴドポスガドクドスドコ
ドゴスコドダドㇻドコスㇻスブコトドドドポドゴスラコトドドラクブダドルスガス
プドダㇻダスㇻㇻダスコブㇻブドコトドドドポドゴスラコトドドラㇻブダドルスガ
スプドダㇻダスㇻㇻダスコブコブクコトドドドポドゴスラコトドドラブブダドルス
ガスプドダㇻダスㇻㇻダスコブドドブコトドドドポドゴスラコトドドラソブダドル
スガスプドダㇻダスㇻㇻダスコブブブガコトドドラソコトドㇻドクスブドコドトド
ルドゴドコスブドソドルスガブダブクブㇻスガスラスガドトスコスブブㇻスガスラ
コトドプスブドルスズドコブダブラブガドコドダコトドゴドコススドルドドコトド
ルドドラブコトドゴドコススドブドポスコドゴスラコトドルコブコトドゴドコスス
ドブドポスコドゴスラコトドルコㇻコトドゴドポドルドゴドラドコドゴスラコトラ
ブスラスㇻスコドココトドトドポドポスガコトドルドドラブコトドルコㇻブダコト
スガドラドドスコドゴドルドドドポスㇻドダドラドコスドドルドクスラドコブㇻコ
トスㇻドコドトドクスプコトドルドドドポドラドラコトドルコㇻコトドブドプドク
スㇻブㇻブガブクコトドブドプドクスㇻブㇻブガブクㇻガコトドコドトスブドココ
トドブドプドクスㇻブクブプブココトドブドプドクスㇻブクブスブルㇻガコトドド
ドルコトドプスブドソドルスガブガスガスラㇻガコトドダスコドトスラドルスガド
トスルコトドルコブブㇻコトドクドラスドドクドゴドブドココトドルコブコトドル
コㇻコトドルドドドゴスコドダコトドルコブブトㇻㇻブクブガブガブガㇻガコトド
コドトスブドココトドクドラスドドクドゴドブドココトドルコブㇻダㇻㇻブクブガ
ブガブガㇻガコトドドドルコトドルドドドゴスコドダコトドルコブブダㇻㇻブプブ
プブプㇻガコトラブドドドクドトスブドココトドドドルコトスㇻドコスガドコドク
スラコトドブドプドクスㇻブㇻブブブブコトドブドプドクスㇻブㇻブクブラコトド
プスブドソドルスガブガスガスラㇻガコトドドラブコトドブドプドクスㇻブㇻブブ
ブㇻコトドドラㇻコトドブドプドクスㇻブクブガブクコトドプスブドソドルスガブ
ガスガスラㇻガコトドドラクコトドブドプドクスㇻブルブスㇻガコトドㇻスルドコ
ゾゾゾド♡♡ド♡！！ド♡♡ド♡♡♡ド♡プダポグブゴソトズプゴガゴㇲド！ド♡
ソトズブタラクゴブソド！ド♡♡ボド♡♡♡ド♡♡ボド！♡♡ゾゾド！ラブ注入♡
```

……なんというか「チョット読みにくい」どころの話ではなく全く**わけがわからない**ですね:upside_down_face: `\def`とか`\expandafter`とかのTeX言語のお馴染みのモノは全く見当たらず、代わりに日本語文字だけが延々と並んでいます。これは本当にTeX言語のプログラムなのでしょうか:astonished:

そういえば、エンジンとしてpdfTeXを使っていました。pdfTeXは、LuaTeXのような「Unicodeを扱うTeX」とも(u)pTeXのような「日本語文字を扱うTeX」とも違い、ソースコードを常に「バイトの列」として取り扱います。ということは、今見ている「奇妙な日本語文字の羅列」のソースコードは、pdfTeXには次のようなバイト列として見えているはずです。

```text
E3 83 89 E2 99 A1 EF BC 81 E3 87 BB E3 82 B4 E3
82 BF E3 83 89 E5 85 A5 E2 99 A1 EF BC 81 E3 83
89 EF BC 81 E3 83 89 E5 85 A5 E2 99 A1 EF BC 81
E3 83 89 E2 99 A1 EF BC 81 E3 87 BB E3 82 B4 E3
82 BF E3 83 89 EF BC 81 E3 83 89 E2 99 A1 E3 83
89 E2 99 A1 EF BC 81 E3 83 89 E2 99 A1 E3 87 BB
E3 82 B4 E3 82 BF E3 83 83 E3 83 89 E2 99 A1 0A
E3 82 B4 E3 87 B2 E3 83 97 E3 82 BA E3 83 96 E3
83 80 E3 82 BA E3 83 9D ......(以下省略)
```

ではこれでTeX言語のプログラムとして解釈できるようになったかというと、結局「80h以上のバイトが延々と並んでいる」状態になっただけです。別に「plainフォーマットでは80h以上のバイトに特別な意味が与えられている」わけでは**ない**ので全く解釈できそうにありません。実際、この文字列（バイト列）自体はそのままでは有効なTeX言語のプログラムにはなっていません。このファイルをコンパイルした際に **“謎のオプション”** を付けていたことを思い出しましょう。

```bash
pdftex -translate-file=./ddsk ddsk.tex
```

ここでは`-translate-file=./ddsk`というオプションを付けています。オプション名に “file” とあるので、恐らく何かのファイルを指定しているのでしょう。そういえば、`ddsk.tex`の他にもう1つ、`ddsk.tcx`という **“謎のファイル”** がありました。

```text:ddsk.tcx（冒頭部分）
0x80 0x64
0x81 0x65
0x82 0x00
0x83 0x00
0x85 0x6e
0x87 0x00
0x88 0x63
0x89 0x5c
```

「80h以上の値と80h未満の値のペア」が書かれた行が並んでいます。どうやらこれが **“暗号解読表”** になっていそうです:sunglasses: 「左列のバイト値を対応する右列のバイト列に置き換える」という変換を`ddsk.tex`に適用してみましょう。

```text
00 00 5C 63 73 6E 61 6D 65 00 00 6C 00 00 65 00
00 74 00 00 5C 65 6E 64 63 73 6E 61 6D 65 00 00
5C 61 6D 65 00 00 5C 65 6E 64 63 73 6E 61 6D 65
00 00 5C 63 73 6E 61 6D 65 00 00 6C 00 00 65 00
00 74 00 00 5C 61 6D 65 00 00 5C 63 73 6E 00 00
5C 63 73 6E 61 6D 65 00 00 5C 63 73 6E 00 00 6C
00 00 65 00 00 74 00 00 00 00 00 5C 63 73 6E 0A
00 00 65 00 00 78 00 00 70 00 00 61 00 00 6E 00
00 64 00 00 61 00 00 66 ...(以下省略)
```

所々にNULバイト（値が00hのバイト）が含まれていますが、TeXにとってはNULバイトも有効な入力です。とりあえずNULバイトを`·`で表して文字に起こすと以下のようになります。

```text
··\csname··l··e··t··\endcsname··\ame··\endcsname··\csname··l··e··t··\ame··\csn··\csname··\csn··l··e··t·····\csn
··e··x··p··a··n··d··a··f··t··e··r··\ame··\ame··\csn···csncsn··\csn··e··x··p··a··n··d··a··f··t··e··r··\ame··\ame
··\csn··e··x··p··a··n··d··a··f··t··e··r··\ame··\csn··l··e··t·····\csncsn··\ame··\csn···csnameame··\csncsn··\ame
··\csn··l··e··t··\ame··\csncsn··\csnameame··\csncsn··\csncsname··\csn··a··d·····a··n·····c··e·····\ame··\csncsn
··\csnameame··\csncsn··\csncsncsn··\csn··l··c··c··o··d··e··\ame··\csncsn··\csnameame··\csncsn··\csnamecsn··\csn
··c··a··t··c··o··d··e··\ame··\csn··n··e·····c··o··u··n··t·····\ame··\ameame··\ameame··`··d··\csncsname··\ameame
·····`··c··\csn··c··h··a··r··d··e··f·····\ame··\amecsn··\ameame··\csnamecsn··`·····\amecsn··\csncsname··\ameame
··\ameame··\csnamecsn··`·····\ameame··\csn··d··e··f··\ame··\amecsncsn·····\ameame··\csncsname··\ameame··\amecsn
...(以下省略)
```

お馴染みの`\csname`や`\endcsname`が出てきました:smiley: さらによく見るとNUL文字に紛れて “expandafter” という文字列も確認できます:heart_eyes: どうやら`ddsk.tcx`が解読表になっているという解釈で当たっているようです。

:::note info
ここで使われているのは**​「TeX Character Translation (TCX)」​**と呼ばれている機能で、本来は「EBCDIC等のASCII非互換の文字コードが使われているシステム」の上でのTeXの運用を容易にすることを目的としています[^tcx]。システムの文字コードからASCIIへの対応を**TCXファイル**（拡張子`.tcx`）に書いておいて、そのファイルを`--translate-file`オプションで指定することで、システムのASCII非互換な文字コードのファイルが（何も改変していない）TeXエンジンでそのまま扱えるようになります。今ではEBCDIC系のシステムはほぼ絶滅してしまっているので、TCXの機能が本来の目的で使われることはもはやなさそうです。
:::

[^tcx]: つまり、TeXの内部で使われているコードは常にASCII互換（7ビット部分がASCII）であるわけです。例えば、``\ifnum`A=65``というTeX言語の条件文は、たとえEBCDIC系のシステムであっても真と判定されます。なお、XeTeXやLuaTeXでは代替となる機能があるためTCXの機能は削除されています。

結局、この “解読後” のソースコードをplain TeXで実行することを考えればいいわけですが、いきなり先頭にNULバイトがあるので、plainでのNULバイトの扱いを調べてみます。plain.texには以下のような記述（コメント）があります。

```text:plain.tex（26行目）
% When INITEX (the TeX initializer) starts up,
% it has defined the following \catcode values:
% \catcode`\^^@=9 % ascii null is ignored
```

つまり、NULバイト（TeXエスケープ表記で`^^@`）のカテゴリコードは「TeXの初期値で9であり、plainではそれを変えていない」ということです。TeXの字句解析においてカテゴリコード9（無視）は以下のような性質をもちます。

1. その文字自体は無視される（トークンにならない）。例えば、`foo^^@bar`は`foobar`と書いたのと等価になる。
2. ただし制御語[^control-word]の名前のスキャン中にその文字が現れた場合はスキャンはそこで終わる。つまり、`\foo^^@X`は「制御綴`\fooX`」ではなく「制御綴`\foo`と文字トークン`X`の列」と解釈される。これは`\foo X`と空白文字を置いたのと等価になる。

[^control-word]: **制御語**（control word）とは「名前が英字からなるcontrol sequence」のことを指します。また、この記事では “control sequence” に対する訳語として「**制御綴***」を用います。

要するに、「NULバイトは制御語の直後では空白文字と同じで、それ以外の箇所では何もないのと同じ」ということになります。NULバイトは邪魔なので、この性質を利用してNULバイトを除去することにします。結果のソースコードは以下の通りです。

```tex
\csname let\endcsname\ame\endcsname\csname let\ame\csn\csname\csn let\csn
expandafter\ame\ame\csn csncsn\csn expandafter\ame\ame
\csn expandafter\ame\csn let\csncsn\ame\csn csnameame\csncsn\ame
\csn let\ame\csncsn\csnameame\csncsn\csncsname\csn advance\ame\csncsn
\csnameame\csncsn\csncsncsn\csn lccode\ame\csncsn\csnameame\csncsn\csnamecsn\csn
catcode\ame\csn newcount\ame\ameame\ameame `d\csncsname\ameame
-`c\csn chardef\ame\amecsn\ameame\csnamecsn `v\amecsn\csncsname\ameame
\ameame\csnamecsn `w\ameame\csn def\ame\amecsncsn v\ameame\csncsname\ameame\amecsn
\csncsncsn w\ameame `x\csncsname\ameame -`a\csncsname\ameame\amecsn\csncsname\ameame
\ameame\csncsncsn `h\amecsncsn `k\amecsncsn `l\amecsncsn `n\amecsncsn `o\amecsncsn
`-\amecsncsn `t\amecsncsn ``\amecsncsn `p\amecsncsn `r\ameame\ameame `a
\csncsname\ameame\amecsn\csncsncsn `s\ameame v\ameame `a\csncsname\ameame `a
\csncsname\ameame -`f\csnamecsn `u\csnamecsn\ameame\csnamecsn\ameame\csnamecsn ``
ucsncsncsn `\ucsncsncsn `tucsn lowercaseuame vucsn xdefuame
uamecsncsn v-c\n\k`o\n\f\o\-\h-c`\nknk-c\n\
k`o\n\f\o\-\h-c``nknklh-c\e\f`h\k\`\-
\e`-\d\l\-`l`n-c\\\f\e`o-c\\oknd\r`h`
p\dld`lld`-nln\-c\\\f\e`o-c\\olnd\r`h
`p\dld`lld`-n-nk-c\\\f\e`o-c\\onnd\r`
h`p\dld`lld`-n\\n-c\\\f\e`o-c\\osnd\r
`h`p\dld`lld`-nnnh-c\\os-c\l\k`n\-\c\
r\e\-`n\s\r`hndnknl`h`o`h\c`-`nnl`h`o
-c\p`n\r`a\-ndnonh\-\d-c\e\-``\r\\-c\
r\\on-c\e\-``\n\f`-\e`o-c\r-n-c\e\-``
\n\f`-\e`o-c\r-l-c\e\f\r\e\o\-\e`o-co
n`o`l`-\--c\c\f\f`h-c\r\\on-c\r-lnd-c
`h\o\\`-\e\r\\\f`l\d\o\-`\\r\k`o\-nl-
c`l\-\c\k`p-c\r\\\f\o\o-c\r-l-c\n\p\k
`lnlnhnk-c\n\p\k`lnlnhnklh-c\-\c`n\--
c\n\p\k`lnknpn--c\n\p\k`lnkn`nrlh-c\\
\r-c\p`n\s\r`hnh`h`olh-c\d`-\c`o\r`h\
c`r-c\r-nnl-c\k\o`\\k\e\n\--c\r-n-c\r
-l-c\r\\\e`-\d-c\r-nncllnknhnhnhlh-c\
-\c`n\--c\k\o`\\k\e\n\--c\r-nldllnknh
nhnhlh-c\\\r-c\r\\\e`-\d-c\r-nndllnpn
pnplh-con\\\k\c`n\--c\\\r-c`l\-`h\-\k
`o-c\n\p\k`lnlnnnn-c\n\p\k`lnlnkno-c\
p`n\s\r`hnh`h`olh-c\\on-c\n\p\k`lnlnn
nl-c\\ol-c\n\p\k`lnknhnk-c\p`n\s\r`hn
h`h`olh-c\\ok-c\n\p\k`lnrn`lh-c\l`r\-
www\csncsn\csnameame\csncsn\csncsncsn\csn pdfunescapehex\ame\csn
scantokens\ame\csncsn v\csncsncsn\csncsn v\amecsncsn ww\ame on-cendcsn
```

以下の解説ではこの “整形後” のコードを読んでいくことにします。

:::note warn
TeX言語では文字のカテゴリコードを変更できるため、実際にはコードの実行を追ってみないと「コードのどの部分が制御語なのか」は正確にはわかりません。（実際、このコードでは途中で`\`のカテゴリコードが変更されています。）しかしここでは説明の便宜のため、NULバイトを “正しく除去” したコードを前もって提示することにしました。実際に “解読作業” を行う際には、影響のあるカテゴリコード変更が判明した時点でそれ以降のNULバイト除去をやり直す必要があります。
:::

## 頑張ってソースコードを解読してみる話

### 1～4行目

```tex
\csname let\endcsname\ame\endcsname\csname let\ame\csn\csname\csn let\csn
expandafter\ame\ame\csn csncsn\csn expandafter\ame\ame
\csn expandafter\ame\csn let\csncsn\ame\csn csnameame\csncsn\ame
\csn let\ame\csncsn\csnameame\csncsn\csncsname\csn advance\ame\csncsn
```

先頭にあるのは`\csname let\endcsname`でこれを展開すると`\let`となるので、結局このコードの “最初の文” はこれになります。

```tex
\csname let\endcsname\ame\endcsname
↓(展開)
\let\ame\endcsname
```

つまり`\ame`が`\endcsname`と等価になりました。

その次には`\csname let\ame`とあり、`\ame`=`\endcsname`なのでこうなります[^let-1]。

[^let-1]: 後の`\csname`はlet文の右辺なので展開されません。うっかり「対応する`\endcsname`の探索」をしないように注意しましょう。

```tex
\csname let\ame\csn\csname
↓(展開)
\let\csn\csname
```

`\csn`が`\csname`と等価になりました。ちょうど “csname” の前半 “csn” と後半 “ame” になっていて意外とわかりやすいですね（えっ:astonished:）。この後にも “csn” と “ame” を並べた名前の制御綴がやたらと登場しますが、これらは（`\csname`以外は）全てTeXにとっては意味のない（未定義の）制御綴です[^csname-1]。

[^csname-1]: 「なぜそんな変な名前の制御綴を多用しているのか」については、賢明な読者に対する演習問題としておきます:upside_down_face:

さて、次の “文” は少し長くて3行目の最初の`\ame`の位置まで続きます[^until-1]。

[^until-1]: もちろん「文がどこまで続くか」も本来は解読して初めて判明することです。

```tex
\csn let\csn expandafter\ame\ame\csn csncsn\csn expandafter\ame\ame\csn expandafter\ame
```

`\csname`（=`\csn`）と`\endcsname`（=`\ame`）の組がネストしていて、しかもこれを展開すると`\expandafter`が多数出現してかなり複雑なことになりそうです。説明のため、`\csname`と`\endcsname`を`\＜`と`\＞`と略記して、かつ各々の出現に番号を付けてみます。

```tex:①
\＜₁let\＜₂expandafter\＞₃\＞₄\＜₅csncsn\＜₆expandafter\＞₇\＞₈\＜₉expandafter\＞₁₀
```

ここで「①を（先頭で）1回展開した結果のトークン列は何か」を考えてみましょう。`\csname`の展開結果は「『対応する`\endcsname`との間にあるトークン列の完全展開』を名前とする制御綴」であることに注意すると以下のようになります。

* 先頭の`\＜₁`を展開しようとする。
* `\＜₁`に対応する`\endcsname`は`\＞₄`なので、制御綴名を得るために`let\＜₂expandafter\＞₃`を完全展開する。
   * `l` `e` `t`は展開不能な文字トークンだからそのまま残る。
   * `\＜₂expandafter\＞₃`を展開して`\expandafter`を得る。`\expandafter`プリミティブは展開可能なので、さらに展開しようとする。
   * `\expandafter`に続く2つのトークンは`\＞₄\＜₅`であり、`\expandafter\＞₄\＜₅`の展開結果は（[例の記事][expandafter]に従って考えると）「`\＞₄`の後に『`\＜₅`を1回展開したトークン列』を続けたもの」になる。
      * （ここに`\＜₅`を1回展開する処理が入る。）
   * `\＞₄`に達したので終了。制御綴名は “let” なので展開結果は`\let`である。

「`\＜₅`を1回展開する」の部分の考慮を一旦措いてまとめると、①の1回展開は以下のようになります。

```tex:①の1回展開
\let【\＜₅以降のトークン列の1回展開】
```

次にその「`\＜₅`の1回展開」を考えますが、ここで「`\＜₁`から`\＞₄`までのトークン列」と「`\＜₅`から`\＞₈`までのトークン列」が全く同じ構造を持っていることに注意してください。つまり「`\＜₅`の1回展開」の際には先ほどの「`\＜₁`の1回展開」での過程がもう一度繰り返されるのです。

* 先頭の`\＜₅`を展開しようとする。
* `\＜₅`に対応する`\endcsname`は`\＞₈`なので、`let\＜₆expandafter\＞₇`を完全展開する。
   * `c` `s` `n` `c` `s` `n`は展開不能だからそのまま残る。
   * `\＜₆expandafter\＞₇`を展開して`\expandafter`を得る。さらに展開しようとする。
   * `\expandafter\＞₈\＜₉`の展開結果は「`\＞₈`の後に`\＜₉`を1回展開したトークン列」になる。
      * （ここに`\＜₉`を1回展開する処理が入る。）
   * `\＞₈`に達したので終了。制御綴名は “csncsn” なので展開結果は`\csncsn`である。

従って結果は以下のようになります。なお、`\＜₅`の1回展開は`\＜₁`の1回展開に付随して行われるため、以下に示す結果は「①の**2回**展開」ではなく「①の**1回**展開」であることにも注意しましょう。

```tex:①の1回展開
\let\csncsn【\＜₉以降のトークン列の1回展開】
```

「`\＜₉`以降のトークン列」というのは`\＜₉expandafter\＞₁₀`であり、その1回展開は`\expandafter`です[^let-2]。ここでようやく「①の1回展開」の正しい姿が判明しました。

[^let-2]: `\＜₉expandafter\＞₁₀`の1回展開で生じた`\expandafter`はそのままlet文の右辺になるため展開されません。このことを見抜くためには複雑な展開過程を精密に追う力が必要です。

```tex:①の1回展開
\let\csncsn\expandafter
```

あの長い文の目的は「制御綴`\csncsn`を`\expandafter`と等価にする」ことでした。

次の文（4行目の最初の`\ame`の位置まで）を見ましょう。

```tex:②
\csn let\csncsn\ame\csn csnameame\csncsn\ame\csn let\ame
‖(等価)
\＜let\expandafter\＞\＜csnameame\expandafter\＞\＜let\＞
```

先ほどの①の場合と同様に展開を追うと（ほとんど同じなので詳細は省略します）、②の1回展開は以下のようになることがわかります。

```tex:②の1回展開
\let\csnameame\let
```

ここまでの動作をまとめると以下のようになります。`\expandafter`と`\let`が“単一の制御綴として使える”ようになりました。

* `\csn` = `\csname`
* `\ame` = `\endcsname`
* `\csncsn` = `\expandafter`
* `\csnameame` = `\let`

### 4～6行目

:::note info
TeXのプリミティブと等価である独自の制御綴は元のプリミティブに置き換えて示します。ただし`\csname`と`\endcsname`は見やすさのため`\＜`と`\＞`で示します。また、前節までで既に実行が終わっている部分は省略して`……`で示します。
:::

```tex
……\expandafter\let\expandafter\csncsname\＜advance\＞\expandafter
\let\expandafter\csncsncsn\＜lccode\＞\expandafter\let\expandafter\csnamecsn\＜
catcode\＞\＜ newcount\＞\ameame\ameame `d\csncsname\ameame
```

前節ではいきなり超絶複雑な展開過程が登場しましたが、そこを乗り越えてしまえば、後に出てくる展開過程はずっと簡単です。

```tex:③
\expandafter\let\expandafter\csncsname\＜₁advance\＞₂
```

これは典型的な**​「\\expandafter の鎖」​**（詳細は[例の記事][expandafter]を参照）のパターンになっていて、先頭の`\expandafter`を展開すると`\＜₁`が1回展開されます。つまりこうなります。

```tex:③の1回展開
\let\csncsname\advance
```

次の2つの文も全く同じ構造をもっています。

```tex
\expandafter\let\expandafter\csncsncsn\＜lccode\＞
↓(展開)
\let\csncsncsn\lccode
```

```tex
\expandafter\let\expandafter\csnamecsn\＜catcode\＞
↓(展開)
\let\csnamecsn\catcode
```

本節の動作のまとめです。“制御綴として使える” プリミティブがさらに増えました。

* `\csncsname` = `\advance`
* `\csncsncsn` = `\lccode`
* `\csnamecsn` = `\catcode`

### 6～8行目

```tex
……\＜newcount\＞\ameame\ameame`d\advance\ameame
-`c\＜chardef\＞\amecsn\ameame\catcode`v\amecsn\advance\ameame
\ameame\catcode`w\ameame\＜def\＞\amecsncsn v\ameame\advance\ameame\amecsn
```

かなりフツーのコードになってきましたね:slightly_smiling_face: ここからは複数の文をまとめて見ていくことにします。

```tex
\＜newcount\＞\ameame
\ameame`d
\advance\ameame-`c
‖(上の3文は下と等価)
\newcount\ameame
\ameame=100
\advance \ameame by -99 % (値は1)
```

整数レジスタトークン`\ameame`を定義して、それに100を代入してすぐに99を引いています。`\ameame`の現在の値は1です。

```tex
\＜chardef\＞\amecsn\ameame
‖(等価)
\chardef\amecsn=\ameame % (値は1)
```

値が1であるchardefトークン`\amecsn`を定義しています。TeX言語プログラミングでよく使われる手法ですが、このchardefは「**整数定数**として使う」ことを意図されています。つまり`\amecsn`は「定数の1」だというわけです。

```tex
\catcode`v\amecsn
\advance\ameame\ameame
\catcode`w\ameame
‖(等価)
\catcode`v=1
\advance \ameame by \ameame % (値は2)
\catcode`w=\ameame % (値は2)
```

文字`v`のカテゴリコードを1（`{`相当）、`w`のカテゴリコードを2（`}`相当）に変更しました。

本節の動作のまとめです。

* 整数レジスタ`\ameame`を定義。（現在の値は2）
* 定数：`amecsn` = 1
* カテゴリコード変更： `v`→1；`w`→2

### 8～12行目

```tex
……\＜ def\＞\amecsncsn v\ameame\advance\ameame\amecsn
\lccode w\ameame`x\advance\ameame-`a\advance\ameame\amecsn\advance\ameame
\ameame\lccode`h\amecsncsn`k\amecsncsn`l\amecsncsn`n\amecsncsn`o\amecsncsn
`-\amecsncsn`t\amecsncsn``\amecsncsn`p\amecsncsn`r\ameame\ameame`a
\advance\ameame\amecsn\lccode`s\ameame v\ameame`a\advance\ameame`a
```

どんどん読み進めていきましょう。

```tex
\＜ def\＞\amecsncsn v\ameame\advance\ameame\amecsn\lccode w
‖(等価)
\def\amecsncsn{\ameame\advance\ameame\amecsn\lccode}
```

`v`と`w`が`{`と`}`に相当するので、要するに単純なマクロ定義です。この中のコードはそのままでは意味を成していないように見えますが、それで問題がないことが後でわかります。

```tex
\ameame`x
\advance\ameame-`a
\advance\ameame\amecsn
\advance\ameame\ameame
‖(等価)
\ameame=120
\advance \ameame by -97 % (値は23)
\advance \ameame by 1 % (値は24)
\advance \ameame by \ameame % (値は48)
```

要するに`\ameame`に48を代入しています。次に続くコードは少し変な形をしています。

```tex
\lccode`h\amecsncsn
```

``\lccode`h``で文を開始しているためこれは「パラメタ``\lccode`h``[^lccodeh-1]への代入文」であり、その途中[^assign-1]にマクロである`\amecsncsn`が現れているため、この`\amecsncsn`は完全展開されます。つまりこうなるわけです。

[^lccodeh-1]: <code>&#96;h</code>は`h`の文字コード（＝104）を表すので、``\lccode`h``は「文字`h`のlccode」のパラメタを意味します。
[^assign-1]: 厳密にいうと、<code>&#96;h</code>という“数字表記”の整数の直後に空白を入れずに`\amecsncsn`があるため、「整数を終結させるために`\amecsncsn`が完全展開される」ことになります。[「\\romannumeral トリック」][romannumeral-trick]を知っている人なら聞いたことがある話でしょう。

```tex
\lccode`h\ameame\advance\ameame\amecsn\lccode
```

すなわち「``\lccode`h\ameame``」「`\advance\ameame\amecsn`」の2つの文が実行されて、そして`\lccode`でまた次の文が開始されています。その後に続いているのは……。

```tex
(\lccode は前の \amecsncsn の展開の残り)
\lccode`k\amecsncsn
```

また同じパターンになっています。つまり、10行目の`\lccode`から次の行の`\ameame`は同じパターンの繰り返しになっていて、`\amecsncsn`を（結局展開されるわけなので）その内容に置き換えると“文単位”に分けて理解できます。

```tex
\lccode`h\amecsncsn
       `k\amecsncsn
       `l\amecsncsn
       `n\amecsncsn
       `o\amecsncsn
       `-\amecsncsn
       `t\amecsncsn
       ``\amecsncsn
       `p\amecsncsn
       `r\ameame
‖(等価)
\lccode`h=\ameame                      % (値は48=`0)
\advance\ameame by 1 \lccode`k=\ameame % (値は49=`1)
\advance\ameame by 1 \lccode`l=\ameame % (値は50=`2)
\advance\ameame by 1 \lccode`n=\ameame % (値は51=`3)
\advance\ameame by 1 \lccode`o=\ameame % (値は52=`4)
\advance\ameame by 1 \lccode`-=\ameame % (値は53=`5)
\advance\ameame by 1 \lccode`t=\ameame % (値は54=`6)
\advance\ameame by 1 \lccode``=\ameame % (値は55=`7)
\advance\ameame by 1 \lccode`p=\ameame % (値は56=`8)
\advance\ameame by 1 \lccode`r=\ameame % (値は57=`9)
```

結局、いくつかの文字（`h`,`k`,`l`,`n`,`o`,`-`,`t`,<code>&#96;</code>,`p`,`r`）のlccodeを数字（`0`～`9`）に設定していることがわかりました。例えばこの直後で``\lowercase{hklno-t`pr}``を実行したとすると「0123456789」と出力されるはずです。

```tex
\ameame`a
\advance\ameame\amecsn
\lccode`s\ameame
‖(等価)
\ameame=97
\advance \ameame by 1
\lccode`s=\ameame % (値は98=`b)
```

今度は`s`のlccodeを`b`に設定しました。

本節の動作のまとめです。どうやら`\lowercase`で文字列変換をしたいようですね。

* `h`,`k`,`l`,`n`,`o`,`-`,`t`,<code>&#96;</code>,`p`,`r`,`s`のlccodeをそれぞれ\
  `0`,`1`,`2`,`3`,`4`,`5`,`6`,`7`,`8`,`9`,`b`に設定。
* `\ameame`の現在の値は98

※`\amecsncsn`の定義はこの先使わないので忘れてもよいでしょう。

### 12～15行目

```tex
……v\ameame`a\advance\ameame`a
\advance\ameame-`f\catcode`u\catcode\ameame\catcode\ameame\catcode``
ulccode`\ulccode`tucsn lowercaseuame vucsn xdefuame
uamecsncsn v-c\n\k`o\n\f\o\-\h-c`\nknk-c\n\
```

15行目の途中から40行目まで、TeXとして明らかに意味をなさない変な文字列が続いています。その直前の`v`までを読みます。

```tex:④
v
```

文頭がいきなり`v`（つまり`{`）なので、これは局所化グループの開始です。

```Tex
\ameame`a
\advance\ameame`a
\advance\ameame-`f
\catcode`u\catcode\ameame
\catcode\ameame\catcode``
‖(等価)
\ameame=97
\advance \ameame by 97     % (値は194)
\advance \ameame by -102   % (値は92=`\)
\catcode`u=\catcode\ameame % (値は0)
\catcode\ameame\catcode``  % (値は12)
```

ここで`\`のカテゴリコードを0から12（通常文字）に変えて、代わりに`u`のカテゴリコードを`\`相当の0に変えています。これ以降は`\`でなくて`u`で制御綴を始めることになります。

```tex
ulccode`\ulccode`t
‖(等価)
\lccode`\\=\lccode`t % (値は54=`6)
```

文字`\`[^backtick-1]のlccodeを`t`のlccodeと同じ、つまり数字の`6`に設定しました。

[^backtick-1]: `\`のカテゴリコードが12である（つまり`\`だけで文字トークンとなる）ため、<code>&#96;&#92;&#92;</code>と書かなくても<code>&#96;&#92;</code>だけで整数の表記として通用します。

```tex:⑤
ucsn lowercaseuame v
‖(等価)
\＜lowercase\＞{
↓(展開)
\lowercase{
```

`\lowercase`の引数の中に入りました。末尾の`v`（=`{`）に対応する`w`（=`}`）が現れるまで、文字トークンには小文字変換が適用されることになります。

```tex:⑥
ucsn xdefuame uamecsncsn v
‖(等価)
\＜xdef\＞\amecsncsn {
(小文字変換しても変化なし)
↓(展開)
\xdef\amecsncsn{
```

小文字変換は文字トークンにのみ適用されるので、`uame`（=`\ame`）等の制御綴は変化しません。`xdef`は文字トークン列なので適用されますが、これらの文字は「先にlccodeを設定した文字」には含まれておらず、またplainのデフォルトでも（元々小文字であるため）lccodeが設定されていません。なので結局`xdef`のまま変化しません[^lccode-1]。結局`\xdef`が実行されることになります。

[^lccode-1]: ちなみに、`v`も文字トークンなので小文字変換が適用されます（結局変化しませんが）。ただし、もし仮にこれで文字が変わったとしても、カテゴリコードが変化していないので、結局`{`の役割を果たすことは変わりません。

本節の動作のまとめです。

* `\lowercase{\xdef\amecsncsn{` の中に入った。

### 15～40行目

```text
……-c\n\k`o\n\f\o\-\h-c`\nknk-c\n\
k`o\n\f\o\-\h-c``nknklh-c\e\f`h\k\`\-
……(中略)……
nl-c\\ol-c\n\p\k`lnknhnk-c\p`n\s\r`hn
h`h`olh-c\\ok-c\n\p\k`lnrn`lh-c\l`r\-
```

ここは以下の文字群のみからなる（TeXとして意味を成さない）文字列となっています。

```text
h k l n o - \ ` p r a s c d e f
```

前節で`\lowercase`の引数の中に入っているため小文字変換が適用され、lccodeの設定によりそれぞれ以下の文字に変換されます（`a`,`c`,`d`,`e`,`f`は変化なし）。

```text
0 1 2 3 4 5 6 7 8 9 a b c d e f
```

つまり、この部分の文字列は「16進数字の列」に変換されることになります。具体的には次の文字列です。なお、改行には何も細工をしてないので、通常のTeXの字句解析規則に従って、改行は空白文字[^space-1]として読み込まれます。

[^space-1]: 正確に言うと「カテゴリコードが10で文字コードが32の文字トークン」です。

```text
5c636174636f6465605c7631315c636
174636f6465605c773131205c6e6f70616765
6e756d626572735c666f6e745c66413d69707
86d2d722d7532365c666f6e745c66423d6970
786d2d722d7535315c666f6e745c66433d697
0786d2d722d7536635c666f6e745c664b3d69
70786d2d722d7533305c664b5c626173656c6
96e65736b69703d31327074706c7573327074
5c6873697a653d3430656d5c6e657769665c6
966435c6e6577636f756e745c69535c6e6577
636f756e745c69525c6e6f696e64656e745c4
3747275655c6c6f6f705c6966435c69523d5c
706466756e69666f726d64657669617465325
c72656c61785c69666f64645c69525c636861
723230315c63686172323031205c656c73655
c636861723138355c63686172313739205c66
695c68736b6970307074205c6d756c7469706
c795c6953325c616476616e63655c69535c69
525c69666e756d5c69533c2231303030205c6
56c73655c616476616e63655c69532d223130
3030205c66695c69666e756d5c69533d22383
838205c4366616c73655c66695c7265706561
745c636861723233335c636861723231345c6
8736b6970307074205c66435c636861723233
325c66425c636861723130315c68736b69703
07074205c66415c636861723937205c627965
```

### 41～42行目

```tex
www\expandafter\let\expandafter\csncsncsn\＜pdfunescapehex\＞\＜
scantokens\＞\expandafter v\csncsncsn\expandafter v\amecsncsn ww\＞ on-cendcsn
```

いよいよ最後の2行になりました。ところでこれまでの動作を振り返ってみると、設定や定義はイロイロとやっているものの、まだ**何も出力していません。​**そういえば、このプログラムはそもそも**​「ドドスコ問題」を解くためのもの**だったはずですが、当問題に関する定義っぽいものも何も出てきていません。一体どうやって問題を解いているのでしょう……:thinking_face:

```tex
ww
```

最初の`w`（=`}`）で⑥に現れた`v`（=`{`）を、次の`w`で⑤に現れた`v`を閉じています。改めて⑤から読み直すと、以下のようなコードを実行したことになります。

```tex
\lowercase{\xdef\amecsncsn{-c\n\k`o\n……lh-c\l`r\-␣}}
(ただし内側の{}の中の \ はカテゴリコード12)
```

`\lowercase`の引数中で実行されたコードは以下の通りで、先ほど示した16進数字の文字列がマクロ`\amecsncsn`にグローバルに定義されました[^xdef-1]。

[^xdef-1]: `\gdef`ではなく`\xdef`なので引数は完全展開されますが、引数には展開不能な文字トークンしかないので実際には何も変化しません。

```tex
\xdef\amecsncsn{5c63617463……205c627965␣}
```

その次にまた`w`があります。

```tex
w
```

これは④の`v`を閉じています。これで`\`と`u`のカテゴリコード（および`\`のlccode）が元に戻ります。つまりこれ以降はフツーに`\`で制御綴が始まる世界に戻ります。

```tex
\expandafter\let\expandafter\csncsncsn\＜pdfunescapehex\＞
↓(展開)
\let\csncsncsn\pdfunescapehex
```

`\csncsncsn`を`\pdfunescapehex`というpdfTeX拡張プリミティブと等価にしました。（これまで`\csncsncsn`は`\lccode`と等価でした。）

```tex:⑦
\＜scantokens\＞\expandafter v\csncsncsn\expandafter v\amecsncsn ww
‖(等価)
\＜scantokens\＞\expandafter{\pdfunescapehex\expandafter{\amecsncsn}}
↓(展開)
\scantokens\expandafter{\pdfunescapehex\expandafter{\amecsncsn}}
```

実質的にこれが「最後の文」となります。`\scantokens`（e-TeX拡張のプリミティブ）と`\pdfunescapehex`という見慣れないプリミティブが使われています。`\scantokens`の直後にある`\expandafter`はその2つ先にある`\pdfunescapehex`を展開するので、⑦は以下の形のコードに帰着されます[^general-text-1]。

[^general-text-1]: `\scantokens`は「`{}`で囲んだ引数をとる」という構文規則なのですが、こういう構文をもつプリミティブ（他には例えば`\everypar`が該当します）は「実行されても引数開始の`{`を見るまでは展開抑止にならない」という特徴をもちます。つまり、直後にある`\expandafter`は`\scantokens`トークンによる影響を受けずに展開することができます。

```tex:⑦と等価なコード
\scantokens{【“\pdfunescapehex\expandafter{\amecsncsn}”の1回展開】}
```

`\pdfunescapehex\expandafter{\amecsncsn}`を展開しようとすると、まずその中の`\expandafter`を展開する[^general-text-2]ことになり、結局`\expandafter`の2つ先にある`\amecsncsn`が展開されます。`\amecsncsn`の内容は前掲の16進数字列でした。

```tex
【“\pdfunescapehex\expandafter{\amecsncsn}”の1回展開】
  ‖
【“\pdfunescapehex{【“\amecsncsn”の1回展開】}”の1回展開】
  ‖
【“\pdfunescapehex{5c63617463……205c627965␣}”の1回展開】
```

[^general-text-2]: `\pdfunescapehex`も「`{}`で囲んだ引数をとる」という構文規則をもつプリミティブであるため`\scantokens`と同じ特徴をもちます。ただし`\pdfunescapehex`は展開可能プリミティブなので、「`\pdfunescapehex`を展開する際にはその直後にある`\expandafter`が展開される」という動作になります。

引数が確定したので、いよいよ問題のpdfteX拡張プリミティブの`\pdfunescapehex`が実際に展開されます。このプリミティブの仕様は以下の通りです。

* `\pdfunescapehex{<16進数字列>}`： 引数の16進数字列を「バイト列の各バイトの値を16進2桁で表記したものを並べたもの」と解釈して、元のバイト列（の \\the-文字列[^the-string-1]）に変換する。16進数字（`0`～`9`、`A`～`F`、`a`～`f`）以外の文字は無視される。

[^the-string-1]: **​「\\the-文字列」​**とは「（本来カテゴリーコードに意味がない）純粋な文字列を、空白（文字コード32）はカテゴリコード10、それ以外の文字はカテゴリコード12をもつ文字トークンの列として表したもの」です。TeXのプリミティブが“純粋な文字列を戻り値としたい”場合に実際には「その戻り値の \\the-文字列に展開される展開可能プリミティブ」として実装されることがよくあります。その代表的な例が`\the`であるためこの名で呼ばれています。

つまり、引数の16進文字列[^space-2]は次のような文字列に変換されます。なんと、別のTeX言語コードが出てきました:astonished:

[^space-2]: なお、引数の文字列には空白文字も含まれますが、空白文字は16進数字に該当しないので`\pdfunescapehex`の仕様に従い無視されます。

```text
\catcode`\v11\catcode`\w11 \nopagenumbers\font\fA=ipxm-r-u26\font\fB=ipxm-r-u51\font\fC=ipxm-r-u6c\font\fK=ipxm-r-u30\fK\baselineskip=12ptplus2pt\hsize=40em\newif\ifC\newcount\iS\newcount\iR\noindent\Ctrue\loop\ifC\iR=\pdfuniformdeviate2\relax\ifodd\iR\char201\char201 \else\char185\char179 \fi\hskip0pt \multiply\iS2\advance\iS\iR\ifnum\iS<"1000 \else\advance\iS-"1000 \fi\ifnum\iS="888 \Cfalse\fi\repeat\char233\char214\hskip0pt \fC\char232\fB\char101\hskip0pt \fA\char97 \bye
```

とりあえず先に進みましょう。`\pdfunescapehex`の展開結果が上記のTeX言語コードらしき文字列なので、以下のコードを実行することになります。

```tex:⑦と等価なコード
\scantokens{\catcode`\v11\catcode`\w11……\bye}
```

`\scantokens`はe-TeX拡張のプリミティブで、その仕様は以下の通りです。

* `\scantokens{<トークン列>}`： 引数のトークン列を脱トークン化して、改めて字句解析対象となる文字列として読み込む。トークン列を一旦（仮想的な）ファイルに書き出してそれをすぐに読み込んだのと同じになる。

つまり、`\scantokens`は「ソースコードの字句解析をやり直す」という作用をもっているわけです。例えば、`\scantokens`の引数の文字列は`\catcode`から始まっていますが、これは（`\pdfunescapehex`の仕様のため）カテゴリコード12の文字トークンの列であって制御綴の`\catcode`ではありません。ところがこれを`\scantokens`で処理すると「字句解析が（現在のカテゴリコード設定に従って）やり直される」ため、制御綴の`\catcode`に変化します。結局、`\scantokens`の実行により、前掲の「TeX言語コードらしき文字列」が実際にTeX言語のコードとして実行されることになります。

さてそのコードですが、最後に`\bye`と書かれています。`\bye`はplain TeXにおいて文書の終了を表す命令（LaTeXの`\end{document}`に相当する）です。つまり前掲のコードを実行すると、その最後で`\bye`が実行されてpdfTeXの実行はそこで終了します。

というわけで、チョット読みにくい`ddsk.tex`の解析もこれで**オシマイ**です。その内容は「中に埋め込まれた別のTeX言語のプログラムをデコードして実行する」というものでした。

実際には、最後の`\scantokens`の文の後にもまだコードが少し残っています。

```text
\ame on-cendcsn
```

しかし`\bye`の実行によりpdfTeXの実行は既に終了しているため、この部分は**実行されません。​**このダミーの部分は元の`ddsk.tex`の末尾の部分を`ラブ注入♡`にするために敢えて追加されているものでした。

以上で一件落着。**おつかれさまでした**:smiley:

## まだ一件落着していない気がする話

ここで記事を終えたいところですが、そうすると、

「アレレ、“中に埋め込まれた”コードの方の解説は？:astonished:」

と思った人もいるでしょう。

```tex:中に埋め込まれたTeXコード
\catcode`\v11\catcode`\w11 \nopagenumbers\font\fA=ipxm-r-u26\font\fB=ipxm-r-u51\font\fC=ipxm-r-u6c\font\fK=ipxm-r-u30\fK\baselineskip=12ptplus2pt\hsize=40em\newif\ifC\newcount\iS\newcount\iR\noindent\Ctrue\loop\ifC\iR=\pdfuniformdeviate2\relax\ifodd\iR\char201\char201 \else\char185\char179 \fi\hskip0pt \multiply\iS2\advance\iS\iR\ifnum\iS<"1000 \else\advance\iS-"1000 \fi\ifnum\iS="888 \Cfalse\fi\repeat\char233\char214\hskip0pt \fC\char232\fB\char101\hskip0pt \fA\char97 \bye
```

でもこちらは別に読みにくいものではありません。少し読みやすく整形してコメントを付けたコードを載せておきます。

```tex
% "本体"部でのcatcode変更を取り消す
\catcode`\v=11 \catcode`\w=11

% レイアウト設定
\nopagenumbers % ノンブル無し
\font\fA=ipxm-r-u26 % 和文サブフォント宣言(U+26xx)
\font\fB=ipxm-r-u51 %   〃  (U+51xx)
\font\fC=ipxm-r-u6c %   〃  (U+6Cxx)
\font\fK=ipxm-r-u30 %   〃  (U+30xx; 仮名用)
\fK % 仮名用フォントを選択
\baselineskip=12pt plus 2pt % 行送り
\hsize=40em % 行長(40文字)

\newif\ifC % ループ用のスイッチ
\newcount\iS % 過去の状態
\newcount\iR

\noindent
\Ctrue \loop \ifC % 無限ループ
  \iR=\pdfuniformdeviate2\relax % 0～1の乱数を取得
  \ifodd\iR % 1なら
    \char201\char201 % "ドド"出力
  \else % 0なら
    \char185\char179 % "スコ"出力
  \fi
  \hskip 0pt % 行分割可能にする
  %↓ iS := (iS×2 + iR) mod 1000h
  \multiply \iS by 2
  \advance \iS by \iR
  \ifnum\iS<"1000
  \else
    \advance \iS by -"1000
  \fi
  \ifnum\iS="888 % "ドドスコスコスコ"×3
    \Cfalse % ループ継続停止
  \fi
\repeat
\char233\char214 \hskip0pt        % "ラブ"出力
\fC\char232 \fB\char101 \hskip0pt % "注入"出力
\fA\char97                        % "♡"出力

% おしまい
\bye
```

この通り、ホントにホントに**フツー**のTeX言語コードです。ここまで`ddsk.tex`の読みにくいコードを解読してきた皆さんなら容易に理解できますね:upside_down_face:

いくつかの点を補足しておきます。

*  本体で行った`v`と`w`のカテゴリコードの変更はまだ有効であり、これによる問題を防ぐために冒頭でカテゴリコードを元に戻している。なお、lccodeの変更もまだ有効であるが、これによる影響は考えられないのでこちらは対処していない。
*  pdfTeXで日本語文字を出力するために、CJKパッケージ等で使われる “Unicodeサブフォント” を利用している。具体的には、IPAフォントの字形を持つ[ipaex-type1パッケージ][ipaex-type1]のフォント群を利用している。
   *  例えば、“ド”（U+30C9）はフォント`ipxm-r-u30`の文字コードC9h（＝201）にあるため、`\font\fK=ipxm-r-u30`でフォント定義した後に`\fK\char201`を実行することで “ド” を出力できる。
   *  pdfTeXでは文字間が自動的に行分割可能になるわけではないので、行分割可能にしたい箇所で幅ゼロのグルーを入れている。
* `\pdfuniformdeviate`は擬似乱数を発生するpdfTeX拡張[^uniformdeviate-1]プリミティブ（展開可能）で、`\pdfuniformdeviate<整数n>`は0以上n未満の一様ランダムな整数値に展開される。
*  「ドドスコ問題」を解くロジックの実装は、[**古のズンドコキヨシ**][zundoko]の時代から知られた「整数値をビット配列と見なす方法」を使っている。

[^uniformdeviate-1]: ただし現在では主要エンジン全てがこのプリミティブをサポートしています。（XeTeX・LuaTeXでは`\uniformdeviate`という名前で定義されています。）

## まとめ

「チョット読みにくいコード」は作るのも読むのもトッテモ楽しいものです:smiley: 「せっかくTeX言語を学習したのだから何か楽しいことがしたい:slightly_smiling_face:」と考えている方は、ぜひとも、TeX言語で**​「チョット読みにくいコード」を作ってみましょう！**:information_desk_person:

## 蛇足してみる

[2017年のアドベントカレンダー][texadvent2017]のフィナーレに出した[チョット読みにくいコード][finale2017]は本記事で紹介した原理（TCX）が使用されています。ぜひ解読してみてください！:smiley:

[texadvent]: https://adventar.org/calendars/7868
[texadvent2017]: https://adventar.org/calendars/2229
[finale2012]: https://zrbabbler.hatenablog.com/entry/20121225/1356433254
[finale2014]: https://zrbabbler.hatenablog.com/entry/20141225/1419506675
[finale2015]: https://zrbabbler.hatenablog.com/entry/20151225/1451036524
[finale2017]: https://zrbabbler.hatenablog.com/entry/20171225/1514197153
[finale2018]: https://zrbabbler.hatenablog.com/entry/20181229/1546070661
[finale2020]: https://zrbabbler.hatenablog.com/entry/2020/12/25/234959
[finale2021]: https://zrbabbler.hatenablog.com/entry/2021/12/25/220904
[awesome-ddsk]: https://github.com/Comamoca/Awesome-DDSK
[ddsk-gist]: https://gist.github.com/zr-tex8r/84784082b05c0f8e2486916c6950f87a
[expandafter]: https://qiita.com/zr_tex8r/items/342bf5c500c35fedb58d
[romannumeral-trick]: https://zrbabbler.hatenablog.com/entry/20150606/1433549572
[ipaex-type1]: https://ctan.org/pkg/ipaex-type1
[zundoko]: https://qiita.com/shunsugai@github/items/971a15461de29563bf90