LaTeXではシングルクォート記号がデフォルトで「’」になる。Unicodeでいえば、RIGHT SINGLE QUOTATION MARK(U+2019)。この挙動は、本文ならよいのだけど、等幅フォントにするような場面では都合が悪い。等幅フォントのときにみんなが期待するシングルクォートは「'」である。
みんなが期待する挙動なので、当然、そのためのパッケージがすでにある。upquoteパッケージである。しかし、このupquoteパッケージは、\verbコマンドおよびverbatim環境向けに作られている。fancyverb環境やalltt環境でも使えるが、\textttコマンド内の挙動には影響しない。つまり、upquoteパッケージを使っても、\texttt内のシングルクォート記号は「’」になる。
\texttt{...}の中でシングルクォート記号を直立タイプ「'」にするにはどうするか。すぐに思いつくのは、「'」をアクティブ文字にして、OT1/cmttの\char13にするという方法。あるいは、textcompパッケージで提供されている\textquotesingleにするという方法。実際、upquoteパッケージの実装もそのようになっている。その方針をそのまま実装するとこうなる。
\let\orgtexttt\texttt
\def\texttt#1{%
\catcode`'=\active
\def'{\textquotesingle}
\orgtexttt{#1}}
もちろん、これは動かない。この\defの中身はすでにTeXの入力ルーチンで読み込まれているので、\def'{\textquotesingle}の時点では「'」がアクティブ文字になっていない。そのため、ふつうの文字である「'」に対して\defすることになってしまうからだ。結果として、「コントロールシーケンスやアクティブ文字であるべき場所にふつうの文字がある」というエラーになる。
! Missing control sequence inserted.\inaccessible
こういう場合の常套手段として、「\lowercaseトリック」と呼ばれるものが知られている。このトリックのポイントは、\lowercaseというコマンドの引数が、「すべての文字を小文字として解釈してくれて、しかもカテゴリーコードに関しては無視してくれるような世界」になることにある。そういう世界で、「'」のことを、なにか「\defの1つめの引数に置いても怒られない都合がいいアクティブ文字」の小文字にしておく。幸い、TeXには、そのような都合がいい文字がある。「~」だ。\def~{...}であれば、\def\texttt#1{...}の定義部の中にいきなり書いても怒られない。そこで、「~は'の小文字」ということにしておいて、\def~{...}と書けば、この\defにより実際には「'」に対する定義が書けることになる。
あるアルファベットに対する「小文字」を定義するためのプリミティブは\lccodeだ。これを使い、「'」のことを「~の小文字である」ということにして、その文脈で\lowercase{...}内で\def~{\textquotesingle}すればよい。
\def\texttt#1{%
\catcode`'=\active
\begingroup
\lccode`~=`' %
\lowercase{\endgroup
\def~}{\textquotesingle}
\orgtexttt{#1}}
\lccode`~=`'のスコープを定めている\begingroupと\endgroupの組に対し、\lowercase{...}のカッコがきちんと入れ子になっていない。あまつさえ\def~{\textquotesingle}にも「}」がまたがってしまっている。気持ち悪いかもしれないけど、これで問題はない。少なくとも、\def~を\begingroup~\endgroupの中に入れてしまうと、この\defがローカルになってしまうので、「'」がアクティブになるだけなって定義がないままとなりエラーになってしまう。
! Use of ' doesn't match its definition.
まあ、\gdefにすればいいだけだけど。
\def\texttt#1{%
\catcode`'=\active
\begingroup
\lccode`~=`' %
\lowercase{%
\gdef~}{\textquotesingle}\endgroup
\orgtexttt{#1}}
(ところで、\endgroupが\lowercase{の直後で問題ないことの根拠は、正直なところよく理解していない。\lowercaseが大文字小文字の対応表をメモリに展開するのが引数を読む前、だからなんだろうなあ。)
さて、\lowercaseトリックのおかげで「'」がアクティブ化される前に「\def'」と書くことを回避できたので、こでれエラーにならずに動く。しかし、挙動は依然としておかしい。ブロック要素で最初に登場する\textttの中では「'」の定義が効いておらず、その後ろに出てくる「'」が、\textttの中だろうが外だろうが\textquotesingleになってしまうのである。
*\texttt{\show'}
> the character '. % \textttの中だけど、文字
...
*\show'
> '=macro:
->\textquotesingle . % \textttの外だけど、アクティブになってる
...
*\texttt{\show'}
> '=macro:
->\textquotesingle . % 2つめの\textttの中ではアクティブに
...
最初に登場する\textttの中で「'」の定義が効かない原因は、すぐにわかった。\textttの引数はすでに読み込まれているから、「'」に対する\catcodeは効いてないのである。\defに対しては\lowercaseトリックのおかげでごまかせたが、この状態では\textttの引数に対して「'」が\textquotesingleとして扱われることはない。
この欠陥に対処するには、\textttの引数が読み込まれるのを遅らせればいい。そういう場合の常套手段は、マクロの展開を二段階にしてサブのほうで引数を読み込ませる、というもの。つまりこんなふうにする。
\let\orgtexttt\texttt
\def\texttt{%
\begingroup
\catcode`'=\active
\x@texttt}
\def\x@texttt#1{%
\begingroup
\lccode`~=`' %
\lowercase{\endgroup
\def~}{\textquotesingle}
\orgtexttt{#1}
\endgroup}
これで、目的のマクロそのものは完成した。
これにて本件は落着といたす、でもいいんだけど、1つ前の定義が「最初に登場する\textttより後ろで、\textttの中だろうが外だろうがシングルクォートが\textquotesingleになってしまう」という挙動だったのが謎い。まったく\textquotesingleにならない、というなら納得できるんだけど、なぜ
\textttの引数内で閉じているはずの\catcode`'=\activeが外に漏れてしまっているのか。
どう考えても、オリジナルの\textttの定義に何かある。
しょうがないのでlatex.ltxを覗くと、\textttの定義は事実上こうなっていた。
\DeclareRobustCommand\texttt[1]{%
\ifmmode
\nfss@text{\ttfamily #1}%
\else
\hmode@bgroup
\text@command{#1}%
\ttfamily\check@icl #1\check@icr
\expandafter
\egroup
\fi}
なんなんだよ、この下から3行めの\expandafter。。これのおかげで、\texttt(を再定義したものの中にある\orgtexttt)の引数が読まれるとき、もりもり後ろのほうまで読まれてしまって、\catcode`'=\activeがしばらく先まで有効になってしまっていたっぽい。
この\expandafterは、\textttに限らず、NFSSが提供するフォント変更コマンドすべてに登場する(ようなマクロになっている)。これ、なんのために必要なの?
なにも謎くなかった。先の動作しない\textttの定義で\catcode`'=\activeは閉じてない。ZRさんありがとうございます。
ダメな方の \texttt で、例えば
— ZR-TeXnobabbler(∉∅) (@zr_tex8r) 2018年4月30日
\texttt{\show'}
を一回展開すると
\catcode`'=\active \begingroup …
となる。\catcode 文はグループ外だから、\begingroup 以降のゴチャゴチャに関わらず、とにかく ' はアクティブになったまま。
0 件のコメント:
コメントを投稿