前回の続きで、「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