LoginSignup
62
63

More than 3 years have passed since last update.

徹底攻略! LuaLaTeXでLuaコードを「書く」ためのコツ

Last updated at Posted at 2020-07-24

Lua(La)TeXエンジンの特徴の一つとして「Lua言語の処理系が組み込まれている」ということがあります。それも、ただ単にLuaのプログラムが動くだけではなく、それを使って(La)TeXの動作を制御することができます。

これは特に、「LaTeXの上でプログラミング処理を伴うマクロ(自作命令)を作りたい」というユーザにとって強力な武器となりえる機能です。従来は、LaTeX上のプログラミング機能(ifthenやpgfforなどのパッケージ)は非常に限られた能力しかもたず、少々複雑な処理を伴うマクロを作成しようとすると、独特な文法をもつ難解なTeX言語:scream:に頼るしかなかった1からです。“普通の”スクリプト言語であるLuaが使えることで、「LaTeX上のプログラミング」の幅が大きく拡がることは間違いないでしょう。

しかし、実際にLuaLaTeX上でのLuaプログラミングを活用しようとすると、多くの場合すぐにある困難にぶち当たります。それは「LaTeX文書中でLuaコードを正しく書く方法が自明ではない」ということです。

この記事では、LuaLaTeX文書(LaTeXコード)中に所望のLuaコードを「書く」ためのコツについて解説します。

前提知識

  • フツーのLuaの知識。
  • フツーのLaTeXの知識。
  • この記事では「Luaコードを書く」というトピックに焦点を絞るため、Luaと(La)TeXの連携に関するその他の話題(LuaTeXのtexモジュールの使い方、等)の解説はしません。

また、記事全体の方針としてはTeX言語の知識は全く仮定しません。ただし、部分的にTeX言語学習者向けの内容を挟むことがありますが、そういう部分には:sushi:を付けて明示することにします。

LuaLaTeXでLuaコードが書けない!

復習:Luaコードを「書く」方法

本題に入る前にまず、LuaLaTeX文書の中でLuaコードを利用する方法について簡単に復習しておきましょう。

LuaLaTeX文書の中でLuaコードを実行するには\directluaという命令を利用します。そして、Luaコードの中でtex.sprintなどの「(La)TeXのコードを書き出す関数」を実行することで、外側のLaTeXの世界2とのやり取りができます。

例えば、次の例ではLuaのmath.sqrt関数を利用して2の平方根の近似値を求めて文書中に出力しています。

test-1.tex
% LuaLaTeX文書, 文字コードはUTF-8 (以下同様)
\documentclass[a4paper]{article}
\begin{document}
The answer is \directlua{tex.sprint(math.sqrt(2))}.
\end{document}

code-01.png

これを見ると、確かにLaTeXとの連携の手順は少し煩雑ですが、「Luaコードを書く」ことについては何も難しいことはないように思えます。

Luaコードを「書けない」理由

しかし話はそんなに簡単ではありません。なぜなら「\directluaの中の文字列も特別扱いされず、普通にLaTeXの字句解析・構文解析の規則が適用される」という厄介な規則があるからです。

一見すると、「LaTeXの規則」はLaTeXユーザには見慣れたもののはずなので大した問題ではないようにも思えます。「LaTeXの規則」の何が厄介なのかを知るため、例をみてみましょう。次のコードの中の\directluaは何を出力するでしょうか?

test-2.tex
\documentclass[a4paper]{article}
\begin{document}
The answer is \directlua{
  local val = 42 % 5
  -- Prints the value.
  tex.sprint(val)
}.
\end{document}

恐らく% 5は「5で割った余り」のつもりで書かれたのでしょうが、ご存じの通り、%はLaTeXのコメント開始文字であるため、% 5の部分は無視されます。となると、valには(2ではなく)42が代入されるので“42”が出力されるのでしょうか。

実行すると判る通り、この\directluaは実際には何も出力しません

code-02.png

その理由を理解するには「LaTeXでは改行文字は単に空白として扱われる」という規則3を思い出す必要があります。つまり\directluaの中身は実際には次のように解釈されています。

※以下では空白文字を明示したい場合にと表記します。

␣local val = 42 -- Prints the value. tex.sprint(val)␣

結局、tex.sprint…の部分は「Luaの行コメントの中にある」ことになるので実行されるはずがないのでした。このように普段見慣れているはずの「LaTeXの規則」もLuaコードの中で適用されると予想外の効果を引き起こすのです。

:sushi:ガチTeX言語者向けの説明

実は、\directluaの引数の解釈の仕様は“TeX言語的”には極めて単純ですので、マッチョTeXプログラマ:muscle:向けの説明としてはこの仕様を述べれば十分でしょう。(要するに \write の引数と同様に扱われます。)

  • 引数のトークン列を完全展開する。
  • 結果のトークン列を(\detokenizeの規則で4)脱トークン化して文字列に変換する。
  • 結果の文字列が「実行されるLuaコード」である。

例えば次のようなコードを実行したとしましょう。

\def\foo{tex.write}\def\bar#1{#1#1}\def\space{ }
The result is \directlua{ \foo (
    \bar\space\bar [\relax\noexpand  \foo\bar]%%
        )}.

ここで\directluaの引数をトークン列としてみると以下のようになります。

\foo(\bar\space\bar[\relax\noexpand\foo\bar])

これを(普通のTeXの規則で)完全展開すると以下のトークン列が得られます。

tex.write([[\relax\foo]])

従って、これを脱トークン化して得られる以下の文字列がLuaのコードとして実行されます5

␣tex.write(␣␣␣[[\relax␣\foo␣]])

結局、以下のような出力が得られます。

code-03.png

LuaLaTeXでLuaコードを書く!

何やら恐ろしいTeX言語:scream:の話が出てきてしまいましたが、この話は同時に「\directluaLaTeXユーザが使いこなすのは絶望的に難しい:sob:」という事実を示唆しています。つまり、\directluaの中の記述がどう解釈されるかを知るためには難解なTeX言語の規則を習得する必要があるわけです。TeX言語を避けるためにLuaを使いたいのに、結局そのためにTeX言語の知識が必要になるのでは本末転倒です。

