LoginSignup
0

More than 1 year has passed since last update.

posted at

updated at

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

これは「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」が実現できました:smiley:
(※ただし制約あり)

LaTeXとSATySFiで結局polyglotできた件

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

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出力エンジン2)でコンパイルすると……。

pdflatex niceornot

赤マフラーのゆきだるま:snowman2:が描かれたPDFが出力されます(素敵:blush:
image-1.png

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

satysfi niceornot

白紙のPDFが出力されます(非素敵:slight_frown:
image-2.png

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

たねあかし

今回気づいた“あること”というのは「実は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のコードは以下のようになっています。

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から見ると以下と同等になります。

{^^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を利用して何か出力する

まとめ

image-1.png
やっぱり、赤マフラーのゆきだるま:snowman2:はトッテモ素敵:blush:(まとめろ)


  1. 前回の記事と同様に「LaTeX」は「フォーマットとしてのLaTeX」(TeX言語の使用を許す)を指すことにします。 

  2. つまりpdflatex、xelatex、lualatexが使えます。 

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

  4. カテゴリコード9は、その文字のみが常に無視されることを表します。例えば、%のカテゴリコードを9に変えると、ab%cdは(abではなく)abcdと同値になります。 

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0