マクロツイーター

はてダから移行した記事の表示が崩れてますが、そのうちに直せればいいのに(えっ)

例の「verb の呪い」を LuaTeX の力で打破する(bxrawstr パッケージ)

これは「TeX & LaTeX Advent Caleandar 2018」の 22 日目の記事です。
(21 日目は tasusu さん でした。23 日目は munepi さん です。)

「verb の呪い」について語ってみる

タイトルにある「verb の呪い」というのは、多くの LaTeX ユーザを悩ませるあの規則のことです。

verb 系の命令は、他の命令の引数の中では使えない。

ここで「verb 系の命令」というのは LaTeX 標準の \verb 命令や verbatim 環境のような、「LaTeX特殊文字を非特殊なものとして入力する*1」機能を指します。例えば、以下のようなことをしようとすると「verb の呪い」のせいで失敗してしまいます。

% \verb は \textsf の引数の中なので使えない!
環境変数\textsf{\verb!%USERPROFILE%!}の値が参照される。

「verb の呪い」の非常に厄介な点は、その原因が TeX 自体の入力機構であるため「実装を工夫すること」では回避できないことです。((jsclasses のクラスや fancyvrb パッケージの \VerbatimFootnotes 命令のように「\footnote の引数の中で \verb を使えるようにする」というものがありますが、これは所望の機能を実現するために \footnote の側に細工をしています。従って、同様の方法では「任意の命令の引数で \verb を使える」ようにすることは不可能です。))パッケージの実装者としては、当然「どこでも使える verb 系命令」を作りたいわけですが、TeX 言語の力をもってしてもそれは実現できないわけです。

LuaTeX の力を使ってみる

「verb の呪い」を打破して「どこでも使える verb 系命令」を実装することはできないのでしょうか。一見すると「本質的に解決困難な問題」のようにも思えますが、今はまだ 12 月ですので、もう少し一般の*2解決策を探ってみましょう。確かに TeX 言語での解決は無理ですが、エンジンを LuaTeX に限定するのであれば状況は変わります。LuaTeX では Lua 言語の力を利用して「TeX の入力規則を変える」ことができるのです。この機構を利用すると「どこでも使える verb 系命令」が実現できそうです。

というわけで、作りました

bxrawstr を使ってみる

bxrawstr の最も単純な使い方は以下のようになります。

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{ltjsarticle}
\usepackage{bxrawstr}
\rawstrenable % これ以降 [[|...|]] が"生文字列"になる
\begin{document}
% 全て非特殊文字として扱われ, 現在のフォントで出力される.
環境変数[[|%USERPROFILE%|]]の値が参照される。
\end{document}

bxrawstr パッケージを読み込んで \rawstrenable を実行すると、それ以降のソース中の [​[|…|]] で囲まれた部分(これを「生文字列」(raw-string)と呼びます)が「LaTeX 的に非特殊な文字の列」として扱われるようになります。先の例のように、生文字列を“単純に出力”した場合は、当該の文字列が現在のフォント設定の下でそのまま出力されることになります。*3

もっと生文字列を“単純に出力”してみる

