マクロツイーター

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

完全解説! TeX言語のプリミティブな制御記号全部まとめ

せっかくの「TeX言語GW特別キャンペーン🍀」であるが、誰も「完全解説! TeX言語のプリミティブな制御記号全部まとめ」の記事を書かないので、仕方がなく自分で書くことにした(ざんねん🙃)

必要な前提知識

  • TeX言語の「プリミティブ」「制御綴」とは何かを知っている。

「プリミティブな制御記号」の定義

制御記号(control symbol)というのは「英字(カテゴリコード11)以外の文字1つからなる名前1の制御綴」のことであり、従って「プリミティブな制御記号」とは「制御記号で表されるプリミティブ」を指すことになるだろうが、そもそもカテゴリコードの設定や制御綴の意味は可変であるためこれをそのまま定義とするのは都合が悪い。INIモードの初期状態を前提にするのも一つの手であるが、LuaTeXのように「初期状態で制御綴に割り当てられていないプリミティブがある」ようなエンジンもある。

そこで、まず条件を緩めて文字の種類は問わないことにする。さらに「制御綴名」の代わりに「プリミティブ名」を考えることにする。「プリミティブ名」というのは「意味がプリミティブであるトークンに\meaningを適用して得られる文字列2」のことであり、例えば「\expandafterプリミティブ」などと説明する場合の「expandafter」がプリミティブ名である。この名前はプリミティブごとに決まっていて不変である。そこで、「プリミティブな制御記号」の定義を「プリミティブ名が1文字のプリミティブ」と定めることにしよう。

「プリミティブな制御記号」の一覧

というわけで、TeXの「名前が1文字のプリミティブ」を全て列挙すると以下のようになる。

プリミティブ説明
\␣(U+0020)通常の欧文空白
\-任意(discretionary)のハイフン
\/イタリック補正

この結果は(メジャーエンジンの範囲3では)どのエンジンでも変わらない。ここで現れる文字(空白、- /)はどれも(一般的なフォーマットで)カテゴリコードが11以外であるので、結果的に「名前が1文字」のプリミティブは制御記号的なものしかないことがわかる。

「プリミティブな制御記号」の解説

本記事の目的は恐らく「一覧がどうなるか」であったはずで、そうすると記事はもう完成していることになるが、せっかくなので各プリミティブについて「具体的な動作の詳細」を解説をしてみる。

なおこの3つのプリミティブは「そのまま4LaTeXカーネルの命令になっている」くらい有名なので、基本的な使い方については周知であるとする(ざんねん🙃)

\␣:通常の欧文空白

名前がASCII空白1文字であるプリミティブ5は「段落に欧文空白としての水平空きを出力する」機能を持つ。

  • 現在が垂直モードである場合は水平モードに移行する6
  • 現在の「欧文空白グルー値」を以下の要領で取得する:
    • グルー値パラメタ\spaceskipの値がゼロ(0pt)である場合は現在のフォントのfontdimenを調べる。「‹fontdimen2の値› plus ‹fontdimen3の値› minus ‹fontdimen4の値›」が「欧文空白グルー値」である。
    • \spaceskipが非ゼロの場合はその値が「欧文空白グルー値」である。
  • 大きさが「欧文空白グルー値」の水平空きを出力する。

\-:任意のハイフン

\-は「普段は何も出ないが、行分割時には現在のhyphencharの文字を行末に出す」ようなdiscretionaryを出力する。

  • \hyphenchar\fontの値を取得し「hyphencharの値」とする。
  • \discretionary{\char‹hyphencharの値›}{}{}を行う。
    • ただし「hyphencharの値」が有効な文字コードの範囲7の外であれば第1引数を空にする。

ただしLuaTeXでは手順が少し異なる。

  • 現在の言語の\prehyphencharの値を取得し「prehyphencharの値」とする。
  • 現在の言語の\posthyphencharの値を取得し「posthyphencharの値」とする。
  • \discretionary{\char‹prehyphencharの値›}{\char‹posthyphencharの値›}{}を行う。
    • ただし「prehyphencharの値」が有効な文字コードの範囲外であれば第1引数を空にし、「posthyphencharの値」が有効な文字コードの範囲外であれば第2引数を空にする。