LaTeXユーザの希望、luacodeパッケージ

しかし実は、LaTeXの世界はこの問題に対しても「luacodeパッケージ」という解決法を与えています。このluacodeパッケージは「LuaLaTeX文書中にLuaコードを書く」際に役立つ機能を提供するものです。以降ではこのluacodeパッケージの基本的な使い方を説明します。

luacodeパッケージの読込は、いつも通り\usepackageで行います。

% プリアンブルで
\usepackage{luacode}

luacode*環境を使う

luacodeパッケージが提供するluacode*環境6「Luaコードを“そのまま”書けるようにした \directlua」に相当するものです。

\begin{luacode*}
  <Luaコード記述>
\end{luacode*}

luacode*環境の中に書かれた文字列は、verbatim環境と同様に、LaTeXの特殊文字や空白や改行も含めてすべて“そのまま”読み取られ、結果の文字列がLuaコードとして(最終的には内部で\directluaを利用して)実行されます。

\end{luacode*}の部分は他の文字を含まない単独の行として書かれる必要があります。また、この行の末尾の改行は空白にならずに無視されるようです。

例えば、前出のtest-2.texの中に登場した「%--を含むLuaコード」もluacode*を使うと意図通りに実行されます。すなわち、%は剰余の演算子と解釈され--は行末までをコメントアウトするので、結果的に“2”がLaTeXに出力されることになります。

test-2a.tex
\documentclass[a4paper]{article}
\usepackage{luacode}
\begin{document}
The answer is
\begin{luacode*}
  local val = 42 % 5
  -- Prints the value.
  tex.sprint(val)
\end{luacode*}
.
\end{document}

code-04.png

luacode*環境が使えない

luacode*を利用すると、「\directluaでLuaコードを書くのが困難」という問題の大部分が解決されますが、残念ながらluacode*は万能ではありません。以下で紹介するケースではluacode*が使えません。

luacode*中でマクロ等を定義することはできない

tex.sprintでは任意のLaTeXコードを出力できるので「\newcommand文を出力してマクロを定義する」なんてこともできそうです。実際、それは可能なのですが、luacode*を使うとうまくいきません。例えば、次のコードではmath.sqrt(2)の値(の10進表記)を\myAnswerというマクロに定義していますが、実際に\myAnswerを文書中で使おうとすると「未定義である」というエラーが発生してしまいます。

test-3.tex(失敗)
\documentclass[a4paper]{article}
\usepackage{luacode}
\begin{luacode*}
  local v = math.sqrt(2)
  -- \newcommand 命令を出力してマクロを定義する…はず
  tex.sprint("\\newcommand{\\myAnswer}{"..v.."}")
\end{luacode*}
\begin{document}
% でもここでは \myAnswer は未定義!
The answer is {\myAnswer}. %==>! Undefined control sequence.
\end{document}

この失敗の原因は、「luacode*中でtex.sprintで出力したLaTeXコードはluacode*環境の中で実行される」からです。ご存じの通り、LaTeXの環境の中で何か設定(フォントなど)を変更した場合、その設定の効果は環境の中に限られます7。この仕様はマクロ定義にも当てはまり、環境の中でマクロを定義してもその定義は環境の外に出ると消えてしまいます。

\begin{center}
  \newcommand*{\myGreet}{Hello}% マクロ定義
  \huge \myGreet!
\end{center}
% ここでは \huge の効果も \myGreet の定義も消える

これと同じ原理で、先ほどのluacode*の例では、\myAnswerは「luacode*環境内で定義されている」ためluacode*環境の実行が終わった時点では未定義に戻ってしまうのです。

マクロの定義本体ではluacode*は使えない

LaTeXの悪名高い仕様として「\verb命令やverbatim環境は他の命令の引数の中では使えない」というものがあります(“\verb の呪い”8)。実はこの仕様は「字句解析の仕様を変えるような機能」一般について成り立ち、件のluacode*もそれに該当します。つまり、「luacode* は他の命令の引数の中では使えない」のです。

この制限は、Luaを利用して複雑な挙動をもつマクロを作りたいというLaTeXユーザにとっては致命的です。それを実現しようとすると、「\newcommandの引数として記述するマクロの定義本体部分にluacode*を含める」必要が生じますが、“\verbの呪い”のためそれは不可能なのです。

例えば、「LaTeX文書中に現在時刻の文字列を出力するマクロ」をLuaのos.dateを利用して実装しようとします。luacode*を使うと以下のようなコードを書くことになるでしょう。

test-4.tex(失敗)
\documentclass[a4paper]{article}
\usepackage{luacode}
\newcommand*{\myCurrentTime}{%
% ここは「\newcommandの引数の中」だからダメ!
\begin{luacode*}
  tex.sprint(os.date("%H:%M:%S"))
\end{luacode*}
}
\begin{document}
It is {\myCurrentTime} now.
\end{document}

しかし、マクロの定義本体を記述する{…}は「\newcommand命令の引数」に他なりません。従って、ここではluacode*を使うことができないのです。

LaTeXユーザがこの先生きのこる方法

結局、luacode*だけで全てを済ませることはできないことが判りました。しかも、その理由の一つは「luacode*がLaTeXの特殊文字の効果を消す」親切な機能をもつがゆえに「\verbの呪い」にかかってしまうからでした。となると、「TeX言語:scream:の複雑な規則」を完全に回避することは不可能なのでしょうか?

ここではこの困難な状況を「luacode* と \directlua の使い分けの戦略を工夫する」ことで乗り切ることにしましょう。以下のような方針をとります。

  • 可能な限り、luacode*環境を利用する。
  • luacode*環境が使えない場合に限って、可能な限りコードの量を絞った上で\directlua命令を利用する。
  • TeX言語:scream:を全く知らないLaTeXユーザでも解釈を予測できるように、\directlua の引数に書ける文字を制限する

大事なのは最後の「書ける文字を制限する」ということです。例えば、コードが完全にLaTeXの特殊文字以外からなるのであれば、\directluaを使っていても“そのまま”読み取られるのは明らか9です。

% Luaコードは"そのまま"読み取られる
\directlua{tex.print(math.sqrt(2))}