そして、bxrawstr の生文字列は「verb 系命令とは異なり、他の命令の引数でもその機能を保つ」という特長をもっています。従って、生文字列に装飾を加えて出力することが自由にできます。

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{ltjsarticle}
\usepackage{xcolor}
\usepackage{bxrawstr}
\rawstrenable
% \xHL{<text>} : 地と文字に色を付けて出力.
\newcommand*{\xHL}[1]{\colorbox{red!15}{\color{red}#1}}
\begin{document}
% 生文字列は命令の引数にも使える.
環境変数\xHL{\textsf{[[|%USERPROFILE%|]]}}の値が参照される。
\end{document}

生文字列はかなり“込み入った箇所”でも使うことができます。次の例では、tikzducks の \duck 命令の key-value 形式の引数の中で生文字列を使うことで #%!& の文字列をそのまま出力しています。

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{article}
\usepackage{tikzducks,xcolor}% DUCK!!
\definecolor{myred}{rgb}{0.87,0.17,0.27}
\usepackage{bxrawstr}
\rawstrenable
\begin{document}

\begin{tikzpicture}% U+1F92Cなアヒル
  \duck[body=myred,bill=myred!75!black,signback=black!85,
    signpost=\scalebox{0.8}{\color{white}\textsf{[[|#%!&|]]}}]
\end{tikzpicture}

\end{document}

他の命令の引数で使えるということは、\newcommand の引数でも生文字列を使えるということになります。すなわち、特殊文字を含む文字列を(LaTeX の)マクロにすることも可能です。

% LuaLaTeX文書; UTF-8
\documentclass[yoko,paper=a5]{jlreq}
\newcommand*{\xIL}[1]{\par\noindent#1\par}
\usepackage{bxrawstr}
\rawstrenable
% 生文字列をマクロにする
\newcommand*{\xXA}{[[|\expandafter|]]}
\begin{document}

傍にいたW氏に訊いてみた。
\xIL{「\xXA ?」}

想定通りの答えが返ってきた。
\xIL{「\xXA\xXA\xXA !」}

\end{document}
\index で生文字列してみる

このように、生文字列を使うと命令の引数に任意の文字列が渡せるのですが、命令の種類によっては注意が必要です。実は、bxrawstr の仕様において、生文字列の形式([[|…|]])は「“展開”((LaTeX のマクロ(\newcommand で定義される命令)の“展開”と同様の概念です。厳密には、TeX 言語における“先頭完全展開”に相当します。))して初めて中身の文字列になる」と定められています。そのせいで少し複雑な扱いが必要になるケースがあるのです。

[[|%USERPROFILE%|]]  (生文字列形式)
↓"展開"
%USERPROFILE%  (非特殊な文字列)

makeidx による索引の自動生成を行う文書において、%USERPROFILE% という語句を索引に(このままの位置で)載せたいとします。通常は \index 命令は「引数に特殊文字をそのまま書ける」という性質をもつので問題は起こりません。ところが、\index 命令自体が他の命令の引数に入っている場合は、この性質が失われてしまう(これも「verb の呪い」の一種です。)ため、特殊文字を含む %USERPROFILE% を引数に渡すのが困難になります。

% これは大丈夫
[[|%USERPROFILE%|]]\index{%USERPROFILE%@\verb+%USERPROFILE%}は…

\xStrong{% 他の命令の引数の中
  % これはダメ!
  [[|%USERPROFILE%|]]\index{%USERPROFILE%@\verb+%USERPROFILE%}は…
}

一見すると、\index の引数の中も生文字列を使えばよいように思えます。

  [[|%USERPROFILE%|]]\index{[[|%USERPROFILE%@\verb+%USERPROFILE%|]]}は…

ところが \index は引数を“展開”せずに用いるという性質があるため、これでは [[|%USERPROFILE%@\verb+%USERPROFILE%|]] という生文字列形式そのもの*4が引数と見なされるため、所望の動作になりません。

bxrawstr パッケージでは、このような状況に対処するため、生文字列の扱いを \rawstr という命令で制御できるようにしています。

  • \rawstr\命令[[|生文字列|]] を((生文字列形式を囲む {} がないことに注意してください。))実行すると、生文字列の内容をもつ(“展開”結果の)非特殊文字列を引数にして \命令 が呼び出される。*5
  • \rawstr[[|生文字列|]](命令を間に挟まない形)は [[|生文字列|]] と同じで非特殊文字列をそのまま出力する。

この \rawstr を使うと、先の問題を解決できます。((ちなみに、[[|…|]] はネストできないので、\index の引数中の \verb を生文字列に置き換えることはできません。))

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{ltjsarticle}
\usepackage{makeidx}
\makeindex
\usepackage{bxrawstr}
\rawstrenable
\newcommand*{\xStrong}[1]{\textbf{#1}}
\begin{document}
\xStrong{環境変数\texttt{[[|%USERPROFILE%|]]%
  % 生文字列の内容を \index の引数に渡す
  \rawstr\index[[|%USERPROFILE%@\verb+%USERPROFILE%+|]]
  の}}値が参照される。
\end{document}

\makeindex により生成される .idx ファイルの内容は以下の通りで、所望の内容になっていることがわかります。

\indexentry{%USERPROFILE%@\verb+%USERPROFILE%+}{1}
複数行にまたがって生文字列してみる

生文字列形式は複数行にまたがって書くことができます。

Hello, [[|%%%
&&&
$$$|]] world!

生文字列形式の中に現れる改行文字の扱いは \rawstr のオプション引数で決められます。

  • \rawstr[s](既定): 元の改行文字は空白文字に変換される。つまり「%%% &&& $$$」となる。
  • \rawstr[d] : 元の改行文字は削除される。つまり「%%%&&&$$$」となる。
  • \rawstr[k] : 元の改行文字を LF 文字(U+000A)として扱う。つまり Lua の文字列リテラルで書くと "%%%\n&&&\n$$$" に相当する文字列になる。

前述の通り、単独で [[|…|]] だけ書いた場合は \rawstr[[|…|]] と同等になるため、この場合は改行文字は空白と見なされます。

[k] オプションの用途はやや特殊です。LaTeX で“LF 文字を出力”しても組版結果で改行が起こるわけではないので、[k] は単純に出力する目的では使えません。この指定が役立つ場面と例としては、「\directlua の引数に LaTeX特殊文字を含む Lua コードを書く」というものがあります。(([TeX 言語者向け情報]オプション付きの \rawstr も完全展開可能であるため、これにより完全展開可能性を保ったまま、\directlua 中に LaTeX特殊文字を書くことが可能になります。))

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{ltjsarticle}
\usepackage{bxrawstr}
\rawstrenable
\begin{document}

% \fizzbuzz{<カウンタ名>}: FizzBuzzなカウンタ出力命令.
% ※カウンタ出力命令なので完全展開可能にしたい!
\newcommand{\fizzbuzz}[1]{\directlua{
  local val = \arabic{#1}%←これは展開される
  \rawstr[k][[| --改行保持の生文字列.
    if val % 15 == 0 then val = 'FizzBuzz'
    elseif val % 3 == 0 then val = 'Fizz'
    elseif val % 5 == 0 then val = 'Buzz'
    else val = tostring(val)
    end
    tex.sprint(val)
  |]]
}}
% 1段目enumerateの番号をFizzBuzzにする.
\renewcommand{\theenumi}{\fizzbuzz{enumi}}

技術書は以下の要素で構成される:
\begin{enumerate}
\item 本体
\item バケツ
\item 枝
\item ボタン
\item マフラー \label{itm:muffler}
\end{enumerate}
% 参照も正しく行われる.
特に、\ref{itm:muffler}は重要。

\end{document}
rescan モードを使ってみる

生文字列を直接出力すると \verb と同じ動作になります。しかし、\verb には「空白文字を空白記号〈␣〉(U+2423)として出力する」ための \verb* という変種が存在します。この \verb* を他の命令の引数中で使いたい(つまり \xHL{\verb*!line width!} を実現したい)場合はどうすればよいでしょうか。

\xHL{% 引数の中
  % ダメ
  [[|\verb*!line width!|]]%
}

これでは \verb*!line width! という文字列自体が出力されてしまうのでダメです。\rawstr を使うのはどうでしょうか。

\xHL{% 引数の中
  % ダメ
  \rawstr\verb*[[|!line width!|]]%
}

これだと \verb*{!line width!} という形に帰着されてしまい、\verb* の書式と合致しないのでやはりダメです。

今の場合、「非特殊文字化」は \verb* 自体がやってくれる前提であるため、必要なのは、\verb*!line width! という文字列を LaTeX のコードとして実行することのはずです。これを実現するのが rescan モードで、\rawstr のオプションに r を指定するとこのモードが適用されます。

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{ltjsarticle}
\usepackage{xcolor}
\usepackage{fontspec}% \verb* を使うためfontspecが必要
\usepackage{bxrawstr}
\rawstrenable
% \xHL{<テキスト>} : 地と文字に色を付けて出力.
\newcommand*{\xHL}[1]{\colorbox{blue!15}{\color{blue}#1}}
\begin{document}
線幅を自由に指定するには
% rescanモードなので, \verb*!line width! が実行される.
\xHL{\rawstr[r][[|\verb*!line width!|]]}%
キーを利用します。
\end{document}

ちなみに、\xHL{\rawstr[r]…} の部分をマクロにすることもできます。((つまり、\rawstr を本体中に含んで生文字列形式を引数にとるマクロは可能です。これに対して、生文字列の部分をマクロにしてそれを \rawstr の引数に渡すことはできません。))

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{ltjsarticle}
\usepackage{xcolor}
\usepackage{fontspec}% \verb* を使うためfontspecが必要
\usepackage{bxrawstr}
\rawstrenable
% \xHL{<テキスト>} : 地と文字に色を付けて出力.
\newcommand*{\xHL}[1]{\colorbox{blue!15}{\color{blue}#1}}
% \xHLr{<生文字列>} : \xHL と \rawstr[r] の合成.
\newcommand*{\xHLverb}[1]{\xHL{\rawstr[r]#1}}
\begin{document}
線幅を自由に指定するには
% \xHLverb は普通のマクロだから引数に{}が必要.
\xHLverb{[[|\verb*!line width!|]]}%
キーを利用します。
\verb*!line width!
\end{document}

rescan モードを使うと、verbatim 環境を他の命令の引数に入れることも可能になります。この場合、生文字列中の改行を保持する必要があるため、\rawstr のオプションを [rk] と指定することになります。

例えば、LaTeX のコード断片とそれに対応する出力を横に並べた上で、それを(tcfaspin パッケージを利用して)回転させるという、トッテモ素敵な文書を作ってみましょう。

% LuaLaTeX文書; UTF-8
\documentclass[a4paper]{article}
\usepackage[svgnames]{xcolor}
\usepackage{scsnowman}% ゆきだるま!
\usepackage{tcfaspin}% 回転!
\usepackage{bxrawstr}
\rawstrenable
\begin{document}

\faSpin{%
  \begin{minipage}[c]{8em}
    \footnotesize\color{Blue}%
    % rescan モードでかつ改行を保持する.
    \rawstr[rk][[|\begin{verbatim}
\scsnowman[
  scale=8,
  muffler=Red,
  hat=Green,
  buttons=Blue,
  arms=Brown,
  snow=SkyBlue]
\end{verbatim}|]]
  \end{minipage}%
  \begin{minipage}[c]{7em}%
    \normalsize\centering
    \scsnowman[
      scale=8,
      muffler=Red,
      hat=Green,
      buttons=Blue,
      arms=Brown,
      snow=SkyBlue]
  \end{minipage}%
}
\end{document}

まとめ

パッケージのデバ​ッグをしながら記事を書くのはツライ。

やっぱり LuaLaTeX はスゴイ!

*1:必ずしも「そのまま出力する」機能とは限りません。

*2:もし今が 8 月なら画期的な解決策を模索したいところでしょう。

*3:入力の文字コードを“そのまま”出力するので、現在のフォントエンコーディングが OT1 だと文字化けが起こります。2017 年以降の LaTeX であれば既定エンコーディングUnicode なので大丈夫ですが、それより古い場合は fontspec を読み込んだ方がいいでしょう。

*4:実際には、字句解析段階で変換した後の中間形式の文字列。

*5:TeX 言語者向け情報]この形式は完全展開可能です。

SATySFi で文書作成が容易なクラスファイルを作成した(※ただし画期的)

これは「SATySFi Advent Caleandar 2018」の 18 日目の記事です。
(17 日目は matsud224 さん でした。19 日目は puripuri2100 さん です。)

みなさん、サティスファイしていますか!?

えっ、あんまり SATySFi していない? ナンデ?

えっ、ぜひとも SATySFi してみたいけど難しくて使いこなせない? じゃあ、代わりに scSATySFi とかを使うのはどうでしょうか?

えっ、確かに scSATySFi の出力はトッテモ素敵だけど、型システムが簡単すぎるので使う気が起きない? やっぱり複雑な型システムをもつ SATySFi を使いこなしてドヤ顔したい? なるほど……。

というわけで、画期的な文書クラスを作りました

簡単! 素敵! 本質的!

scarticle クラスを使うと、誰でも簡単に、scSATySFi と同じくらい素敵な文書を作れます。具体的には、SATySFi 文書ソースを次のようになります。

% scarticle を使おう!
@require: scarticle

document (|
  title = `サンプル`; % タイトル
  show-title = true; % タイトルを出力するか
|) '<
  % 本文の記述
  +p {scarticle で誰でも\emph{簡単}に\SATySFi;文書を作れます。}
>

えっ、stdja クラスの文書とほとんど同じでやっぱり難しそう? いえ、心配は無用です。タイトルのところを見てください。

  title = `サンプル`; % タイトルは文字列で指定

stdja などの多くのクラスでは title の値は {...} という書き方をします。これは「SATySFi の組版機能を使って組まれるテキスト」*1を表していて、高機能ではあるのですが、その代わりに、「正しい書き方を習得しないといけない」という難しさを伴っています。

このため、scarticle ではタイトルは単なる文字列の値として指定します。SATySFi の文字列値の書き方は非常に単純で、` ` の中にそのまま文字列を書くだけです。{...} のテキストを書くときにはエスケープが必要な <; も含めて、あらゆる文字をそのまま書けます。(ただし ` だけは書けない((もちろん、SATySFi の文法を知っている強い人なら ` を含む文字列値も書けます。『The SATySFibook』の「基本的な基本的なデータ」(3.2.3 節)で説明されています。))のですが、これが必要なことはまずないでしょう。)

では本文('< > の中身)はどうでしょうか。これは stdja などと同じ書き方をしていて((実際は stdjabook と同じコマンド(+p+section\emph\SATySFi など)が使えるようになっています。ただし数式関連の命令には対応していません。))何か難しそうです。しかしこれも心配はいりません。scarticle クラスは文書を本質的にすることを指向していて、そこでは本文の内容は全く本質的でないので完全に無視されます。ヘタに書いても型エラーの原因になるだけなので、思い切って本文は省略しましょう!

[thesis.saty]
@require: scarticle

document (|
  title = `卒業論文`;
  show-title = true;
|) '< % 省略!!
>

タイトルの文字列以外はそのまま書き写すだけなので、とても簡単になりました! えっ、本文を省略してしまうと、内容がない文書になってしまう? いえ、そんなことはありません。実際にこの文書ソースをコンパイルしてみましょう。

> satysfi thesis.saty
 ---- ---- ---- ----
  target file: 'thesis.pdf'
  dump file: 'thesis.satysfi-aux' (will be created)
  parsing 'thesis.saty' ...
  parsing 'scarticle.satyh' ...
  parsing 'pervasives.satyh' ...
  parsing 'list.satyg' ...
 ---- ---- ---- ----
……(略)……
  writing pages ...
 ---- ---- ---- ----
  output written on 'thesis.pdf'.

この通り、本質的な内容をもつトッテモ素敵な文書になっています。しかも、箒もついてきます。*2スバラシイ!☃︎

まとめ

というわけで皆さん、scarticle クラスを使って、本質的な文書をサティスファイしましょう!

*1:SATySFi の用語では「インラインテキスト」(inline text)と呼びます。

*2:なんといっても、今日は 18 日ですからね。

TeX でイオニア式記数法(1)

先日の「TeX で数式を生成する 〜アッカーマン関数編〜」の記事に関して、ツイッタァーでこんな感想があった。

うーむ、どうやら、TeX 芸のコードは完全展開可能でないといけないようである。仕方がないので、完全展開可能な TeX 芸ネタを作ることにした。お題はまた「SATySFi Advent Calendar 2018」参加者募集中!)から取ってくることにする。

目標

TeX で以下の要件を満たす完全展開可能なマクロを実装する。

※とりあえず、今回は“結果が単純な文字列で表せる”10000 未満の数に限定する。暇があったら大きな数への対応について考える予定。

方針

  • 先頭完全展開可能にする。
  • 元ネタの記事の中の人が奨励しているように、「汎用的に使い回せる関数」の組み合わせで実装する。
  • (値を返す)関数を普通に「値(を表すトークン列)に展開されるマクロ」として表すと「関数の合成」が困難を極めることになる(参照)ので、ここでは関数を「継続渡し形式のマクロ」で表す。
  • ギリシャ文字の扱いに苦労したくないので XeLaTeX・LuaLaTeX を前提にする。Unicode 文字トークンを扱うこと以外は e-TeX 拡張の範囲に留める。

準備

ユニークトーク

ユニークトークとは、「それ自身と \let で複写したもの以外の、他のいかなるトークンとも意味((「意味(meaning)」は TeX 言語の用語であることに注意。「意味」が同じであるかを判定するのが \ifx である。))が等価になる(つまり \ifx-等価になる)ことがない」ようなトークン((\def\CSa{\CSb} で定義される \CSa をユニークトークンとして使うには、「制御綴 \CSb の名前を(規約的に)ユニークにする」上にさらに「自分も \CSb を他の場所で使わない」という規約を守る必要がある。もちろん \CSa は他の場所での使用が前提になる。通常は、\CSaが誤って実行されたときにエラーを出すために、\CSb は未定義のままにしておく。))で、TeX 言語プログラミングの重要技巧の一つである。

%% ユニークトークン
% \my@nil : 値がないことを表す.
% ※デバッグの便宜のため保護付にする.
\protected\def\my@nil{\my@nil@}
% \my@mt : \my@if@empty 専用.
\def\my@mt{\my@mt@}
% \my@b : リストの空判定のため.
\def\my@b{\my@b@}
% \my@mk : 展開不能なマーカトークン.
% ※"ユニーク"ではないので, 専らマクロ引数の区切りトークンとして使う.
\let\my@mk\indent
  • \my@nil は SATySFi の ’a option 型の None に相当するものとして使う。(要するに TeX 言語は静的型がないので安易に nil を使う。)((\my@nil を保護付にしているのは、デバッグ用途で \message\write で端末表示したときに展開されない方が便利だからである。))
  • \my@mk は単に展開不能な適当なプリミティブを充てている。もちろんこれは「外形上ユニーク」なだけで「意味上ユニーク」ではない。
末尾呼出の条件分岐

完全展開可能なマクロの実装において頻繁に使うやつ。((\my@cond の #1 の区切りを \fi にしているのは、\my@cond をつかった文を外形的に \if-均衡にするため。\def の前に(何もしないはずの)\@gobbletwo\if\if があるのは \my@cond の定義文を \if-均衡にするため。))

%% \my@cond\ifXXX...\fi{<真>}{<偽>}
% \ifXXX... の判定結果により<真>と<偽>の何れかに展開される.
% TeXのif-トークンを末尾呼出で使うための機構.
\@gobbletwo\if\if \def\my@cond#1\fi{%
  #1\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi}

例えば次の 2 つは等価になる。

\ifnum\my@foo=42 \my@good \else \my@bad \fi {54}\my@cond\ifnum\my@foo=42\fi{\my@good}{\my@nad}{54}

しかしここで後ろの {54}\my@good\my@bad の引数として扱いたい場合、TeX\if 文のままだと、\else\fi を先に処分するための展開制御が必要になって非常に厄介である。\xx@cond を使うと \my@good\my@bad が「末尾呼出」される形になるので展開制御が不要になる。(TeX での末尾呼出の扱いについては過去の記事を参照されたい。)

%% \my@if@empty{<入力>}{<真>}{<偽>}
% 入力が空であるかを判定する.
\def\my@if@empty#1{%
  \my@cond\ifx\my@mt#1\my@mt\fi}

カスタムの条件分岐を定義するのも、末尾呼出形式を前提にすると簡単である。((TeX\if 文のままではカスタムの条件分岐を作りにくい、というのはよく知られた話である。))

その他
%% \my@iexpanded{<トークン列>}
% edef中で一回展開の結果を置く.
\def\my@iexpanded#1{%
  \unexpanded\expandafter{#1}}

先日の記事でも出てきた、「edef 中でマクロを一回だけ展開させたい」を実現するもの。((無論、展開不能である edef は \greeknumber の展開中に使うのではなく、それを定義するためのコードの途中で利用する。))

継続渡しの機構

継続渡し形式とはなにか

※この小節に出てくるコード(名前空間を“xx@”にした)は \greeknumber の実装コードの一部ではない。以降の “xx@名前空間のコードも同様。

「継続渡し」というのは、本来は「関数」という概念をもたない TeX 言語において「関数」を表現する方法の一つである。

完全展開可能を前提とした TeX プログラミングで「関数」を表現する場合、通常は「値(のトークン列*1)に先頭完全展開されるマクロ」を用いる。例えば、整数式を(e-TeX\numexpr を利用して)評価する「関数」は次のように表される。

% \xx@num@eval{<整数式>}
% 整数式の値(の十進表現)に展開される.
\def\xx@num@eval#1{%
  \the\numexpr#1\relax}

これで例えば「\xx@num@eval{1+5*8+1}」を 2 回展開すると「42」が得られる。しかしここで問題なのは「2 回展開する必要がある」ということである。もし「42」を他の「関数」の引数に渡そうとする((この \xx@num@eval の場合は評価せずに元のトークン列のまま別の「関数」に渡しても多くの場合は通用するだろうが、何れにしてもそれは普通の「関数」という概念からは外れる。))と、その途端に山ほどの \expandafter を書く羽目に陥る。多くの「関数」を組み合わせてプログラミングをしようとする*2場合はこの性質は致命的になる。

この欠点を避けるための「関数」の別の表現方法が「継続渡し形式」*3である。この方式では、「関数」を表現するマクロの末尾に引数(「継続」と呼ぶ)を追加する。

\xx@num@eval{<整数式>}{<継続>}

この「継続」は「関数の値を受け取るコード」を表していて、普通は((「継続」には複数トークンからなるトークン列を指定してもよい。「\xx@num@eval{1+5*8+1}{\xx@modulo{100}}」のように別の引数を伴う形もよく使われ、この場合「\xx@modulo{100}{42}」に展開されることになる。))次のようにマクロの制御綴を指定する。

\xx@num@eval{1+5*8+1}{\xx@foo}

そしてこれを展開すると、\xx@num@eval を評価した値である「42」を引数にして \xx@foo が呼び出された形である「\xx@foo{42}」に到達する、というのが「継続渡し形式の関数」の規約である。

継続渡し形式の利点は、「関数」の呼出側での展開制御が不要になることである。実際、先に挙げた例は \xx@num@eval の値を別のマクロ \xx@foo に渡す例とみることもできる。\xx@foo は値そのものの「42」を受け取るので、展開制御をする必要がなくなる。

継続渡し形式するための諸々

以下のマクロは継続渡し形式における恒等関数の実装である。「\my@return{foo}{\xx@cont}」を展開すると「\xx@cont{foo}」となるので確かに継続渡し形式の規約を満たしている。

%% \my@return{<値v>}{<継続>}
% 継続渡しの恒等写像関数.
% ※関数の中で値を返すのに用いる.
\def\my@return#1#2{#2{#1}}

このマクロは継続渡し形式のマクロを実装する際に値を“返す*4”時に利用できる。例えば、今回のプログラムでの \my@num@eval の実装コードは次のようになっている。

%% \my@num@eval{<整数式>}{<継続>}
% 整数式の評価結果を返す.
\def\my@num@eval#1{%
  \expandafter\my@num@eval@a\the\numexpr#1\my@mk}
\def\my@num@eval@a#1\my@mk{\my@return{#1}}

値を“返す”のに専ら \my@return を使うようにすると、当該のマクロのコード中では継続(第 2 引数)を全く扱わなくてよい。

もう一つの例として、「引数のトークン列を一回展開する」という「関数」を継続渡し形式で書くと次のようになる。

%% \my@expand{<トークン列>}{<継続>}
% 一回展開する.
\def\my@expand#1{%
  \expandafter\my@return\expandafter{#1}}

これで継続渡し形式について大体理解できたと思う。ここで一つ問題がある。継続渡し形式の「関数」を呼ぶには必ず「継続」を渡す必要がある。もし \my@num@eval{1+5*8+1} の値の「42」を単に“その場に吐き出す”ようにしたい場合、言い換えると、「完全展開方式の \xx@num@eval の動作が欲しい」場合はどうすればよいか。そういう場合は「完全展開方式の恒等関数」を利用すればよい。((一見すると、「継続」部分を空トークン列にすればよいような気もするが、これだと展開結果は「{42}」と余計な括弧がついてしまう。))

%% \my@get{<値v>}
% 恒等写像のマクロ.
% ※継続渡しの一連の計算の結果を取得するのに用いる.
\def\my@get#1{#1}

例えば、「\my@num@eval{1+5*8+1}{\my@get}」を先頭完全展開すると「42」になる。

※これ以降は、「継続渡し形式に従って『関数』を表すマクロ」のことを単に「関数」と呼ぶことにする。

関数の合成

以下のような関数を次々と適用していく状況を考える。

funD (funC (funB (funA val)))

% SATySFi では次のようにも書ける
funA val |> funB |> funC |> funD

継続渡し形式の TeX コードでは次のように書くことができる。

\xx@funA{val}{\xx@funB}{\xx@funC}{\xx@funD}{<継続>}

もし、「val |> funA |> funB |> funC |> funD」みたいに書きたいのであれば、最初を \my@return(恒等関数)をすればよい。

\my@return{val}{\xx@funA}{\xx@funB}{\xx@funC}{\xx@funD}{<継続>}

そこで次のようなマクロを用意した。

%% \my@compose{<継続のリスト>}{<値>}{<継続>}
% 継続の"合成"みたいなやつ.
\def\my@compose#1#2{\my@return{#2}#1}

この \my@compose を利用すると、先のコードを次のように書ける。

\my@compose{{\xx@funA}{\xx@funB}{\xx@funC}{\xx@funD}}{val}{<継続>}

つまり、「\my@compose{{\xx@funA}{\xx@funB}{\xx@funC}{\xx@funD}}」というトークン列が 4 つの関数を合成した関数として扱える。(引数と継続が後に続くという規約に従っているため。)

補助関数

\greeknumber の実装にあたって、ネタ元のコードと無関係に自分が用意したもの。

%% \my@pack@two{<値x>}{<値y>}{<継続>}
% xとyからなるリストを返す.
\def\my@pack@two#1#2{\my@return{{#1}{#2}}}

%% \my@unpack{<リスト>}{<継続>}
% リストの要素を順に返す多値関数.
\def\my@unpack#1#2{#2#1}
  • ここでは [1; 2; 34; 567] のようなリスト値は {1}{2}{34}{567} のようなトークン列で表すことにする。
  • \my@pack@two{foo}{bar}{foo}{bar} というリストを返す。*5これは多引数関数の例である。
  • \my@unpack は多値の関数で、リストの要素をそのまま値として返す。(つまり、リストの長さが n なら n 個の返り値をもつ。)((Luatable.unpack() に相当するもの。これは多値であるだけでなく値の個数が可変になっている。同様に、引数の個数が可変な関数も作ることができる。しかし引数や値が可変な関数は、その個数を知る方法がないので使いにくい。))
値の表現

SATySFi における値を次のようなトークン列で表すことにする。*6

  • 文字列(string)は文字トークン列で表す: `foo`foo
  • 整数(int)は十進表現の文字トークン列で表す: 4242
    • TeX の実装は内部値で与えられる場合も考慮するが、基本的には、真っ先に \my@num@eval を適用して数字列に転換する。
  • リスト(α list)は「各要素を括弧で囲んで並べたもの」で表す: [2; 8]{2}{8}
    • ただし(入力については)「単一トークンの要素は囲んでなくてもよい」ことにする。例えば、foo[`f`;`o`;`o`] という string list 値の表現でもある。
  • タプル(α * β)もリストと同じ方法で表す: (42,`x`){42}{x}
  • α option 型は、Some x は x と同一の表現とし、None\my@nil で表す。
List モジュールの関数

SATySFi 標準の List モジュールの関数のうち \greeknumber の実装に必要なものを実装する。map、mapi、nth、reverse が該当する。

%% \my@map{<関数f>}{<リストxs>}{<継続>}
\def\my@map#1#2{%
  \my@map@a{#1}{}#2\my@b\my@mk}
\def\my@map@a#1#2#3#4\my@mk{%
  \my@cond\ifx\my@b#3\fi{\my@return{#2}}{%else
    #1{#3}{\my@map@b{#1}{#2}{#4}}}}
\def\my@map@b#1#2#3#4{%
  \my@map@a{#1}{#2{#4}}#3\my@mk}

%% \my@mapi{<関数f>}{<リストxs>}{<継続>}
\def\my@mapi#1#2{%
  \my@mapi@a{#1}{}{0}#2\my@b\my@mk}
\def\my@mapi@a#1#2#3#4#5\my@mk{%
  \my@cond\ifx\my@b#4\fi{\my@return{#2}}{%else
    #1{#3}{#4}{\my@num@eval{#3+1}{\my@mapi@b{#1}{#2}{#5}}}}}
\def\my@mapi@b#1#2#3#4#5{%
  \my@mapi@a{#1}{#2{#5}}{#4}#3\my@mk}

%% \my@nth{<整数n>}{<リストxs>}{<継続>}
\def\my@nth#1{%
  \my@num@eval{#1}{\my@nth@a}}
\def\my@nth@a#1#2{%
  \my@nth@b{#1}#2\my@b\my@mk}
\def\my@nth@b#1#2#3\my@mk{%
  \my@cond\ifx\my@b#2\fi{\my@return{\my@nil}}{%else
  \my@cond\ifnum#1=\z@\fi{\my@return{#2}}{%else
    \my@num@eval{#1-1}{\my@nth@c{#3}}}}}
\def\my@nth@c#1#2{%
  \my@nth@b{#2}#1\my@mk}

%% \my@reverse{<リストxs>}{<継続>}
\def\my@reverse#1{%
  \my@reverse@a{}#1\my@b\my@mk}
\def\my@reverse@a#1#2#3\my@mk{%
  \my@cond\ifx\my@b#2\fi{\my@return{#1}}{%else
    \my@reverse@a{{#2}#1}#3\my@mk}}

入口と出口の部分を「継続渡し形式」の規約に合わせればよく、途中の処理については、普通の完全展開可能マクロと同じ要領で実装することになる。

ユーティリティー関数

以上でようやく準備が整ったので、ここからは元ネタの記事の方針に従って、\greeknumber を実装することになる。

まずは元記事の「ユーティリティー関数」の節にある関数。自明に対応がつくように、名前も引数の順番もそのままにしている。

%% \my@apply@non@empty{<関数f>}{<値s>}{<継続>}
\def\my@apply@non@empty#1#2{%
  \my@if@empty{#2}{\my@return{}}{#1{#2}}}

%% \my@zip{<リストxs>}{<リストys>}{<継続>}
\def\my@zip#1#2{%
  \my@zip@a#1\my@b\my@mk#2\my@b\my@mk{}}
\def\my@zip@a#1#2\my@mk#3#4\my@mk#5{%
  \my@cond\ifx\my@b#1\fi{\my@return{#5}}{%else
  \my@cond\ifx\my@b#3\fi{\my@return{#5}}{%else
    \my@zip@a#2\my@mk#4\my@mk{#5{{#1}{#3}}}}}}

%% \my@split@by{<整数n>}{<リストys>}{<継続>}
\def\my@split@by#1{%
  \my@num@eval{#1}{\my@split@by@a}}
\def\my@split@by@a#1#2{%
  \my@split@by@b{#1}{}{}{#1}#2\my@b\my@mk}
\def\my@split@by@b#1#2#3#4#5#6\my@mk{%
  \my@cond\ifx\my@b#5\fi{\my@return{#2{#3}}}{%else
  \my@cond\ifnum#4=\z@\fi{\my@split@by@b{#1}{#2{#3}}{}{#1}{#5}#6\my@mk}{%
    \my@num@eval{#4-1}{\my@split@by@c{#1}{#2}{#3}{#5}{#6}}}}}
\def\my@split@by@c#1#2#3#4#5#6{%
  \my@split@by@b{#1}{#2}{#3{#4}}{#6}#5\my@mk}

%% \my@repeat{<整数n>}{<リストxs>}{<継続>}
\def\my@repeat#1{%
  \my@num@eval{#1}{\my@repeat@a}}
\def\my@repeat@a#1#2{%
  \my@repeat@b{#1}{#2}{}}
\def\my@repeat@b#1#2#3{%
  \my@cond\ifnum#1=\z@\fi{\my@return{#3}}{%else
    \my@num@eval{#1-1}{\my@repeat@c{#2}{#3}}}}
\def\my@repeat@c#1#2#3{%
  \my@repeat@b{#3}{#1}{#2#1}}

%% \my@concat@maybe{<リストxs>}{<継続>}
\def\my@concat@maybe#1{%
  \my@concat@maybe@a{}#1\my@b\my@mk}
\def\my@concat@maybe@a#1#2#3\my@mk{%
  \my@cond\ifx\my@b#2\fi{\my@return{#1}}{%else
  \my@cond\ifx\my@nil#2\fi{\my@concat@maybe@a{#1}#3\my@mk}{%else
    \my@concat@maybe@a{#1{#2}}#3\my@mk}}}

%% \my@intersparse{<値c>}{<リストxs>}{<継続>}
\def\my@intersparse#1#2{%
  \my@if@empty{#2}{\my@return{}}{%else
    \my@intersparse@a{#1}#2\my@b\my@mk}}
\def\my@intersparse@a#1#2{%
  \my@intersparse@b{#1}{{#2}}}
\def\my@intersparse@b#1#2#3#4\my@mk{%
  \my@cond\ifx\my@b#3\fi{\my@return{#2}}{%else
    \my@intersparse@b{#1}{#2{#1}{#3}}#4\my@mk}}
  • 先述の値の表現の規約に従うと、explode-into-digits は恒等関数になるので省いた。
  • max-length は今回の \greeknumber の範囲では不要で、また完全に実装する場合でも不要になる可能性があるので取りあえず保留。

文字列に関する扱いの差異を吸収するため、以下の関数を用意する。例えば「\my@implode{{f}{o}{o}}」は「foo」を返す.

%% \my@implode{<リストxs>}{<継続>}
% リストの各々の文字列を連接した文字列.
\def\my@implode#1{%
  \my@implode@a{}#1\my@b\my@mk}
\def\my@implode@a#1#2#3\my@mk{%
  \my@cond\ifx\my@b#2\fi{\my@return{#1}}{%else
    \my@implode@a{#1#2}#3\my@mk}}

1,0000 未満の表記

続いて「1,0000 未満の表記」節にある定義。

文字の定義。

%% 定数
\def\my@s@gnls{͵}
\def\my@ones{{}{α}{β}{γ}{δ}{ε}{ϛ}{ζ}{η}{θ}}
\def\my@tens{{}{ι}{κ}{λ}{μ}{ν}{ξ}{ο}{π}{ϟ}}
\def\my@hundreds{{}{ρ}{σ}{τ}{υ}{φ}{χ}{ψ}{ω}{ϡ}}
%% \my@thousands
\def\my@tmpa#1{\my@expand{\my@s@gnls#1}}
\my@expand{\my@ones}{\my@map{\my@apply@non@empty{\my@tmpa}}}{%
  \def\my@thousands}

%% \my@number@symbols
\edef\my@number@symbols{%
  {\my@iexpanded{\my@ones}}{\my@iexpanded{\my@tens}}%
  {\my@iexpanded{\my@hundreds}}{\my@iexpanded{\my@thousands}}}
  • \my@thousands\my@number@symbols の定義には多少の処理を行うことになるが、ここは完全展開可能である必要はない。
  • \my@thousands の定義では最後の継続を \def\my@thousands にしている。この値に {<結果の値>} を付けて呼び出されるから、これで所望の動作になっている。
  • \my@number@symbols の定義は普通の TeX コード;-)

1,0000 未満の数を変換。

%% \my@simple@digits{<リストxs>}{<継続>}
\def\my@simple@digits#1{%
  \my@expand{\my@number@symbols}{\my@zip{#1}}{\my@reverse}{%
    \my@map{\my@compose{{\my@unpack}{\my@nth}}}}{\my@implode}}
  • 元のコードの「fun (d, ss) -> List.nth d ss` |> Option.from `!`」にあたる関数は my@compose{{\my@unpack}{\my@nth} としている。タプルはリストと同じく {<d>}{<ss>} で表されているので、\my@unpack で 2 つの値に分解して、それを \my@nth の引数に渡している。ここで実際に nth の値が \my@nil(None) になることはないので、option の処理は省略した。

\greeknumber の実装

元記事ではこの後に万以上の数に対応するためのコードが続くが、本記事ではそこは対象外になる。

元記事の「1,0000 未満の表記」まで読んだ段階を考えると、本記事の \greeknumber に相当する SATySFi のコマンドは、次のコードで実装できる。((元記事でも「比較」という節にそれに相当する話があるのだけど、そこで挙げられている \greeknumber の実装は色々とアレである。))

let s-keraia = `ʹ`
let-inline \greeknumber n =
  let s = n |> explode-into-digits |> List.reverse |> simple-digits
  in embed-string (s ^ s-keraia)

これに相当する TeX 言語での実装は以下のようになる。

\def\my@s@keraia{ʹ}
%%<*> \greeknumber{<整数n>}
\newcommand*{\greeknumber}[1]{%
  \my@num@eval{#1}{\my@reverse}{\my@simple@digits}{%
    \my@greeknumber@a}{\my@get}}
\def\my@greeknumber@a#1{%
  \my@expand{\my@s@keraia}{\my@pack@two{#1}}{\my@implode}}
  • \my@greeknumber@a は後ろに \my@s@keraia を付ける関数。
  • \greeknumber は結果の値のトークン列に展開される必要があるため、最後の継続に\my@get を指定している。

無事に実装が完了したので、テスト用のコードを書いてみる。

% (文書本体中で)
\newcommand*{\Test}[1]{%
  #1 = \greeknumber{#1}}
\begin{itemize}
\item\Test{1}
\item\Test{2}
\item\Test{3}
\item\Test{5}
\item\Test{8}
\item\Test{13}
\item\Test{21}
\item\Test{34}
\item\Test{55}
\item\Test{89}
\item\Test{144}
\item\Test{233}
\item\Test{377}
\item\Test{610}
\item\Test{987}
\item\Test{1597}
\item\Test{2584}
\item\Test{4181}
\item\Test{6765}
\end{itemize}

完璧!

※完全なソースを以下の場所に置きました。

まとめ

やっぱり TeX 言語は超絶アレなので、関数型プログラミングをしたい人は是非とも SATySFi を使いましょう!

(続けばいいね)

*1:完全展開可能を前提とする場合、基本的にデータ型は「トークン列」しかないことに注意。全ての「値」は何らかのトークン列として表現する必要がある。

*2:もしかして:関数型プログラミング

*3:ここでいう「継続渡し」が、一般の言語における「継続渡し(continuation passing)」と対応する概念に本当になっているのかは、深く考えてはいけない;-)

*4:だから“return”という名前である。

*5:単体ではほとんど存在意義がないようにみえるが、他の「継続渡し形式の関数」と組み合わせると有用になる場合がある。

*6:TeX 言語に静的型なんて存在しない!

scSATySFi による Markdown 文書からの PDF 出力

もしかして: SATySFiによるMarkdown文書からのPDF出力

概要

scSATySFi には Markdown で書かれた文書を PDF へと組み上げて出力する機能が実装されています。2018 年 11 月 18 日にリリースされたバージョンである v0.8.28 において入った機能であり、単に scsatysfi を「go get -u」すれば既に使えます。コマンドライン--markdown オプションをつけて

$ scsatysfi --markdown ‹s:体裁設定名› ‹foo.md:入力文書› -o ‹out.pdf:出力先ファイル名›

を叩くことにより Markdown 文書から PDF が生成できます。体裁設定名 ‹s› を変えることで様々な体裁を指定できるようにしていますが、ユーザによる体裁設定は本質的でないため出力には影響しません。単に Markdown 文書を(結局無視される、任意の体裁設定を用いて)PDF に組み上げるだけなら scSATySFi 言語はおろか scSATySFi 文書の書き方を一切知らなくとも使うことができます。つまり、markdown-pdfのような、Markdown 文書を処理する専用のソフトウェアであるかのように scSATySFi をブラックボックス視することさえ可能です(※ただし画期的)。

仕組みとしては、Markdown 文書からの入力は通常の scSATySFi 文書の処理の流れと式評価が行われた後の☃の段階で合流します。体裁設定はもちろん、Markdown 文書の各記法や内容も全く本質的でないため無視されます。

既存ツールに対する優位性(☃)

scSATySFi が Markdown に対応することに関して(前述のような markdown-pdf などのツールに比べて)現時点で嬉しい点は

  • 入力の文書の内容がイマイチ本質性に欠いていても出力 PDF は常に本質的で素敵になる
  • その気になれば --muffler オプションでユーザがマフラーの色をかなり柔軟にカスタマイズできる

などにあると言えるかと思います。

対応している構文

対応している Markdown の構文の範囲は、実は開発者も正確には把握しきれていません。というのも、Markdown 文書のデコードの際に構文規則を考慮する必要がなく評価結果は確定しているためです(scSATySFi の文書は essential 型なので、その値は☃に決まっています)。ただし、少なくとも次のような範囲は対応しています:

  • 構文違反を規定しないような全ての構文*1

体裁設定の仕組み

体裁設定名 ‹s› は、ヒト類の手に負えなくなる事態を避けるため、完全に無視されます。

Future Work

  • SATySFiに多段階計算に基づくプリプロセシング機能を実装したら、何か次の画期的なネタを考える

まとめ

  • Markdown から☃を生成するだけなら Markdown などを知らなくても使えます

*1:Markdown における重要な設計思想として「不正なMarkdownなんてものは存在しない」(There is no such thing as “invalid” Markdown.)というものがあります。このため、元祖 Markdown や CommonMark などの多くの Markdown 方言は構文違反を全く規定していません。

TeX で数式を生成する 〜アッカーマン関数編〜

……いや、「SATySFi Advent Calendar 2018」(参加者募集中!)の記事「SATySFiで数式を生成する 〜アッカーマン関数編〜」を読んで、何となく作りたくなったので。

目標

TeX で以下の命令(マクロ)を実装する。

方針

  • 元ネタで代数的データ型が使われているので、TeX で代数的データ型を扱う手法を利用する。
  • 特にその必要がないため、完全展開可能にはしない。「関数の返り値」の扱いは「特定の制御綴への代入」を採用する。
  • plain TeXLaTeX の両方で通用するコードを書く。e-TeX 拡張は使わない。

式構造

式構造を表すデータは以下のトークン列で表す。

  • \my@Num{<n>}: 整数n
  • \my@Fun{<m>}{<n>}: Ack(m,n)

例えば、「Ack(2,8)」という式は \my@Fun{\my@Num{2}}{\my@Num{8}} で表される。

値を \edef で代入するときの便宜のため、以下のマクロを用意する。

%% 構築子
\def\my@@Num{\noexpand\my@Num}
\def\my@@Fun{\noexpand\my@Fun}

式を印字する

式を印字するための構築子の定義を与える。

\def\my@print@Num#1{#1}
\def\my@print@Fun#1#2{A(#1,#2)}

\my@use@print を実行すると実際の構築子の意味がこれらの定義に切り替わるようにする。

%% \my@use@print
% 式を印字する状態に移行する.
\def\my@use@print{%
  \let\my@Num\my@print@Num
  \let\my@Fun\my@print@Fun}

例えば、

\my@use@print $\my@Fun{\my@Num{2}}{\my@Num{8}}$

を実行すると以下の出力が得られる。

式が整数であるかを判定する

先の \my@use@isnum と同じ要領で構築子を定義する。真偽値を“返す”必要があるので、そのための変数を用意する。

\newif\ifmy@ok % 真偽値の返り値専用のスイッチ

%% \my@use@isnum
% "式が単なる整数であるか"を判定する状態に移行する.
% この状態で構築子を実行するとスイッチ my@ok に結果を返す.
\def\my@use@isnum{%
  \let\my@Num\my@isnum@Num
  \let\my@Fun\my@isnum@Fun}
\def\my@isnum@Num#1{\my@oktrue}
\def\my@isnum@Fun#1#2{\my@okfalse}

例えば次のようになる。

\my@use@isnum \my@Fun{\my@Num{2}}{\my@Num{8}}
\message{\ifmy@ok True\lese False\fi}
%→ False

式を一段評価する

次の仕様を満たす構築子のマクロ(\my@eval@...)を実装したい。

\let\my@ret\relax % トークン列返り値専用のマクロ

%% \my@use@eval
% 式を一段評価する状態に移行する.
% この状態で構築子を実行すると \my@ret に一段評価した結果の式を
% 表す値が代入される.
\def\my@use@eval{%
  \let\my@Num\my@eval@Num
  \let\my@Fun\my@eval@Fun}

例えば次のようになってほしい。

\my@use@eval \my@Fun{\my@Num{2}}{\my@Fun{\my@Num{8}}{\my@Num{0}}}
\message{\meaning\my@ret}
%→ macro:->\my@Fun{\my@Num{2}}{\my@Fun{\my@Num{7}}{\my@Num{1}}}
式が Num の場合

数値はそれ自身に評価されるので、次のようになる。

% 数値mの場合
\def\my@eval@Num#1{%{<m>}
  \def\my@ret{\my@Num{#1}}}
式が Fun の場合

「構築子の定義を切り替える」という方法で通すことも可能であるが、見通しが極めて悪くなるため、別の方法を考えることにする。

まず、構築子の名前(Num または Fun)を取り出す補助マクロを作る。

%% \my@name\my@XXX : 'XXX'に展開される.
\begingroup\lccode`\?=`\@ \lowercase{%
  \gdef\my@name#1{\expandafter\my@name@a\string#1;}
  \gdef\my@name@a#1?#2;{#2}
}\endgroup

その上で、引数の式の構築子が何であるかによって別のマクロに処理を委譲する。このとき、構築子の引数は委譲先のマクロの引数に渡す。

% Ack(X,Y)の場合 (X,Yは式)
\def\my@eval@Fun#1#2{%{<X>}{<Y>}
  \my@eval@Fun@a#1*;#2*;}% {}を外す
  %※'*'はダミーで, 後で除去される
\def\my@eval@Fun@a#1#2;#3#4;{%\X...*;\Y...*;
  \csname my@eval@Fun@@\my@name#1@\my@name#3\endcsname#2;#4;}

例えば、

\my@eval@Fun{\my@Num{2}}{\my@Fun{\my@Num{8}}{\my@Num{0}}}

を実行すると、結果的に次のトークン列が実行される。

\my@eval@Fun@@Num@Fun{2}*;{\my@Num{8}}{\my@Num{0}}*;

それでは、Fun の引数が両方とも Num である場合の処理(\my@eval@Fun@@Num@Num)を作ろう。

% 変数
\newcount\my@m
\newcount\my@mm
\newcount\my@n

% Ack(m,n)  (m,nは数値)
\def\my@eval@Fun@@Num@Num#1*;#2*;{%{<m>}*;{<n>}*;
  %※#1,#2の{}は外れるこに注意
  \my@m=#1\relax \my@n=#2\relax
  \ifnum\my@m=\z@ % Ack(0,_)
    \advance\my@n\@ne
    \edef\my@ret{\my@@Num{\the\my@n}}%
  \else\ifnum\my@n=\z@ % Ack(_,0)
    \advance\my@m\m@ne
    \edef\my@ret{\my@@Fun{\my@@Num{\the\my@m}}{\my@@Num{1}}}%
  \else % Ack(_,_)
    \my@mm\my@m \advance\my@m\m@ne \advance\my@n\m@ne
    \edef\my@ret{%
      \my@@Fun{\my@@Num{\the\my@m}}{%
        \my@@Fun{\my@@Num{\the\my@mm}}{\my@@Num{\the\my@n}}}}
  \fi\fi}

ここで、\my@ret に代入すべきトークン列を構成するのに、先ほど用意した \my@@... を利用している。例えば、\my@n が 8 のときに

\edef\my@ret{\my@@Num{\the\my@n}}%

を実行すると、\my@ret\my@Num{8} となる。

次に、引数が (Num,Fun) である場合の処理を定義する。

\newtoks\my@toks

% Ack(m,Ack(X,Y))  (mは数値)
\def\my@eval@Fun@@Num@Fun#1*;#2#3*;{%{<m>}.;{<X>}{<Y>}*;
  % Ack(X,Y) の一段評価を求める(結果は \my@ret)
  \my@eval@Fun{#2}{#3}%
  % \my@ret の内容のトークン列を \my@toks に代入する
  \my@toks\expandafter{\my@ret}%
  \edef\my@ret{\my@@Fun{\my@@Num{#1}}{\the\my@toks}}}

\my@ret の内容を一旦トークン列レジスタに移しているのは、「edef 中でトークン列レジスタの参照はそれ以上展開されない」という性質を利用したいからである。もし、e-TeX 拡張の \unexpanded を使ってよいなら、最後の \my@ret の代入は次のように書ける。

% 中の \my@ret は一度だけ展開されてほしい
\edef\my@ret{\my@@Fun{\my@@Num{#1}}{\unexpanded\expandafter{\my@ret}}}

残りは引数が (Fun,Num) と (Fun,Fun) である場合であるが、実はこれの実装は不要である。なぜなら、評価の途中で Fun の第 1 引数が Fun になることは、実際には起こらないからである。従って、これまでのコードで \my@use@eval は完成である。

公開マクロ \Ack の実装

これは今までに作った部品を組み合わせるだけなので、特に難しいところはない。

%%<*> \Ack{<m>}{<n>}
% 公開マクロ.
\def\Ack#1#2{%
  % 元の式構造の値を \my@expr に代入.
  % (#1, #2の値を評価しておく.)
  \my@m=#1\relax \my@n=#2\relax
  \edef\my@expr{\my@@Fun{\my@@Num{\the\my@m}}{\my@@Num{\the\my@n}}}%
  % "(元の式)"を印字した結果を \my@box に入れる.
  \my@use@print \setbox\my@box\hbox{$\my@expr$}%
  % \my@expr を一段評価する.
  \my@use@eval \my@expr \let\my@expr\my@ret
  % 1行目"(元の式)=(今の式)"を印字する.
  \par\noindent \copy\my@box
  \my@use@print ${}=\my@expr$%
  \my@Ack@loop}
\def\my@Ack@loop{%
  % \my@expr を一段評価する.
  \my@use@eval \my@expr \let\my@expr\my@ret
  % "(今の式)"を左側の空きを入れて印字する.
  \par\noindent \hskip\wd\my@box
  \my@use@print ${}=\my@expr$%
  % 式構造がNumであるか検査する.
  \my@use@isnum \my@expr
  \ifmy@ok\else % Numでないならばループ
    \expandafter\my@Ack@loop
  \fi}

完成

以上のプログラムをプレアンブルに書いた上で、本体で \Ack{3}{1} を実行する LaTeX 文書を用意した。*1

これを LaTeXコンパイルすると、Ack(3,1) の計算過程が印字された文書が得られる。

(1 ページ目先頭)
(3 ページ目先頭)

まとめ

というわけで、TeX 言語はアレなので、アッカーマン関数したい人は是非とも SATySFi を使いましょう!(えっ)

*1:えっ、パッケージに仕上げたほうがいいですか?;-)

SATySFi のデモ文書をチョット素敵にする

これは「SATySFi Advent Caleandar 2018」の 3 日目の記事です。
(2 日目は hanachin さん でした。4 日目は na4zagin3 さん です。)

「The SATySFi book」の頒布(@技術書典)から、もうすぐ 2 ヶ月が経とうとしています。世の中(ツイッタァー)を見た感じでは、SATySFi のユーザも大分増えてきているという印象です。

例の画像、それが問題だ

さて、SATySFi を実際に導入した人々を観察してみると、SATySFi の導入の目的は大まかに分類すると次の 2 つに分かれるようです。

  1. 自分で作った SATySFi 文書を組版したい。
  2. SATySFi 付属のデモ文書(demo.saty)を組版したい。

1 については「SATySFi 完全に理解した」強い人に任せて、ここでは 2 について考察してみます。この場合、demo.saty が無事にコンパイルできればコンプリート、ということになるのですが、その際の一番の懸案となるのが「あの画像」でしょう。

SATySFi のロゴ画像

件のデモ文書の中では、画像挿入機能の実例として、「SATySFi のロゴ」の画像が掲載されています。

この図の部分を出力するコードは以下のようになっています。

+p{
  画像が挿入できる証拠として,ロゴを図\ref(`fig:logo`);に掲げておこう。
  \figure ?:(`fig:logo`){\SATySFi;のロゴ}<
    +image-frame{\insert-image(7cm)(`satysfi-logo.jpg`);}
  >
}

これを見ると、図の中にある画像の正体は satysfi-logo.jpg というファイルであることがわかります。しかし SATySFi のレポジトリの demo.sty があるディレクトリには、satysfi-logo.jpg というファイルは置かれていません。従って、自分で satysfi-logo.jpg を置かないと、コンパイルは失敗していしまいます。

> satysfi demo.saty
 ---- ---- ---- ----
  target file: 'demo.pdf'
  dump file: 'demo.satysfi-aux' (will be created)
  parsing 'demo.saty' ...
  parsing 'stdjabook.satyh' ...
……(略)……
  reading 'demo.saty' ...
  type check passed. (document)
 ---- ---- ---- ----
  evaluating texts ...
! [Error] satysfi-logo.jpg: No such file or directory
フツーのロゴ画像、フツーのデモ文書

実は SATySFi のロゴ画像のファイルはレポジトリの中に含まれていて、tests/images/satysfi-logo-rgb.jpg にあります。

従って、このファイルを作業ディレクトリに satysfi-logo.jpg という名前でコピーすれば、コンパイルが通って、フツーのロゴ画像が入ったデモ文書の PDF ファイルが得られます。

この方法はいわば“模範解答”的なものですが、「デモ文書の組版を最終目標とする」人にとってはどこか物足りなさを感じるでしょう。ここで生成されるのは、これまでに大勢の人が得てきた「フツーのデモ文書」に過ぎません。目標がデモ文書であったとしても、せめてロゴ画像くらいはもっと自分好みのものを使いたいところでしょう。

素敵なロゴ画像、素敵なデモ文書

というわけで、「チョット素敵なデモ文書を作りたい」という人という人のために、チョット素敵なフォント*1を使ったロゴの画像を作ってみました。


satysfi-logo-fm.jpg(JPEG 画像;1282px×376px)


satysfi-logo-aho.jpg(JPEG 画像;1282px×376px)


satysfi-logo-csm.jpg(JPEG 画像;1282px×376px)


satysfi-logo-pp1.jpg(JPEG 画像;1282px×376px)

……えっ、もっと派手な方が好みでしょうか? そこで、もっと彩豊かなロゴ画像も用意しました!


satysfi-logo-aec.jpg(JPEG 画像;1282px×376px)


satysfi-logo-pdd.jpg(JPEG 画像;1282px×376px)


satysfi-logo-are.jpg(JPEG 画像;1282px×376px)

彩豊かなデモ文書

それでは早速、素敵なロゴ画像を使ってデモ文書をコンパイルしてみましょう。

トッテモ素敵なデモ文書ができましたね!

*1:上から順に、「Fetamont」「Computer Modern Funny Italic」「Comic Sans MS」「HGS創英角ポップ体」。

非常にアドベントがはじまった ― \begin{texadvent2018}

☃︎
 
TeX & LaTeX Advent Calendar ☃︎
 

2018/12/01 〜 2018/12/25
〜とにかくLua(La)TeXしよう〜

TeX & LaTeX アドベントカレンダー 2018

*  *  *

TeX 界のおける年末の恒例イベントとなったかも知れないアドベントカレンダーが今年も始まりました。

重点テーマ

今年の重点テーマはコレです。

とにかく Lua(La)TeX しよう

「LuaTeX はいいぞ」とよく言われますが、具体的に何がいいのかは十分に知られていません。熱狂的な LuaTeX ファンの皆さん、是非とも LuaTeX の魅力について熱く語りましょう!

※例によって、重点テーマは「必須」ではありません。あんまり LuaTeX でない TeX ネタ、さっぱり LuaTeX でない TeXネタ、なんでも歓迎しております。

で、初日のネタは

こちらになります。

まだ参加できます!

TeX & LaTeX Advent Calendar 2018(#texadvent2018)は

まだまだ参加者募集中です。

LaTeX なネタ、TeX 言語なネタ、expl3 なネタ、MiKTeX なネタ、dvips なネタ、CaTeX なネタ、pTeX-ng なネタ、LaTeXML なネタ、その他ナントカカントカ、お持ちの方はぜひぜひ、

ご参加おねがいします!

もう空きが残りわずかとなっています。登録はお早めに!

*  *  *

あっ、SATySFi な人はこっち。

〠︎
 
SATySFi Advent Calendar 〠︎
 

2018/12/01 〜 2018/12/25
〜皆で一緒にサティスファイ!〜