\/:イタリック補正

\/は直前の文字に対するイタリック補正の水平カーンを出力する。

  • 垂直モードではエラーになる。
  • 数式モードでは常にゼロの水平カーンを出力する。
    ※数式モードでは文字のイタリック補正の分のカーンが原則常に出力される8のでそれ以上空ける必要がない。
  • 水平モードの場合は実際にイタリック補正のカーンを出力する:
    • 直前に出力したものが文字でない場合は何もせず終了。
    • 直前に出力した(フォントの)文字のイタリック補正の値を取得する。
      ※TFMに記録されている9
    • 取得した値の水平カーンをイタリック補正として出力する。

まとめ

というわけで皆さん、もっともっとTeX言語🤮の記事を書きましょう!💁


  1. 制御綴の「名前」にはエスケープ文字は含めない。
  2. ここで得られる文字列自体は\expandafterのような制御綴の形の文字列であり、正確にはここからエスケープ文字を除いたものをプリミティブ名とする。なお、少なくとも元祖TeXではINIモードの初期状態において「全てのプリミティブについて、それと同名の制御綴にプリミティブが紐づけられている」(例えば\expandafter制御綴の意味はexpandafterプリミティブである)が成立している。
  3. 元祖TeX、pdfTeX、XeTeX、LuaTeX、(e-)(u)pTeX。
  4. \-についてはLaTeXカーネルで再定義が行われていて少し挙動が異なる。
  5. ちなみに「名前がASCII空白1文字である制御綴」のことは「制御空白(control space)」と呼ばれる。
  6. ちなみに「空白トークンを実行した」場合は、現在が水平モード以外(垂直モードまたは数式モード)である場合は何も起こらない。
  7. LuaTeXでは1~0x10FFFF(0は無効扱い)、XeTeXでは0~0x10FFFF、イマドキのe-upTeXでは0~0x2E7F、それ以外は0~255。
  8. OpenTypeの数式フォントを利用する場合は「数式イタリック補正」のデータに基づいて自動的に(常に)補正が行われる。
  9. XeTeX・LuaTeXでOpenTypeを使う場合もそれに相当するデータが存在する。

徹底解説! TeXエンジンがTectonicであるかを判定する方法

Tectonic、スゴイですね!(多分🙃)

というわけで、本記事では「今動いているTeXエンジンがTectonicであるかを判別する方法」について解説します。

必要な前提知識

  • Tectonicが何か知っている。
  • そもそも「エンジンを判別する」とはどういうことか解っている。
  • 一般的な「エンジンを判別する」ための方針について知っている。

いきなり説明

エンジン判別の一番基本的な方法は「そのエンジンだけの特有の拡張プリミティブ」に注目して、それが定義済であるかを\ifxなり\ifdefinedなりで判断するというものです。

では「Tectonicだけの特有の拡張プリミティブ」はあるのかというと、現状ではただ1つだけ存在します。それは\TectonicCodaTokensというプリミティブです1。トークン列レジスタの一種のようですが、この機能自体はどうでもよくて、要するに「これが定義されていること」が重要です。\TectonicCodaTokensという制御綴が定義済かどうかを調べることで「Tectonicであるか」を判定できます。

% \ifx で判定する方法
\ifx\TectonicCodaTokens\@undefined\else
  % Tectonicである
\fi

% \ifdefined で判定する方法
\ifdefined\TectonicCodaTokens
  % Tectonicである
\fi

もちろん、もっと厳密に判定したいならプリミティブ判定をすることもできます。\ifpdfprimitiveを使うのが簡単ですが、ここで「TectonicはXeTeXの一種である」ことに注意が必要です。XeTeX(とLuaTeX)では\ifpdfprimitive\ifprimitiveに改名されているので、「まずXeTeXであることを判別してから\ifprimitiveを使う」ようにします。

