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 件のコメント:
コメントを投稿