Runaway argument

Runaway argument? は、LaTeX のエラーのなかで唯一 行番号があてにならない——どころか、行番号がまったく表示されないことすらあるエラーです。閉じ } を書き忘れると、TeX はそれを探して本文を延々と飲み込み、飲み込みきったところではじめて悲鳴を上げます。そのとき報告されるのは、間違えた場所ではなく 限界に達した場所。代わりに TeX は、自分が飲み込んだ文字列をそっくり印字してくれます——Runaway argument? の次の行がそれで、これが本当の地図です。このページでは、その地図の読み方、! Paragraph ended before \@sect was complete という自分では書いた覚えのない名前が出る理由、そして \newcommand* を「わざと引っかかる仕掛け」として使う手を扱います。

行番号ではなく runaway text を読む

Runaway argument? の次の行は、閉じられていない { のちょうど直後から始まっています。 つまりその行頭の数語をエディタで検索すれば、書き忘れた { が見つかります。行番号を眺めるより確実で速い方法です。下は \textbf{ を閉じ忘れた実際のログです。

terminal
Runaway argument?
{an important point that never closes. Another sentence on the next l\ETC.
! File ended while scanning use of \textbf .
<inserted text>
                \par
<*> t6.tex

ここで注目すべき点が二つあります。ひとつは末尾の \ETC.——飲み込んだ文字列が長すぎて画面幅で切られたという TeX の印です。もうひとつは、最終行が l.6 のような行番号ではなく <*> t6.tex というファイル名になっていること。これは「読むものが尽きた」=ファイル末尾まで走り切ったという意味で、この形が出たら { はどこか遥か上にあると分かります。行番号が出るのは、途中で何かに引っかかって止まれた場合だけです。

Paragraph ended before ... was complete——短い引数に入った空行

引数の中に空行がある、というのがこのメッセージの意味です。 TeX にとって空行は改行ではなく \par というトークンで、\par は「短い」引数——\long を付けずに定義されたマクロの引数——には入れられません。だから \section{...} の中で改行したつもりに空行が混じると、} はちゃんと書いてあるのに runaway になります。

terminal
Runaway argument?
{A heading that runs on
! Paragraph ended before \@sect was complete.
<to be read again>
                   \par
l.5 and on across a blank line}

\@sect という名前を書いた覚えはないはずです。じつは \section 自体は \long で定義されていて、空行を通します——通した先で仕事を引き受ける内部マクロ \@sect\long ではない、というのが真相です(TeX Live 2024 で \show を使えばそのまま確認できます)。メッセージに出るのは、あなたが打ったコマンドではなく、実際に引数を受け取ろうとしていた内部マクロの名前です。\caption\footnote、目次まわりでも似た名前が顔を出します。名前に見覚えがなくても慌てず、Runaway argument? の行を読んでください。

なぜ引数に \par を入れさせないのか

この制限は不便のためではなく、閉じ括弧の書き忘れを見つけるためにわざと設けられたものです。 TeX Live に同梱されている Victor Eijkhout の TeX by Topic は、\par を引数に許さない規則について「閉じ括弧の書き忘れを突き止めるのに役立つ」と述べ、続けて「空行は \par を生む。それはたいていの場合、閉じ括弧が忘れられたという意味だ」と書いています。つまり短い引数は、暴走を早めに止めるための安全装置です。もしこの規則が無ければ、{ をひとつ落とすたびに TeX は無言で文書全体を飲み込み、何の手がかりも残さないまま止まることになります。

この仕掛けの厳密さは、ちょっと試すと見えてきます。TeX が拒むのは \par という名前のトークンそのもの であって、「段落が終わる」という意味ではありません。\endgraf\par と同じ動きをする別名ですが、短い引数の中にそのまま書いても何も起きません。同じ引数に \par と直接書くと、そこで ! Paragraph ended before ... was complete が出ます。TeX はトークンの名前を見比べているだけ——だからこそ空行という「意味のない改行」が、確実に検知できる合図として機能します。

File ended while scanning use of ... との違い

Runaway argument? の後に続く行が二通りあり、それが「どこまで暴走したか」を教えてくれます。 途中の空行で止められたなら Paragraph ended before ... was complete、最後まで止まらなかったなら File ended while scanning use of ... です。さらに細かい手がかりが、その下の一行にあります。<to be read again> \par は「空行から生まれた \par を読んで、押し戻した」、<inserted text> \par は「読むものが尽きたので TeX が自分で \par を差し込んだ」という意味。前者なら原因は近くにあり、後者なら遠くにあります。

メッセージ意味ログが指す場所
Runaway argument?引数の収集が終わらなかった。必ず次の 1 行に飲み込んだ文字列が続く次の行の先頭が、閉じ忘れた { の直後
Paragraph ended before ... was complete短い引数の中に空行(\par)が入った空行のある行番号。{ はその少し上
File ended while scanning use of ...ファイル末尾まで } が現れなかった行番号ではなく <*> ファイル名{ は遥か上

\newcommand* をわざと引っかかる仕掛けにする

\newcommand\long なマクロを作り、\newcommand* は短いマクロを作ります。 \show で確かめると、前者は \long macro: と表示され、後者はただの macro: です。この差は runaway のときにはっきり出ます。星なしで定義したマクロの { を閉じ忘れると、TeX は空行を平気で越えてファイル末尾まで走り、! Emergency stop. で PDF も出さずに終わります。星付きなら 最初の空行で止まり、そのマクロの名前を告げ、しかも組版は最後まで通ります。差はログ数行ではなく、原因を数十行に絞れるか文書全体になるかの差です。

latex
% short: the runaway stops at the first blank line, and the run survives
\newcommand*{\keyword}[1]{\textbf{#1}}
%   ! Paragraph ended before \keyword was complete.
%   <to be read again>
%                      \par

% long (the default): the runaway reaches end of file
\newcommand{\keyword}[1]{\textbf{#1}}
%   ! File ended while scanning use of \keyword.
%   ! Emergency stop.

実務上の指針は単純です。引数に段落をまるごと入れる必要がないマクロは \newcommand* で定義する。 見出し、用語、単位、短いラベル——ほとんどの自作マクロがこれに当たります。段落を渡す前提のもの(囲み記事や注釈の本文など)だけ星なしにします。この星印そのものの意味と \renewcommand との関係は「マクロの定義」のページが扱います。なお、\verbverbatim 環境を引数の中に入れると別種の壊れ方をします——理由は「そのまま出力」のページにあります。

欠けた { を実際に探す手順

まず runaway text の先頭を検索し、見つからなければ runaway text の「中身」を読みます。 飲み込まれた文字列には、本来そこにあるはずのないものが混じっていることがあり、それが範囲を教えてくれます。たとえば runaway text の中に \begin {document} が見えたら、閉じ忘れた {プリアンブルにあります——\newcommand の定義本体を閉じ忘れると、実際にこの形になります。\end {document} が見えれば、暴走が文書の末尾まで届いたということです。おおよその見当がついたら、エディタの括弧対応機能の出番です——怪しい { にカーソルを置き、相方がどこに着地するかを見ます。それでも絞り込めなければ、文書を半分コメントアウトして再コンパイルし、エラーが出るほうを半分にする——これを数回繰り返せば必ず行き着きます。

terminal
Runaway argument?
{Hello \begin {document} Body. \end {document}
! File ended while scanning use of \@argdef.