% if-均衡の維持のため少し複雑...
\let\ifTemp\iffalse
\def\next{\let\ifTemp\ifprimitive}
\ifdefined\XeTeXversion \next \fi
\ifTemp\TectonicCodaTokens
  % Tectonicである
\fi

もちろん、“string-meaningテスト”を使うこともできます2

プリミティブ判定の方法については別の記事で詳しく解説しました。

まとめ

ジェミニ氏もチャッピ~氏もデタラメな答えを返してきたのでムシャクシャして書いた(めでたしめでたし🙂)


  1. \TectonicCodaTokensはTectonicのかなり初期の0.1.6版[2017-07-10]から存在するようです。
  2. \TectonicCodaTokens\meaningを適用して得られる文字列は普通に\TectonicCodaTokensです。

スゴイLuaTeXの新プリミティブ \begincsname を徹底紹介

LuaTeX、スゴイですね!😍

というわけで、本記事ではLuaTeXのスゴイ(多分🙃)拡張プリミティブである\begincsnameについて解説します。

必要な前提知識

  • \csnameが何か知っていること。

いきなり説明

  • \begincsname‹トークン列›\endcsname[展開可能]: 「\csname‹トークン列›\endcsname」と同様に、トークン列を完全展開して得られる文字トークン列1を名前とする制御綴に展開される。ただしその制御綴の意味が未定義である場合は代わりに空に展開され、この場合に「\relax化」が発生しない2

つまり従来は

\ifcsname‹トークン列›\endcsname
  \csname‹トークン列›\endcsname
\fi

のように書かないといけなかった(しかもこれは先頭完全展開可能でない)のが\begincsnameを使うと一回展開で済む形になります。

まとめ

スゴイ(多分🙃)


  1. 完全展開した結果に制御綴が混じっている場合はエラーになります(\csnameと同様)。
  2. さらに「文字列プールの消費」も発生しません。この辺りの仕様は\ifcsnameと同じです。

\noexpandを完全攻略しない理由

今年も例によって始まりました🌸

というわけで、早速\noexpandについて徹底解説する記事を書きました。

記事中でも述べられていますが、この記事の大きな特徴は「\noexpand定義」特に「\noexpand‹トークン›を一回展開した結果がどうなるかの(一般的に通用する)規則」を示すことを敢えて避けているということです。

ソレの地獄絵図

\noexpandの定義」を追求するのは危険です。次のような例をみてみましょう。

[test.tex]
% plain TeX
\def\xA{\noexpand\xB}
\def\xB{\noexpand\xC}
\def\xC{\noexpand\xD}
\def\xD{\noexpand\xE}
\def\xE{\noexpand\xF}
\def\xF{pt}
\dimen0=1 \xA\relax
\message{RESULT=\the\dimen0}
\bye

このplain TeXコードには\noexpandが大量にあります。もし仮にこれらの\noexpandないとすると、\xAは結局ptに展開されるため\dimen0レジスタに1ptが代入されて、その結果RESULT=1.0ptが端末に表示されます。それでは元々の\noexpandがあるコードを実行するとどうなるでしょう。

test.texをetexコマンド(pdfTeXエンジン)で実行すると実際にRESULT=1.0ptが表示されました。

This is pdfTeX, Version 3.141592653-2.6-1.40.29 (TeX Live 2026) (preloaded format=etex)
 restricted \write18 enabled.
entering extended mode
(./test.tex RESULT=1.0pt )
No pages of output.
Transcript written on test.log.

ところが、同じtest.texをtexコマンド(元祖TeXエンジン)で実行すると「! Illegal unit of measure」のエラーが発生します。

