マクロツイーター

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

テンプレート的 LaTeX、再び (2)

前回の続き)

実のところ、先の例で N の値を引数にするようなユーザ命令(マクロ)を定義することは簡単ではない。単純に以下のようにしたのは上手くいかない。理由を大雑把に言うと、「<? ... ?> 中の Lua コードは(実質的に)その場で実行されるのでそこで未知のマクロパラメタを扱うことはできない」からである。

\begin{templatex}
  \newcommand*{\drawFibonacci}[1]{%
    \begin{tikzpicture}
<?    local N = #1
      local curr, prev
?>    \node (F0) {1};
      …(中略)…
    \end{tikzapicture}
  }
\end{templatex}
% 後で実行する
\drawFibonacci{10}

これについて、もう少し詳しく説明しておく。次のようなもっと単純な例を考える。

\begin{templatex}
<?local N = 5
  for i = 1, N do ?> (<?= i ?>) <? end ?>
\end{templatex}

これが「実行」されると、次のような Lua のコードが実行される。*1ここで、texprint() は仮に tex.print() だと思ってよい。(後で詳しく扱う。)

do
  local N = 5
  for i = 1, N do
    texprint(" (") -- <? ?> の外にある文字列
    texprint(tostring(i)) -- <?= ?> の部分
    texprint(") ")
  end
  texprint("\n") -- これは \end 直前の改行に因る
end

これが実行された結果、TeX 側に書き出されるコードは以下のようになる。((単純に tex.print() を使う場合は実はそうならないのだが、現状の templatex の実装の結果を挙げる。))これは期待通りである。

 (1)  (2)  (3)  (4)  (5) 

また、次のように引数なしのユーザ命令の定義を行うコードも正常に動作する。

\begin{templatex}
  \newcommand{\Test}{%
<?  local N = 5
    for i = 1, N do ?> (<?= i ?>) <? end ?>
  }
\end{templatex}

何故なら、先と同様に考えると、最終的に TeX に次のコードが書き出されるからである(途中経過の Lua コードは省略)。

\newcommand{\Test}{%
 (1)  (2)  (3)  (4)  (5) 
}

ところが、次のように Lua コード(<? ... ?>)の中にパラメタがある場合はおかしなことになる。

\begin{templatex}
  \newcommand{\Test}[1]{%
<?  local N = #1
    for i = 1, N do ?> (<?= i ?>) <? end ?>
  }
\end{templatex}

この場合の途中経過の Lua コードは以下のようになる。Lua コードの中に「#1」があるのは絶対におかしい。

do
  texprint("\\newcommand{\\Test}[1]{%\n")
  local N = #1
  for i = 1, N do
    texprint(" (")
    texprint(tostring(i))
    texprint(") ")
  end
  texprint("\n")
end

さらに注意してほしいのは、このコードを(仮に #1 のエラーを措いて)実行すると、そこで for ループが実行されるということである。つまり、この段階で N の値は確定していなければならない。だから、「パラメタの『#1』という文字列の Lua への渡し方を何とか工夫すれば修正できる」という類の話でもないことが解る。Lua コードが LaTeX 命令のパラメタに依存するような構造はそもそも実現できないのである。

それならば、LaTeX のマクロでなく N を引数とする Lua の関数を使い、改めて LaTeX のマクロで \directlua(ここで <? ?> でないのがミソ)を使ってみればどうか。

\begin{templatex}
  \newcommand{\Test}[1]{%
    \directlua{ Test(#1) }%
  }
<?function Test(N)
    for i = 1, N do ?> (<?= i ?>) <? end
  end ?>
  \Test{5}
\end{templatex}

実は、これは原理的には成功するはずだが、現在の templatex の実装では上手くいかないという微妙なことになっている。この場合の Lua のコードは以下の通り。

do
  texprint([[  \newcommand{\Test}[1]{%
    \directlua{ Test(#1) }%
  }]])
  function Test(N)
    for i = 1, N do
      texprint(" (")
      texprint(tostring(i))
      texprint(") ")
    end
  end
  texprint("\n"..[[
  \Test{5}
]])
end

渡される TeX のコードは以下の通り。Lua コード実行時に Test() が定義され、マクロ \Test はそれを \directlua で呼ぶので本来なら正しく動作するはずである。

  \newcommand{\Test}[1]{%
    \directlua{ Test(#1) }%
  }
  \Test{5}

現状の templatex で失敗する理由は、実際の Lua コードは次(と等価)のようになっているからである。

do
  -- 前準備
  local _out = {}
  local function texprint(s)
    table.insert(_out, s)
  end
  -- 変換結果
  texprint([[  \newcommand{\Test}[1]{%
    \directlua{ Test(#1) }%
  }]])
  function Test(N)
    for i = 1, N do
      texprint(" (")
      texprint(tostring(i))
      texprint(") ")
    end
  end
  texprint("\n"..[[
  \Test{5}
]])
  -- 後処理
  ixbase.print(table.concat(_out, "")) -- tex.print() の変種
end

要するに、出力すべき TeX コードをバッファ _out に溜めておいて後で一気に書き出している。これは「最終的に得られる TeX コードは『テンプレートを解決した後の文字列』をそのまま解釈したものと等しい」という直感的な結果と合わせるための処置である。((tex.print()tex.spinrt() で個別に出力したのでは、「全部を繋げたもの」を出力したのと異なる結果になる場合がある。))しかしその反面、上の例では、\Test を実行すると、確かに _out((_out は do ブロックのローカル変数であるが、Test() の上位値となっていて活きている。)) には出力文字列が溜まるが、既に _out の「出力処理」は済んでいるので結局無益である――という結果になっている。この辺りはもう少し考慮する必要がありそうだ。

ところで、「テンプレート的 LaTeX」は、非常に強力で便利な機構であり、だからもっと真剣に検討する価値があると思っている。完璧な「テンプレート的 LaTeX」を作るという意欲のある人は現れないのだろうか。

*1:もちろんこれは見易く整形したものである。