マクロツイーター

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

イマドキのmacOSで平和に“pLaTeXでヒラギノ”する話(※ただし画期的)

ここ数日、異常な暑さが続いています。いや、もはやこの「異常な暑さ」が正常なのかもしれません。考えてみれば、他にも「異常な物価高」など、「異常」が常態化して……(中略)……ゆきだるま☃!

というわけで、今年も正常に「ゆきだるま☃の日」がやってきました!

某氏🙃がざんねん🙃な話

……といいたいところですが、某氏🙃については残念ながら今年は異常なことになってしまいました。少し体調が思わしくない状況が続いていて、事前に構想していた大ネタが完成できませんでした。

何か小ネタで代わりになるものはないかと、「X」(旧ツイッタァー)1を眺めていると……。

よし、コレに決めました(奥村先生、ありがとうございました🙇(えっ))

というわけでpLaTeXで“Macヒラギノ”を使う」について考察してみます。

Macヒラギノ”の難しさを語る(つもりだった)話

最近のMac者なら「LaTeXで“Macヒラギノ”を使うのは極めて難しいので、素人は手を出すな」という話を聞いたことがあるでしょう。「“Macヒラギノ”でLaTeX」が困難である理由はTeX関連のソフトウェアの事情が深く関わっていて……(略2

LuaLaTeXなら簡単に“Macヒラギノ”できる話

実は、イマドキの日本語LaTeXの世界では“Macヒラギノ”を使う極めて簡単な方法があります。それはLuaLaTeX(+LuaTeX-ja)を使うことです。例えば、luatexja-fontspecを利用して次のように3書くと標準の明朝体を「ヒラギノ明朝ProN W3」に設定できます。

% LuaLaTeX文書
\documentclass{ltjsarticle}
\usepackage{luatexja-fontspec}
\setmainjfont{Hiragino Mincho ProN W3}

ここで注目すべきなのは「Hiragino Mincho ProN W3」というフォント名(英語名ですが4)を指定していることです。LuaLaTeXも「OSのフォント管理」とは全く独立に動いているという点では従来の(p)LaTeXと同じです。しかしLuaTeXでは自前の(Luaによる)実装により「フォント名からOSにインストール済のフォントファイルを見つける」という処理を実現しています。このため、LuaLaTeXでは「macOSのバージョンによるフォントファイルの構成の差異」の影響を受けることなく、OSに「Hiragino Mincho ProN W3」(という英語名のフォント)がある限りはその名前を指定して利用できるわけです。

やっぱりLuaLaTeXはスゴイですね🙂

チョット補足してみる

「LuaTeX-jaで“Macヒラギノ”」を実用したいという場合は、luatexja-presetパッケージが便利です。次のようにパッケージを読むだけで“Macヒラギノ”の明朝2ウェイト・ゴシック3ウェイト・丸ゴシック1ウェイトが標準の和文フォントに設定されます5

% LuaLaTeX文書のプリアンブルで
\usepackage[hiragino-pron,deluxe]{luatexja-preset}% ProN系を使う場合
%\usepackage[hiragino-pro,deluxe]{luatexja-preset}% Pro系を使う場合

deluxeは多ウェイト設定を有効にするためのオプションです(japanese-otfパッケージと同様)。

なお、jlreqクラスを利用する場合は注意が必要(参考)で、このときはluatexja-presetパッケージのオプションリストに

jfm_yoko=jlreq,jfm_tate=jlreqv

を追加する必要があります。

% LuaLaTeX文書
\documentclass[tate,book,paper=a6,fontsize=9pt]{jlreq}
\usepackage[hiragino-pron,deluxe,jfm_yoko=jlreq,jfm_tate=jlreqv]{luatexja-preset}

でもやっぱりpLaTeXで“Macヒラギノ”したい話

話を戻しましょう。LuaLaTeXはスゴイわけですが、今でも日本ではpLaTeXが広く使われています。先にあげた奥村氏のアンケート結果を見る限りは「pLaTeXで“Macヒラギノ”を使う」という課題は大いに考える価値があるでしょう。もちろん、「macOSをアップデートしたら何も動かなくなって詰んだ」というリスクを抱え続けるのも全く望ましくありません。「pLaTeXで“Macヒラギノ”を平和に使い続ける方法」が求められているわけです。

一見して極めて困難な問題です。しかしこれがLaTeXの困難な問題」の一つと判断できているのであれば、自ずと解決方針も見えてきます……そうです。

出力を☃に変えればよい。

今回もこの方針に従って画期的な解決方法を考えてみましょう。出力を☃に変えるといっても、そもそも目的が「ヒラギノフォントの文字を出力する」ことなので、いつものように「scsnowmanの赤マフラーの☃を出力して終わり(めでたしめでたし😊)」というわけにはいきません。あの“無表情な☃”pLaTeXで出力する必要があります。

結局「“Macヒラギノ”の文字をpLaTeXで扱う必要がある」ので全く問題が簡単になっていないようにも見えますが、大きな進展もあります。これまではヒラギノの2万グリフを全て使おうとしていたところを、本質的に必要な1つのグリフ(CID+8218)だけに絞ることに成功しました。1つの文字だけであればフォント自体を扱う必要はありません。そう、画像化すればよいわけです!

画期的な解決策のアイデア

先述の通り、LuaLaTeXでは“Macヒラギノ”を簡単に取り扱えるので、LuaLaTeXでstandaloneパッケージを使うなどの方法で「“Macヒラギノ”の☃」の「PDF形式の画像」を作り出すことができます。

% LuaLaTeX文書; UTF-8
\documentclass{standalone}
\usepackage{fontspec}
\setmainfont{Hiragino Mincho ProN W3}
\begin{document}% 中身は☃だけ
\end{document}

pLaTeX+dvipdfmxでは「PDF形式の画像」を貼り込めるので、あとは“まるで直接文字を書いたように見えるように適切に調整”してこの☃の画像を貼ればいいわけです。PDFの中にPDFを貼った形なので外の文字と全く同じように「フォントの字」が配置されている状態になるため、検索・コピーペーストも問題なく行えます。

pLaTeXで本質的に“Macヒラギノ”できる話

このアイデアに基づいて、実際にLaTeXパッケージをつくってみました

以下の2つのファイルを使います。

  • genschira.sh : ☃のPDF画像を生成するためのbashスクリプト
  • schira.sty: ☃のPDF画像を貼り込むためのLaTeXパッケージ。

まずは、macOSのコマンドシェルで、genschira.shを何も引数を付けずに実行します。

genshira.sh

すると、XeLaTeXが実行されて「“Macヒラギノ”の☃」のPDF画像ファイル“schira-k.pdf”が生成されます。

本質的な“Macヒラギノ”(もどき🙃)の画像

※あいにく自分はWindows者でヒラギノは持っていないので、この記事ではscsnowmanでつくった「ヒラギノもどき」の無表情な☃の画像を代わりに使います。賢明な読者の皆さんは「本当はヒラギノの無表情な☃である」と思い込んでください(えっ😲)

※先の説明ではLuaLaTeXを使いましたが、実は今の用途だとLuaLaTeXよりもXeLaTeXの方がより“安全”である6ため、genschira.shではXeLaTeXを使っています。

schiraパッケージは次の命令を提供します。

  • \schira: genschira.shで生成した☃のPDF画像を“まるで直接☃の字を書いた”ように適切に配置する。

※schiraパッケージはpLaTeX+dvipdfmxに限らず、全ての「PDF画像を扱えるワークフロー」をサポートします。ドライバオプションはグローバルオプションに指定してください。

% pLaTeX+dvipdfmx
\documentclass[dvipdfmx,a4paper]{jsarticle}
\usepackage{schira}
\begin{document}
吾輩は{\schira}である。
意味はまだない。
\end{document}

出力結果

もちろん、schiraパッケージは縦組みの文書もサポートしています。次に挙げるのはjlreqクラスを利用した縦組みの文書の例です。

% pLaTeX+dvipdfmx
\documentclass[tate,dvipdfmx]{jlreq}
\usepackage{schira}
\begin{document}
吾輩は{\schira}である。

意味はまだない。
\end{document}

出力結果

もちろん、☃以外の文字はdvipdfmxの既定設定のまま(上掲の画像では原ノ味フォント)なのでヒラギノではないのですが、そもそも☃以外の文字は全く本質的ではないため、結果として「pLaTeX文書が本質的にヒラギノになった」といって問題ないでしょう。

今回も「出力を☃に変える」という画期的なアイデアにより見事にLaTeXの困難な問題を解決することができました。やっぱり☃はスゴイ!😃

まとめ

ヒラギノの無表情な☃も非ヒラギノの無表情な☃も、どっちも素敵😊


  1. 最近のニュース記事だとこの書き方が多いようです😉
  2. 本当は書く予定でしたが時間切れのため省略(ざんねん🙃)
  3. この例はあくまで説明のためのものなので、初級者はマネをしないでください。
  4. LuaTeX+luaotfloadではフォント名は英語名のみが認識されるようです。
  5. . "kanji-config-updmapで標準和文フォントをヒラギノに設定して、(u)pLaTeX+japanese-otf(deluxe付)を使った場合と同じです。「ヒラギノ明朝W2」があればそれも利用できます。"
  6. TeX関連のソフトウェアとしては極めて例外的なのですが、macOSのXeTeXは「macOSのフォント管理」を通してフォント名の解決処理を行います。だから「macOSヒラギノが使える限りはLaTeXでも使える状態が維持されてほしい」という目的に鑑みるとXeLaTeXを使うのが最適ということになります。

dvipdfmxの色スタックがアレなのをなんとかする(ナントカもする)

アレForumのアレな話

dvipdfmxがアレらしい。

Each \color command executes

\special {color push \current@color }\aftergroup \reset@color

but the push is never followed by a pop (except at the end of the job).

The color stack apparently has a limitation of 127 or 128 positions (you get red, I get blue, so it can be off by one depending on the version of the software).

ただし、このTeX Forumの問題のように「トップレベルで\colorを大量に使うと失敗する」ことについては割と簡単に対処できそうである。

トップレベルではそもそも\aftergroupで挿入したトークンは実行されないので、\reset@colorは元々一度も実行されていないはずである。だから色スタックに関するトップレベルでの整合性は元々要求されていないことになる。となると、次の動作をすればよい。

  • まず最初に初期色をスタックに積んでおく。*1
  • トップレベルで\set@colorが実行された場合は本来の動作の代わりに「色スタックをpop → \current@colorをpush」をする(トップレベルなので\aftergroupはそもそも無意味)。これでスタックの深さは変わらなくなる。

イマドキのTeXエンジンであればe-TeX拡張の\currentgrouplevelが使えるので「いまトップレベルにいるか」が判定できる。

やってみた

というわけで、なんとかしてみた。

あとは適当な例で試すだけだが、折角なんとかしたのだからナントカすることにする。

% upLaTeX + dvipdfmx (essential)
\documentclass[dvipdfmx]{jsarticle}
\usepackage{xcolor}
\colorlet{R}{red}\colorlet{B}{blue}\colorlet{G}{green!50!black}
\usepackage{bxdpx-tchack}
\newcommand*\cS[1]{\color{#1}\color{black}}
\newcommand*\cNice[7]{%
  \cS{#1}\cS{#2}\cS{#3}\cS{#4}\cS{#5}っ \cS{#6}っっ♪\cS{#7}っっ♪}
\newcommand*\cNicePar{%
  \cNice{R}{B}{R}{G}{B}{R}{R} \cNice{B}{B}{G}{R}{G}{B}{B}\par
  \cNice{G}{R}{R}{R}{B}{G}{G} \cNice{R}{G}{R}{B}{G}{R}{R}\par}
\begin{document}
\cNicePar \cNicePar \cNicePar \cNicePar \cNicePar
\end{document}

出力(トッテモ素敵😊)

まとめ

色とりどりのナントカはトッテモ素敵😊😊😊

*1:これはcolorパッケージ自体が既に行っているようではあるが、ここでの内部動作は“\color{black}”を(トップレベルの)プリアンブルで実行したのと全く同じなので、これが想定外の状態を引き起こすことはないはずである。

例のマークシートのアレを実際にzrefでやってみた話

というわけで、実際にやってみた。

問題の整理

元ネタ。

某マクロツイーターのzref関連の解説記事。

実装

変更部分のコードのみを示す。

%%↓★以下を追加
\RequirePackage{zref}
\zref@newprop{countermark}[1]{0}
\zref@newlist{marksheet}
\zref@addprop{marksheet}{countermark}

%%↓\markboxletter の実装を修正
\newcommand{\markboxletter}[2][]{%
  \stepcounter{countermark}%
  \ifx{#1%
    }{}{}%
  \else
    \ExpandArgs{nx}\zref@setcurrent{countermark}{\thecountermark}%★変更
    \zref@labelbylist{#1}{marksheet}%★変更
  \fi
  \katakana{countermark}%
  \setcounter{itermark}{1}%
  \whiledo{\value{itermark}<#2}{%
    \stepcounter{countermark}%
    \katakana{countermark}%
    \addtocounter{itermark}{1}%
  }%
}

%%↓\refmarkboxletter の実装を修正
\newcommand{\refmarkboxletter}[2][]{%
  \setcounter{tempcountermark}{\zref@extract{#1}{countermark}}%★変更
  \setcounter{itermark}{0}%
  \whiledo{\value{itermark}<#2}{%
    \katakana{tempcountermark}%
    \stepcounter{tempcountermark}%
    \addtocounter{itermark}{1}%
  }%
}

変更内容は以下の通り。

  • “★”の箇所の追加・修正。
  • LaTeXカーネルの相互参照機能は使わないので\refstepcounterを全部\stepcounterに変更した。

zrefする場合の問題点

この実装の中で、特にTeX言語初級者にとって一番難しいと思われるのは次の箇所である。

    \ExpandArgs{nx}\zref@setcurrent{countermark}{\thecountermark}%★変更

ここでは「LaTeXのcountermarkカウンタ」の現在の値を「zrefのcountermarkプロパティ」に設定しようとしている。ところが、zrefのプロパティは「トークン列を格納する」ものであるため、カウンタの現在の値(を表すトークン列)を得ようとすると、何らかの展開制御が必要になってしまう。具体的には次のようなコードを実行する必要がある。

\zref@setcurrent{countermark}{\thecountermark の完全展開】}

TeX言語の展開制御に慣れている人であれば、次のような「踏み台パターン」を用いたコードを書くのは容易であろう。

\edef\next{%
  \noexpand\zref@setcurrent{countermark}{\thecountermark}}
\next

しかし、所望の展開パターンからこのコードへ到達する過程は直観的ではなく、TeX言語初級者にとって大変な困難を伴うものであることは間違いない。zrefは別に「初級開発者向け」のパッケージではないので仕方がないことではあるが、もう少しどうにかならないものだろうか。

そこでここでは「\ExpandArgs を使う」という方法を採用してみた。\ExpandArgsは2022-06-01版のカーネルで追加された命令で「expl3の直観的な展開制御をTeX言語のコードで使えるようにする」ためのものである。今の場合は次のように考える。

  • 以下の\zref@setcurrentの呼出の引数について、展開制御をしたい。
    \zref@setcurrent{countermark}{\thecountermark}
  • 第1引数のcountermarkはそのままにしたい → n指定
  • 第2引数の\thecountermarkは完全展開したい → x指定
  • 従って、\ExpandArgs{nx}\zref@setcurrentの前に置けばよい。

その結果が本節の冒頭に示したコードになる。

    \ExpandArgs{nx}\zref@setcurrent{countermark}{\thecountermark}%★変更

まとめ

  • やっぱりzrefパッケージは便利
  • 初級者が展開制御したい場合は\ExpandArgsが便利かもしれない

チョットTypstしてみた話

Typstって何

Typstは今年(2023年)の3月頃に一般公開された、新しい組版エンジンです(現在はベータ版)。

詳細については以下の記事を参照しましょう。

zenn.dev

開発も活発に続けられていて、5月20日リリースの0.4.0版では、なんと、日本語組版のサポートが始まりました😲 今も着実に改良が加えられているようです。

というわけで、早速Typstでチョット文書を作ってみました。

Typstでナベアツする件

プログラマブル組版ソフトウェアで何か文書を作るとなれば、最初にやるべきなのはやっぱりNabeAzzですね。元ネタはもう忘れ去られていると思うので、改めて問題の内容を説明しておきます。

「NabeAzz問題」は以下の版面出力を行うプログラムを実装する問題です。

  • 与えられた正整数 n に対して、1から n までの整数の十進表記を順に空白区切りで出力する。
  • ただし、整数が「3の倍数である」または「その十進表記に数字3を含む」場合は、“アホなフォント”に切り替えて出力する。

「フォントを変えて出力する」という要件があるので組版用プログラム言語専用の問題といえます。これまでTeXPostScriptMetaPostでの実装が知られています。

やってみた

#import "zrsimple.typ"

#show: zrsimple.doc.with(
  paper: "a5",
  title: "NabeAzz with Typst",
  author: "ZR"
)

// "アホなフォント"のファミリ名
#let aho-font = "Allura"
// nabeazz(n integer) -> content
// 入力がnのときの"NabeAzz問題"の出力.
#let nabeazz(lmt) = {
  range(1, lmt+1).map(n => {
    if calc.rem(n, 3) == 0 or "3" in str(n) {
      text(size: 2em, font: aho-font, str(n))
    } else {
      str(n)
    }
  }).join([ ])
}

#nabeazz(40)

Typstでドドスコする件

TypstでNabeAzzができたとなると、当然の帰結として次はドドスコをやりたくなりますね🙃

「ドドスコ問題」は次のようなプログラムを実装する問題です。

この問題には組版的要件が含まれないため、イロイロな言語での実装が行われています。組版用言語に限ると、TeXSATySFiでの実装があります。

やってみた

#import "zrjasimple.typ"

#show: zrjasimple.doc.with(
  title: "Typst でドドスコしてみた",
  author: "某 ZR"
)

// ddsk(seed integer) -> string
// 乱数種をseedとしたときの"ドドスコ問題"の出力の文字列.
#let ddsk(seed) = {
  let (m, a) = (0x7FFFFFFF, 16807)
  let next(v) = calc.rem(v * a, m)
  let r = next(next(seed))
  let s = 0
  while s != 0x888 {
    let b = calc.odd(int(r / 0x4000))
    s = calc.rem(s * 2 + int(b), 0x1000)
    if b {"ドド"} else {"スコ"}
    r = next(r)
  }
  "ラブ注入♡"
}

#ddsk(80000)

まとめ

現在のところ、TeXやSATySFiと比べるとTypstには以下のような大きなメリットがあります。

というわけで、皆さんも、Typstでイロイロやってみましょう!💁

TeXの変態的プリミティブは本に載っているのか?

“10分でわかる”のシリーズ(なのか?)の記事を書いてみて、ふと「変態的なTeX言語のプリミティブはTeX言語の参考書においてどの程度扱われているのか」ということが気になったので、チョット調べてみた。

対象とする参考書は、自分が所有している日本語の参考書の中の、次の5冊(表の見出しで用いた略称とともに示す)。

  • 「作法」=藤田眞作『LaTeX2ε マクロ作法』ISBN 978-4-86401-010-8
  • 「独習」=吉永徹美『独習 LaTeX2e』ISBN 978-4-7981-1536-8
  • 「黄色」=ページ・エンタープライゼズ(株) 『LaTeX2e マクロ&クラス プログラミング基礎解説』ISBN 978-4-7741-1546-7
  • 「緑色」1=吉永徹美『LaTeX2e マクロ&クラス プログラミング実践解説』ISBN 978-4-7741-1758-4
  • 「ポケリ」=本田 知亮『LaTeX2e 標準コマンド ポケットリファレンス』ISBN 978-4-320-12165-2

対象とするプリミティブは、ツイッタァーでよく見かけるあの画像に書かれているもの。(ただしこの画像にあるプリミティブが全て変態的であるわけではない。)

あの画像

何を調べたのか

各プリミティブについて、まず「本の索引2にそのプリミティブが載っているか」を調べて、載っている場合は、そこで参照しているページの全てについて、「そのページ(の近辺)にプリミティブの定義(規則)が記述されているか」を調べることにより、「本のどこかにプリミティブの定義が載っているか否か」を判断した。さらに「定義が載っている」場合は「その記述が(TeX言語のフツーの学習者にとって)十分であるか否か」を自分の主観で評価した。

調べた結果

  • ◎:プリミティブの十分な定義が載っている。
  • 〇:プリミティブの定義が載っている。
  • △:プリミティブへの言及はあるが定義は載っていない(既知扱いの場合もある)。
  • 無印:(少なくとも索引では)プリミティブへの言及がない。
プリミティブ 独習 作法 黄色 緑色 ポケリ
\afterassignment
\aftergroup
\catcode
\csname
\expandafter
\fi
\futurelet
\ifvoid
\meaning
\parshape
\prevdepth
\spacefactor
\vadjust
\xdef

まとめ

変態的プリミティブは「TeX言語の参考書を1つマスターしたような学習者でも知らない」ということが起こりうるため、「愚直な解説記事を書く」ことにも意味がありそう。


  1. 「緑色」は「黄色」をマスターした後に読む本であることに注意。
  2. 索引からたどりつけるページのみを対象としていることに注意。例えば、\fiは「実際には定義が載っているページがあるが、索引にはその項目がない、あるいは項目があってもそのページを含めていない」ことが多かった。

10分でわかる \afterassignment

前回に引き続いて、TeX言語の“変態的なプリミティブ”について解説してみる。今回は\afterassignmentについて解説する。

例によって、\afterassignmentを使ったコードを“読む”ことを目標とする。(ただしTeX on LaTeX1のコードでの\afterassignmentの使用頻度はかなり少ない。)

前提知識

  • TeX言語の「トークン(token)」とは何かを理解している。
  • レジスタやマクロ(macro)についてのキホンを知っている。

とにかく \afterassignment の規則を知ろう

\afterassignmentはその名の通りで「“代入の後”に実行されるトークンを前もって指定する」という機能をもつ。具体的な定義は以下の通りである。

  • \afterassignment‹トークン›: 次に実行される代入文が実行された直後に‹トークン›を実行する。

まず書式についての注意であるが、\afterassignmentの“引数”は「その直後にある(単一の)トーク」である。例えば、

\afterassignment{ABC}

というコードの場合、この\afterassignmentの“引数”はABCではなく2{となる3

前述の定義には「代入文」という言葉がある。TeX言語学習者が「代入文」と聞いて思い浮かべるのは次のような「レジスタ・パラメタに対する代入」の文であろう。

\dimen@=42pt % 寸法レジスタ'\dimen@'に42ptを代入
\catcode`\!=13 % '!'のカテゴリコードを13に変更
\setbox0=\box\voidv@x % 0番のボックスレジスタにボックスレジスタ'\voidb@x'の内容を代入
\toks@{\TeX!} % トークン列レジスタ'\toks@'にトークン列'\TeX!'を代入

しかし「TeXにおける代入文」はこれに限らない。\let文のような「トークンの意味を変更する文」も代入文に含まれる。

\let\@tempa=\relax % '\@tempa'の意味を`\relax'プリミティブに変更する
\chardef\my@bs=`\\ % '\my@bs'の意味をchardefトークンに変更する
\font\my@rm=cmr12 at 13pt % '\my@rm'の意味をfontdefトークンに変更する

さらにいうと\defなどを用いた「マクロ定義文」も「トークンの意味を変更する文」であるため代入文の一種である。

\def\my@name{ZR} % '\my@name'の意味を(とある)マクロに変更する
\long\xdef\@gtempa{\@tempa}

とにかく \afterassignment するコードを読もう

\afterassignmentの規則が解ったので、例によって簡単なコードを“読む”練習をしてみよう。以下のコードにおいて、マクロ\myAssignCountが何をするものなのかを言葉で説明してほしい。

LaTeXフォーマットでetoolboxパッケージを読み込んだ状態を仮定する。

%% \myAssignCount{<整数レジスタ>}{<整数>}
\newif\ifmy@ok
\newcount\my@count
\let\my@mark\indent % 展開不能なマーカー
\def\myAssignCount#1#2{%
  \afterassignment\my@assign@count@a
  \my@count=#2\my@mark
  \ifmy@ok #1=\my@count
  \else \@latex@error{Bad number format}{\@eha}% エラー表示
  \fi}
\def\my@assign@count@a#1\my@mark{%
  % '\ifblank{<トークン列>}{<真>}{<偽>}'はetoolboxのマクロで,
  % "<トークン列>が空白文字トークンしか含まないか"を判定する.
  \ifblank{#1}{\my@oktrue}{\my@okfalse}}

先頭のコメントから\myAssignCountが整数レジスタと整数を引数にとることがわかるので、取りあえず次のコードの実行を追ってみよう。

\myAssignCount{\@tempcnta}{42}

この場合、マクロの最初の2行は次のようになる。

\afterassignment\my@assign@count@a
\my@count=42\my@mark

\afterassignmentを実行した直後に整数レジスタ\my@count)への代入文がある。\afterassignmentの規則に従うと、\my@countに42が代入された直後にトーク\my@assign@count@aが実行されることになる。つまり以下のコードが実行される。

\my@assign@count@a\my@mark

マクロ\my@assign@count@aの引数#1は区切りトーク\my@mark4までのトークン列だから、今の場合は#1は空になる。

\ifblank{}{\my@oktrue}{\my@okfalse}

\ifblankの第1引数は空である(何も含まない)ので、スイッチ5my@okは真になり、\myAssignCountの残りのコードの実行に戻る。

\ifmy@ok \@tempcnta=\my@count
\else \@latex@error{Bad number format}{\@eha}% エラー表示
\fi

my@okが真なので\@tempcnta\my@countの値が代入される。結局「\@tempcntaに42が代入される」ことになった。\myAssignCountの引数の値を変えても実行の流れは同じで「第1引数のレジスタに第2引数の整数を代入する」ことになりそうだ。

ではmy@okが偽になるのはどんな場合だろうか。このときにBad number formatというエラーを出力することが指示されていることから考えると、「何か入力の整数の書式に異常がある場合」と推測できる。そこで次のような異常な入力(整数値の後ろにゴミがある)での動作を追ってみよう。

\myAssignCount{\@tempcnta}{42A}% 余計な'A'がある

このとき\afterassignmentの後のコードは

\my@count=42A\my@mark

となり、この中で代入文である部分は「\my@count=42」までなので、結局次のコードが実行される。

\my@assign@count@a A\my@mark
↓(展開)
\ifblank{A}{\my@oktrue}{\my@okfalse}

今度は区切りの\my@markの前に空白でないトークAが存在するので、my@okは偽になり、ゆえに(予想通り)エラーが出る。(このときには\@tempcntaへの代入は行われないことに注意。)

以上の考察をまとめると、\myAssignCountの動作は以下の通りである。

  • 引数にレジスタと整数値を受け取り、そのレジスタに整数値を代入する。ただしその際に「整数値が正しい書式をもっているか」を検査し、異常であった場合はエラーを出力して6代入を行わない。
\myAssignCount{\@tempcnta}{42}
%→ '\@tempcnta'に42が代入される
\myAssignCount{\@tempcnta}{42A}
%→ 'Bad number format'エラーが発生, '\@tempcnta'は変わらない

TeX on LaTeXのコードにおいて\afterassignmentが使われる典型的な場面は、ここで扱った\myAssignCountのように「入力の書式を検査する」処理である。それ以外で\afterassignmentが使われていたら、それは上級者向けのトリッキーなコードであることが多い。

補足

  • 規則上は\afterassignmentの実行の直後に代入文を置く必要はなく間に処理を挟むことが可能であるが、現実のコードにおいては間の処理はないことがほとんどである7ので、初級者はその使い方だけを知っていればよい。
  • 代入文が「右辺が即値のボックスであるようなボックスレジスタへの代入文」(つまり\setbox0=\hbox{...}のような文)である場合は少々特別な規則8が適用される。(初級者は特別規則があることに留意していればよい。)

まとめ

2回目にして早くも「絶対に10分では読めない記事」になってしまった(ざんねん🙃)


  1. LaTeXのフォーマットの上で「ユーザレベルのLaTeX」の範囲を超えたTeX言語の機能を使うこと。公式には「プログラマレベルのLaTeX」などと呼ばれる。
  2. そもそもABCは「トークン列」ではあるが「(単一の)トークン」ではない。
  3. もっとも、私はこういうトリッキーなコードが実用されているのを見たことがない。
  4. 〈中級者向き補足〉\my@markは単なる区切りとして用いるので、\indentでなくても展開不能トークンであればよい。展開可能トークン(LaTeXカーネル\@nil等)は整数表記を終結しない(代入文の実行の際に展開されてしまう)ので使えない。そう考えると\my@markの意味は\relaxでもよいはずなのであるが、引数に\numexpr6*7のような「終結していない\numexpr式」が与えられた場合には\relaxは吸収されてしまうので、その対策として\indentを使っている。
  5. スイッチ(switch)」は「\newifで定義されるif-トークン」を表す用語。一般のプログラミング言語における真偽値変数の役割を果たす。
  6. なお第2引数の値がABCのように「最初から整数ではない」場合は、まずTeXの「Missing number」のエラーが出た後に所定のエラー処理が行われる。実は今の\myAssignCountの実装には不備があって、第2引数が空白の場合は「Missing number」エラーが出た後に0が代入されてしまう。これを防ぐためには、前もって「第2引数が空白でないか」の検査を入れる必要がある。
  7. 間に処理を挟む場合を勘案すると、「\afterassignmentを同時に2回以上使えるのか」(→答え:できない)や「グルーピングの影響は受けるのか?」(→答え:受けない)などの複雑な考慮事項が増えてしまう。
  8. 〈中級者向き補足〉「右辺が即値のボックスであるボックスレジスタへの代入」である場合は、\afterassignmentの引数のトークンが実行されるタイミングが通常の「代入文が実行された直後」ではなく、「右辺のボックスのトークン列の実行の直前」になる。例えば\afterassignment\X\setbox0=\hbox{ABC}の場合は\XAの直前に実行される。

10分でわかる \futurelet

TeX言語🤮でよくネタにされるものとして\expandafter\aftergroupといった“変態的なプリミティブ”がある。今回のテーマである\futureletもその“変態的なプリミティブ”の一種として紹介されることが多い。

ところが実は\futurelet「その動作を理解するだけ」であればそんなに難しいものではない。特に「他人が書いた\futureletを含むコードの動作を把握したい」という場合は、\futureletの定義さえ知っていれば「とにかく定義に愚直に従って動作を把握する」ことができる。\futureletが“読める”ことはそれだけでも大きなメリットになるだろう。

そういうわけで、本記事では\futureletの動作を理解したうえで、その知識を使って実際に\futurelet“読む”ことを目標とする。

前提知識

  • TeX言語の「トークン(token)」とは何かを理解している。
  • \letプリミティブの動作を理解している。

とにかく \futurelet の規則を知ろう

\futureletの動作を把握するには「TeXのコードをトークンの列”として見る」ことが大事である。\futureletが実行される場面において、実際に\futurelet以降のコードを“トークンの列”と考えてみよう。

\futurelet‹トークン1›‹トークン2›‹トークン3›‹トークン4›……

この前提の下で、「\futurelet‹トークン1›」という文は、以下の動作を行う。

  • この文の2つ先にあるトークン、すなわち‹トークン3›‹トークン1›\letする1

注意として、(少なくとも本記事の定義では2\futureletの“引数”に相当するのは‹トークン1›だけであるので、文を実行した後も‹トークン2›‹トークン3›そのまま残っている。従って、この文の次には‹トークン2›が実行されることになる。

補足

  • \futureletは代入文の一種である。従って、一般の代入文についての諸々の注意事項(\global修飾や\afterassignmentなど)は\futureletについても当てはまる。

とにかく \futurelet するコードを読もう

\futureletの規則を把握したので、次はその規則を使って\futureletを使った簡単なコードを“読む”練習をしてみよう。以下のコードにおいて、マクロ\Checkが何をするものなのかを言葉で説明してほしい。

\def\Check{\futurelet\my@tok\my@check@a}
\let\my@bgroup={ %←暗黙文字トークン
\def\my@check@a{%
  % \my@tokが'{'であるなら...
  \ifx\my@tok\my@bgroup \message{Yes}%
  \else \message{No}%
  \fi}

読んでみる

パッと見たところでは\Checkは引数を取らないようなので、\Check単体で展開を追ってみる。

\Check
↓(展開)
\futurelet\my@tok\my@check@a

\futureletの規則を愚直に適用すると、\futurelet\my@tokが実行されるとその文の2つ先にあるトークンを見ることになるが、今考えている範囲では、文の後にあるトークンは1つしかない。結局、\Checkの直後に何かトークンがある状態を考える必要があったようである。\Checkの直後のトークンを‹トークンA›とする。

\Check‹トークンA›…

\Checkを展開すると以下のようになる。

\futurelet\my@tok\my@check@a‹トークンA›…

改めて\futureletの規則を適用すると、\futurelet\my@tokを実行すると、2つ先にある‹トークンA›\my@tok\letされる3。その後の入力バッファは以下のようになる。

\my@check@a‹トークンA›…

これ以降は\my@check@aの展開・実行となる。\my@check@aは単純なコードなので、以下の動作をすることは容易に理解できるであろう。

  • \my@tokの意味が(カテゴリコード1の){に等しいかを調べて、そうであればYes、なければNoを端末に出力する。

ここで\futureletの実行により「\my@tokの意味」は「‹トークンA›の意味」と等しくなっている。従って、\my@check@aは実質的には‹トークンA›の意味を調べていることになる。

これで\Checkのコードを完全に“読む”ことができた。結局、\Checkの動作を言葉で説明すると以下のようになる。

  • 自身の直後にあるトークン(の意味)が(カテゴリコード1の){に等しいかを調べて、そうであればYes、なければNoを端末に出力する。

まとめ

もうこれで、読んでいるコードの中に\futureletが出てきてもコワくありません! どんどんTeX言語🤮のコードを読んでいきましょう💁


  1. 要するに\let‹トークン1›‹トークン3›に相当する動作を行う。もっと厳密にいうと「\let‹トークン1›=␣‹トークン3›」と同値になる(‹トークン3›=や空白トークンである場合まで考慮するとこの形で書く必要がある)。
  2. ‹トークン3›までを\futureletの引数とする考え方もある。なお、コードを整形する目的では「\futurelet‹トークン1›‹トークン2›」までを一つの文として扱うことが多い。
  3. つまり\let\my@tok=␣‹トークンA›が実行される。