This is TeX, Version 3.141592653 (TeX Live 2026) (preloaded format=tex)
(./test.tex
! Illegal unit of measure (pt inserted).
<to be read again>
                   p
<to be read again>
                   t
l.8 \dimen0=1 \xA
                 \relax
?

確かにpdfTeXでしか使えない単位(pxndなど)は存在しますが、ここで使っているのはptなので元祖TeXでだけエラーになる理由を説明するのは困難1でしょう。

この例の特に厄介なところは、このコードに「一見重要そうでない変更」を加えただけでエラー発生の有無が変わることです。

  • 元のtest.texでは\xAからptへの展開を“6段”(\xAから\xFまで)にしていますが、これを“5段”(\xEまで)に減らすとetexとtexの両方で通るようになります。
  • 逆に“7段”(\xGまで)に増やすとetexとtexの両方でエラーが発生します。
  • 元の“6段”に戻した上で\xFの定義をptからmmに変えると、再びetexとtexの両方で通る(RESULT=2.84526ptと表示)ようになります。

これを前提にした上でもし「一般的な\noexpandの定義」を用意しようとするなら、当然その定義はこの極めて不可解な結果を説明できるものにする必要があります。それが絶望的に困難であることは容易に想像できます。仮に、そういう定義ができたとしてもそれは極めて複雑な規則になるでしょうから、そもそも一般原則を習得することのメリットがかなり乏しくなります。\noexpandが実際に使われるパターンが数少ないことも加味すると、一般原則の追求は賢明ではないのです。

\relax する話の真相

TeX言語に詳しい人なら「\noexpand‹トークン›」の展開結果について、「意味が “一時的に”\relax(あるいは“\relaxモドキ”2)に置き換わった状態の‹トークン›になる」という説明を聞いたことがあるかもしれません。この説明自体は間違ってなくて、実際に展開結果を\showで調べた結果とも整合します。

% "\noexpand\jobname"を1回展開して得られるトークンの意味を調べる.
\expandafter\show\noexpand\jobname
%==> "\jobname=\relax"と表示される

この定義の問題点は「“一時的”が具体的にいつまでか判らない」ということです。意味が\relaxに置き換わる効果がいつまで持続するのかが判っていないと、肝心の「具体的なTeXコードの動作を導出する」という目的には役に立ちません。

実際に“一時的”の範囲を明確にしようとすると、TeX処理系の個々の内部動作の手順を把握する必要があります。先ほどの“地獄絵図”において元祖TeXとpdfTeXで動作が異なる原因は「サポートする単位の種類がpdfTeXの方が多いため、単位をスキャンする際の内部動作が両者で相違する」からです。「単位をスキャンする際の内部動作を把握する必要がある」なんて事態はほぼ誰も望まないところでしょう。

ソレの豆知識

このように「一般的な状況での\noexpand‹トークン›の展開結果」の把握は困難なわけですが、実は‹トークン›展開不能である場合に限っては極めて単純明快な規則があります。

  • ‹トークン›展開不能であるとき、\noexpand‹トークン›の一回展開の結果は‹トークン›になる。

これは知っておく価値が十分にあると思います。元のQiitaの記事で扱った「トークンの展開可能性を判定する条件文」もこの規則が根拠になっています。

% ‹トークン›が展開不能ならば
\expandafter\ifx\noexpand‹トークン›‹トークン›

この条件文は「\noexpand‹トークン›の1回展開」と‹トークン›\ifxで比較していますが、ここで‹トークン›が展開不能であれば前者は単なる‹トークン›なので当然後者と\ifx-等価になる3わけです。

また、当該の規則を知っておくと、次のような変則的な事例についても結果を予測できます。

% plain TeX
\noexpand\$
\bye
% ※plainでの"\$"の定義はchardefトークンの「\char"24」

これの実行結果はどうなるでしょうか。chardefトークンは展開不能であるため\noexpand\$の展開すると\$になるため普通に「$」が印字されます。

まとめ

というわけで、今年も皆さん💁


  1. さらに奇妙なのは、エラーコンテキスト表示から判るように\xAptまで展開されているということです。つまり、スキャンしている箇所に実際にはptがあるのに「単位がない」というエラーになっています。
  2. 実行時の動作\relaxプリミティブと全く同じだが\relaxプリミティブと\ifx-等価にならない特殊なトークンのことを指します。
  3. 逆に‹トークン›が展開可能の場合は、\noexpand‹トークン›の1回展開は「意味が“\relaxモドキ”であるトークン」になります。展開可能である以上‹トークン›の意味が“\relaxモドキ”では決してありえないので、判定は確実に偽になります。

今年もまたもや Merry TeXmas! ― \end{texadvent2025}


TeX & LaTeX Advent Calendar 2025
*  *  *

アドベントカレンダー完走!

[greetings]
--*--+--*--+--*--+--*--+--*--+--*--+--*--+--*--+--*--+--*--+--*--+--*---
________,_,_T,_e,_X,_m,_a,_s,__=_,_G,_r,_e,_e,_t,_i,_n,_g,_s,___________
_8=[=[96978570890987108370858683790987:7284866709'[0405060607\\63_929495
]',97096810837085868379'\\84907867807792'..68:679085700910..'94'70796910
:7284866709'\87','95'1010707969;9697846866790910;969739848509...10837085
868379018566677770.6880796866850992...9410707969;969739858909...10837085
868379018570890939848509...1010707969;9697398668098710837085868379018685
7125.687366830985807986786770830987,18231010707969;96973966830966,67,68,
691083708586837992[66]=67,[68018083'']=6994707969;9697397078096810837085
8683790197098710837085868379'\\'..68..'92'..87..'94'70796901707969;9677=
77817072;9649,52,51,55,36=77.49,77.52,77.51,77.55,77.36;9685,8969=52'01\
85'1249'//'110918-49'\79'1063171249'/11'110918-49'11/'1011'11/',51'1726'
1251'6671'1251'3439'9684,81,8670=8512'\79',18-51'\17\2018',49'\\8692'110
989696318/3986681011'94'9691,89,8477=856317,846317,49'"'110986701249'\\'
11360952'"\\'1012360981-52'"'10106317/39848511'"'967977,80=9111'\79'1189
,867012856318/'01'1249'95'/'\87'1249'\\'1152'\85\79'/'\79'1249'\\'113609
811012360981-52'040511293133_65'10968574,7886=36'8574857770'118911':'118
9118477,36'78867171777083'118911':'118911'"04'113609896963181011'"'96668
3=0909857411098911','11891178861063-1812788611098911','11891185741063-18
12''1011098911','1063-1810/396683;968589=4992'52';52=09806318/3985891249
'_'110955'52'/39707809'70788173'101011'_'1249'11'110955'52'/39707809'848
583807972'101011'11'106317/39848594837085868379018911'04747881808385'119
111'"3379808873708370/857089786684:18.17.17"'119111':'1191110949'11'12'8
57089786684'1011797711'0484738088'119111':'119111'857089786684'119111'.'
119111'8874857309'1189116683118911'10'117977118589118911-1801707969;9697
8586837909841074710184==''857370790183708586837901707969;9681,67,85=7781
7072.786685687309846866790910,8410747101810185737079;9678=0981.788671717
77083018083'391717'10:7284866709'6309.1009.1009.1005','06180618061906190
6200620'1074710478==230185737079;85=09[[\697071\685392068494\697071\6846
92068494\697071\683592068494]]10:718083786685098570890981.85748577700180
83"47746870"10,78,67018083"929594"10707969;707969;84=098510667969[[\6970
7174797068807780839268469492415346459492\684694\79806970[8468667770=1823
,838085668570=-1822]668509192522,1825251092\8574769192\69866876[77668672
73747972,7866727468736685=517069]9494;\79806970[8468667770=1825,83808566
8570=1822]6685092425,1825251092\8574769192\8088779494;\79806970[84686677
70=2019.202122]668509182517,1825171092\78667670678089[178185][77]92\8468
84798088786679[67806990=5673748570]94\846884798088786679[78867171777083=
6846,67868585807984=518090667735778670,66837884=408077697079838069]94;\7
9806970[8468667770=21.19,85708985=40807769]668509182517,2017251092\71807
98584817068925053358386847352858380767094\685394;\79806970[8468667770=19
.21,85708985=5673748570]668509182517,21171092\71807985848170689253705701
40908370014966727077776694\683594;\677072747992846880817094[8473747185=9
2092222,22221094,8468667770=192217,77747970018874698573=20.21246781]\685
2\70796992846880817094]]8083[[\71747777[40807769]09182517,18251710687483
687770[836669748684=181917];\71747777[37747840836690]09182019,1822171070
777774818470[8901836669748684=1825,9001836669748684=1921];\71747777[3774
7840836690]09191925,1822171070777774818470[8901836669748684=1825,9001836
669748684=1921];\69836688[37747840836690,77747970018874698573=1820.22678
1,7774797001686681=8380867969]09182025,191919108580[808685=2017,7479=182
217]09191919,19191910;]]84=09[[068492\77708595\7980708981667969\697071\:
04180419042004219209.04180419,.042004211094\697071\;04180419042004210422
04230424042592..6880798583807784\:0418041904200421667969\:04220423042404
25..\:94\697071\'92192294\697071\"92242294\89697071\68529295\69836688[71
747777=517069]\:20222419\;202224192022252522172525\;2217252522\'25252225
2525\;222525252417252524202519\;242025192420251924192518\;24192518232325
2223182520\;231825202323242523222419\;221724192022241920222419--68906877
70;95\69836688[71747777=5673748570]\:20222324\;20222324201923\"20192417\
;20192417201924\'20222420\;202224202217242123222420\;23222420232524\'232
52417\;23252417232523\"23222324\;221723232022232420222324--6890687770;95
\71747777\:2421251868748368777009.172010;95\71747777[5673748570]\:242125
1868748368777009.171910;9494\69806886787079856877668484[8487727966787084
]928485667969667780797094\86847081666876667270927180798584817068,8574769
1,846884798088786679,857476916986687684,857476917774797284-8088778494\81
66727068807780839237668376408370707994\677072747992698068867870798594\67
7072747992857476918174688586837094[89=186781,90=186781]\8684706684678086
79697479726780890917,171083706885667972777009202317,20231710;0684\707969
92857476918174688586837094\70796992698068867870798594]]10:71808378668509
85018083'',841083708586837901840170796901668484708385096866777767666876.
837072748485708309'80817079_83706669_71747770',970979109670=9271=6684847
08385097480.808170790979,'83'1010,79=799474710179:786685687309'06.859081
05'1085737079;70.837066697083=97091083708586837901858683790970.71:837066
6909'66'1010707969;70778470;70.837066697083=9709108370858683790170.71:83
70666909'77'10707969;707969;8370858683790170017079691010]=];_88,_42_='8'
_['\108\111\97\100'](_8["\108\111\119\101\114"]('\76\79\65\68\40_8:\071'
..'\83\85\66\40\'\92\78\',\'\'\41:\71\83\85\66\40\'\40\37\68\37\68\41'..
'\',\70\85\78\67\84\73\79\78\40_\41\82\69\84\85\82\78\32_.\67\72\065\82'
..'\40\84\79\78\85\77\66\69\82\40_\41\04331\41\69\78\68\41:\71\83\85\66'
..'\40\'\\127\',\'\76\79\67\65\76\32\'\41:\71\83\85\66\40\'\\128\',\''..
'\70\85\78\67\84\73\79\78\32\'\41,_88\41\40\41'))(_88)--'_\"-:2025\12\25
_,_8,__8___,_88,____=________--_._8_:,_8_8__,__8._-__88_._8__,_8_-_-..__
[texmas.typ]
#import "@nowhere/texmas:1.0.0": *
#show: texmas.with(
  title: "Merry TeXmas",
  muffler: "#FF0000", // HTML color
)
*See You _Next_ Year!*
(コンパイル方法)

