あるいは 〜私の TeX プログラム変換環境〜
ステップ 3 : 関数に関連する処理を行う
元のプログラムは前回の記事の最後に掲載した eltaso1.lua。
関数を追放する
ここで「関数」というのは「戻り値を返す手続き」(だから式の中で使うことができる)のことである。かなり多くの言語で、「戻り値を返さない手続き」(以降単に「手続き」と呼ぶ)も「関数」として扱われているが、そのような「手続き」には手をつけない。何故関数を無くす必要があるかというと、
TeX には関数が存在しない
(マクロは関数じゃない!)
からである。といっても、関数の呼び出しを全部中身に書き換えてプログラム全体を 1 つの手続きにしてしまう、なんて間抜けなことをやってはいけない(絶対!)。ここで行うのは「関数を手続きに変換する」ことである。
関数を手続きに変換するには、「戻り値」の扱う方法を考える必要があり、種々の方法が考えられるが、ここでは最も愚直な方法である「特定の変数に戻り値を代入する」という方式を採用する。すなわち:
- 戻り値を受け取る専用の変数として、整数用に ires、文字列用に sres を用意する。
- 値を return で返している箇所を全て ires または sres への代入に変える。
- (式の中での)関数の呼出を、「手続きの呼出 + ires/sres の参照」に変える。
function remainder(_1, _2) return _1 % _2 end
この関数は単純に式の値(整数)を返している。なので、return
を ires =
に変更する。
function remainder(_1, _2) ires = _1 % _2 end
もう少し複雑な例。
function eltaso_name(_1) return knumeral(_1) .. "反田" .. alpha_name[remainder(_1, 26)] end
値を「返す」側の対処としては、return
を sres =
に換えればよい。「返される」側については、これまで「knumeral(_1)
」自体が(式として)戻り値を表していたのが、knumeral()
を手続きに変換した後は、「knumeral(_1)
」を実行すれば sres
に求める値が入る、という規約に変更される。remainder()
の方も同様である(こちらは上で実際に変更を行った)。結果的に、以下のようになる。
function eltaso_name(_1) knumeral(_1) -- sres に返る remainder(_1, 26) -- ires に返る sres = sres .. "反田" .. alpha_name[ires] end
このように、「手続きへの変換」は各々の関数ごとに独立して行うことができることに注意。((ただし、eltaso_name()
のコードの場合、実は「remainder()
が sres
を破壊しない」ということに依存しているのだが。))この作業で特に問題になるのが、split_digit()
関数である。
function split_digit(_1) return ("%04d"):format(_1):match("^(.)(.)(.)(.)$") end function knumeral(_1) dm, dc, dx, di = split_digit(_1) -- ここで呼び出している km = knum_pos(dm, "千") -- 以下略 end
この関数は 4 つの整数値を戻り値として返す。一般的な対処法としては、こういう場合は単純に「戻り値専用変数」の個数を増やせばよい、つまり iresa、iresb、……を新たに設ければよい。ただこの 1 つの関数のためだけに 3 つ変数を増やすのはあまり嬉しくない。この split_digit()
は knumeral()
の完全な下請けである(knumeral()
の中に 1 回呼出があるだけ)であるので、ここでは、呼出側が受け取っている変数(dm
等)に直接代入する形に変更することで済ませよう。
function split_digit(_1) dm, dc, dx, di = ("%04d"):format(_1):match("^(.)(.)(.)(.)$") end function knumeral(_1) split_digit(_1) -- dm, dc, dx, di に戻り値が入る knum_pos(dm, "千"); km = sres -- 以下略 end
knum_pos()
関数には本体の途中で return
で値を返して抜ける嘉穂sがある。*1
function knum_pos(_1, _2) if tonumber(_1) == 0 then return "" elseif tonumber(_1) == 1 then return _2 else return digit[tonumber(_1)] .. _2 end end
このようなパターンは、「ires
(sres
)への代入 + return
(脱出)」に置き換える。そして、return
の処理を次の段階で考える。
function knum_pos(_1, _2) if tonumber(_1) == 0 then sres = ""; return elseif tonumber(_1) == 1 then sres = _2; return else sres = digit[tonumber(_1)] .. _2; return end end
手続きの途中で return している箇所を無くす
TeX のマクロの途中で「return で脱出」することはできない*2ので、手続きの途中で return で抜けている箇所は、何とかして return のない形に書き直す。例えば、次のコードの場合:
function example(_1) foo(_1) if ires == 0 then ires = -1; return end bar(_1, ires) if ires == 0 then ires = -1; return end gee(_1, ires) end
以下のようにして、return のない等価なコードに書き直せる。
function example(_1) foo(_1) if ires == 0 then ires = -1 else bar(_1, ires) if ires == 0 then ires = -1 else gee(_1, ires) end end end
eltaso1.lua において該当するのは、先に示した knum_pos()
関数であるが、この場合、全ての条件分岐において、return
は「その手続きで最後に実行された文」になっていることが判る。だから、この場合は単純に return
を取ればよい。
変換後のプログラム
-- [変数一覧] -- 整数: max, j, ires(戻り値) -- 文字列: dm, dc, dx, di, km, kc, kx, sres(戻り値) -- 配列: alpha_name, digit alpha_name = { [0] = "ぜっと", "えー", "びー", "しー", "でー", "いー", "えふ", "じー", "えいち", "あい", "じぇー", "けー", "える", "えむ", "えぬ", "おー", "ぴー", "きゅー", "あーる", "えす", "てぃー", "ゆー", "ぶい", "だぶりゅー", "えっくす", "わい" } digit = { [0] = "", "一", "二", "三", "四", "五", "六", "七", "八", "九" } 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 .. digit[tonumber(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 = digit[tonumber(_1)] .. _2 end end function eltaso_name(_1) knumeral(_1) remainder(_1, 26) sres = sres .. "反田" .. alpha_name[ires] end function remainder(_1, _2) -- TeXでは再実装が必要 ires = _1 % _2 end function eltaso(_1) max = _1 if max > 9999 then max = 9999 end for j = 1, max do eltaso_name(j) print(sres) end end eltaso(1000)