LaTeXでもSATySFiでもコンパイル可能なファイルを作れた話

:::note info
これは「<a href="https://adventar.org/calendars/6724">TeX &amp; LaTeX Advent Caleandar 2021</a>」の8日目の記事です。
（7日目は <a href="https://qiita.com/zr_tex8r/items/7ff1247ec862842185aa">zr_tex8r さん</a>、9日目は <a href="https://blog.wtsnjp.com/2021/12/09/new-packages/">wtsnjp さん</a>です。）
:::

:::note info
これは「<a href="https://adventar.org/calendars/6336">SATySFi Advent Caleandar 2021</a>」の7日目の記事でもあります。
（7日目は <a href="https://qiita.com/zr_tex8r/items/7ff1247ec862842185aa">zr_tex8r さん</a>、9日目は <span>??? さん</span>です。）
:::

:::note info
この記事は
<a href="https://qiita.com/zr_tex8r/items/7ff1247ec862842185aa">「TeXでもSATySFiでもコンパイル可能なファイルを作りたい話」</a>
（カレンダー7日目の記事）の続編にあたります。
:::

[前回の記事][previous]において

>   私が考える限りでは「“LaTeXフォーマットのTeX”とSATySFiのpolyglot」を実現するのは残念ながら無理そうです。

と書いたのですが、その記事を書く際の検証作業の中で**”あること”**に気が付いて、結局「LaTeX[^format]とSATySFiでpolyglot」が実現できました:smiley:
（※ただし制約あり）

[^format]: 前回の記事と同様に「LaTeX」は「フォーマットとしてのLaTeX」（TeX言語の使用を許す）を指すことにします。

## LaTeXとSATySFiで結局polyglotできた件

以下のソースがLaTeXとSATySFiのpolyglotになります。

```text:niceornot
{^^5ccatcode37=9 %\endlinechar=-1 \gdef\DOC{\relax
% \documentclass{standalone}
% \usepackage{scsnowman}\setlength{\unitlength}{1bp}
% \begin{document}\begin{picture}(100,100)
%  \put(50,50){\makebox(0,0){\scsnowman[scale=10,
%  hat,snow,arms,buttons,muffler=red]}}
% \end{picture}\end{document}}}\DOC
} |> (fun _ ->
page-break (UserDefinedPaper (100pt, 100pt))
(fun _ -> (| text-origin = (0pt, 0pt); text-height = 0pt |))
(fun _ -> (| header-origin  = (0pt, 0pt); header-content = block-nil;
 footer-origin  = (0pt, 0pt); footer-content = block-nil |))
block-nil)
```

これをpdfLaTeX（などのPDF出力エンジン[^pdfout]）でコンパイルすると……。

[^pdfout]: つまりpdflatex、xelatex、lualatexが使えます。

```sh
pdflatex niceornot
```

赤マフラーの**ゆきだるま**:snowman2:が描かれたPDFが出力されます（素敵:blush:）
![image-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14550/1924a980-4ae9-a02e-b7ed-e8ef307286f0.png)


一方、SATySFiでコンパイルした場合は……。

```sh
satysfi niceornot
```

白紙のPDFが出力されます（非素敵:slightly_frowning_face:）
![image-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14550/bb9e0993-21cf-252d-3941-3c14c205e90d.png)

実はこの記事で述べる方法には制限があって**SATySFiでの出力は白紙にしかなりません**。それでも一応エラーが発生せずに出力が得られているので、polyglotの条件は満たしています:upside_down:

## たねあかし

今回気づいた**“あること”**というのは「実はSATySFiの**インラインテキストの中では“^”は特殊文字ではない**[^caret]」ということです。つまり、`^`は特殊でないのだから以下の表記が通用します。

[^caret]: SATySFiでも`^`は上添字を表すので一見「`^`は特殊文字である」にみえますが、SATySFiでは「数式」と「インラインテキスト」は別のものであり、`^`は「数式」では特殊であっても「インラインテキスト」では特殊ではないのです。

```text
{^^5ccatcode...
```

これで話は全て終わったようなものですが、もう少し説明してみます。

### TeX的に考えると

前回説明したようにLaTeXでは「“本体”を開始するまで何も出力してはいけない」という制約があるのですが、niceornotの先頭の`{`はグループを開始するだけで何も出力しないので問題ありません。