というわけで、14回目の開催となる TeX & LaTeX Advent Calendar 2025 も、素敵なTeXネタを途絶えさせることなく無事にクリスマスの日を迎えられました。今年の参加者は全部で19名でした。参加者の皆様に心からの感謝を捧げたいと思います。

今年の重点テーマは「(La)TeXをカンタンにする方法」でした。(La)TeXの世界ではカンタンなものはツマラナイと敬遠されがちです。しかし(La)TeXでの製作の価値は「どれほど難しいことをしたか」ではなく「何を作ったか」の方にあるはずです。カンタンで誰でもできる方法の紹介はとても価値があることです。時には「誰でもカンタンに使える」ようにするために難しいことをする必要もあるでしょう。Advent Calendarは終わりましたが、「LaTeXをカンタンにする」ことをいつも心の片隅に置いてください。

TeX & LaTeX Advent Calendar 2025 を楽しんでくれた皆さんに、

ありがとう!

そして

2026年のパズル年賀状

今年の年賀状。

以前に述べたとおり、年賀状にはその年の数に関連した数学パズルを載せるのが通例である1。今年は令和☃年なのでゆきだるま⛄にちなんだ2虫食い算になった。

肝心のパズル問題の部分の文字が(例によって)小さくで読みづらいが、以下のように書かれている。


