LoginSignup
7
0

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-12-07

これは「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の両方でコメントを開始する」状態に戻ってしまいます。 

7
0
0

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
  3. You can use dark theme
What you can do with signing up
7
0