1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

徹底解説! TeX言語のプリミティブ判定の方法

1
Posted at

本記事ではトークンの​「プリミティブ判定」​の方法、すなわち「制御綴トークンが本来のプリミティブであるか」を判定する方法を説明します。

必要な前提知識

  • 「TeXのプリミティブとは何か」について知っている。
  • 一般の条件文の文法について知っている。

なぜ「プリミティブ判定」したいのか

この記事において、制御綴トークンが「本来のプリミティブである」とは「外形が制御綴であり、制御綴名と同名のプリミティブを意味として持っている」ことを指します。\expandafterを例にすると、\expandafterトークンが「意味が\expandafterプリミティブである」という性質を満たすことです。

TeXの初期状態では\expandafterトークンの意味は\expandafterプリミティブであり、また通常の運用では\expandafterトークンの意味を\expandafterプリミティブ以外で上書きすることはありません。このため、\expandafterが「本来のプリミティブである」ことは、通常は「実行中のエンジンが\expandafterプリミティブをサポートしている」ことを意味します。

この性質のため、プリミティブ判定は「実行中のエンジンの種類」を判別する手段としてよく用いられます。例えば、pTeX系のエンジンには\kanjiskipという特有のプリミティブがあるので、「\kanjiskipトークンのプリミティブ判定を行う」ことで「実行中のエンジンがpTeX系であるか」が判別できるわけです。

この話を聞いて「pTeX系以外では\kanjiskipはそもそも定義されていないのだから『\kanjiskip定義済であるか』を調べるだけで十分である」と思ったかもしれません。

\ifx\kanjiskip\@undefined\else % '\kanjiskip'が定義済なら…
  % エンジンがpTeX系である??
\fi

しかしそれでは不完全な場面が実際に存在します。pTeX系エンジン用の\kanjiskipを利用したコードがあるとして、それをpTeX系以外のエンジン(例えばLuaLaTeX+LuaTeX-ja)でも動かすために\kanjiskipの“実装”1を与えるという手段(一種のポリフィル)がとられることが実際にあります。

\protected\def\kanjiskip{%
  % '\kanjiskip'の"実装"のコード
}

これを前提にした場合、「\kanjiskipの有無を判定する」のはその目的が「\kanjiskipを使う」ことであれば合理的といえますが、目的が「エンジンがpTeX系であるかを判定する」の場合は妥当性を欠きます。pTeX系を前提とするコードでは当然\kanjiskip以外の(ポリフィルがない)「pTeX系特有の機能」が使われうるからです。

というわけで、以降ではTeXでプリミティブ判定をするための方法を紹介していきます。

\if(pdf)primitive でプリミティブ判定する

実は今のTeXエンジンには「プリミティブ判定のためのプリミティブ」が存在します。これを利用するのが最も直感的でしょう。

\ifpdfprimitive プリミティブの話

\ifpdfprimitiveはプリミティブ判定をするためのif-トークンです。引数に任意のトークンを取り、そのトークンが(制御綴でありかつ)「本来のプリミティブであるか」を判定します。

\ifpdfprimitive‹トークン›
  % ‹トークン›は本来のプリミティブである
\fi

\ifpdfprimitive がヤヤコシイ話

これだけで話が済めばいいのですが、残念ながらそうはいきません。この\ifpdfprimitiveプリミティブは元祖TeXには存在しない拡張プリミティブで、それ自体がエンジン依存をもつからです。

  • pdfTeXとe-(u)pTeX2では\ifpdfprimitiveがサポートされています。
  • XeTeXとLuaTeX(およびHiTeX3)ではこのプリミティブが\ifprimitiveという別名でサポートされています。
  • それ以外のエンジンではこのプリミティブはサポートされていません。

\ifpdfprimitiveのプリミティブ名の中にpdfが含まれているのは、それが元々pdfTeX拡張の一つであったことに由来します。機能的にはPDFバックエンドとは全く無関係であるため、XeTeXやLuaTeXではpdfを除去した\ifprimitiveという名前が採用されました。

しかしこの状況をよくみると、「LaTeXフォーマットで使われているエンジン」に関しては(名前の差異はあっても)\if(pdf)primitiveがサポートされていることがわかります4

