\futurelet において \par を読み飛ばす方法

\futurelet において \par を読み飛ばす方法

- 上山 英二 の投稿
返信数: 4

こんにちは.最近 TeX 言語の学習をしている者です.

\futurelet の挙動を確認する練習で,以下のような「次に来るトークンが \quad であれば True を,そうでなければ False を出力する」マクロである \quadcheck を作成しました:

\def\quadcheck{\futurelet\my@nextchar\check@quad}%
\def\check@quad{%
  \ifx\my@nextchar\quad
    True%
  \else
    False%
  \fi
}
 
このマクロは \quadcheck\quad という自明な場合や,
 \quadcheck(ソースコード上での 1 回改行)\quad
等の場合には確かに True を返してくれます.しかし \quadcheck と \quad の間にソースコード上での改行を 2 回以上挟んでしまうと,\quadcheck と \quad の間に \par が自動挿入されてしまい,\futurelet によって \my@nextchar には \quad ではなく \par が代入されるため \ifx での判定が偽となり False が出力されてしまいます.
ソースコード上で見やすさの為に改行した結果意図しない出力がなされてしまうのは嫌なので,\quadcheck と \quad の間に挟まった \par を読み飛ばし,\par の一つ先のトークン(先の例の場合だと \quad)を \my@nextchar に代入できるよう上のマクロを改善したいです.

\@ifnextchar\par{…}{…} で次に来るトークンが \par であるか否かにより処理を分ける方法等も考えてみましたが,僕の浅薄な知識では中々上手くいきませんでした.

TeX Forum でこのような事をお聞きするのはとても場違いかもしれませんが,この質問に対して僕に問題解決へのヒント等をご教授してくださる方はいらっしゃらないでしょうか.

読みずらい長文となってしまい大変申し訳ございませんが,何卒よろしくお願い申し上げます.

上山 英二 への返信

Re: \futurelet において \par を読み飛ばす方法

- Z. R. の投稿

方針としては普通に「直後が\parであるか判定する」処理を挟むことになります。

  • 直後が\parである場合は:
    • 直後のトークンを一旦読み込んで保存する。
      ※これは普通に引数付きのマクロを使う。
    • 「直後が\quadかを判定してTrue/Falseを出力する」をやる。
    • 保存したトークンを置く。
  • それ以外は:
    • 「直後が\quadかを判定してTrue/Falseを出力する」をやる。

このときに注意すべきことは:

  • マクロの引数に\parを含めたい場合、マクロを\long付で定義する必要がある。

  • コードが複雑になると「意味のない\futurelet」を書いてしまうことがある。例えば以下のコードでは、\futureletの3つ先にあるトークンは\fiに決まっているので、この\futureletには意味がない。

    \futurelet\my@nextchar\my@some@macro
  \fi
  \my@other@macro

実際の実装コードは以下のような感じになるでしょう。

\def\checkquad{%
  % 直後のトークンをスキャン
  \futurelet\my@nextchar\my@checkquad@a
}
\def\my@checkquad@a{%
  % 直後が \par であるかで場合分け
  \ifx\my@nextchar\par
    \let\my@next\my@checkquad@with@par
  \else
    \let\my@next\my@checkquad@without@par
  \fi
  % "直後のトークン"を読みたいのでifを脱出しておく
  \my@next
}
% \long が必要
\long\def\my@checkquad@with@par#1{% #1は\parと等価
  \def\my@par{#1}% 一旦保存する
  % \par の次のトークンをスキャン
  \futurelet\my@nextchar\my@checkquad@with@par@a
}
\def\my@checkquad@with@par@a{%
  % 保存したトークン(\my@par)を戻している
  \ifx\my@nextchar\quad
    true\my@par
  \else
    false\my@par
  \fi
}
\def\my@checkquad@without@par{%
  \let\my@par\@empty
  \futurelet\my@nextchar\my@checkquad@with@par@a
}
%%% \my@checkquad@without@par の実装は省略
上山 英二 への返信

Re: \futurelet において \par を読み飛ばす方法

- 北見 けん の投稿
> ソースコード上での改行を 2 回以上挟んでしまうと,\quadcheck と \quad の間に \par が自動挿入されてしま

ということは、改行しない場合とは異なるトークン列が生成されるような事態になっているのですから、それはもはや
「ソースコード上で見やすさの為に改行した」とは言えないですよね。つまり、そのようなところであたかも「改行がなかった」かのような振る舞いをこのマクロに仕込むことは、事故を生み出す元になってしまって良くないのではないでしょうか? マクロの設計思想としては、避けるべき対応ではないかと思います。
北見 けん への返信

Re: \futurelet において \par を読み飛ばす方法

- 上山 英二 の投稿

元々この質問をさせて頂いた背景には「別行見出しが連続している場合(例えば \section{ほげ} の直下に \subsection{ふが} がある場合)等に,見出し間の垂直方向のスペースを制御できるようにしたい」というものがございまして,そこで \futurelet を用いたトークンの先読みテクニックを使うことで何とか実現できないかと思い立った,という経緯があります.

別行見出しを連続で掲げる際に

\section{ほげ}

\subsection{ふが}
 
のように「\section と \subsection にソースコード上の空行を 1 行挟む」ということを僕はソースコードの見やすさの為によくやるのですが(ひょっとするとこの事自体がとても良くない事な気もするのですが…),その際に \section と \subsection の間に挟まった空行によって \par が挿入されて意図しない出力になることを避けたい,という動機でこの質問をさせて頂きました.
確かに「ソースコード上で空行を挟むと \par が自動挿入される」という TeX の仕様とも言えるものに抗って,それを無理やり捻じ曲げようとしてしまうと思わぬ所で TeX/LaTeX を壊してしまい,事故を引き起こしてしまうのではないか,普通にソースコードの書き方を改善すれば済む話なのではないか,という事は僕もこの質問をする前に思いました.しかし,そのような挙動を実装をするにはどうすれば良いのか,という事には純粋に興味があり,もしかすると今後の役にも立つかもしれないと思ったため,その事を承知の上で質問させて頂きました.
誤解を招くような質問内容となってしまった事を深くお詫び申し上げます.
上山 英二 への返信

Re: \futurelet において \par を読み飛ばす方法

- 北見 けん の投稿
なるほど。TeX言語的な話については Z. R. さんの回答で解決でしょうか。
それはそれとして、「別行見出しが連続している場合(例えば \section{ほげ} の直下に \subsection{ふが} がある場合)等に,見出し間の垂直方向のスペースを制御できるようにしたい」ということであれば、\section や \subsection の定義を工夫するのが良いと思います。\section のところで \everypar に細工を仕込むことで、垂直モードで無視される余分な \par に影響されない形で、\section と \subsection の間に地の文がある場合とない場合で動作を変えることができると思います。