ユーザ定義マクロのみを展開する手法に関する考察(1)
はじめに
こんな話が出ていた.
独自マクロを使った「適度にセマンティックな記法」で書くほうが書く側としては楽(というかそこが LaTeX を使う大きな意義のひとつ)なので,「独自マクロで書かれた LaTeX ソースを LaTeX 標準のマクロまで展開するツール」が求められている?
— Acetaminophen (@aminophen) February 10, 2016
面白そうなので,実際に作成してみることにした.
中間報告
で,作ってみたのがこれ.
実行してみると,ユーザ定義マクロが展開され,マクロの定義が消失することがわかる.アイデア自体は簡単で,次を実行するプログラムを作成しただけである.
\@argdef
を書き換えることにより,ユーザ定義マクロの定義を取得(header.texの内容)- 要は\newcommandの内側をフック
- 結果はgathergetuser.texに書きこまれる
- 元文書をの各行について,次を処理($\mbox{expander.texの内容} + \alpha $)
\documentclass
など,LaTeXが定義しているマクロに\relax
を代入- 例外的に
\newcommand
はコメント行を出力するようなマクロに再定義
- 例外的に
- 1で取得したユーザ定義マクロを再定義
- 対象の行を
\edef
により展開- このタイミングでユーザ定義マクロのみが展開される
- 得られた結果をファイルに出力
各行ごとに処理しているのは,改行を保持するためである.
考察
一見するとうまくいっているように感じるこの手法だが,実はうまくいかないケースが多い.かなり数が多いのだが,いくつか例を挙げておこう.
展開しないコントロールシーケンスの列挙が面倒
展開したくない制御綴りはexpander.texで列挙している.今回の試作ではサンプルを通すための必要最低限しか定義していないので,このあたりのメンテナンスがつらい.また,各種(自作以外の)パッケージ由来のマクロも展開してはいけないはずなので,このあたりも考慮して展開してはいけないマクロ一覧を列挙する必要がある.
実は,「展開したくないもの」よりも「展開したいもの」を列挙する方が圧倒的に楽なので,文書中の全トークンをなめて,展開したいものならば展開し,そうでないならそのままというような処理の方が適切であるように思える*1.
完全展開してはいけないコンテキスト
verbatim環境の中など,そもそもユーザ定義マクロであろうとも展開してはいけないケースがあるが,現在の手法ではそれに対応していない.そのようなコンテキストはそんなにないはずなので,その部分のみ特殊な展開処理をしてやる必要がある.
ユーザ定義マクロを完全展開することが不適切な場合
ユーザ定義マクロが副作用を含む場合,単純な完全展開では求める結果が得られない.ユーザ定義マクロを安全に展開できるのは1段階のみである.しかし,ユーザ定義マクロが別のユーザ定義マクロを使用している場合,1回の展開ではそれを除去しきれない.
このあたりは,「ユーザ定義マクロは完全展開しても安全なものに限定する」,「ユーザ定義マクロがある程度残ることを無視して1回の展開に限定する」などの対策しか思い浮かんでいない.
展開が複数行にまたがる場合
今回は改行を保存するために1行ごとに処理しているが,そもそも展開に複数行使う場合がある.
「TeXのエラーをハンドリングして,エラーが出なくなったらその出力を採用する」という方法や,「そもそも文書全体を処理した後,改行を何らかの方法で復元する」,「改行をあきらめる」などが考えられる.
まとめ
まだまだである.なので,別アプローチで試作している.
\csnameの挙動
この記事は TeX & LaTeX Advent Calendar 2015 の20日目の記事です。
昨日はumireonさんでした.明日はtermoshttさんです.
基本的なこと
\csnameは,端的に言うと文字列から制御綴りを生み出すためのプリミティブであり,\csnameと\endcsnameの間にある文字トークン列を名前とする制御綴りに展開される.例えば,
\csname Large\endcsname
は\Largeと書いたものと同様に扱われる.これだけでは利便性がないが,中の文字列を動的に変更できるようにすると,使用する制御綴りを動的に選択できるようになる.次のようなマクロを考えるとわかりやすいだろう.
\newcommand{\hoge}[1]{\expandafter\show\csname#1\endcsname}
この\hogeというマクロは,マクロもしくはプリミティブの名前について,その意味をコンソールに出力する.\hoge{Large}とすると,(クラスファイルにもよるが)次のような出力が得られるはずである.
> \Large=\long macro: ->\@setfontsize \Large \@xivpt {18}. <recently read> \Large l.4 \hoge{Large}
\csnameは便利なプリミティブであり,LaTeXでは\beginおよび\endの実装に使われていることで有名[要出典]である.簡単にいうと,\begin{hoge}は\hogeに,\end{hoge}は\endhogeに展開される.この実装に\csnameが使われている.
与えることが可能なトークン
The TeXbookでは,\csnameを次のように説明している.
When TeX expands \csname it reads to the matching \endcsname, expanding tokens as it goes; only character tokens should remain after this expansion has taken place.
すなわち,文字トークン以外が与えられるとエラーになる.同書では,\csname\TeX\endcsnameがエラーになる例として紹介されている*2.
未定義な名称を使った時の挙動
\csnameで与えた名称が事前に定義されていない場合はどうなるのだろうか? The TeXbookの\csnameに関する記述には続きがある.
Then the "expansion" of the entire \csname...\endcsname text will be a single control sequence token, defined to be like \relax if its meaning is currently undefined.
そう,定義されていない場合は\relaxのように定義されるのである.もし与える名前が間違っていても無意味なものとして取り扱ってくれる.
しかし,この性質はTeXにおいて特異なものでもある.TeXは,マクロの世界とプリミティブの世界を厳格に区別していて,マクロ展開時には定義や代入を含むすべての副作用を発生させることができない,純粋な言語だ*3.にもかかわらず,\csnameだけはマクロ展開時に定義を行う.実は,この定義の動作自体も,他の定義と違っていたりする部分がある.ここからは,\csnameがマクロを\relaxに定義する際の挙動を見ていこう.
\relax自身が書き換えられている場合
TeXは色々なものをユーザが書き換え可能である.プリミティブもその対象で,\relaxを別のマクロに置き換えることができる.では,\relaxが書き変わっている状況で\csnameで未定義の制御綴りを呼び出したらどうなるか? 答えはオリジナルの\relaxが使用される.*4この動作は次のコードで試すことができる.
\show\relax \def\relax{yukidaruma} \let\hoge\relax\show\hoge \expandafter\show\csname fuga\endcsname \bye
これの出力は次のようになる.
> \relax=\relax. l.1 \show\relax ? > \hoge=macro: ->yukidaruma. l.3 \let\hoge\relax\show\hoge ? > \fuga=\relax. <recently read> \fuga l.4 \expandafter\show\csname fuga\endcsname ?
\relaxを代入した\hogeが書き変わった後の定義になっているのに対し,\fugaはオリジナルの\relaxになっている.つまり,あるコードで\relaxを書き換えられてしまっても,それを復元する手段が常にあるということである*5.
\globaldefsとの組み合わせ
TeXには\globaldefsというパラメータがあり,このパラメータが正の値の場合,定義や代入は常に\globalが付与されたものとして扱われる.*6すなわち,グルーピングの枠を超えて,マクロが大域的に定義されるようになる.実は,\csnameによる代入はこの\globaldefsに関係なく常にローカルに行われるようである.次のコードを見てみよう.
\globaldefs=42 \show\hoge \show\fuga \begingroup \let\hoge\relax \csname fuga\endcsname \show\hoge \show\fuga \endgroup \show\hoge \show\fuga \bye
結果は次のようになる.
> \hoge=undefined. l.2 \show\hoge \show\fuga ? > \fuga=undefined. l.2 \show\hoge \show\fuga ? > \hoge=\relax. l.6 \show\hoge \show\fuga ? > \fuga=\relax. l.6 \show\hoge \show\fuga ? > \hoge=\relax. l.8 \show\hoge \show\fuga ? > \fuga=undefined. l.8 \show\hoge \show\fuga ?
\hogeは大域的に定義されているのがわかるが,\fugaはそうなっていない.
\afterassignmentとの関係
TeXは様々な場所にフックを仕掛けておくことができる.\afterassignmentは定義や代入の直後に対するフックである.しかし,\csname由来の代入では\afterassignmentは動かない.次のコードを見てみよう.
\def\hook{\show\expandafter} \afterassignment\hook \let\hoge\relax \afterassignment\hook \csname fuga\endcsname \show\hoge \show\fuga \bye
実行すると,次の結果が得られる.
> \expandafter=\expandafter. \hook ->\show \expandafter l.2 \afterassignment\hook \let\hoge\relax ? > \hoge=\relax. l.4 \show\hoge ? > \fuga=\relax. l.5 \show\fuga ?
\fugaに\relaxが代入されているにも関わらず,\afterassignmentが起動しない.
おわりに
いささか,TeXの闇を見せたような気がするが,\csnameは便利なプリミティブである.ぜひ活用していってほしい.
LISP on TeX v2.0リリースノート
はじめに
10/25,LISP on TeXのv2.0(CTAN: Package lisp-on-tex)をリリースした.簡単に今回のリリース内容をまとめておく.
READMEの更新
READMEをMarkdown形式に移行した*1.結果,CTANのページ上からREADME.mdが見れるようになっているはずである.
ごみ集め機構(GC)の実装
通常のTeXプログラミングと違い,LISP on TeXでは内部で大量の制御綴りをCONSセルとして使用する.そのため,大量に資源消費するプログラムを作成すると,「! TeX capacity exceeded, sorry」のメッセージとともにTeXの実行そのものが終了してしまう問題があった.その対策として,LISP on TeXにごみ集め機構(GC)を実装した.使用するためには,markGCオプションを使用する.
\usepackage[markGC]{lisp-on-tex}
これでGCが有効になる.これにより,未使用になったCONSセルが再利用されるようになる.
デフォルトでGCはオフになっている.これは,GCを有効にすることによりアロケーション時間が1.5倍かかるようになってしまうのと,実際にGCが必要な場面の方が少ないためである.
なお,GCopt={heapsize=n}というオプションを追加で与えると,ヒープサイズをnにすることができる.指定しない場合は32768が指定されたものとみなす.
エラー出力の強化
以前のリリースでone-shot continuationを実装したことにより,LISP on TeXでは内部的に例外を扱えるようになっていた.そこで,今回のリリースではエラー出力を強化した.例えば.
\lispinterp{(\define 'aaa' :42)}
とすると,
! LISP on TeX ERROR: The 1st argument of \define or \defineM must be a symbol o r valid list..
と出力される.ほかにも引数の個数チェックなどが追加されている.
\defineなどの省略記法
これは,LISP on TeXの初期の課題であった,(\define (\foo \x) ...)の形式を認めるようにする変更である.正直,LISP on TeXにはマクロがあるので,そちらで実装してもいいのだが,速度を考えてTeX側で実装してある.ちなみに,これを実装する最大の動機がこちら.
#LISP on #TeX に欲しい機能.
1. デバッグの機構(macroexpandやGaucheのリーダマクロ #?=).
2. internal define.
3. 文字列処理.
4. 健全なマクロ.
— keno (@keno_ss) 2012, 11月 18
だいぶ時間をあけてしまった.
l3regexのラッパー
文字列,すなわちTeXのトークン列処理の強化として,LISP on TeXに正規表現を導入した.ただし,正規表現エンジンを自前で実装するのはさすがに困難なので,既存のTeX上の正規表現エンジンであるl3regexを使用することにした.
使い方は簡単で,追加のパッケージlisp-mod-l3regexを読み込むだけである.
\usepackage{lisp-mod-l3regex}
これで,いくつかの関数が使用可能になる.例えば
\lispinterp{(\regReplaceAll '(\w+?)to(\w+?)' '$\1\c{to}\2$' 'AtoB BtoC')}
とすると,文字列
'$A\to B$ $B\to C$'
が得られる.正規表現そのものの記法についてはl3regexのドキュメント*2を参考にしてほしい.
また,導入される関数については,LISP on TeXのREADME.mdに記載があるのでそちらを確認してほしい.置換,分割,パターンマッチの関数をそれぞれ用意してある.
もしTeX芸人が遠藤侑介の『あなたの知らない超絶技巧プログラミングの世界』を読んだら
Quine書きますよね。
というわけで、TeXでQuineを書く話。ただし、前例(TeX de Quine!! - 0番染色体)にあるように、dviやpdfに書き出す例は多いみたいなので、私はコンソール出力結果がQuineになるようにしてみた。コードはこちら。
これをquine.texとし、
max_print_line=10000 etex -no-shell-escape quine.tex
なんていう風に実行すると、そのコンソール出力がソースコードと一致する。これを書くための技法を紹介しよう。
基本的なQuineの書き方
まずは『あなたの知らない超絶技巧プログラミングの世界』(Amazon.co.jp: あなたの知らない超絶技巧プログラミングの世界: 遠藤 侑介: 本)を読もう。その第3章の技術を使っただけである。なお、その技術をTeXで再現するに際し、e-TeX拡張の\unexpandedを用いて展開制御している*1。
TeXでやるにあたっての工夫
TeXのコンソール出力は使いづらいことで有名である[要出典]。まず、バージョン情報や読み込んだファイル、実行結果のサイズなどを勝手に出力する。しかも抑制できない。また、長い文字列を出そうとすると勝手に改行する。これらを解決する必要があった。
ユーザの意図しない出力への対応
まずは、ユーザの意図しない出力への対応である。今回のQuineではdviやpdf側の出力は一切気にする必要がないので,どんな文字列が出力されようが問題ないだろう,と思う読者もいるかもしれない.しかし,それは大きな間違いである.etexを起動したときの文字列を見てもらいたい.
$ etex This is pdfTeX, Version 3.14159265-2.6-1.40.15 (TeX Live 2014/W32TeX) (preloaded format=etex) restricted \write18 enabled. **
TeXエンジンのバージョン情報などとともに,restricted \write18が有効になっている旨の出力がある.この「\write18」という文字列がQuineでは邪魔になる.そのままソースコードにこれを書くとエラーになるためである.これを回避するために,いささか邪道ではあるが-no-shell-escapeオプションをつけて回避した.\write18さえ禁止してしまえば,出力は
$ etex -no-shell-escape This is pdfTeX, Version 3.14159265-2.6-1.40.15 (TeX Live 2014/W32TeX) (preloaded format=etex)
となり,無害な出力が得られる.なお,この出力を見てもらえばわかるが,TeXエンジンの情報が入るので,Quineのソースコードは実行するTeXエンジンに(バージョンなども含めて)依存する.
長い出力の強制改行対策
この問題を認識してもらうために,次のコードを考える.
\message{wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww}\bye
これをsample.texとして実行してみる.
$ etex sample This is pdfTeX, Version 3.14159265-2.6-1.40.15 (TeX Live 2014/W32TeX) (preloaded format=etex) restricted \write18 enabled. entering extended mode (./sample.tex wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww wwwwwwwwwwwwwwwww ) No pages of output. Transcript written on sample.log.
なぜか改行される.これは,このツイート
texmf.cnf での設定としては、max_print_line という変数がある。
TeX の中で変えるのは無理そう。 #TeX
— ZR-TeXnobabbler(既定値) (@zr_tex8r) 2015, 10月 5
にあるように,TeXの設定ファイルで規定されるものらしい.TeX側からこれを変更するのはつらい.そこで,その設定自体は環境変数を使えば動的に書き換えられることを利用し,これも邪道ではあるが,max_print_line=10000と環境変数を設定して実行することにより,これを回避した.
まとめ
TeXのコンソール出力は扱いづらい.
*1:別に使わなくてもできる
Windows PhoneでテザリングしつつICSするために
数か月ぶりの記事はTeX関係ないという.
はじめに
Windows 10にしてから数日が経った.Hyper-Vを有効にしてもスリープできるようになったと聞き,喜々としてICSでNATもどきして楽しもうした.うまくいっているようだった.しばらくして,持っているWindows Phoneでそのマシンをテザリングして外につないだ.なぜか,ホスト側のWifiに与えられているIPアドレスと,ICSで共有している仮想ネットワークのNICに与えられているIPアドレスが同じだった.別に問題ないけども気持ち悪い.
問題
Windows Phone(試した実機はMADOSMA)でテザリングしたとき,ICSしているWifiアダプタ(試した実機はSurface Pro 3)に割り当てられるIPアドレスと,ICSのローカル側(Hyper-Vの内部ネットワーク)に与えられるIPアドレスが同一になる場合がある.
LaTeXでマクロの引数をオーバーロード
はじめに
TeX Forumにこんな質問が来ていた.
要は,TeXのマクロを関数っぽいものとみなし,その上で同名のマクロに引数の個数が異なるものをオーバーロードさせたいというものである.TeXのマクロは単なる字句の置き換え規則に過ぎない*1ので,これを実現するにはTeX言語沼に入らないといけない*2.
先の記事の場合,要件は1引数および2引数の場合のみだったが,今後,複数の場合でオーバーロードしたい場合があるかもしれない.単に,そのようなマクロを定義するためのマクロをつくるのが面白そうなので作ってみた.
スタイルファイルの紹介
公開場所
今回,スタイルファイルをoverload.styとして,hak7a3 / overload — Bitbucketに公開しておいた.
使い方
overload.styの使い方は,非常に簡単である.まず,\usepackageでoverload.styを読み込む.
\usepackage{overload}
次に,\newoverloadでオーバーロードするコントロールシーケンスを登録する.
\newoverload{\foo}
ここでは,\fooを「これから複数の引数に対応するマクロ」として登録している.最後に,各引数の個数に対応する動作を\addoverloadで登録する.\addoverloadは\newoverloadで登録したコントロールシーケンス,引数の個数,およびマクロの置き換えテキストを引数として受け取り,オーバーロード用のマクロを作成する.
\addoverload{\foo}{2}{(2):#1 and #2} \addoverload{\foo}{1}{(1):#1 only} \addoverload{\foo}{0}{(0):orz}
この例では,上から順に2引数,1引数,および引数がない場合の動作を登録している*3.
引数をオーバーロードするように定義したマクロは,引数の末尾を示す\endinvokeを末尾に付加して呼び出す必要がある.例を示す.
\foo\endinvoke \foo{bar}{baz}\endinvoke \foo{bar}\endinvoke
上から順に,引数なし,2引数,および1引数呼び出しに対応する,先に述べたように,TeXのマクロは単純なテキストの置き換えしかできないので,そもそも引数の扱いが普通の言語と異なる.それを回避する方法として,今回は末尾を明記する手法を用いた.
これを実行すると,次の結果を得ることができる.
引数の個数に従って,マクロの動作が変化していることがわかる.
overload.styで提供されるマクロ展開部分は完全展開可能にしてあるので,\edefの内部でも利用可能である.実際,overload.styのテストコードでそのような使い方をしている.
TeXで色々な無限ループ
小ネタ.思うところがあったのでいくつか試してみた.
自身が再帰的に展開されていく例
TeXで無限ループやる場合は大抵これ[要出典].実装法はいくつかある.
単純なマクロ
\documentclass{article} \begin{document} \newcommand\hoge{\hoge} \hoge \end{document}
よくやる.
\aftergroup
\documentclass{article} \begin{document} \newcommand\hoge{{\aftergroup\hoge}} \hoge \end{document}
\aftergroupにより,\hogeが波閉じ括弧(グループの終了)の直後に挿入される.結構使う.
\afterassignment
\documentclass{article} \begin{document} \newcount\fuga \newcommand\hoge{\afterassignment\hoge\fuga=0 } \hoge \end{document}
\aftergroupと同様に,代入の終了時に\hogeが挿入される.
垂直モードに移行しない\par
\documentclass{article} \begin{document} \def\par{} hoge \end{document}
どこで見たか思い出せないけど,割と面白い例.TeXは\parを挿入することで,垂直モードに移行しようとする.オリジナルの\parには垂直モードに移行する機能があるが,この例では,\defすることによってその機能を奪っている.結果,垂直モードに移行できない.で,水平モードのままなのでTeXが\parを挿入し,……を無限に繰り返す.なぜこんな仕様になってしまったんだ.
追記: あった
水平モードにおける垂直モード用グルーと\par