前回の続きで、「if-トークンの罠」に陥らないため対策を述べる。実際に「罠」を構成するコードを修正する方法が中心となるが、危険なコードを避ける方法についても触れている。
問題を起こすコードを if 文の外に追い出す
つまり、「未定義かも知れない if-トークン」や「if 文でない if-トークン」が if 文の中にあることが問題なので、その部分をマクロにする等の方法で if 文の外に出してしまう。
% hoge の設定をコピーする
\def\xx@copy@left@hoge@to@right{%
\let\ifxx@right@hoge\ifxx@left@hoge
}
% (当該の部分)
\ifxx@balanced@hoge
\xx@copy@left@hoge@to@right
\fiこの処置はある程度「機械的に」行うことができるが、あまり闇雲に行うとコードの可読性を損なうことになるので、できるだけ、「意味のあるまとまりをマクロとして括り出す(そしてそのマクロに適切な名前を付ける、要するに利ファクタリングをするということ)」という形で行うのが望ましい。((今の場合、\xx@copy@left@hoge@to@right をその使用箇所から分離された「部品」と見做すということ。望ましくないのは、「追い出した部分」を if 外には置くが、別個の部品とせずに \ifxx@temp 等の名前を付けて元の if 文と一体と見做すことである。これでは単にコードが読み難くなってしまう。)) e-TeX 対応の例の場合は、例えば次のような対策が考えられる。
% if 文の外で \somecommand の実体を定義しておく
\@onlypreamable\xx@somecommand
\def\xx@somecommand#1{%
\ifdefined#1%
………… % 何かの処理
\fi
}
\ifxx@engine@is@eTeX %---- e-TeX の場合のみ実行する
\let\somecommand\xx@somecommand
\fi %----
% 非 e-TeX の場合は \somecommand は未定義のままで、
% 無駄な \xx@somecommand は \@onlypreamble のために処分される。私自身は、「if 文でない if-トークン」を含むコード((これには当然「if-トークンの定義」(\newif)も含まれる。特に、if 文中で \newif を実行するというのは、わざわざ自分で危険な「未定義かも知れない if-トークン」を作っていることになるので、絶対に避けるべきである。))は、それが実際に if 文中にあるかに関わらず、プログラム中に散在させることを避けて(パッケージ冒頭などの)特定の場所に纏めて置くことにしている。その理由は、後でパッケージの改修(例えばパッケージオプションを新設した、等)を行った際に、「コード中のある領域をまとめて if の中に入れる」ということが安全に行えるようにするためである。
%--------
%* ↓↓↓ このセクションは絶対に if 中に入れてはいけない!
\newif\ifxx@left@hoge % 左をhogeにするか
\newif\ifxx@right@hoge % 右をhogeにするか
\newif\ifxx@balanced@hoge % hogeの設定を左右で均等にするか
% hoge の設定をコピーする
\def\xx@copy@left@hoge@to@right{%
\let\ifxx@right@hoge\ifxx@left@hoge
}
%* ↑↑↑
%--------
%* このように危ないコードを一箇所に纏めておくと、
%* 残りの部分は安全に改修できるようになる。これが最も基本的な対策だが、種々の理由で実施ができない(あるいは難しい)場合がある。以下では別の対策方法を述べるが、これら何れも当該部分のコードが少し技巧的に見えるという欠点がある。
\csname を利用する
「if 文でない if-トークン」の問題については、読み飛ばされる場合に見つけて欲しくない if-トークンや \fi を \csname で隠してしまうという方法が使える。
\ifxx@balanced@hoge \expandafter\let\csname ifxx@right@hoge\expandafter\endcsname \csname ifxx@left@hoge\endcsname \fi
しかし「未定義かも知れない if-トークン」をこれで解決しようとしても多くの場合失敗する。
% これは正しくない
\ifxx@engine@is@eTeX %---- e-TeX の場合のみ実行する
\def\somecommand#1{%
\csname ifdefined\endcsname#1%
…………
\csname fi\endcsname
}
\fi %----何故なら、もし \ifdefined が実際に実行されてその結果が偽だった場合、対応する \fi を「読み飛ばし」で見つける必要があるからである。上のコードでは対応する \fi が見つけられない。
ダミーの if や fi を置く
これも「if 文でない if-トークン」の場合に有効な対策の一つ。
\ifxx@balanced@hoge \let\ifxx@right@hoge\ifxx@left@hoge \@gobble\fi \@gobble\fi % if-トークンをマッチさせる \fi
カテゴリコードに訴える
……使用を検討したことなら実際にある。
\ifxx@balanced@hoge \catcode`\|=0 % 読み飛ばしの際には if-トークンに見えない |let|ifxx@right@hoge|ifxx@left@hoge \catcode`\|=12 \fi
別ファイルにして \input で読み込む
これは、「未定義かも知れない if-トークン」の使用が広大な領域に散在している場合に有効な対策である。典型的なのは、e-TeX 対応の例である。
% e-TeX 依存部分のコードを読み込む
\ifxx@engine@is@eTeX
\input{mypkg-etex.def}
%* mypkg-etex.def の中では、\ifdefined や \ifcsname も
%* 含めて e-TeX 依存コードが使い放題になる
\fi別ファイルにして \input で読み込む
先の例と同じ状況で、別ファイルを使わない解決策。e-TeX 依存コードをファイルの後ろの部分に纏めて記述して、非 e-TeX の場合はその領域に入る直前に \endinput で読込を途中で止めてしまう。
% パッケージ読込終了時の処理
\AtEndOfPackage{%
%* ここに記述したコードはパッケージ読込終了時に実行される、
%* つまり \ifxx@engine@is@eTeX の値に関わらず
%* ファイルの一番最後にあるかのように扱われる。
}
% e-TeX でない場合はここで読込終了
\ifxx@engine@is@eTeX\else
\expandafter\endinput
\fi
%------------
%% e-TeX 用コード
%* ここでは e-TeX 依存コードが使い放題
%% EOF