これを踏まえると、実際に\if(pdf)primitiveを使いたい場合には次のような手順を踏むのが適切です。

  • \ifpdfprimitiveが使用可能ならば\ifpdfprimitiveを使う。
  • そうでなくて\ifprimitiveが使用可能ならば\ifprimitiveを使う。
  • そうでもないなら、\if(pdf)primitiveは使えない。LaTeXの場合はこれは異常(かなり古いLaTeXカーネルである)と見なしてよい。

\if(pdf)primitiveの存在判定は「定義済であるか」で行うことにします。目的が「\if(pdf)primitiveの機能を使うこと」なので、仮にポリフィルであってもかまわないと判断できるからです。(実際によく行われるポリフィルは「2つの制御綴のうちの使える方を使えない方にコピーする」ことです。)

ただし、\if(pdf)primitiveはそれ自体がif-トークンであるため、if-均衡性(if-トークンと\fiの均衡がとれていること)が崩れないように注意してコードを書く必要があります。

「if-文のif-均衡性」について補足しておきます。

\if‹引数› ‹真分岐›\else ‹偽分岐›\fi

このようなif-文において、if-均衡性が要求されるのは‹真分岐›‹偽分岐›のトークン列です。‹引数›の部分は“必ず解釈される”ためif-均衡でなくとも大丈夫です。(もちろん「このif-文全体をif-均衡にしよう」と希望する場合は話が別。)

この辺りの煩雑さを避けるため、\if(pdf)primitiveの意味をもつ独自のif-トークンである\ifmy@primitiveを先に用意しておくことにしましょう。少し技巧的ですが次のようなコードで実現できます5

% \ifmy@primitive@ok: '\ifmy@primitive'が実際に使えるか
\newif\ifmy@primitive@ok
% \ifmy@primitive: '\if(pdf)primitive'の役目を果たすif-トークン
\let\ifmy@primitive\iffalse % "if-トークンである"ことを保証する

% \ifprimitive が使えるならそれを \ifmy@primitive にコピーする
\def\my@next{\let\ifmy@primitive\ifprimitive \my@primitive@oktrue}
\ifx\ifprimitive\@undefined\else \my@next \fi
% \ifpdfprimitive が使えるならそれを \ifmy@primitive にコピーする
\def\my@next{\let\ifmy@primitive\ifpdfprimitive \my@primitive@oktrue}
\ifx\ifpdfprimitive\@undefined\else \my@next \fi

この\ifmy@primitiveを利用して、プリミティブ判定は次のように実現できます。

\ifmy@primitive@ok
  \ifmy@primitive\kanjiskip
    % エンジンがpTeX系である
  \else
    % エンジンがpTeX系でない
  \fi
\fi

“string-meaningテスト”でプリミティブ判定する

\if(pdf)primitiveを用いたプリミティブ判定は直感的ですがそれ自体にエンジン依存性があり、それへの対処が複雑になるという欠点がありました。ここではどのエンジンでも実行可能なプリミティブ判定手法である“string-meaningテスト”を紹介します。

“string-meaningテスト”の話

「意味が本来のプリミティブである」トークン、例えば\expandafterについて\showで意味を調べると以下のような結果になります。

%('\show\expandafter'の結果)
> \expandafter=\expandafter.
<*> \show\expandafter

ここで=の左側にあるのは「トークンに\stringを適用して得られる外形の文字列」、右側にあるのは「トークンに\meaningを適用して得られる意味の文字列」です。プリミティブであればこの2つは同じになりそうです。この性質を利用して「\string\meaningの適用結果が等しい場合に本来のプリミティブと見なす」のが「string-meaningテスト」です。

%% \my@test@primitive‹トークン›
% "string-meaningテスト"により‹トークン›のプリミティブ判定を行う.
% 結果は'\if@tempswa'スイッチに返る.
\def\my@test@primitive#1{%
  \edef\my@temp@a{\string#1}\edef\my@temp@b{\meaning#1}%
  \ifx\my@temp@a\my@temp@b \@tempswatrue \else \@tempswafalse \fi
}

この\my@test@primitiveマクロを用いてプリミティブ判定ができます。

\my@test@primitive\kanjiskip \if@tempswa
  % エンジンがpTeX系である
\else
  % エンジンがpTeX系でない
\fi

\my@test@primitiveマクロの定義中に登場するプリミティブはどれも元祖TeXに存在するものばかりなので、この手法がエンジン非依存であることがわかります。

“string-meaningテスト”がヤヤコシイ話