以下の条件に従って虫食い算を解きなさい。

  • この問題では 8 をで表記している。
  • 各横列のどこかに必ずが入る。
  • 各横列・縦列・(45°方向の)斜め列について2つ以上のが入ることはない。

(問題の図)

※灰色マスは列確認用で数字は入らない。

一番上にあるのはアルバニア語ヴィスクチ文字表記)での新年の挨拶。


  1. Typstの和文用のマトモなテンプレートの作製に時間を取られている人が多いためか、年を追うごとに年賀状で数学パズルを見ることが少なくなっているようで感じる。
  2. ⛄「なお『出題の内容をみる限り⛄である必然性が全くない』というツッコミに関しては地中に埋めてしまうことにしましょう」

“TeX”でナベアツする件とか、暮れのごあいさつとか

年末なので、ぼんやりと某キ~タを眺めていたら、こんな記事を見つけた。

qiita.com

懐かしい🙂……おや?

ある日インターネットを徘徊してると「ナベアツのコードを自力で書けない人は,そのプログラミング言語を使える人たちの集合には属さない」という言説?を目にしました.

チョット調べたところ、確かにそういう言説を口にしているアレな人🙃が存在するようである。

TeX”でナベアツをしたい話

さらにその記事を読んでいくと「LaTeXをどこまで許容するのか」について議論されていた。

