「いまプリアンブルなのか、それとも本文中なのか」判定
この記事は、サブブログの 2016-08-11 投稿記事を移転してきたものです。
LaTeX パッケージを作るときに、「この命令がプリアンブルで実行されたら…、本文中で実行されたら…」のような分岐が必要な場合に役立つ判定法のメモ。なお、「プリアンブル」とは \documentclass … \begin{document}
で挟まれた部分のこと*1。
最初に「答え」
- プリアンブルより前の場合
- これは「以下の 2. から 4. のいずれでもない場合」。
- プリアンブルの場合
- 「
\documentclass
の定義=\@twoclasseserror
」かつ「4. でない」。
- 「
- 本文中の場合
- 「
\documentclass
の定義=\@notprerr
」。
- 「
- プリアンブルの中で、特に
\AtBeginDocument
の中の場合- 「
\AtBeginDocument
の定義=\@firstofone
」。
- 「
これで判定可能なことは、以下に掲げる latex.ltx のソースを読めばわかる(以下の行番号は LaTeX2e 2016/03/31 Patch level 3 の source2e.pdf に対応している)。
ltdefns.dtx
43 \def\@preamblecmds{} 44 \def\@onlypreamble#1{% 45 \expandafter\gdef\expandafter\@preamblecmds\expandafter{% 46 \@preamblecmds\do#1}} 47 \@onlypreamble\@onlypreamble 48 \@onlypreamble\@preamblecmds
ltfiles.dtx
11 \def\document{\endgroup … 中略 … 47 \let\AtBeginDocument\@firstofone 48 \@begindocumenthook … 中略 … 56 \gdef\do##1{\global\let ##1\@notprerr}% 57 \@preamblecmds … 中略 … 60 \ignorespaces} 61 \@onlypreamble\document
ltclass.dtx
206 \def\documentclass{% 207 \let\documentclass\@twoclasseserror 208 \if@compatibility\else\let\usepackage\RequirePackage\fi 209 \@fileswithoptions\@clsextension} 210 \@onlypreamble\documentclass
385 \def\AtBeginDocument{\g@addto@macro\@begindocumenthook} 386 \def\AtEndDocument{\g@addto@macro\@enddocumenthook} 387 \@onlypreamble\AtBeginDocument
以下で簡単に説明しておく。
「プリアンブルより前」と「プリアンブル」の区別
ltclass.dtx の206-209行目の \documentclass
の定義を読むと
一度\documentclass
が実行された(=プリアンブルに入った)時点で\documentclass
は\@twoclasseserror
に再定義される
ことがわかる。したがって、素直にこれを利用してしまおう。ちなみに \@twoclasseserror
とは LaTeX Error: Two \documentclass or \documentstyle commands. というエラーのこと。
「プリアンブル」と「本文中」の区別
ltclass.dtx の210行目が意味するのは
\begin{document}
が実行された(=本文開始した)時点で\documentclass
を\@notprerr
に再定義する
ということである(ちなみに \@notprerr
とは LaTeX Error: Can be used only in preamble. というエラーのこと)。これだけでは難しいのでもう少し詳しくみていく。
まず、ltclass.dtx の210行目に登場する \@onlypremable
は「引数にとった命令に \do
を付けて \@preamblecmds
という“リスト”に次々と追加していく」という命令である(ltdefns.dtx 44-46行目)。すぐ上の43行目をみるとわかるとおり、リストは「空」に初期化されていて、以降でたとえば \@onlypremable\ナントカ
が施されると、“リスト”末尾に \do\ナントカ
が追加される。この命令は LaTeX カーネルや各種パッケージで大量に登場する*2ので、最終的に \@preamblecmds
は \do\ホゲホゲ \do\フガフガ \do\ピヨピヨ …
のような長大なリストになる。
出来上がったリスト \@preamblecmds
は、\begin{document}
(=実体は \document
という命令)の一部として実行される(ltfiles.dtx 57行目)。この実行直前の56行目で \do
が「引数にとった命令を \@notprerr
へと再定義する」という意味に定義されるので、結果としてリストに追加されていたすべての \ナントカ
が一気に \@notprerr
に再定義されることになる。最初に述べたとおり、このリストの中には \documentclass
も含まれているため、\documentclass
がこのタイミングで \@notprerr
に再定義されるのである。
特殊なケース:「\AtBeginDocument の中」の扱い
そもそも \AtBeginDocument
とは「プリアンブル中に命令を書いておくが、その実行を \begin{document}
する時点まで遅延させる」場合に用いる命令である。すなわち、実行のタイミングは “2. と 3. の境目” であり、ほとんどの場合は 2. に含めてしまって問題ない。しかし、特別扱いが必要なごく稀なケースが存在することは確かである。ここで役立つのが
\AtBeginDocument
の中身が実行されるときには\AtBeginDocument
が\@firstofone
に再定義されている
という事実である。
改めて \AtBeginDocument
の定義を確認すると、引数にとったモノを \@begindocumenthook
という“リスト”に追加していく命令であることがわかる(ltclass.dtx 385行目)。このリスト \@begindocumenthook
は \begin{document}
(=\document
)の一部としてようやく実行される(ltfiles.dtx 48行目)のだが、いったんこれを実行する段階に達したらもうこれ以上遅延させる必要はないことに気づくだろう。
そこで、たとえリストの中身(あるいはそれ以降)に\AtBeginDocument
が使われていたとしても、もうこれ以上遅延せずその場で実行するのが妥当である。そこで、\@begindocumenthook
の実行直前にあたる47行目で \AtBeginDocument
は \@firstofone
に再定義されているのである。なお、ltclass.dtx の387行目により、\AtBeginDocument
はすぐ後の ltfiles.dtx 57行目の時点で \@notprerr
に再定義されてしまうので、\AtBeginDocument
の意味が \@firstofone
であるような範囲はごく限られていることも保障されている。
ここまでで見てきた判定は、例えば以前の記事で紹介した scsnowman パッケージの \makedocumentsnowman
で使われている。
おまけ:LaTeX で書いてはいけないコード
コレは無限ループするので、書いてはダメ。考えてみれば当然である。
\documentclass{article} \AtBeginDocument{\begin{document}} \begin{document}
$ latex test This is pdfTeX, Version 3.14159265-2.6-1.40.17 (TeX Live 2017/dev) (preloaded format=latex) restricted \write18 enabled. entering extended mode (./test.tex LaTeX2e <2016/03/31> patch level 3 Babel <3.9r> and hyphenation patterns for 83 language(s) loaded. (/usr/local/texlive/2016dev/texmf-dist/tex/latex/base/article.cls Document Class: article 2014/09/29 v1.4h Standard LaTeX document class (/usr/local/texlive/2016dev/texmf-dist/tex/latex/base/size10.clo)) No file test.aux. (./test.aux) (./test.aux) (./test.aux) (./test.aux) (./test.aux) (./test.aux) … 中略 … (./test.aux) (./test.aux) (./test.aux) (./test.aux) (./test.aux) (./test.aux) ! TeX capacity exceeded, sorry [input stack size=5000]. <write> LaTeX Font Info: \space \space \space Checking defaults for OML/cmm/... l.3 \begin{document}
test.aux を 4,992 回読み込んでいた…*3。
*1:つまり \documentclass より前はプリアンブルではない。これは、\usepackage を \documentclass より前に書くと出てくるエラーのヘルプメッセージ \usepackage may only appear in the document preamble, i.e., between \documentclass and \begin{document}. が示唆している。なお、本記事に登場する \@onlypreamble という命令は、名前の上からは「(ある命令を)プリアンブル専用(にする)」だが、実際にはその命令をプリアンブルより前で使うこともできる。
*2:これは、プリアンブル以外で使っても何の意味を持たない命令や有害な命令だけでなく、あるいは使う頻度が格段に下がる命令を「エラー」に再定義することで、メモリ使用量を節約するという効果があるとされている。
夏といえば、やっぱり「ゆきだるま」!
暑い日々が続いていますが、いかがお過ごしでしょうか。私は暑くて融けてしまいそうなのですが、今日は8月8日です。待ちに待ったナントカの日ですね!
梅雨も明けて、いよいよ夏到来!
— ZR-☃nobabbler (@zr_tex8r) 2016年7月28日
夏といえば、もちろん、ゆきだるま☃!
というわけで、今日は、暑い日々にもめげずに必死で融けずにがんばっているゆきだるま☃を見て、涼むことにしましょう。
☃ ⛄ ⛇ ☃ ⛄ ⛇ ☃ ⛄ ⛇ ☃ ⛄ ⛇ ☃ ⛄ ⛇ ☃ ⛄ ⛇ ☃ ⛄ ⛇ ☃
続きを読むetex パッケージの \extrafloats が少し優しくなった話
新しい pLaTeX の話:非公式リリースノート 2016 年版 (3)
本ブログでは、先月「コミュニティ版 pLaTeX」の 2016/05/07 について勝手に解説しました。割と好評でしたので、今回はそれ以降にリリースされた 2016/06/10、2016/06/10 patch level 1、2016/07/01 の 3 回分の(正味の)変更内容を勝手に解説します。
昨日 pLaTeX <2016/07/01> と upLaTeX <2016/07/01u01> がリリースされました。お手元の TeX Live にも数日以内に更新が反映されるはずです。
正式なリリース告知と重要なお知らせ(開発に関する議論)を、forum:1967 に出してあります。こちらもあわせてご参照ください。
以前の記事はこちらから:
目次
- 1. トラブル続きの「アクセント文字パッチ」を削除(重要:バグ修正)
- 2. 8-bit encoding の欧文文字周囲の
\xkanjiskip
を適切に設定 - 3. 縦組で graphics/color の Incompatible direction list ... エラー解消
- 4. 起動時に platex.cfg というファイルが見つかれば読み込む(新機能)
- 5. pfltrace パッケージの追加(LaTeX への追随)
- 今後の改善のために…テストのお願い!
続きを読む
単独でタイプセットできるパッケージファイル
この記事は、サブブログの 2016-06-25 投稿記事を移転してきたものです。
パッケージとそのドキュメントを一緒に開発したいとき、最もよく使われていると思われるのは「docstrip」という仕組みである。最近よく見かける docstrip の使い方は
dtx ファイル(コードとドキュメント本体)+ ins ファイル(dtx から sty をストリップするためのバッチファイル)のセット
だろう。あまり書き方を説明したオンライン日本語文献はほぼ見当たらないが、例えば、ut さんちの「dtx ファイル一般の話とサンプル」の節に
例として、いんちきな dtx ファイルを用意してみました (foobar.dtx.txt):
として簡単な dtx サンプルも付いた記事がある。
以下では「通常の docstrip については既知である」と仮定して(相当読者が絞られるが…)話を進める。もし知らなかった場合は、TeX Live に付属する sty2dtx という Perl スクリプトに適当なパッケージ (hoge.sty) を与えれば、それらしい hoge.dtx が得られるので試してみてほしい*1。
でも:パッケージファイルひとつにしてみたい
確かに「コードの間近に説明を書き込めて、しかも LaTeX で処理して PDF ドキュメントまで作れる*2」という点は便利である。しかし、いちいち dtx から sty をストリップするのは面倒であるし、わかりづらい。
そうなると、「こんなことはできないだろうか」と考えたくなるのは自然であろう:
- sty ファイルを直に配布
- 当然これは
\usepackage
できる - 時に
\documentclass
より前で\RequirePackage
されうる
- 当然これは
- sty ファイル単独でもコンパイルが通る(→ PDF 化できる)
- dtx 同様のコード解説を含められる
…と思ったのだが、この試みは案外なされていない。そこでやってみた。
- tcstyalone.sty (aminophen's gist)
実用に供するパッケージではなく、あくまで実装の例示である。 → と思っていたのだが、結局後で実際に exppl2e.sty(pLaTeX の実験的コードを含めたパッケージ)で実践活用することとなる。
ちょっとだけ解説
コード中に解説は書いたが、トリッキーなのでもう少し詳しく。
トリックの中心は
\ifx\undefined\@undefined\relax % (パッケージの宣言) \else % (ドキュメント用「ドライバ」コード) \fi
の部分である(以下「トリックコード」と呼ぶ)。これは〈@〉のカテゴリーコードが「パッケージとして読まれた場合」と「TeX ファイルとして読まれた場合」で異なることを利用している。
case 1: パッケージとして読まれた場合
\usepackage{tcstyalone}
あるいは
\RequirePackage{tcstyalone}
の場合、上記「トリックコード」実行時には \makeatletter
が有効になっている。すなわち \ifx
が比較するトークンは \undefined
と \@undefined
である。どちらのコントロール・シーケンスも「未定義」なので、判定は真となり(パッケージの宣言)が実行される。
case 2: TeX ファイルとして読まれた場合
$ pdflatex tcstyalone.sty
あるいは
\input{tcstyalone.sty}
のように読まれた場合、上記「トリックコード」は \makeatletter
無効である。この場合、\ifx
が比較するトークンは \undefined
と \@
である。\@
はコントロール・シンボルで、LaTeX では
\spacefactor\@m{}
と定義されている*3ため「未定義」とは異なる。すなわち判定は偽となり(ドキュメント用「ドライバ」コード)が実行される。判定部の後にくっついている undefined\relax
なる部分は、真の場合のコードの一部とみなされて、必ずすっ飛ばされることに注意。
さらなる帳尻合わせ
ここで sty を単独でタイプセットしたい場合、「ドライバ」コード部には
\documentclass{...} \begin{document} ... \end{document}
を含める必要があるのだが、先の「トリックコード」では \fi
が \end{document}
より後に来てしまう。つまり、case 1 では既に \if...\fi
が釣り合っている一方で、このままでは「case 2 の場合だけ一回分 \fi
が足りない」という問題が発生する:
(\end occurred when \ifx on line 15 was incomplete)
これを解消するため、ここでは docstrip の「\DocInput
では行頭の %
が無視される」という仕様を利用してみた。case 2 だけ余分の \fi
を %
付きで発行するのである。そうして書いたのが、例の gist のコードである。
先行研究の例
以上の実装コードを書いたあとで、別解らしきものを見つけた。
*1:実際に sty2dtx を使った例として、pLaTeX がコミュニティ版に移行した後の ascmac パッケージ (tascmac.sty) とそのドキュメント (ascmac.pdf) が挙げられる。アスキーによる tascmac.sty は元々説明なしにコードが書かれていたが、コミュニティ版では sty2dtx で ascmac.dtx を作成したのち文書が書き下ろされている。
*2:もちろん普通の sty でも「%」を使ってコメントを付ければよいわけだが、そのまま LaTeX でタイプセットして PDF 化することはできない。
*3:2014 年までは {} が無かったが、ここではどうでもいい。