実際には非特殊文字だけでは不便なので、少し条件を緩和して以下の文字を使用可能と決めます。

  • LaTeXの特殊文字以外の全てのUnicode文字。
  • 特殊文字のうち、$&_の3つ。
    ※これらは\directluaの中では特殊な挙動にならずに“そのまま”読み取られます。
  • 対になっている{}の組。
    ※命令の引数の位置にない限りは{ }は“そのまま”読み取られますが、\directluaの引数の{…}の判定のため対になる必要があります。

逆に言うと、以下に挙げる文字は一切使用禁止とします。

  • 以下の文字: \#^~%
  • 対にならない{}

もちろん、このような“文字禁止縛り”を設けた上でのLuaプログラミングは不便であることは確かです。そこで「可能な限りluacode*を利用する」という方針と組み合わせるわけです。もう少し具体的に、以下のようにします。

  • 具体的な処理は全て関数として切り出して luacode* の中に書くことにして、\directluaの中の処理はなるべく10「別の場所で定義した関数を呼び出すだけ」にする。

このように用途を限定する限りは\directluaの「使用禁止文字の規則」はほとんど問題にならないことが、少し考えればわかるでしょう。(「マクロで文字列の引数を扱いたい」場合には問題が生じますが、それは後で扱います。)

LuaLaTeXで実際にLuaコードを書いてみる

「戦略」の適用の例として、先ほどの「luacode*が使えないケース」のコードを「戦略」に従って書き直してみましょう。

まずは、test-3.texですが、これは要するに「最終的に\directluaで実行する必要がある」というわけなので、とにかく「何か関数を\directluaで呼び出す」というコードをまず書いてみます。

\directlua{ my_init() }

そうすると、Luaで書くべき残りの処理は「my_initの実装」だけです。こちらは\directluaで書く必要が全くないので、方針に従いluacode*の中に書くことになります。

\begin{luacode*}
  function my_init()
    local v = math.sqrt(2)
    tex.sprint("\\newcommand{\\myAnswer}{"..v.."}")
  end
\end{luacode*}

全体のコードは以下のようになりました。

test-3a.tex(成功)
\documentclass[a4paper]{article}
\usepackage{luacode}
% 実行する内容を関数にまとめてluacode*で定義する
\begin{luacode*}
  function my_init()
    local v = math.sqrt(2)
    tex.sprint("\\newcommand{\\myAnswer}{"..v.."}")
  end
\end{luacode*}
% その関数を \directlua で実行する
\directlua{ my_init() }
\begin{document}
% 今度は \myAnswer が正しく定義されている
The answer is {\myAnswer}.
\end{document}

code-01.png

test-4.texの方も方針はほぼ同様です。マクロ\myCurrentTimeの定義本体の中では\directluaを使う必要があるため、ここを「関数を呼ぶだけ」のコードにします。

\newcommand*{\myCurrentTime}{%
  \directlua{ my_current_time() }%
}

あとは、Lua関数my_current_timeの定義をluacode*の中に書くだけです。以下のようになりました。

test-4a.tex(成功)
\documentclass[a4paper]{article}
\usepackage{luacode}
\begin{luacode*}
  function my_current_time()
    tex.sprint(os.date("%H:%M:%S"))
  end
\end{luacode*}
\newcommand*{\myCurrentTime}{%
  \directlua{ my_current_time() }%
}
\begin{document}
It is {\myCurrentTime} now.
\end{document}

code-06.png

※もちろん、実際にはコンパイル時の時刻が出力されます。

LuaでLaTeXマクロしよう!

「Luaコードを書く」ためのコツがつかめたと思いますので、次は「Luaを利用して複雑な挙動をもつマクロを作る」場合の書き方のコツについて解説を進めます。

※「LaTeXのマクロの定義の仕方」を復習したい人は、以下の記事を参考にしてください。

「LuaでLaTeXマクロ」のキホン

先の節で扱ったtest-4a.texの例は、「Luaを利用して複雑な挙動をもつマクロを作る」場合の典型的なパターンを示しています。

  1. 現在時刻を出力するマクロ\myCurrentDateを作りたい。
  2. Luaの関数my_current_date\directluaで呼ぶだけのマクロを\myCurrentDateとして定義する。
  3. Luaの関数my_current_dateを“フツーのLuaプログラミング”を駆使して実装し、そのコードをluacode*環境内に書く。

特に、2の部分はLaTeX(というよりTeX言語:scream:)とLuaが交錯する複雑怪奇な世界:skull:なので、LaTeXユーザはここの部分は定型コードで済ませて3の関数の実装に注力するのがいいでしょう。3の関数の実装では「TeX言語:scream:の規則」を一切気にせずに済みます。

もう少し例を挙げてみましょう。次のようなマクロを作りたいとします。

  • \myDice:1~6の範囲の数字をランダムに選んで出力する。

まずはマクロの方を定義します。これは何も考えずに次のように書きます。

\newcommand*{\myDice}{%
  \directlua{ my_dice() }%
}

あとは、Lua関数my_diceの実装をじっくりと考えましょう。(といっても、この場合は実装はほぼ自明ですが11。)

  function my_dice()
    tex.sprint(math.random(6))
  end

これをluacode*の中に書けば完成です。

test-5.tex
\documentclass[a4paper]{article}
\usepackage{luacode}
\begin{luacode*}
  function my_dice()
    tex.sprint(math.random(6))
  end
\end{luacode*}
\newcommand*{\myDice}{%
  \directlua{ my_dice() }%
}
\begin{document}
\myDice, \myDice, \myDice.
\end{document}

code-11.png

※出力は実行のたびにランダムに変わります。(以下、math.random利用の例は全て同様。)

補足:マクロできるとき・マクロできないとき

LaTeXの動作としては、「\directluaの実行」は「引数のないLaTeXマクロの実行」と同様の振る舞いをします。例えば、

\directlua{ tex.sprint(1 + 5 * 8 + 1) }

のような\directluaの実行は、

\newcommand*{\answer}{42}

のようなマクロが予め定義されているという前提で\answerを実行したのとほぼ同等の動作になります。

