マクロツイーター

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

整数表記を内部整数に変換する

TeX の構文規則では「整数」は次の 2 種類がある。

  • 内部整数 (internal integer): 要するに TeX での変数や定数を表すもの。\year\hyphenchar<フォント>\lastpenalty\count<整数>、countdef トークン(LaTeX\@tempcnta 等)、chardef トークン(「大きな定数を定義する話」で述べたように整数定数として使われる)、\numexpr<整数式>、等。
  • 整数表記 (integer denotation): 数字列で表される定数。10 進、16 進、8 進、文字(符号値)表記の 4 種類がある。

この 2 種類の「整数」は構文規則上の扱いが微妙に異なる。従って、例えば以下のように「定数」を表すトークンを定めた時、それがどちらの方法で定めたものなのかを常に意識する必要がある。


\chardef\answer=42 % 内部整数
\def\theanswer{42} % 整数表記に展開されるマクロ
% 下の 2 つは同じにならない
\advance\@tempcnta\answer \ifnum\@tempcnta<100 ...\fi
\advance\@tempcnta\theanswer \ifnum\@tempcnta<100 ...\fi

「内部整数」を「整数表記」(数字列)に変換することは \the を使うとできる。((つまり、\the(内部整数) はその整数の標準の 10 進表記に展開される。ちなみに、\number<整数> も同様の機能をもつが、これはどちらの表記の整数にも適用可能で、これは他の型(寸法等)からのキャストも取り扱える。))そこで、ここでは逆の方向、つまり「整数表記」を「内部整数」に変換する(仮に「内部化」と呼ぶ)方法を模索する。(ただし展開限定文脈を仮定する。)

例えば次のような状況を考える。LuaTeX において、整数値を返す Lua の関数を作成して、それを LaTeX の命令(TeX のマクロ)として使えるようにしたいとする。単純に「整数表記」を返すインタフェースで実装するならば、tex.write() ((こういう場合は、tex.print() より tex.sprint()tex.write() の方が適切である。(整数に限らず)「値を表す文字列」を返す場合には、\the-文字列のカテゴリコードに従う tex.write() が最適だと思う。なお、tex.write() には(tex.print() と異なり)第 1 引数に数値を渡す引数形式が存在しないが、それでも明示的な tostring() が望ましいと思う。))でその値を書き出せばよい。((このコード中で、\xx@luanum は、\intsqrt の引数に数値でないものがある場合に正しくエラーを出すためのマクロ。))


\directlua{% 数値 n の平方根を切り捨てた値
function intsqrt(n)
return math.floor(math.sqrt(n))
end
}
\def\intsqrt#1{% TeX 上のインタフェース
\directlua{
tex.write(tostring(intsqrt(\xx@luanum{#1})))
}%
}
\def\xx@luanum#1{% 数値を安全に Lua に渡す
tonumber('\luatexluaescapestring{\detokenize{#1}}')
}

当然、この場合、「内部整数」を想定して書いたコードは正しく動かない。


\count255\@tempcnta\relax % これはOKだが
\count255\intsqrt{17}\relax % これはエラー (\count2554\relax となるから)

では、どうしても Lua からの返り値を「内部整数」として扱いたい場合はどうすればよいか。(なお、上の例から判るように、展開限定で正しく動作する必要がある。)

一つの解決法は、「展開限定文脈でも Lua の中では TeX レジスタへの代入が可能である」ことを利用して、数字列で渡された整数を一旦レジスタに代入してそのレジスタを返すというものである。


\newcount\xx@intrnum
%% \xx@internalize{<整数表記>}
% 整数(標準形の数字列を仮定する)を内部化する。
\def\xx@internalize#1{%
\directlua{
tex.count['xx@intrnum'] = (#1)
}%
\xx@intrnum
}
%% \intsqrt に \xx@internalize を仕込む
\let\xx@isqrt@a\intsqrt
\def\intsqrt#1{%
\xx@internalize{\xx@isqrt@a{#1}}%
}

同じレジスタを使い回しているので、「同じ構文の中で」複数回使うと前の値が上書きされて失敗する恐れがあるような気がするが、今のところ、実際にそれが起こる例は見つかっていない。


\count255\intsqrt{17}\relax % エラーなし
\showthe\count255 % →「4」
\edef\test{\ifnum\intsqrt{3}=\intsqrt{5}Yes\else No\fi}
\show\test % →「No」(1=2 は偽だから正常)
\count255=\numexpr1+\intsqrt{10}+\intsqrt{100}\relax
\showthe\count255 % →「14」(1+3+10=14 だから正常)

最後に補足しておくと、LaTeXTeX のインタフェースとして、必ずしも内部整数(あるいは内部値)を返すことに拘る必要はないと私は考えている。要するに、内部変数と整数表記のどちらを返すのかを仕様として明記されていればよい訳である。実際、pdfTeX には結果の整数を整数表記で返す(展開可能な)プリミティブが存在する(\pdfstrcmp\pdfnormaldeviate 等)。実は、pTeX\jis 等のコード変換プリミティブも同じ性質をもつ。((だから、\@tempcnta=\jis"3021 のような代入文を終結させるには空白文字トークンが 2 個必要になる。))