はじめは生の TeX 言語のみで(つまり plain TeX で使えるコードのみで)ナベアツを実装しようとしていたのですが,

これを読んで、昨年(2024年)の春にあった​「“TeX”とは何を指すのか」​の議論を思い出した。

zrbabbler.hatenablog.com

このときの議論に従うと、「LaTeXTeXではない」という見解の下では、“TeX”といえそうなものはiniTeXしかない。となると今までの議論の当然の帰結として、iniTeXで動くナベアツのコードが書けないと「自分がTeX言語を使える人である」と主張できなくなってしまう。

TeXができない人になるのはアレなので、iniTeXでナベアツをやってみることにした。

ただし、「2次方程式の解の公式」が固定された単発の文書として成立するのに対して、ナベアツは入力(上限の整数値)をもつ“機能”である。慣習が何も定まっていないiniTeXに対してライブラリのインタフェースを設計するのは困難である。そこで、ナベアツ専用のTeXのマクロパッケージ、名付けてNabeAzzTeXをつくることにする。もちろんplainなどの既存の実装は一切使わず、また拡張プリミティブをもたない元祖TeX上で動作させるという条件を課す。

TeX”でナベアツをしてみた話

構想が決まったので、早速NabeAzzTeXをつくってみた😃

[nabeazztex.tex]
% レイアウト設定
\hsize=97mm \vsize=159mm
\hbadness=1000 \vbadness=1000
\baselineskip=15pt
\parfillskip=0pt plus 1fil