:sushi:TeX言語者向け補足:\directluaは展開可能であり、単純化すると「\directlua{<Luaコード>}を一回展開するとLuaコードが実行された上で『LuaコードがTeX側に書き出した内容』になる」と見なすことができます12。従って、特に出力が展開不能トークン列である場合は、Luaで実装したLaTeXマクロは自動的に完全展開可能の性質を得ることになります。例えば、前出の\myCurrentTimeを使って“\edef\myTime{\myCurrentTime}”を実行することで\myTime実行時の現在時刻の文字列を代入できます。

このため、あるLaTeXの命令の引数について「中にマクロを含めてもよくてその場合はマクロの定義内容を書いたのと同等になる」という性質を満たすのであれば、その引数において\directlua(およびそれを定義内容とするマクロ)を含めることもできます。

例えば、一般に「数値(整数・実数)や長さ値の引数」はマクロを含めることが可能とされているので、先の節で紹介したマクロ\myDiceについて、次のようなコードを実行できます13

% \setcounter の"代入する整数値"の引数をマクロにした
\setcounter{section}{\myDice}
\section{Random section number}% 節番号は2~7の範囲でランダムに選ばれる

code-12.png

ただし、現実問題としては引数が「マクロを含められる」という性質をもつかは仕様として明示されていない14ことがほとんどです。このため、数値・長さ値以外の引数については、マクロを含めるのは避けた方が無難でしょう。

例えば、「マフラーの色を赤・青・緑の3色からランダムに選んだゆきだるま:snowman:」を出力する命令\mySnowmanを作りたいとします(素敵:blush:)。このとき、色名を出す部分を\myColorとして切り出した次のようなコードは“正しい”でしょうか?