次の`^^5c`は所謂**“TeXエスケープ表現”**でありASCIIコード0x5cの文字`\`と同値になります。ということは`^^5ccatcode`と書くことで **“\\catcode”という制御綴を導入することに成功した**わけです。

`\catcode`が書けてしまえばあとは好きなようにできます。まずは`%`のカテゴリコードを14（コメント）から9（無視[^ignored]）に変えます。

[^ignored]: カテゴリコード9は、その文字**のみ**が常に無視されることを表します。例えば、`%`のカテゴリコードを9に変えると、`ab%cd`は（`ab`ではなく）`abcd`と同値になります。

```text
%↓"\catcode`\%=9"を実行している
{^^5ccatcode37=9 %...
```

これで、ソース行中の`%`より後にある文字列が**「SATySFiでは無視されてTeXでは読まれる」**ようになりました。この“領域”を利用することで任意のTeXコードが書けます。

ただし注意すべきことがあります。この“領域”の中は「グルーピングの内側でかつ`%`のカテゴリコードが9」という特殊な状態になっていて、この状態でLaTeXの命令（`\documentclass`等）を実行しても動作が保証されません。一度グルーピングを閉じればいいわけですが、そうすると「実行したいTeXコード」を1行に全て書かないといけません[^one-line]。これを避けるために「実行したいTeXコード」を一度マクロ`\DOC`に定義することにします。

[^one-line]: グルーピングを抜けると当然`%`の意味が元に戻ります。当該の行の`}`以降の部分は“領域”として使えますが、以降の行の`%`は「TeXとSATySFiの両方でコメントを開始する」状態に戻ってしまいます。

```text
{^^5ccatcode37=9 %\gdef\DOC{
% ……【LaTeXコード】……
% }}\DOC
```

3行目の最初の`}`で`\gdef`が完結し、次の`}`でグルーピングを抜けます。すると「`\DOC`が定義済である」以外はカテゴリコード等の設定も含めて全て起動直後の状態に戻ります。あとは`\DOC`を展開すれば、普通にLaTeXで実行するのと同じことになるわけです。実際のniceornotのコードは以下のようになっています。

```text:niceornot
{^^5ccatcode37=9 %\endlinechar=-1 \gdef\DOC{\relax
% \documentclass{standalone}
% \usepackage{scsnowman}\setlength{\unitlength}{1bp}
% \begin{document}\begin{picture}(100,100)
%  \put(50,50){\makebox(0,0){\scsnowman[scale=10,
%  hat,snow,arms,buttons,muffler=red]}}
% \end{picture}\end{document}}}\DOC
……【SATySFiのコード】……
```

マクロ`\DOC`の中はscsnowmanパッケージ（素敵:snowman:）を利用したごく普通のLaTeXソースです。`\DOC`が実行されるとその最後にある`\end{document}`のためLaTeXが終了して以降のソースは読まれないため、これ以降には任意のSATySFiソースが記述できます。

※`\endlinechar=-1`は「行末による空白を抑止する」ために入れています。コメント文字が使えなくなることに対する代替手段です。

### SATySFi的に考えると

SATySFiでは`%`は常に行コメントを開始するので、先の構成はSATySFiから見ると以下と同等になります。

```text
{^^5ccatcode37=9 }
```

先述の通り、`^`は特殊文字ではないので、これは文字だけからなる単純なインラインテキストです。従って、これ自体はSATySFiとしても合法なコードになりますが、しかし、SATySFi文書のソースは全体としてはdocument型の値の記述となる必要があります。

inline-text型の式の**後ろだけ**に何か書いてdocument型の値を作ることは可能でしょうか？ この形を見ると後ろに演算子を書くしかなさそうですが、SATySFiの演算子というと、`|>`というトッテモ便利なものがあります。

```text
{^^5ccatcode37=9 } |> f
```

この式は`f {^^5ccatcode37=9}`と同値です。ということはfの部分に「引数を読み捨ててdocument型の値を返す」ラムダ式を書けばよいわけです。

```text
{^^5ccatcode37=9 } |> (fun _ =>【document型の値】)
```

niceornotの後半部分では、documentの値として「100pt×100ptの白紙の文書」を与えています。例によってpage-breakプリミティヴを直接使うのですが、この部分は[前回][previous]とほぼ同様なので説明は省略します。

```text
{^^5ccatcode37=9 %……
% ……【TeXのコード】……
} |> (fun _ ->
page-break (UserDefinedPaper (100pt, 100pt))
(fun _ -> (| text-origin = (0pt, 0pt); text-height = 0pt |))
(fun _ -> (| header-origin  = (0pt, 0pt); header-content = block-nil;
 footer-origin  = (0pt, 0pt); footer-content = block-nil |))
block-nil)
```

#### 補足：なぜ白紙しか出力できないのか

一見すると「document型の値には任意のSATySFiの式が書ける」状態であるため（パッケージが使えない点を除くと）好きな文書を出力できそうなのですが、残念ながら、実際にはこの構成では白紙の文書しか出力できません。これは以下の理由によります。

  * 何かを実際に出力しようとするとcontext値が必要になる。
  * context値を得ようとすると最低一度はget-initial-contextプリミティヴを呼ぶ必要があり、そうすると、その第2引数に（数式ハンドラとして）指定するインライン命令が必要になる。
  * `@require:`等が書けないので、let-inlineで自分で定義するしかない。
  * ところが今のSATySFiの仕様では**let-inlineはトップレベルにしか書けない。**
  * このためcontext値を得るのは不可能であり、何も出力できない。


※もし**仮に**let-inlineが普通に式の構成要素として書けるのだったら、例えば次のような記述によって「白紙でない」document値が得られるはずでした。

```
{……}
|> (fun _ =>
  let-inline \math _ = inline-nil in % 実際にはダメ
  let ctx = get-initial-context 100pt (command \math) in
  page-break …… % ctxを利用して何か出力する
```

## まとめ

![image-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14550/1924a980-4ae9-a02e-b7ed-e8ef307286f0.png)
やっぱり、赤マフラーの**ゆきだるま**:snowman2:はトッテモ素敵:blush:（まとめろ）

[previous]: https://qiita.com/zr_tex8r/items/7ff1247ec862842185aa