といっても、あの tangle でなく別の tangle の話。
某奥底の「Lua 内で TeX コードの実行を完了させる」で紹介されている「tangle モジュール」を新しいバンドル(IXbase)に持ってくるために手直しをしていたが、そこで重大な欠点に気付いた、という話。
通常は Lua のコードの中で TeX のコードを実行することはできず、tex.print()
でコードを出力させても、それが実行されるのは Lua の実効コンテキストを抜けた後である。tangle モジュールを使うと、Lua コード内で、「それまで tex.print()
で出したコードの実行を完了させる」ことが可能になる。詳しくは「Lua 内で…」にあるサンプルを見てほしい。
で、何が問題かというと、「TeX の制限に引っかかるせいで、一つの Lua の実行の中では 14 回までしか tangle.run_tex()
が呼べない」ということである。これを見るために、「Lua 内で…」のサンプル(課題は「文字列を指定の字幅で切り捨てる」)で、わざとループ回数を増やすために二分探索でなく線形探索に変えた版を試してみる。
% 文字コードは UTF-8 \documentclass[a4paper]{article} \usepackage{ixbase0} \begin{execluacodeblock} function truncate_to_width(str) local wdt = ixbase.length.ttwTarget.width local max = str:len() for l = 1, str:len() do -- あえて線形探索を使う texio.write("("..l..")") -- デバッグ出力(*) -- str:sub(1,l) を組版しその幅を \ttwMeasured に代入する tex.print([[\settowidth{\ttwMeasured}{]]..str:sub(1, l)..[[}%]]) tangle.run_tex() -- TeX の実行を完了させる local wdm = ixbase.length.ttwMeasured.width if wdm > wdt then max = l - 1; break end end tex.print(str:sub(1, max)) end \end{execluacodeblock} \newlength{\ttwTarget} \newlength{\ttwMeasured} \newcommand*{\truncatetowidth}[2]{% \setlength{\ttwTarget}{#1}% \directlua{ tangle.execute(truncate_to_width, "\luaescapestring{\detokenize{#2}}") }% } \begin{document} \begin{itemize} \item \truncatetowidth{10pt}{I never feel like saying hello to world!} \item \truncatetowidth{50pt}{I never feel like saying hello to world!} \item \truncatetowidth{90pt}{I never feel like saying hello to world!} \end{itemize} \end{document}
これをコンパイルすると、エラーが出て終了する。
This is LuaTeX, Version beta-0.70.2-2012052919 (TeX Live 2012/W32TeX) restricted \write18 enabled. ……(中略)…… (c:/usr/local/share/texmf/tex/latex/base/omscmr.fd)(1)(2)(3)(1)(2)(3)(4)(5)(6)( 7)(8)(9)(10)(11)(12)(13)(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)(11)(12)(13)(14)(15) ! TeX capacity exceeded, sorry [text input levels=15]. l.2 \directlua{tangle.resume()} \relax l.40 ...{I never feel like saying hello to world!} 670 words of node memory still in use: 12 hlist, 1 rule, 29 glue, 1 kern, 6 penalty, 14 glyph, 62 glue_spec, 1 shap e, 1 write, 2 local_par, 1 dir nodes avail lists: 2:12,3:1,4:5,5:1,6:13,7:1,9:1 ! ==> Fatal error occurred, no output PDF file produced! Transcript written on test.log.
デバッグ出力 (*)
を見ると、3 回目の \truncatetowidth
の実行で、15 回目のループに入ったところで「tex input level」の制限超過によって落ちてしまっている。
「tex input level」というのは、\input
による他ファイルの読み込みのネストの回数である。メインの文書で \input
したらそこで 1 段、その中で \input
したらそこで 2 段、……となり、TeX の制限として、ネストは 15 段までしか許されない。普通に \input
を使うという用途ではこれで十分過ぎるであろう。
今回の件で何故この制限が関係するかというと、TeX から \directlua
で Lua を呼び出して、その中で tex.print()
された「書出コード」を Lua 実行後に実行する、という一連の流れの中で、「書出コード」を処理するのに内部で他ファイル読込(\input
)の機構を利用しているからだと思われる。((e-TeX の \scantokens
が内部的な他ファイル読込を用いているが、多分それと同じだろう。なお、別に物理的な一時ファイルが生成される訳ではなく、すべてメモリ上で行われている。))今の tangle.run_tex()
の実装では、Lus のコルーチン*1を利用して、Lua の実行から脱出した後にまた Lua に戻るために、予め「書出コード」に \directlua{ tangle.resume() }
を入れておくという方法を採っている。従って、仮想的なファイルからの \input
のネストが増えていく、という状況になっているのだと思われる。
解決策の案はあるのだけど……、面倒だなあ。