TeXで簡単プログラミング♪
今回は TSG Advent Calendar 2015 の7日目の記事として投稿しています.昨日は SECCON お疲れ様でした.明日は cookies146氏 の予定でした.
なお,本稿は必ずしも プログラミングサークル TSG 関係者向けに書いたというわけではなく,プログラムを書くすべての人を対象にしています.要するに,あらゆるプログラマ向けの TeX on LaTeX の入門記事(のつもり)です.
LaTeX で文書作成をする多くのユーザにはあまり馴染みがないかもしれませんが,TeX はプログラミング言語です.大事なことなので繰り返します.TeX はチューリング完全性を有する関数型プログラミング言語です.
にもかかわらず,実際のところ TeX 言語 *1 を使ってプログラムを書いたことがあるという人はそれほど多くありません.
確かに,TeX はそもそも「プログラムを書くこと」を目的に開発された言語ではなく,また表現能力にも限界があるのでリアルタイムに顔認識をしてメガネをかけるお洒落な人工知能や鳴子をインターフェースとする画期的な音ゲーを開発するのに向いているとはとても言いがたいです.
しかし,理系であれば多くの人が文書作成に LaTeX を用いているはずなので,論文やレポート執筆時にするような「ちょっとした計算」であれば,LaTeX の編集画面から移動せずに計算ができるので非常に便利です.あるいは,普通の「文書を書くためのマクロ」であっても,多少の条件分岐やループ構造が使用できる方が,そうでない場合よりもずっと柔軟なものを作成することができます.さらに,「任意言語の課題」を TeX (on LaTeX) で解くと,ほとんどの言語で実装した人に対して「○○での実装は甘え」などと主張することができます(主張出来るだけです.因みに実行時間の話になると,まず勝ち目はありません.).
課題の TeX 実装はさておき,とにもかくにも TeX on LaTeX でちょっとプログラムが書けると何かと便利です.そして,その方法は意外と簡単です.本稿では,そんな TeX on LaTeX の基礎の基礎を解説し,さらに TeX on LaTeX を少しばかり拡張する拙作のパッケージを紹介した上で,いくつかの(ある程度)実用的なマクロを例に実践的な TeX on LaTeX プログラミングを提示することを試みます.
TeX on LaTeX 基礎の基礎
「TeX on LaTeX 入門」を大真面目に書いてしまうと記事の分量が爆発してしまうので,ここでは本稿で紹介するいくつかのマクロを理解するのに必要最小限と思われる「基礎の基礎」の部分だけを解説することにします.なお,本稿を読んで(もしくは本稿とは無関係な理由で)もっと TeX on LaTeX について学びたいという人のためには本稿の最後に「TeX on LaTeX の勉強法」という項を用意していくつか有用な文献を紹介しているので,是非それらを参考にしてください.
マクロ定義
TeX における「命令」はほとんどのものがバックスラッシュ(\)から始まります *2 .こうした命令は制御綴と呼ばれ,次の2種類に大別できます.
- プリミティブ:TeX に元々備わっている命令
- マクロ:ユーザが定義した命令(上記以外すべて)
ここで「TeX でプログラミングする」というのは,ほとんどの場合「マクロを定義する」という作業に他なりません.したがって,TeX on LaTeX プログラミングを知るためにはまずマクロ定義の仕方を学ぶ必要があります.TeX on LaTeX において,マクロ定義をする方法は TeX 式と LaTeX 式の2通りがありますが,ここでは TeX 式に絞って解説することにします.
TeX 式のマクロ定義はプリミティブ \def を用いて行います.この命令の書式は以下に示すとおりです.
\def〈制御綴〉〈パラメータテキスト〉{〈定義〉}
- 〈制御綴〉:定義したい制御綴(マクロ)
- 〈パラメータテキスト〉:
#1#2……
のように,必要な引数の数だけ “# + 整数” を書き並べる(最大9個) *3 - 〈定義〉:定義後に〈制御綴〉が現れたときに置換される内容.“# + 整数 ” が登場すると,その部分は 番目の引数の内容に置き換えられる
上記の書式でマクロ \macro が定義されている場合,TeX コード中に現れた \macro は〈定義〉の内容に置換されます.とはいえ,言葉で説明しただけではわかりにくいと思うので,1つ簡単な例を示しておきます.
【入力】
% マクロ定義 \def\Hello#1#2{Hello #1 on #2!} % 定義したマクロを使用 \Hello{TeX}{LaTeX}
【出力】
Hello TeX on LaTeX!
とっても簡単ですね.
これでは簡単過ぎるので,3点ほど補足をしておきます.まず〈制御綴〉に使える文字の種類ですが,基本的にはアルファベットの大文字・小文字(区別される)だけです
*4
.ただし,この制約は少し特殊な操作により変更することが可能で,実際 TeX on LaTeX においては「ユーザに使って欲しくない内部命令」には @
を含めるということがよく行われます.制御綴に @
を含められるようにするためには \makeatletter という「おまじない」を唱えます.逆に \makeatother を唱えると,@
が使用できない状態に戻ります.
% @ を含む制御綴は扱えない \makeatletter % ここでは @ を含む制御綴を扱うことができる \makeatother % @ を含む制御綴は扱えない
本稿では,以降特に断りなく @
を含む制御綴を使っていくので,各自で実行する際には \makeatletter を補うようにしてください.
2つ目の補足は「マクロは〈制御綴〉を〈定義〉に置き換えるルールを設定したものに過ぎない」ということです.要するに,マクロとはただの置換ルーチンです(TeX 的にはこの「置換」を指して展開と呼びます).そのため,〈定義〉の中に,マクロ “定義” 時点で「未定義」な制御綴が含まれていたとしても,そのマクロを “使用” 時に「定義」されていれば何の問題も起こりません.さらに言えば,〈定義〉の中に〈制御綴〉自身を含めることさえ可能です.したがって「再帰的」なマクロを定義することが可能です.
最後に(おそらくこれが最も重要なことですが)\def では定義しようとした〈制御綴〉が既に「定義済み」であったとしても,特に警告なく定義の上書きをします(プリミティブであったとしてもです).これは結構恐ろしい性質なので,定義する〈制御綴〉はよく考えて決める必要があります.
TeX の整数型:カウンタレジスタ
TeX には厳密に言えば「変数」やその「型」というものが存在しませんが,特定の型をもつ変数に「似たようなもの」が用意されていて,それらをレジスタと呼びます.レジスタにはいくつか種類がありますが,ここでは「整数型の変数」に相当するカウンタレジスタのみを扱うことにします.
カウンタレジスタは整数を保存したり,簡単な演算(四則演算)を行うときに用いられます.TeX on LaTeX プログラミングでよく用いられるカウンタは \@tempcnta と \@tempcntb の2つで,これらは LaTeX で定義されているため,特に何の手続きもせずに使用することが可能です.
もちろん,上記2つ以外のカウンタレジスタを宣言して使用することも可能ですが「初代の TeX ではカウンタレジスタは256個までしか作れなかった」という歴史的な事情から,TeX on LaTeX プログラミングにおいては「なるべくカウンタレジスタを作らない」ようにコードを書く伝統があります *5 .本稿では,この伝統にしたがってカウンタレジスタは \@tempcnta と \@tempcntb の2つだけを使用することにします.
前置きが長くなりましたが,以降カウンタレジスタの具体的な使用法を説明していきます.
値の代入と出力(表示)
カウンタレジスタへの値の代入は非常に簡単で
〈カウンタレジスタ〉=〈代入するもの〉
と書くだけです.〈代入するもの〉は数値
*6
またはカウンタレジスタです.ここで =
は省略可能であり,また〈カウンタレジスタ〉と〈代入するもの〉の間には(等号の有無にかかわらず)いくつでも空白を置くことが可能です.
\@tempcntb=2 % 以下の各行はすべて等価 \@tempcnta2 \@tempcnta 2 \@tempcnta = 2 \@tempcnta=\@tempcntb
またカウンタレジスタの値を出力(PDF や DVI に印字)したい場合には,\the 命令を使用します.
\the\@tempcnta %=> 2
ソース中に単に \@tempcnta
などと書いても出力されないので注意してください.
四則演算
カウンタレジスタを用いて四則演算を行うことができます.それぞれ次の命令を使用します.
- \advance:加算・減算
- \multiply:乗算
- \divide:除算(小数点以下は切り捨て)
使い方はどれも同じなので,\advance を用いて説明します.
\advance〈カウンタレジスタ〉〈加算するもの〉
〈加算するもの〉は今回も数値またはカウンタレジスタで,演算結果は〈カウンタレジスタ〉に代入されます.ちょうど,アセンブリにおける ADD 命令のようなものです.減算を行うには〈加算するもの〉に負の値を示すものを与えます.
また TeX on LaTeX においては「常に同じ値が代入された状態にあるカウンタレジスタ」がいくつか存在します.これらを無理に使う必要はないですが,TeX on LaTeX コード中ではよくみかけるので覚えておくとよいでしょう.
- \@ne:常に 1
- \tw@:常に 2
- \m@ne:常に -1
- \z@:常に 0 *7
スコープ
ここまでに紹介してきた「マクロ」や「カウンタレジスタ」にはスコープが存在しています.といっても,別に難しいものではなく,基本的には「{
と }
で囲まれた領域で行われた操作(定義・宣言や値の変更など)は,その外に対しては一切影響を及ぼさない」ということを覚えておけば問題ありません.{
や }
の代わりに,\bgroup と \egroup の組(LaTeX 式)や \begingroup と \endgroup の組(TeX 式)を用いても同じ結果となります.
【入力】
\def\myTeX{TeX は世界\the\@tempcnta のすばらしい言語} \@tempcnta=1 外:\myTeX { \def\myTeX{TeX は\the\@tempcnta 年近い歴史をもつ} \@tempcnta=40 内:\myTeX } 外:\myTeX
【出力】
外:TeX は世界 1 のすばらしい言語
内:TeX は 40 年近い歴史をもつ
外:TeX は世界 1 のすばらしい言語
その他の基本事項
ここまでに紹介できなかった基本事項の中で,以降に紹介するマクロを理解する上で必要な知識を,このタイミングで箇条書きにしておきたいと思います.
- マクロの〈定義〉内で改行する際には,文末を
%
でコメントアウトしないと余分な空白が入る可能性がある.ただし,行末が制御綴である場合には%
は不要である. - \relax は「何もしない」プリミティブで,バグ防止などの目的でソース中に頻出する.TeX & LaTeX Advent Calendar 2015のネタとして5日前に投稿した記事にその使い方を詳述した.ただし,本稿を読むだけであれば \relax は「おまじない」という理解でもおそらく特に問題ない.
- \let を用いると制御綴の代入を行うことができる.例えば
\let\macroA=\macroB
とすると,\macroA は \macroB と同じ働きをするようになる(等号は省略可).ここで,\macroB の定義をあとから変更したとしても,\macroA のはたらきは「代入時の \macroB と同じ」である点に注意されたい.スコープは \def によるマクロと同じである.
TeX で配列を扱う:TLArray パッケージ
TeX にも整数型(のようなもの)やスコープの概念が存在することは既に説明しましたが,それでも多くの “普通の” プログラミング言語には存在して TeX に存在しないものは色々あります.そうしたものの中で,特に「あれば便利だろうな」と思われるのが配列です.配列は「なきゃなくてもなんとかなる」類のものだと思いますが,あると様々な処理が圧倒的に簡潔に記述できます.
ということで,TeX on LaTeX で「配列のようなもの」を使うためのライブラリ・TLArray パッケージを作ってみました.
ただし,このパッケージで扱っているのはあくまで「擬似的な配列」であり,厳密な意味での配列ではありません *8 .また(技術的には可能ですが,現時点では)TLArray パッケージは配列の多次元化には対応していません.とはいえ,TeX on LaTeX プログラミングにおいては1次元配列が使えれば十分なことも少なくないはずで,さらに1次元配列として使用している分には「擬似的な配列である」ということが問題になる場面はまずないと考えられます.
では,ここからはこのパッケージの使い方を簡単に説明し,その後具体的な使用例をいくつかご紹介していくことにします.
基本的な使い方
TLArray に関する網羅的な説明は『簡易マニュアル』(doc/simple-manual-ja.pdf)に記してあるので,ここでは必要最低限の基礎的な説明をするに留めます.
まず,TLArray で定義された命令を使用するためには(当然のことながら)パッケージを読み込む必要があります.入手したパッケージをどこかパスの通ったところに配置し,使用した LaTeX ソースのプリアンブルに以下を記述します.
\usepackage{tlarray}
(現時点では)オプションはありません.これで,当該ソース中で TLArray の機能が利用可能になりました.
TLArray は基本的に Ruby の配列に近い性質を有します.すなわち,配列全体は可変長で要素として代入できるもの(以下,値)に型の制限等は一切ありません(あらゆるトークン列を代入可能).
配列を使用するには,まず \NewArray を用いて配列を宣言する必要があります.
\NewArray{〈配列名〉}
〈配列名〉には半角英数字を使用できます *9 .これにより空の配列が生成され,以降〈配列名〉に指定した名前の配列が利用できるようになります.逆に,\NewArray で宣言がなされていない配列に対しては,いかなる操作も行うことができません.
一方,一度宣言した配列を破棄するには \DestructArray を用います.
\DestructArray{〈配列名〉}
すると,指定した配列は「未宣言」と同様の状態になり,一切の操作が行えなくなります.ここで再び同名の配列を宣言した場合,その配列は「まったく新しい配列」として生成され,かつての配列からは一切の情報を引き継ぐことはありません.
次に配列から基本的な情報を得る方法を説明します.TLArray パッケージの命令を用いて何らかの値を「得る」場合,次の2通りの方法のうちのいずれかを選択することができます.
- 出力:DVI または PDF 上に印字する
- 取得:指定した制御綴(以下,ターゲット)に値を格納する
具体的に説明します.\LengthofArray(\SizeofArray という別名もある)を用いると,指定した配列の要素数(長さまたはサイズという)を「得る」ことができます.
\LengthofArray[〈ターゲット〉]{〈配列名〉}
ここで [〈ターゲット〉]
は省略可能で,省略した場合〈配列名〉で指定した配列の長さが紙面に印字されます(空の配列なら 0).逆に省略せず,[\hoge]
のようにした場合は,\hoge に長さを表す数字が格納されます.この挙動は,書式中に〈ターゲット〉が表れるすべての命令に共通です.
TLArray の配列はスタックのような使い方もキューのような使い方も両方可能で,そうした操作のために以下のような命令が用意されています.
\PushArray{〈配列名〉}{〈値〉} % 末尾に要素を追加 \PopArray[〈ターゲット〉]{〈配列名〉} % 末尾の要素を取出(当該要素は配列から除去) \ShiftArray{〈配列名〉}{〈値〉} % 先頭に要素を追加 \UnShiftArray[〈ターゲット〉]{〈配列名〉} % 先頭の要素を取出(当該要素は配列から除去)
また,配列であるからにはインデックスが存在し,任意の要素に対するランダムアクセスが可能です.インデックスは,先頭要素を 0 として続く要素に対して連続する自然数が割り当てられています *10 .
\InsertArray{〈配列名〉}{〈インデックス〉}{〈値〉} % 指定位置に〈値〉を挿入 \GetArray[〈ターゲット〉]{〈配列名〉}{〈インデックス〉} % 指定位置の〈値〉を出力または取得
最後に,配列と文字列を相互に変換する方法を説明しておきます.\StringtoArray を用いてると指定した文字列を1文字ずつ分解して配列に格納することができます.逆に\toStringArray を用いると,指定した配列の要素をすべてつなげた文字列を出力させることができます.
\StringtoArray{〈配列名〉}{〈文字列〉} % hoge => [h, o, g, e] \toStringArray{〈配列名〉} % [f, u, g, a] => fuga
だいぶ雑でしたが,以上で基本的な説明は終わりにします.『簡易マニュアル』にはもっとずっと詳しい説明を日本語で記述してあるので,困ったらそちらを参照してください.
使用例
文字列反転
まずは文字列を反転(ABC→CBA)するマクロ \ReverseStr を作成してみます.配列がないと若干の技工を要しますが,配列を使えば極めて簡単です.
\def\ReverseStr#1{% \NewArray{temp}% <- 配列tempを宣言 \StringtoArray{temp}{#1}% <- 文字列を配列に格納 \ReverseArray{temp}% <- 配列を反転 \toStringArray{temp}% <- 文字列として出力 \DestructArray{temp}% <- 配列tempを破棄 }
動作はコメントに細かく書いたので特に補足は不要でしょう.\ReverseArray は指定した配列の要素の順序を真逆にします.
実際に使ってみます.
【入力】
\ReverseStr{ジョルダン}の\ReverseStr{標準形}
【出力】
ンダルョジの形準標
これで「活版特有の誤植」を見事 LaTeX 上で再現することができました.
@Watson_DNA なんと,約50年ぶりにとうとう修正されたのですね!!第45刷ではまだンダルョジの形準標でした。 pic.twitter.com/oYvqYq8w8E
— Yusuke Terada (@doraTeX) November 29, 2015
【緩募】 LaTeXで柱のタイトルを「ンダルョジの形準標」にする方法
#TeX #LaTeX #えっ
— ☃ZR-TeXnobabbler☃ (@zr_tex8r) November 29, 2015
因みにですが,上記 \ReverseStr マクロに使用されている \ReveseArray の処理は非常に重いので,次のように定義した方がより高速に動作します.
\def\ReverseStr#1{% \NewArray{temp}% <- 配列tempを宣言 \StringtoArray{temp}{#1}% <- 文字列を配列に格納 \EachArray{temp}{\GetArray}% <- 後ろから出力 \DestructArray{temp}% <- 配列tempを破棄 }
4桁区切り
欧米では桁数の大きな数を表すとき一般には3桁区切りでカンマを打ちますが,この処理を自動で行うパッケージは世の中にいくつか存在しています(例えば comma.sty).しかし,日本語では「万・億・兆……」と4桁ずつ呼称が変化していくので,3桁区切りよりも4桁区切りでカンマを入れた方が読みやすいと考えられます.そこで,4桁ごとにカンマを入れるマクロ \CommaSep を作成します.
\def\CommaSep#1{\bgroup \NewArray{temp}\StringtoArray{temp}{#1}% \LengthofArray[\length]{temp}\@tempcnta\length\relax \@whilenum\@tempcnta>4\do{\advance\@tempcnta-4\InsertArray{temp}{\@tempcnta}{,}}% \toStringArray{temp}\DestructArray{temp}\egroup}
今度は何のコメントを入れませんでしたが,4行目以外は特に問題ないでしょう.
4行目に登場する \@whilenum は LaTeX が定義する繰り返し処理用命令で,次の書式で使用されます.
\@whilenum〈条件式〉\do{〈繰り返し処理〉}
条件式は,2つの数値またはカウンタレジスタを等号または不等号(=, <, >)でつないだもので,条件を満たしている限り〈繰り返し処理〉が実行され続けます.すなわち,ここでは末尾要素から数えて4つおきにコンマを \InsertArray で前方に向かって挿入しています.ここで,\InsertArray を行うと挿入位置以降の要素のインデックスが1つずつ後方にずれる都合上,後方から処理をした方が楽である点に注意が必要です.
では,実際に使用してみます.
【入力】
\CommaSep{12345678910111213}
【出力】
1,2345,6789,1011,1213
期待通りの結果が得られました.
マイナンバーの整合性検査
最近何かと話題のマイナンバーですが,最後の1桁が「チェックデジット」になっていることはご存知でしょうか.すなわち,12桁のマイナンバーの12桁目はその前の11桁の数字からある数式によって算出することができます.そして,そうして算出した値と実際の12桁目が一致しているかどうかでマイナンバーに誤りがないか判定することができるようになっています(誤り検出).
このチェックデジットの計算式が,難しくはないものの手計算するには些か煩雑なので,その検査を行う小プログラムを各言語で書くことがちょっとした流行になっているようです.例えば,以下の記事は Ruby での実装が紹介されています.
様々な言語で実装することが流行っているのであれば,これを TeX で実装しない手はありません.ということで,チェックデジットの整合性が取れている場合には「黒字」,取れていない場合には「赤字」で入力値を出力するマクロ \MyNumber を実装することにします.
マイナンバーのチェックデジットの計算式は上述のリンク先で説明されていますが,念のためここにも掲載しておきます.
および を次のように定義する.
- :チェックデジット以外の11桁のうち,末尾の数を1桁目としたときの 桁目の数
- : のとき , のとき
このとき,チェックデジット は以下の計算式で算出される.
ただし,上式で となった場合は に置き換える.
また,「色」を扱うため tlarray.sty の他に xcolor.sty を読み込む必要があります.
\usepackage[〈ドライバ名〉]{xcolor}
では,順に実装していきます.最初に大枠を作っておきます.
\def\MyNumber#1{\bgroup\def\mynumcolor{\textcolor{red}}% % ここでチェックデジットを検証し,整合性が確認できれば % \mynumcolor に \relax を代入する. \mynumcolor{#1}\egroup}
ここでは \mynumcolor という命令の意味を整合性が取れるときとそうでないときで変化させることで目的の挙動を達成することにします.
ここからマイナンバーの検証に入りますが,本稿ではカウンタレジスタを \@tempcnta と \@tempcntb の2つしか使わないことにしているので,計算の途中結果を配列に放り込んでおくと便利です.ということで,2つの配列を用意します.
\NewArray{mynum} \NewArray{pq}
配列 mynum にはマイナンバーそのものを格納します.一方の配列 pq には各 ()に対応する の値を格納しておくことにします.pq を使うのは処理の後半なので,さしあたって受け取った引数をそのまま mynum に代入し,その長さを \length に取得しておきます.
\StringtoArray{mynum}{#1} \LengthofArray[\length]{mynum}
さて,そもそも引数として与えられたものが12桁でなければそれは絶対にマイナンバーではないので,そうした場合には先に素通りしてもらう必要があります.
\ifnum\length=12 % チェックデジットの検証 \fi
\ifnum の説明をしていませんが,上記の使用例から察してください.条件式の与え方は \@whilenum と同様で,\fi までが条件文の有効範囲となります.今回は使いませんが,\ifnum と \fi の間に \else を書いて,条件式が偽となる場合の処理を書くことも可能です.
さて,12桁であることが確認できた場合は,mynum からチェックデジットを分離して,残りの部分は反転させておいた方がわかりやすいでしょう.
\PopArray[\checkdigit]{mynum} \ReverseArray{mynum}
あとは,チェックデジットの計算をするだけです. の値を管理するために \@tempcnta を使用します.まず, の処理をしておきます.今更こんな注意は不要だと思いますが,インデックスは0から始まるため の値と1つずれることに留意してください.
\@tempcnta\z@ \@whilenum\@tempcnta<6\do{% \GetArray[\tempnum]{mynum}{\@tempcnta}\@tempcntb\tempnum\relax \advance\@tempcnta\tw@\multiply\@tempcntb\@tempcnta\relax \PushArray*{pq}{\the\@tempcntb}\advance\@tempcnta\m@ne }%
\@whilenum の説明は既にしました.3行目で の値を \@tempcntb に格納しています.4行目で \@tempcnta の値がちょうど になるように調整をし,\@tempcnta と \@tempcntb の積を計算して結果を \@tempcntb に代入しています.さて,いま \@tempcntb が保持している値が の値なので,最初に用意した配列 pq に追加したいわけですが,ここで少し注意が必要です.何も考えずに
\PushArray{pq}{\@tempcntb}
などとすると,\@tempcntb そのものが格納されてしまいます.しかし,\@tempcntb の値は今後次々と書き換えられるので,これではせっかく計算した の値が失われてしまいます.つまり,配列 pq には \@tempcntb が現在保持している「値そのもの」を追加する必要があります.カウンタレジスタの値を取り出すには \the 命令を使うと説明しましたが,ここで
\PushArray{pq}{\the\@tempcntb}
としても,まだうまくいきません.なぜかというと,\PushArray は \the\@tempcntb をこのままの(非展開の)形で格納するので,結局 \@tempcntb の値が変化する影響を受けてしまうからです.\PushArray に「展開後の配列格納」を実行させるには * オプションをつけて次のようにしなければなりません.
\PushArray*{pq}{\the\@tempcntb}
これで,\@tempcntb の現在の値が 2 であれば,2 という数字そのものが配列 pq に格納されます.
以上の処理の後,\@tempcnta を次に処理すべき mynum のインデックスに戻します(当たり前ですが,これを怠ると無限ループに陥るので注意が必要です).
続いて, の場合の処理を書きます.前と一緒なので説明は割愛します *11 .
\@whilenum\@tempcnta<11\do{% \GetArray[\tempnum]{mynum}{\@tempcnta}\@tempcntb\tempnum\relax \advance\@tempcnta-4\multiply\@tempcntb\@tempcnta\relax \PushArray*{pq}{\the\@tempcntb}\advance\@tempcnta5 }%
ここまで処理ができると,配列 pq に必要な の値がすべて格納されているので,\SumArray 命令で全部足し合わせ,\pqsum で受け取ります.\pqsum はカウンタレジスタではないためそのままでは演算に使えないため,この値を( としての役目を終えた)\@tempcnta に代入し,11 による剰余をとります.ここで,「剰余を計算する」などという “高級な” 命令は TeX には存在しないので,四則演算を組み合わせて頑張ります.最後に, となった場合の処理もぬかりなく書き込みます.
\SumArray[\pqsum]{pq}\@tempcnta\pqsum\relax \divide\@tempcnta11\multiply\@tempcnta11\relax \advance\@tempcnta-\pqsum\advance\@tempcnta11\relax \ifnum\@tempcnta>9\@tempcnta\z@\fi
さて,これで無事チェックデジットを計算できたので,あとは mynum 配列から分離してあった入力値の12桁目(\checkdigit)と計算したチェックデジットの値(\@tempcnta)を比較し,一致していれば \mynumcolor に \relax を代入してやります.
\ifnum\checkdigit=\@tempcnta\let\mynumcolor\relax\fi
完成したマクロは次のようになります.
\def\MyNumber#1{\bgroup\def\mynumcolor{\textcolor{red}}% \NewArray{mynum}\NewArray{pq}% \StringtoArray{mynum}{#1}\LengthofArray[\length]{mynum}% \ifnum\length=12 \PopArray[\checkdigit]{mynum}\ReverseArray{mynum}\@tempcnta\z@ \@whilenum\@tempcnta<6\do{% \GetArray[\tempnum]{mynum}{\@tempcnta}\@tempcntb\tempnum\relax \advance\@tempcnta\tw@\multiply\@tempcntb\@tempcnta\relax \PushArray*{pq}{\the\@tempcntb}\advance\@tempcnta\m@ne }% \@whilenum\@tempcnta<11\do{% \GetArray[\tempnum]{mynum}{\@tempcnta}\@tempcntb\tempnum\relax \advance\@tempcnta-4\multiply\@tempcntb\@tempcnta\relax \PushArray*{pq}{\the\@tempcntb}\advance\@tempcnta5 }% \SumArray[\pqsum]{pq}\@tempcnta\pqsum\relax \divide\@tempcnta11\multiply\@tempcnta11\relax \advance\@tempcnta-\pqsum\advance\@tempcnta11\relax \ifnum\@tempcnta>9\@tempcnta\z@\fi \ifnum\checkdigit=\@tempcnta\let\mynumcolor\relax\fi \fi\DestructArray{mynum}\DestructArray{pq}% \mynumcolor{#1}\egroup}
動作検証します.
【入力】
\MyNumber{123456789012}\\ % => 不整合 \MyNumber{123456789018} % => 整合
【出力】
123456789012
123456789018
これだけではテストが少なすぎますが,とりあえず大丈夫そうです *12 .
TeX on LaTeX の勉強法
本稿で解説したのは TeX on LaTeX する上で必要な知識のうちの本の一部でしかありません.まともに TeX on LaTeX に取り組みたいと思うのであれば,ある程度体系的に解説されている文献を当たるべきでしょう.
はじめに読むべき手軽な入門文献として,TeX wiki の「マクロの作成」というページが挙げられます.分量はそれほど多くないですが,重要なことが簡潔にまとめられているように思うので,TeX でマクロを書きたいと思うのであれば一読しておくことを強くお薦めします.
その後に読むべき手頃な TeX on LaTeX の入門書の選択肢は(他の言語のそれと比較すると)それほど多くありませんが,いくつか有名なもの(主観)を紹介しておきます.
- 作者: 藤田眞作
- 出版社/メーカー: ピアソン桐原
- 発売日: 2010/10
- メディア: 単行本
- クリック: 3回
- この商品を含むブログを見る
- 作者: ページエンタープライゼズ
- 出版社/メーカー: 技術評論社
- 発売日: 2002/08
- メディア: 単行本
- クリック: 1回
- この商品を含むブログ (4件) を見る
それぞれ,TeX の界隈ではそれぞれ『マクロ作法』・『黄色い本』として知られる本です.難易度的には後者の方が高く,いきなり読むと少し難しいと感じるかもしれません.また,前者についてはその前身である『マクロの八衢』という本のオンライン版を著者である藤田眞作氏のサイトから入手することが可能です.
ただし,上述の2書籍は(残念なことに)いずれも流通量が少ないので,現在では若干入手しにくい状況にあります.もう少し入手しやすいところでは,『黄色い本』と同じ著者による以下の書籍が『もっと黄色い本』として知られています.
- 作者: 吉永徹美
- 出版社/メーカー: 翔泳社
- 発売日: 2008/03/14
- メディア: 大型本
- 購入: 3人 クリック: 21回
- この商品を含むブログ (3件) を見る
上記は TeX on LaTeX を解説する文献の紹介でしたが,もっと別のアプローチも考えられます.それは「TeX と LaTeX をそれぞれ極める」という方法です.TeX に関しては,とりあえず開発者である Knuth 先生の『The TeXbook』を読んでおけば大丈夫そうです(邦訳もあることにはあります).
TeXbook, The (Computers & Typesetting)
- 作者: Donald E. Knuth
- 出版社/メーカー: Addison-Wesley Professional
- 発売日: 1984/01/01
- メディア: リング製本
- クリック: 6回
- この商品を含むブログ (2件) を見る
TEX(テック)ブック―コンピュータによる組版システム (アスキー・電子出版シリーズ)
- 作者: ドナルド・E.クヌース,Donald E. Knuth,鷺谷好輝
- 出版社/メーカー: アスキー
- 発売日: 1992/07
- メディア: 単行本
- クリック: 40回
- この商品を含むブログ (6件) を見る
「LaTeX を極める」というのは「LaTeX の使い方を極める」という意味ではなく「LaTeX の実装について熟知する」という意味なので,LaTeX のソースコード(latex.ltx というファイルにまとめられていて,某弊愛好会がこれを読むゼミを開催しているようです)やそれを解説した source2e.pdf(英語)を読むぐらいしか適当な手段がないように思われます.このあたりは正直いばらの道といえそうですが,乗り越えれば大きなものが得られるのだろうと思います.
おわりに
なんだか偉そうに TeX on LaTeX の基礎だの勉強法だのを書いてきましたが,著者の私自身が決して TeX on LaTeX に習熟しているわけではないので,あちこちおかしなことを書いているかもしれません(見つけたら指摘していただけると幸いです).TLArray パッケージの方も(最低限度のテストはしていますが)バグがある可能性が高いので,何かお気づきの方は連絡していただけると助かります.
TeX on LaTeX はしばしば「アレ」とか「闇」とか言われますが(そもそも某著者が毎日言っているような……)それは言い換えれば「奥が深い」ということでもあります.やってみればきっと「面白い」と感じるはずです.プログラミング言語としての TeX を意識したことのなかった方が,本稿を通して少しでもプログラミング言語・TeX に興味もっていただけたのであれば,著者としてこれほど嬉しいことはありません.
Happy TeX on LaTeXing!
*1:単に “TeX” というと(どういうわけか)LaTeX と勘違いする人がいるので,LaTeX ではない素の TeX,特にプログラミング言語としての TeX を指す場合には “TeX 言語” と表記されることがあります.
*2:Windows など一部の環境においては円マークで表示されるかもしれません.
*3:実はもっと高度な指定をすることができますが,今回は使用しないので割愛します.
*4:日本語用に開発された pTeX などではデフォルトでひらがな・カタカナ・漢字の類も使用可能です.
*5:もちろん,最近の TeX にはカウンタレジスタがたくさんあるので,実際には量産して使ったとしてもまず問題になることはないと思われます.
*6:厳密には「展開すると数字列になるトークン列」です.
*7:実は,\z@ だけは厳密にはカウンタレジスタではないのですが,そう思って使っても多くの場合問題ありません.
*8:「名前参照」という手法を用いて擬似的な配列を実現しています.その基本的な「アイデア」は下記の記事で解説されています.
それでも TeX でプログラミングしたい人のための何か (8) - マクロツイーター
*9:それ以外の文字を用いても正常に動作する場合がありますが,環境依存であり,あくまで「仕様は未定義」です.
*10:Ruby の配列を目指しているので,実は負数表記にも対応しています.詳細は『簡易マニュアル』を参照してください.
*11:なので,その気になればもっと DRY に書けるでしょう.
*12:一応内部値等も確認はしました.もしバグがあるようなら指摘していただけると幸いです.