\usepackage{xcolor,scsnowman}% ゆきだるま☃!
\usepackage{luacode}
\begin{luacode*}
  local colors = {"red", "blue", "green!50!black"}
  function my_color()
    local ci = math.random(#colors)
    tex.sprint(colors[ci])
  end
\end{luacode*}
% \myColor は"red"などの色名を返す
\newcommand*{\myColor}{\directlua{ my_color() }}
\newcommand*{\mySnowman}{%
  \scsnowman[scale=1.5,snow,arms,hat,muffler=\myColor]%
}

実際に実行してみるとこのコードは期待通りの動作をします。しかし、\scsnowman命令のmufflerの引数に「マクロを含められる」ことは保証されていないことを考えると、このコードは“正しい”とはいえないでしょう。実際、「:snowman:の帽子の色もマフラーに合わせたい」ということで、次のようにコードを書き換えると、ほとんど同じコードであるにも関わらず、こちらはエラーになっていまいます15

\usepackage{xcolor,scsnowman}% ゆきだるま☃!
\usepackage{luacode}
\begin{luacode*}
  local colors = {"red", "blue", "green!50!black"}
  function my_color()
    local ci = math.random(#colors)
    tex.sprint("hat="..colors[ci]..",muffler="..colors[ci])
  end
\end{luacode*}
% \myColor は"hat=<色名>,muffler=<色名>"を返す
\newcommand*{\myColor}{\directlua{ my_color() }}
\newcommand*{\mySnowman}{% これはダメ!
  \scsnowman[scale=1.5,snow,arms,\myColor]%
}

結局のところ、「引数(またはその一部)をマクロで置き換える」のは避けて、「命令全体をマクロで置き換える」という方針に徹するのが安全です。今の場合は、\scsnowman命令全体をLuaで出力するようにすればよいわけです。

test-6.tex
\documentclass[a4paper]{article}
\usepackage{xcolor,scsnowman,luacode}
\begin{luacode*}
  local sc = "\\scsnowman[scale=1.5,snow,arms,hat=%s,muffler=%s]"
  local colors = {"red", "blue", "green!50!black"}
  function my_snowman()
    local c = colors[math.random(#colors)]
    tex.sprint(sc:format(c, c))
  end
\end{luacode*}
\newcommand*{\mySnowman}{\directlua{ my_snowman() }}
\begin{document}
\mySnowman\mySnowman\mySnowman?
\mySnowman\mySnowman\mySnowman\mySnowman\mySnowman!!
\end{document}

code-13.png

今度は引数付きでLaTeXマクロしよう!

これまで「LaTeXのマクロをLuaで実装する」話を続けてきましたが、そこでは専ら「引数をとらないマクロ」を扱いました。ここでは「引数をとるマクロ」を実装する方法を解説します。

引数を扱おうとすると、\directluaの中でマクロの引数をLua側に渡す処理が必要になります。例によってこの処理は単純ではなく、正しく動作するコードを書くにはコツが要ります。ただし引数が数値(整数・実数)である場合は話はとても簡単なので、まずはそこから始めましょう。

数値の引数をどうにかする

myDiceは1~6の範囲の乱数を発生させるものでしたが、ここで上限は6で固定はなく引数で指定できた方が便利でしょう。そこで次のような仕様のマクロを実装してみます。

  • \myRandom{<上限n>}: 1~nの範囲の整数をランダムに選んでその10進表記を出力する。

\myRandomは引数を1つとるので、その定義は以下のような形になります。

\newcommand*{\myRandom}[1]{% #1=<上限n>
  \directlua{ ... }% 中身は後で考える
}

\directluaの中身は後で考えることにしますが、とにかく「マクロの定義は\directluaの実行だけにする」という原則は守ることにします。

Lua関数my_randomの実装を先に考えましょう。とにかく上限nの値を使うのは確実なので、これを関数の引数にします。

  function my_random(n)
    tex.sprint(math.random(n))
  end

my_randomの仕様をこのように決めたとすると、結局、次のような関係が成立している必要があります。

  • \myRandom{42}が実行されると

    \directlua{ my_random(42) }が実行される。

すると、マクロ\myRadnomの定義は次のようにすればよさそうです。

\newcommand*{\myRandom}[1]{% #1=<上限n>
  \directlua{ my_random(#1) }%
}

もちろん、\directlua中に#を書くのは「使用禁止文字の規則」に違反します。しかし、そもそも「使用禁止文字の規則」の目的は「どう解釈されるかが(LaTeXユーザにとって)不明なものを\directlua中に紛れ込ませない」ことでした。一方で「マクロの引数の規則」はLaTeXユーザにとって周知のものなので、目的に照らせば問題は起こらないはずです。そこで、「使用禁止文字の規則」を改訂してしまうことにしましょう:upside_down:

  • マクロの定義本体中の#1#2、…はマクロ実行時に与えられた実引数の内容に置き換わる。(LaTeXの規則)
  • 従って、当該のマクロの仕様16を前提とするときに、#1#2、…を実引数の内容に置き換えた結果が「使用禁止文字の規則」に決して違反しないのであれば#1#2、…を\diretluaの中に含めてもよい。

というわけで、先の\myRadnomの定義が晴れて採用できることになったので、マクロの実装が完成しました。

test-7.tex
\documentclass[a4paper]{article}
\usepackage{luacode}
\begin{luacode*}
  function my_random(n)
    tex.sprint(math.random(n))
  end
\end{luacode*}
\newcommand*{\myRandom}[1]{% #1=<上限n>
  \directlua{ my_random(#1) }%
}
\begin{document}
% 1~99999の整数がランダムに選ばれる.
Today's lucky number is \myRandom{99999}.
\end{document}

code-14.png

もう一つの例として、次のマクロを実装しましょう。

  • \mySqrt{実数x}: xの平方根の値を固定小数点表記17で出力する。

今度は引数が整数でなくて実数ですが、「そのままの表記をLuaに渡せばよい」という点は変わらないためマクロ定義時の扱いは\myRandomと同じです。

\newcommand*{\mySqrt}[1]{% #1=<x>
  \directlua{ my_sqrt(#1) }%
}

my_sqrtの実装は次のようになります。

  function my_sqrt(x)
    -- 固定小数点表記にするためstring.formatを通す
    tex.sprint(("%.7f"):format(math.sqrt(x)))
  end

これらを組み合わせれば完成です。完成版のコードには、\mySqrtの使用例として、「LaTeXの\framebox命令の引数に\mySqrtを使用することで、面積が1、2、3、4の正方形を順に出力する」ためのコードを載せました。

test-8.tex
\documentclass{article}
\usepackage{luacode}
\begin{luacode*}
  function my_sqrt(x)
    tex.sprint(("%.7f"):format(math.sqrt(x)))
  end
\end{luacode*}
\newcommand{\mySqrt}[1]{%
  \directlua{ my_sqrt(#1) }%
}
\begin{document}
\setlength{\unitlength}{1cm}
\framebox(1,1){1}
\framebox(\mySqrt{2},\mySqrt{2}){2}
% ↑\framebox(1.4142136,1.4142136){2} と等価
\framebox(\mySqrt{3},\mySqrt{3}){3}
\framebox(2,2){4}
\end{document}

code-08a.png

チョット注意しておく

ただし、今の\myRandomの実装には欠点があります。例えば、\myRandomの引数に99999を指定しようとして間違えて次のように書いてしまったとします。

\myRandom{99,999}% 間違い:余計なコンマがある

すると、次のような\directluaが実行されることになります。

\directlua{ my_random(99,999) }

my_random(99,999)はLuaの文法として正しいのでエラーにはなりません。第2引数の999は無視されるため、結局「1~99の範囲の乱数が出る」という意図しない結果になります。

とはいっても、LaTeX自体が元々グダグダな世界であり、「記述を間違えてもエラーにならずに意味不明な動作が起こる」という事例が日常的に起こっています。なので、この“欠点”についても特に気にしないことにしましょう:upside_down:18

もう少し深刻(?)な問題は、「引数の整数は10進表記の即値で与える必要があり、16進表記19やカウンタ参照20などの別の形式は受け付けられない」ということです。

\myRandom{42}% 10進表記, OK
\myRandom{"2A}% 16進表記, ダメ
\myRandom{\value{section}}% カウンタ参照, ダメ

しかし、少なくともLaTeXの範囲では10進表記以外の数値の表現は実際にはほとんど現れないのも確かです。従って、この点についても「そういう仕様である」と規定しておけば十分でしょう。

もっともっと引数してみる

これまでに述べた方針は、引数の数が増えた場合でも基本的に変わりません。例えば以下の例を考えましょう。

  • \myRandomRange{下限m}{上限n}: m~nの範囲の整数をランダムに選んでその10進表記を出力する。

引数が2つになったので、#1に加えて#2もLua関数に渡すだけです。

\newcommand*{\myRandomRange}[2]{% #1=<下限m>,#2=<上限n>
  \directlua{ my_random_range(#1, #2) }%
}
  function my_random_range(m, n)
    -- 実はmath.randomにも2引数の形式がある
    tex.sprint(math.random(m, n))
  end

完成版のコードには、\myRandomRangeの使用例として、「LaTeXの\symbol命令と併用して、A~Zの英字をランダムに選んで出力する」ためのコードを載せました。

test-9.tex
\documentclass[a4paper]{article}
\usepackage{luacode}
\begin{luacode*}
  function my_random_range(m, n)
    tex.sprint(math.random(m, n))
  end
\end{luacode*}
\newcommand*{\myRandomRange}[2]{% #1=<下限m>,#2=<上限n>
  \directlua{ my_random_range(#1, #2) }%
}
\begin{document}
% A~Zの英字がランダムに選ばれる.
Today's lucky letter is \symbol{\myRandomRange{65}{90}}.
\end{document}

code-15.png

オプション引数を扱う例も挙げておきます。

  • \myYoubi[<年>]{<月>}{<日>}: 指定の年月日の曜日を漢字1文字で(“木”のように)出力する。年を省略した場合は現在の年とする。

LaTeXの\newcommand命令でオプション引数をもつマクロを定義する場合、必ず「オプション省略時のデフォルト値」を指定することになります。今の場合、無理に「LaTeXの側で現在年を取得しよう」などと頑張ったりせずに、「引数省略時はnilをLuaに渡して、あとはLuaの側で処理する」という方針をとります。要するに#1nilになればいいので、\myYoubiの定義は以下のようになります。

\newcommand*{\myYoubi}[3][nil]{% #1=年;#2=月;#3=日
  \directlua{ my_youbi(#1, #2, #3) }%
}
test-10.tex
\documentclass[a4paper]{ltjsarticle}% 日本語なので
\usepackage{luacode}
\begin{luacode*}
  -- y年m月d日の曜日を1~7で返す
  local function youbi(y, m, d)
    local t = os.time({year=y, month=m, day=d})
    return os.date("*t", t).wday
  end
  local chars = {"日", "月", "火", "水", "木", "金", "土"}
  function my_youbi(y, m, d)
    y = y or os.date("*t", os.time()).year
    tex.sprint(chars[youbi(y, m, d)])
  end
\end{luacode*}
\newcommand*{\myYoubi}[3][nil]{% #1=年;#2=月;#3=日
  \directlua{ my_youbi(#1, #2, #3) }%
}
\begin{document}
\begin{itemize}
\item 今年の8月8日は\myYoubi{8}{8}曜日です。
\item 2008年8月28日は\myYoubi[2008]{8}{28}曜日です。
\end{itemize}
\end{document}

code-16.png

:sushi:TeX言語者向け補足:先ほど「Luaで実装したLaTeXマクロは完全展開可能になる」といいましたが、(\newcommandで)オプション引数付きで定義したマクロは例外になります。これはオプション引数の処理の部分が完全展開可能にならないからです。

数値以外の引数をどうにかする

引数の扱いに慣れてきたところで、いよいよ数値以外の引数について考えてみましょう。次のような単純な機能のマクロを例にして引数の扱いを説明します。

  • \myRepeat{<回数n>}{<テキスト>}: 指定のテキストをn回出力する。

まずは第2引数が“普通の文字列”である場合を想定して、マクロ\myRepeatをどう定義すべきかを考えましょう。例えば

\myRepeat{5}{Duck}

が実行されたとき、\myRepeatを処理するLua関数が受け取るべき引数は

my_repeat(5, "Duck")

となるはずです。Luaの文字列リテラルは"で囲まれているということを考えると、\myRepeatの定義は次のものでよいはずです。

\newcommand{\myRepeat}[2]{% #1=<n>, #2=<テキスト>
  \directlua{ my_repeat(#1, "#2") }%
}

my_repeatの実装は次のようになるでしょう21

  function my_repeat(n, txt)
    for i = 1, n do
      tex.sprint(txt)
    end
  end

この2つを組み合わせると、確かに\myRepeat{5}{Duck}は期待通りに動作します。

code-17.png

さらに少し考えるとわかるように、この実装は引数のテキストが「LaTeXの特殊文字を含まない単なる文字列」である限りは、ほとんどの場合に通用します。唯一の例外は"を含む場合です。

\myRepeat{5}{A"B}

この場合、実行されるLuaコードはmy_repeat(5, "A"B")となりますが、これは不正であり、Luaの文法に従うと、正しくするには"\でエスケープする必要があります。

my_repeat(5, "A\"B")

実用上は、"などよりもむしろ「LaTeXの命令を書けるようにする」という要件の方が大事かもしれません。例えば

\myRepeat{5}{\"H\o\~g\k{e}!}
\myRepeat{3}{{\TeX} is \emph{evil}!! }

のように特殊文字やテキスト装飾の命令を使いたいかもしれませんし、あるいは「赤マフラーのゆきだるま:snowman:をいっぱい出力したい」(素敵:blush:)ということで

\myRepeat{20}{\scsnowman[hat,snow,arms,muffler=red]}

を実行したいかもしれません。これらを実現するには、それぞれ次のようなLuaコードを実行する必要があります。

my_repeat(5, "\\\"H\\o\\~g\\k{e}!")
my_repeat(3, "{\\TeX} is \\emph{evil}!! ")
my_repeat(20, "\\scsnowman[hat,snow,arms,muffler=red]")

仮にこれらがLuaで実行されれば、確かに期待通りの動作になります。ここで問題なのは、「このコードは禁止文字を含むためそもそも \directlua の中に書けない」ということです。

これはかなり致命的な問題のようにみえますが、幸いなことに、luacodeパッケージはこの問題を解決するための手段を提供しています。それが \luastringN 命令22です。

\luastringN{テキスト}% 引数は命令を含んでいてもよい

LaTeXの範囲内でこの命令の動作を正確に述べるのは困難23なのですが、簡単にいうと、\luastringNはテキストを「Luaの文字列リテラル」に置き換えます。例えば:

  • \luastringN{A"B}と書くと、"A\"B"と解釈される。
  • \luastringN{\"H\o\~g\k{e}!}と書くと、"\\\"H\\o \\~g\\k {e}!"24と解釈される。

そして重要なこととして、この変換後の文字列はverbatimなものとして扱われるため、たとえ\のような特殊文字が入っていても\directluaの中に書いたときに常に“そのまま”読み取られます。

以上のことをまとめると、結局、LaTeXで

\directlua{ my_repeat(5, \luastringN{\"H\o\~g\k{e}!}) }

を実行するとLuaで

my_repeat(5, "\\\"H\\o \\~g\\k {e}!")

が実行されるということです。この\luastringNさえあれば、懸案だった\myRepeatの実装はいとも簡単に済んでしまいます。

\newcommand{\myRepeat}[2]{% #1=<n>, #2=<テキスト>
  \directlua{ my_repeat(#1, \luastringN{#2}) }%
}

これだけで、#2の中にどんな文字や命令が入っていても、「そのテキストを表すLua文字列」が関数に渡されるのです。(luacodeパッケージスゴイ:astonished:

\directluaの中の\luastringNの挙動はもう理解しているわけなので、「使用禁止文字の規則」の例外に\luastringN{テキスト}25を追加しておきましょう:upside_down:

\myRepeatの完成版のコードは以下の通りです。使用例として「赤マフラーのゆきだるま:snowman:をいっぱい出力する」を入れました(素敵:blush:)。

test-11.tex
\documentclass[a4paper]{article}
\usepackage{xcolor,scsnowman}% ゆきだるま☃!
\usepackage{luacode}
\begin{luacode*}
  function my_repeat(n, txt)
    for i = 1, n do
      tex.sprint(txt)
    end
  end
\end{luacode*}
\newcommand{\myRepeat}[2]{% #1=<n>, #2=<テキスト>
  \directlua{ my_repeat(#1, \luastringN{#2}) }%
}
\begin{document}
\myRepeat{20}{\scsnowman[hat,snow,arms,muffler=red]}
\end{document}

code-17a.png

イロイロつくってみた:muscle:

LaTeX的FizzBuzz

FizzBuzzで「FizzやBuzzに置き換える」代わりに「数字を装飾する」としたものです26

  • \myFizzBuzz{<整数n>}{<命令列1>}{<命令列2>}: 1~nまでの整数の10進表記を欧文空白区切りで出力する。<命令列1><命令列2>には宣言型27のテキスト装飾命令を指定し、3の倍数のときには<命令列1>、5の倍数のときには<命令列2>が適用される。(両方に該当する場合は<命令列1><命令列2>の順に実行される。)
test-12.tex
\documentclass[a4paper]{article}
\usepackage{xcolor,luacode}
\begin{luacode*}
  function my_fizzbuzz(n, cmd3, cmd5)
    for i = 1, n do
      tex.sprint((i == 1) and "{" or " {") -- 区切り空白
      if i % 3 == 0 then tex.sprint(cmd3) end
      if i % 5 == 0 then tex.sprint(cmd5) end
      tex.sprint(tostring(i).."}")
    end
  end
\end{luacode*}
\newcommand{\myFizzBuzz}[3]{%
  % とにかく \luastringN を使えばOK
  \directlua{ my_fizzbuzz(#1, \luastringN{#2}, \luastringN{#3}) }%
}
\begin{document}
% 3の倍数は太字イタリック, 5の倍数は赤色になる
\myFizzBuzz{100}{\bfseries\itshape}{\color{red}}
\end{document}

code-18.png

漢数字変換

よくあるネタ:smiley:

  • \myKansujiOf{<整数n>}: 整数nを十字方式(数の日本語での読みの漢字表記)で出力する。※ただしnは0~0x7FFFFFFFの範囲に限る。
  • \myKansuji{<カウンタ名>}:\myKansujiOf`の形式で出力するカウンタ出力命令28

:sushi:Luaで実装したので完全展開可能になることに注意。カウンタ出力命令は原則的に完全展開可能性が必要なのでTeX言語で実装するのは極めて困難なことが多いのですが、Luaだと勝手に完全展開可能性が得られます:upside_down:

test-13.tex
\documentclass[a4paper]{ltjsarticle}% 日本語なので
\usepackage{luacode}
\begin{luacode*}
  local kds = {"一","二","三","四","五","六","七","八","九"}
  kds[0] = ""
  local function digit(j, km)
    if j >= 2 then tex.sprint(kds[j]) end
    if j >= 1 then tex.sprint(km) end
  end
  local function block(j, kbm)
    digit(j // 1000, "千")
    digit(j // 100 % 10, "百")
    digit(j // 10 % 10, "十")
    tex.sprint(kds[j % 10])
    if j > 0 then tex.sprint(kbm) end
  end
  function my_kansuji_of(n)
    if n == 0 then tex.sprint("〇")
    elseif 1 <= n and n <= 0x7FFFFFFF then
      block(n // 100000000 % 10000, "億")
      block(n // 10000 % 10000, "万")
      block(n % 10000, "")
    end -- 範囲外は何も出力しない
  end
\end{luacode*}
\newcommand{\myKansujiOf}[1]{% #1=<整数n>
  \directlua{ my_kansuji_of(#1) }%
}
\newcommand{\myKansuji}[1]{% #1=<カウンタ名>
  \myKansujiOf{\arabic{#1}}% \value でなく \arabic を使う
}
\begin{document}
% 節番号の出力形式を漢数字にする
\renewcommand{\thesection}{\myKansuji{section}}
\setcounter{section}{10098}% 10099節から開始

\section{\TeX}\label{sec:tex}
\myKansujiOf{800}屋 % 特に意味はありません;-)

\section{\LaTeX}\label{sec:latex}
\myKansujiOf{40010}川 % 特に意味はありません;-)

\section{まとめ}
\ref{sec:tex}節と\ref{sec:latex}節はアレ。
\end{document}

code-19.png

マフラーの色を少しずつ変えていく

素敵:blush:

  • \mySnowmanRow{<整数n>}{<色1>}{<色2>}: n個のゆきだるま:snowman:の列を描く。マフラーと帽子の色について、左端は色1、右端は色2にして、その間のものはグラデーションにする。
test-14.tex
\documentclass[a4paper]{article}
\usepackage{xcolor,scsnowman,luacode}% ゆきだるま☃!
\begin{luacode*}
  local prm = "\\scsnowman[scale=1.5,snow,arms,hat=%s,muffler=%s]"
  function my_snowman_row(n, lclr, rclr)
    for i = 1, n do
      local clr = ("%s!%.1f!%s"):format(
          lclr, (n - i) / (n - 1) * 100, rclr)
      tex.sprint(prm:format(clr, clr))
    end
  end
\end{luacode*}
\newcommand{\mySnowmanRow}[3]{%
  \directlua{ my_snowman_row(#1, \luastringN{#2}, \luastringN{#3}) }%
}
\begin{document}
\mySnowmanRow{10}{red}{blue}
\end{document}

code-20.png

まとめ

TeX言語を知らない幸せなLaTeXユーザ:blush:でも、Luaを活用すれば複雑なマクロをつくれます! ただしその際は、自分の知らないTeX言語:scream:からは徹底的に逃げ回るように心がけましょう!


  1. 外部のプログラム言語処理系と連携する方式(PythonTeXパッケージなど)もありますが、それも制約が多くて不便でした。なお、今ではexpl3を使うという方法も考えられますが、expl3という言語自体がTeX言語の“異質性”を引きずっているため、TeX言語の知識を持たない人がexpl3を習得するのは現状では困難だと思われます。 

  2. この記事は原則としてLaTeXユーザ向けであるため、以降の解説では「LaTeX」で通しますが、実際には\directluaの外が純粋なLaTeXであるかは全く無関係なので、「TeXの世界」という方が正確です。 

  3. この他にも「行頭の空白文字は無視される」という規則も適用されます。 

  4. :sushi:ただし、1つだけ例外があり、\parという制御綴のトークンについては、(その現在の意味が何かに関わらず)本来は“`\par␣”という文字列になるところが代わりに空文字列に変換されるようです。 

  5. [[…]]はLuaのraw文字列リテラルです。tex.writeを使っているため、結局、の部分がそのまま出力されることになります。 

  6. luacode*という“*-形”の環境があるならluacodeもあるはずで、事実、luacode\{}の特殊扱いを保持して(所謂“準verbatim”の状態で)コードを記述する環境です。ただし私自身は、luacodeは「Luaコードで必要な\を簡単に書けない」という問題があるため推奨せず、luacode*と生の\directluaだけ使い分ければ十分だと考えています 

  7. つまり、環境の中は(素の)グルーピングの中と同じで局所化されているわけです。ただし、LaTeXの“設定変更”動作のうち「カウンタ値の変更」などのごく一部のものは局所化の影響を受けない仕様(つまり変更が大域的)になっています。:sushi:もちろん、TeX言語の\gdef等のプリミティブを使えば大域的にマクロを定義することが可能です。 

  8. 私はこの仕様のことを「\verbの呪い」と呼んでいます。この制限はTeX言語の字句解析の仕様に起因するものであり、当該の機能の(TeX言語での)実装の工夫により解消することができないため、「制限が決して外れない」のいう意味合いを込めています。 

  9. 空白類文字は“そのまま”読み取られるわけではないですが、Luaの字句解析自体がフリースペースなので、空白類文字の解釈は文字列リテラル以外では問題にならないでしょう。 

  10. 「なるべく」とあるように、こちらは厳密に規定された規則とはしていません。「使用禁止文字の規則」が気にならない程度であれば、「\directluaの中の記述」の範囲を拡げてもよいでしょう。 

  11. 結果的にmy_diceのコードの中には禁止文字は全く現れないのでluacode*環境を使う必要はなかったことになりますが、だからといって「無駄に複雑なコードを書いた」ことにはなりません。大事なのは、「使用禁止文字の規則」禁止文字を気にせずにLuaコードを書けることですから。 

  12. :sushi:厳密にいうと、\directluaの展開の仕様は単純なトークン列置換ではなくて、\inputによるファイル読込と同等です。例えば\directlua{tex.sprint(1+5*8+1)}の展開の場合、“42”とだけ書かれたファイルを\inputするのと同等です。これはまた\scantokens{42}を展開したのとも同等といえます。(ただしこれらも完全に同等ではありません。) 

  13. \sectionを実行すると新しい節を始めたことになりsectionカウンタが増分されます。 

  14. :sushi:TeX言語の知識をもつ人であれば、対象の命令の実装を調べて動作を推定することができますが、仕様ではない以上、それは結局「実装に依存している」ことになってしまいます。 

  15. 実際の挙動としては、値区切るのコンマ(,)やkey-valueの区切りの等号(=)の部分をマクロに含めると、多くの場合に想定外の動作を引き起こします。 

  16. もし(今実装しようとしている)マクロの引数に何でも入れてよいのであれば、そこに~\\を含めることもできて、すると結局\directlua中に禁止文字が含まれてしまうことになります。従ってマクロの仕様として「正しい用法である限りそんなことは起こらない」という保証は必要です。 

  17. 1.23e-5のような指数表現を使わずに0.0000123のように表すのが「固定小数点表記」です。(La)TeXの文法では実数値は全て固定小数点表記で表すため、マクロの出力を(La)TeXの数値として利用したい場合は固定小数点表記を指定する必要があります。ちなみに、TeXでは実数値の内部表現も固定小数点(小数部16ビット)であるため、常に小数部7桁の精度を指定すれば十分です。 

  18. どうしても気になるようであれば、「引数を文字列として扱って(後述の方法で)Luaの関数に渡して、Luaの関数内で“適切な解読処理”を行う」という処置をすればいいでしょう。 

  19. もっとも、整数の16進表記("26C4のような表記)はLamport本では「\symbol命令の引数としての用法」としてしか現れないため、LaTeXの文法の体系の中にあるのかは微妙です。 

  20. ちなみに、「10進表現しか受け付けない」引数に対してカウンタ参照をどうしても渡したいという場合は、\arabic命令を使うという手段があります。「\directluaの引数内で\arabic{<カウンタ名>}を書いたらそれは当該カウンタの現在値の10進表記を書いたのと同等になる」という規則があるからです。 

  21. n回分の命令列をstring.repを使って作る方が簡単にみえますが、こうするとtxtの末尾に命令がある場合に先頭の英字と不用意に繋がってしまう可能性があります。 

  22. ちなみに、\luastringNの“N”は“no-expand”(展開なし)という意味です。“N”のない\luastringという命令もあり、こちらは「展開あり」なのですが、既に述べたようにLaTeXユーザにとっては「展開」は理解不能なものなので\luastringには用はないでしょう。 

  23. :sushi:TeX言語者向けの説明:\luastringN{トークン列}を完全展開すると、「引数のトークン列を展開せずに脱トークン化してできる文字列にLua文字列リテラルのエスケープを施した後に全体を"…"で囲った形の \the-文字列」に展開されます。ちなみに、\luastringの方は、先の説明の「展開せずに」を「完全展開して」に変えた動作を行います。 

  24. よく見ると、\o\\o␣\k\\k␣のように空白文字が入ってますが、元々LaTeXの規則で\o\o␣は同値であるので「空白がある方に“正規化”された」と考えればよいでしょう。 

  25. ここで\luastringNの引数には「LaTeXのテキストとして妥当なもの」なら何でも(使用禁止文字を含んでいても)入れられることにも注意してください。 

  26. なお、某NabeAzzはやめました。もう元ネタが忘れ去られていると思われるので:upside_down: 

  27. つまり、\textbf\textcolor{red}ではなく\bfseries\color{red}の方を指定するということです。 

  28. 「カウンタ出力命令」とは、\arabic\roman\Alphのように、カウンタ名を引数にとってカウンタの現在値を特定の形式で出力する命令のことを指します。 

62
63
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
62
63