このようにstring-meaningテストはエンジン依存がないのが長所ですが、短所もあります。それは一部のトークンで誤判定が起こることです。

先述の通り、プリミティブへの\meaningの適用結果は「プリミティブ名の制御綴」になるのが原則ですが、これには若干の例外があります。元祖TeXの範囲では以下の物が例外であり、これらについてはstring-meaningテストは偽と誤判定してしまします。

  • \nullfont
    \meaningの文字列はselect font nullfontである。
  • マークレジスタ: \botmark\firstmark\splitbotmark\splitfirstmark\topmarkの5つ。
    \meaningの文字列は\‹プリミティブ名›: ‹内容›という形の文字列で、‹内容›は現在のレジスタの内容。

逆に、英字と非英字の両方を含む(普通ではない)名前の制御綴については、string-meaningテストが真と誤判定する可能性があります。

% これは偽と判定される
\my@test@primitive\nullfont \if@tempswa
  %...
\fi

% 'char"A'という名の制御綴の意味を"値が10のchardefトークン"
% (これのmeaning文字列は'\char"A')にする.
\catcode`\"=11 % " を英字扱い
\chardef\char"A=10
% これは真と判定される
\my@test@primitive\char"A \if@tempswa
  %...
\fi

しかし実際のプログラム中でプリミティブ判定をしたいトークンがこのような「変なトークン」であることは滅多にないので、誤判定が問題になることはないでしょう。

その他の方法でプリミティブ判定する

pdftexcmdsパッケージを使う話

pdftexcmdsパッケージには、ここまでで述べた2つのプリミティブ判定方法を補助するための機能が備わっています。

  • \pdf@ifprimitive:[if-トークン] \if(pdf)primitiveの意味をもつif-トークン。ただしエンジンがサポートしていない場合は未定義。
    ※つまり先の説明で定義した\ifmy@primitiveが最初から用意されているわけです。
  • \pdf@isprimitive\‹制御綴1›\‹制御綴2›{‹真›}{‹偽›}: 「\‹制御綴1›\stringの文字列」と「\‹制御綴2›\meaningの文字列」が等しいかを検査する。(LaTeX式条件文)
    \‹制御綴1›\‹制御綴2›を同じにするとstring-meaningテストになります。

expl3の\token_if_primitive:N~を使えない話

expl3には「トークンがプリミティブであるかを判定する」ための\token_if_primitive:N~という述語がありますが、これは本記事でいうプリミティブ判定ではありません

  • \token_if_primitive:N~ ‹トークン›:[述語・完全展開可能] ‹トークン›が(何らかの)プリミティブであるか。
    ※「本来(制御綴と同名)のプリミティブ」であるかは考慮しない。

混同しないように注意しましょう。

まとめ

というわけで、せっかくの連休:four_leaf_clover:なのでドンドンTeX言語しましょう!:information_desk_person:

  1. あるいは「機能劣化してもいいからとにかくコードを通したい」という場合に\newskip\kanjiskip等の“ダミーの実装”を与える場合もあります。

  2. e-(u)pTeXではe-TeX拡張モードが有効のときにのみサポートされます。pdfTeXではe-TeX拡張モードの有効無効にかかわらずサポートされます(\ifpdfprimitiveはe-TeX拡張ではなくpdfTeX拡張だから)。XeTeX/LuaTeXではe-TeX拡張は常に有効で\ifprimitiveも常にサポートされます。少し複雑ですが、後述の通り、今のLaTeXフォーマットでは「e-TeX拡張も\if(pdf)primitiveも必須」とされているため、今のLaTeXでは\if(pdf)primitiveは(名前はさておき)常に使えると考えてかまいません。

  3. HiTeXは「リフロー可能な出力をする」ことを目指した新しいTeXエンジンです(出力もDVIやPDFではなく独自の形式を用いる)。HiTeXでは-ltxオプション(恐らく「LaTeXの要件を満たすモード」の意味)付きで起動した場合に\ifprimitiveがサポートされます。

  4. 実はこれは、「現在のLaTeXカーネルがエンジンに対するサポート要件として\if(pdf)primitiveを要求しているから」という理由があります。

  5. \if(pdf)primitiveが使えない」という異常ケースへの対処(\ifmy@primitive@okの部分)は実地での前提条件と照らし合わせて簡略化するといいでしょう。かなりトリッキーになりますが、「\ifmy@primitive\iffalseであることを積極的に利用する」という手段もあります。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?