これは「TeX & LaTeX Advent Caleandar 2021」の8日目の記事です。
(7日目は zr_tex8r さん、9日目は wtsnjp さんです。)
これは「SATySFi Advent Caleandar 2021」の7日目の記事でもあります。
(7日目は zr_tex8r さん、9日目は ??? さんです。)
この記事は
「TeXでもSATySFiでもコンパイル可能なファイルを作りたい話」
(カレンダー7日目の記事)の続編にあたります。
前回の記事において
私が考える限りでは「“LaTeXフォーマットのTeX”とSATySFiのpolyglot」を実現するのは残念ながら無理そうです。
と書いたのですが、その記事を書く際の検証作業の中で**”あること”**に気が付いて、結局「LaTeX1とSATySFiでpolyglot」が実現できました
(※ただし制約あり)
LaTeXとSATySFiで結局polyglotできた件
以下のソースがLaTeXとSATySFiのpolyglotになります。
{^^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出力エンジン2)でコンパイルすると……。
pdflatex niceornot
赤マフラーのゆきだるまが描かれたPDFが出力されます(素敵)
一方、SATySFiでコンパイルした場合は……。
satysfi niceornot
実はこの記事で述べる方法には制限があってSATySFiでの出力は白紙にしかなりません。それでも一応エラーが発生せずに出力が得られているので、polyglotの条件は満たしています
たねあかし
今回気づいた**“あること”というのは「実はSATySFiのインラインテキストの中では“^”は特殊文字ではない**3」ということです。つまり、^
は特殊でないのだから以下の表記が通用します。
{^^5ccatcode...
これで話は全て終わったようなものですが、もう少し説明してみます。
TeX的に考えると
前回説明したようにLaTeXでは「“本体”を開始するまで何も出力してはいけない」という制約があるのですが、niceornotの先頭の{
はグループを開始するだけで何も出力しないので問題ありません。
次の^^5c
は所謂**“TeXエスケープ表現”**でありASCIIコード0x5cの文字\
と同値になります。ということは^^5ccatcode
と書くことで “\catcode”という制御綴を導入することに成功したわけです。
\catcode
が書けてしまえばあとは好きなようにできます。まずは%
のカテゴリコードを14(コメント)から9(無視4)に変えます。
%↓"\catcode`\%=9"を実行している
{^^5ccatcode37=9 %...
これで、ソース行中の%
より後にある文字列が**「SATySFiでは無視されてTeXでは読まれる」**ようになりました。この“領域”を利用することで任意のTeXコードが書けます。
ただし注意すべきことがあります。この“領域”の中は「グルーピングの内側でかつ%
のカテゴリコードが9」という特殊な状態になっていて、この状態でLaTeXの命令(\documentclass
等)を実行しても動作が保証されません。一度グルーピングを閉じればいいわけですが、そうすると「実行したいTeXコード」を1行に全て書かないといけません5。これを避けるために「実行したいTeXコード」を一度マクロ\DOC
に定義することにします。
{^^5ccatcode37=9 %\gdef\DOC{
% ……【LaTeXコード】……
% }}\DOC
3行目の最初の}
で\gdef
が完結し、次の}
でグルーピングを抜けます。すると「\DOC
が定義済である」以外はカテゴリコード等の設定も含めて全て起動直後の状態に戻ります。あとは\DOC
を展開すれば、普通にLaTeXで実行するのと同じことになるわけです。実際の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パッケージ(素敵)を利用したごく普通のLaTeXソースです。\DOC
が実行されるとその最後にある\end{document}
のためLaTeXが終了して以降のソースは読まれないため、これ以降には任意のSATySFiソースが記述できます。
※\endlinechar=-1
は「行末による空白を抑止する」ために入れています。コメント文字が使えなくなることに対する代替手段です。
SATySFi的に考えると
SATySFiでは%
は常に行コメントを開始するので、先の構成はSATySFiから見ると以下と同等になります。
{^^5ccatcode37=9 }
先述の通り、^
は特殊文字ではないので、これは文字だけからなる単純なインラインテキストです。従って、これ自体はSATySFiとしても合法なコードになりますが、しかし、SATySFi文書のソースは全体としてはdocument型の値の記述となる必要があります。
inline-text型の式の後ろだけに何か書いてdocument型の値を作ることは可能でしょうか? この形を見ると後ろに演算子を書くしかなさそうですが、SATySFiの演算子というと、|>
というトッテモ便利なものがあります。
{^^5ccatcode37=9 } |> f
この式はf {^^5ccatcode37=9}
と同値です。ということはfの部分に「引数を読み捨ててdocument型の値を返す」ラムダ式を書けばよいわけです。
{^^5ccatcode37=9 } |> (fun _ =>【document型の値】)
niceornotの後半部分では、documentの値として「100pt×100ptの白紙の文書」を与えています。例によってpage-breakプリミティヴを直接使うのですが、この部分は前回とほぼ同様なので説明は省略します。
{^^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を利用して何か出力する
まとめ
-
前回の記事と同様に「LaTeX」は「フォーマットとしてのLaTeX」(TeX言語の使用を許す)を指すことにします。 ↩
-
つまりpdflatex、xelatex、lualatexが使えます。 ↩
-
SATySFiでも
^
は上添字を表すので一見「^
は特殊文字である」にみえますが、SATySFiでは「数式」と「インラインテキスト」は別のものであり、^
は「数式」では特殊であっても「インラインテキスト」では特殊ではないのです。 ↩ -
カテゴリコード9は、その文字のみが常に無視されることを表します。例えば、
%
のカテゴリコードを9に変えると、ab%cd
は(ab
ではなく)abcd
と同値になります。 ↩ -
グルーピングを抜けると当然
%
の意味が元に戻ります。当該の行の}
以降の部分は“領域”として使えますが、以降の行の%
は「TeXとSATySFiの両方でコメントを開始する」状態に戻ってしまいます。 ↩