\begingroup
  % コード設定
  \catcode`\{=1 \catcode`\}=2
  \catcode`\#=6 \catcode`\@=11

  % フォント設定
  \global\font\NA@normal=cmr10
  \global\font\NA@aho=cmfi10 at 12pt

  % 変数定義
  \global\countdef\NA@limit=100
  \global\countdef\NA@cnta =101
  \global\countdef\NA@cntb =102

  %% \NA@main
  % 1から \NA@limit までナベアツを出力した後ジョブを終了する.
  \gdef\NA@main{%
    \NA@normal % フォント設定
    % ナベアツする
    \par % 改段落
    \NA@cnta=1
    \NA@loop
    % ジョブを終了する
    \par \end
  }

  %% \NA@loop
  % ナベアツのループ処理.
  \gdef\NA@loop{%
    % 整数がアホかを判定する
    \chardef\NA@isaho=0
    \NA@cntb=\NA@cnta
    \divide\NA@cntb 3 \multiply\NA@cntb 3
    \ifnum\NA@cnta=\NA@cntb
      \chardef\NA@isaho=1
    \fi
    \expandafter\NA@has@three\the\NA@cnta3\relax
    % 整数を出力する
    \ifnum\NA@isaho=1
      {\NA@aho \the\NA@cnta}%
    \else
      \the\NA@cnta
    \fi
    \ % 区切りの空白
    % 末尾再帰
    \ifnum\NA@cnta<\NA@limit
      \advance\NA@cnta1
      \expandafter\NA@loop
    \fi
  }

  %% \NA@has@three
  % 文字列が3を含むかの判定の補助マクロ.
  \gdef\NA@has@three#13#2\relax{%
    \ifx!#2!\else
      \chardef\NA@isaho=1
    \fi
  }

  %% '\\'が無視されるようにする
  \gdef\\{}
  % ジョブ開始時に自動実行される処理
  \global\everyjob{%
    % 数字を読み取って \NA@main を呼ぶ
    \afterassignment \NA@main
    \NA@limit=%
  }
\endgroup

% おしまい
\dump

以下で使用法を説明する1。独自マクロパッケージなのでまずフォーマットを作成する必要があるが、これは以下のコマンドで行える。

tex -ini nabeazztex.tex

これでフォーマットファイルnabeazztex.fmtが生成されるが、以下ではこのファイルをカレントに置く2ことを前提とする。

NabeAzzTeXの入力ファイルは以下のように「ナベアツの入力となる整数値」だけが書かれたテキストファイルである。

[input.tex]
40

この場合、以下のコマンドで入力ファイルをタイプセットできる。

tex -fmt=nabeazztex input.tex

入力の整数値は(一般のTeXの仕様として)コマンド行(または対話モード)でも与えることができるが、この場合は「\から始まらない入力文字列はファイル名とみなされる」という問題がある。このため、NabeAzzTeXでは\\が無視される(空に展開される)ようになっている。

(コマンドプロンプトの場合)
tex -fmt=nabeazztex \\40
(bashの場合)
tex -fmt=nabeazztex '\\40'

何れの場合もナベアツの結果が書かれたDVIファイル3が出力される。

出力結果

というわけで、無事に「TeXが使える人」になることに成功した(あんしん🤯)

まとめ

今年も一年、ありがとうございました!

いつものやつ🙃

ZR「というわけで、来年も当(くだらない)ブログをよろしくお願いします!」
*「アレレ、いつの間にか『除夜のカントカ』が定番ネタ扱いになってる😲」


  1. TeX Live等の現代的なTeXシステムを前提とする。
  2. まあ多分、ちゃんと“インストール”して使いたい人はいないでしょう🙃
  3. 出力DVIファイルはA5判縦の用紙を前提としたレイアウトになっている。用紙サイズ設定の情報は含まれないので、例えばdvipdfmxでPDFに変換する場合には-p a5のオプションを指定する必要がある。