マクロツイーター

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

それでも TeX でプログラミングしたい人のための何か (9)

あるいは 〜私の TeX プログラム変換環境〜

【しつこく宣伝】

TeXLaTeX Advent Calendar

LaTeXTeXTeX のうち

初心者さん、いらっしゃ〜い!

(詳細は ATND のページで)

配列を「名前参照」に書き換える

前回の考察を踏まえて、Lua のプログラム(シリーズ(7) の最後に示した eltaso3.lua)について、TeX のプログラムに書き換える前に行う「Lua 内での変換」の最終段階として、配列を「名前参照」に書き換える。何故 Lua の中で一旦変換するのがよいかというと、そうすることで変換後の結果を Lua の正しいプログラムとして実行する(そして変換が正しいかを確認する)ことが可能になるからである。これは、今までに行った一連の変換についても同じで、TeX に変換するときには「実際に動くプログラムから機械的に書き換える」という形に近づけたいのである。

Lua において、Perl の名前参照(${<文字列値>})に一番近いのは、Lua 5.2 で導入された特殊変数 _ENV を用いて _ENV[<文字列値>] である。ただし、グローバル変数に限る(今の段階ではそれしか存在しない)と、_G[<文字列値>] でも等価になるので、今回はこちらを用いることにする。*1すなわち、元のプログラムで例えば alpha_name[k] という配列変数の参照があればそれを _G["alpha_name/"..tostring(k)] と言う形に書き換える。((tostring() が無くとも実際には暗黙的に変換されるが、どうせ TeX では \the が必須なので念のために付けておこう。))

--(knum_pos() の中)
sres = digit[tonumber(_1)] .. _2
--(eltaso_name() の中)
sres = sres .. "反田" .. alpha_name[ires]

例えば、上記のコードはそれぞれ次のようになる。((文字列に変えるため tonumber() は逆に不要となる。))

--(knum_pos() の中)
sres = _G["digit/".._1] .. _2
--(eltaso_name() の中)
sres = sres .. "反田" .. _G["alpha_name/"..tostring(ires)]
配列リテラルの処理

配列を名前参照で表す場合、「配列リテラル」(Lua では { ... })は直接対応するものがないので厄介である。一般にはリテラルを使わないで同等のコードに書き直す必要が生じる。

digit = {
  [0] = "",
  "一", "二", "三", "四", "五", "六", "七", "八", "九"
}

上のコードの場合、一番愚直な方法は次の様なものである。

_G["digit/0"] = ""
_G["digit/1"] = "一"
_G["digit/2"] = "二"
………
_G["digit/9"] = "九"

無論これでも全く構わないわけだが、実は少しうまい方法がある。TeX(on LaTeX)では for-each 構文(\@for マクロ)を使うことができる。だから、「元のプログラム」でも for-each 構文を用いて

(これは Lua コードではない)
j = 0
foreach x in ("", "一", "二", …(略)…, "九") do
  digit[j] = x
  j = j + 1
end

という形にすれば綺麗に書くことができる。実際に Lua で for-each 構文を書こうとするとそこでまた配列リテラルが登場することになるが、それは既に TeX にそのまま置き換わることが解っているので問題ない。つまり以下のように書ける。((Lua では for-each を「for i, v in ipairs(<配列>) do ... end」で表す。i に添字、v に要素の値が入る。i を使うのは反則なのでここでは無視している。))

j = 0
for _, x in ipairs({
  "", "一", "二", "三", "四", "五", "六", "七", "八", "九" }) do
  _G["digit/"..tostring(j)] = x
  j = j + 1
end
補足:他の言語を使っている場合はどうするか

前回述べたように、変数の名前参照は全ての言語でサポートされている訳ではない。Lua ではグローバル環境を表す変数 _G を使ってほぼ同等のことができた((ちなみに、JavaScript でも、「グローバルオブジェクト」を _G という変数に代入すれば同じことができる。))が、例えば、Ruby にはその類の機能は用意されてない((どうしても必要な場合は eval() を使うようだ。))らしい。だから、そういう言語では、グローバル変数を名前参照するようなプログラムは書けないことになるだろう。

でもよく考えてみたら、ここでの目的は、TeX と同等な構造を持っていてしかも「動く」プログラムを作ることであって、それ故、「digit/0」というのが実際にグローバル変数であるかは問題にならない。*2なので結局適当な連想配列を 1 つ用意して、それを Lua_G のように使えばよいことになる。例えば、digit への代入のコードは Ruby では次のように書けるだろう。

G = {}  # 名前参照の(似非)グローバル変数の格納場所
j = 0
["", "", "", "", "", "", "", "", "", ""].each do |x|
  G["digit/"+j.to_s] = x
  j += 1
end
変換後のプログラム

文字列変数に x が追加されている。

[eltaso4.lua]
-- [変数一覧]
-- 整数: max, j, ires(戻り値)
-- 文字列: dm, dc, dx, di, km, kc, kx, x, sres(戻り値)
-- 配列: alpha_name, digit

j = 0
for _, x in ipairs({
  "ぜっと",
  "えー", "びー", "しー", "でー", "いー", "えふ", "じー",
  "えいち", "あい", "じぇー", "けー", "える", "えむ", "えぬ",
  "おー", "ぴー", "きゅー", "あーる", "えす", "てぃー", "ゆー",
  "ぶい", "だぶりゅー", "えっくす", "わい" }) do
  _G["alpha_name/"..tostring(j)] = x
  j = j + 1
end
j = 0
for _, x in ipairs({
  "", "一", "二", "三", "四", "五", "六", "七", "八", "九" }) do
  _G["digit/"..tostring(j)] = x
  j = j + 1
end

function knumeral(_1)
  split_digit(_1)
  knum_pos(dm, "千"); km = sres
  knum_pos(dc, "百"); kc = sres
  knum_pos(dx, "十"); kx = sres
  sres = km .. kc .. kx .. _G["digit/"..di]
end
function split_digit(_1) -- TeXでは再実装が必要
  dm, dc, dx, di =
  ("%04d"):format(_1):match("^(.)(.)(.)(.)$")
end
function knum_pos(_1, _2)
  if tonumber(_1) == 0 then sres = ""
  elseif tonumber(_1) == 1 then sres = _2
  else sres = _G["digit/".._1] .. _2
  end
end

function eltaso_name(_1)
  knumeral(_1)
  remainder(_1, 26)
  sres = sres .. "反田" .. _G["alpha_name/"..tostring(ires)]
end
function remainder(_1, _2) -- TeXでは再実装が必要
  ires = _1 % _2
end

function eltaso(_1)
  max = _1
  if max > 9999 then max = 9999 end
  j = 0
  while j < max do
    j = j + 1
    eltaso_name(j)
    print(sres)
  end
end

eltaso(1000)

*1:LuaTeX の Lua エンジンは 5.1 版であるので、texlua で実行させようとすると 5.1 版に合わせる必要がある。

*2:このような非標準の名前の変数には「普通の変数のように」アクセスされることがあり得ない。