\csnameの挙動

この記事は TeX & LaTeX Advent Calendar 2015 の20日目の記事です。
昨日はumireonさんでした.明日はtermoshttさんです.

はじめに

今年もAdvent Calendarの季節がやってきた.今年の重点テーマは

「今さら人に聞けない、TeXのキホン」

だそうだ.そこで,今回はTeXの基本的なプリミティブである\csnameについて,その挙動を紹介しよう*1と思う.

基本的なこと

\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は便利なプリミティブである.ぜひ活用していってほしい.

*1:毎年のように重点テーマと関係ない記事書いていたことに目をつぶりつつ

*2:定義の中に\kernが入っている

*3:なのに,二つの世界の文法がほぼ同一.

*4:某言語のmethod_missing()のようなものには使えない.

*5:どこかの言語でundefinedを入手する手段に似ている

*6:逆に,負なら\globalが無視される.通常は0で,この時は\globalが付与されているかを考慮する

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側で実装してある.ちなみに,これを実装する最大の動機がこちら.

だいぶ時間をあけてしまった.

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に記載があるのでそちらを確認してほしい.置換,分割,パターンマッチの関数をそれぞれ用意してある.

*1:本当はLaTeXにしようと思っていたのだが,思ったより時間をとることができず,Markdown形式になった

*2:texdoc l3regexで見られるはず

もしTeX芸人が遠藤侑介の『あなたの知らない超絶技巧プログラミングの世界』を読んだら

Quine書きますよね。

というわけで、TeXでQuineを書く話。ただし、前例(TeX de Quine!! - 0番染色体)にあるように、dviやpdfに書き出す例は多いみたいなので、私はコンソール出力結果がQuineになるようにしてみた。コードはこちら。

gist.github.com

これを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.

なぜか改行される.これは,このツイート


にあるように,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アドレスが同一になる場合がある.

原因

テザリングで割り振られるアドレスの範囲と,ICSで振られるIPアドレスの範囲が同じ192.168.137.0/24になっている.

対処法

ICSで振られるIPアドレスの範囲を変更する.レジストリ「HKLM\System\CurrentControlSet\services\SharedAccess\Parameters」の

  • (REG_SZ) ScopeAddress
  • (REG_SZ) StandaloneDhcpAddress

の値を,192.168.x.1に変更する.サブネットマスクは255.155.255.0固定で変更できない.


参考
https://support.microsoft.com/en-us/kb/230148

LaTeXでマクロの引数をオーバーロード

はじめに

TeX Forumにこんな質問が来ていた.

TeXにおける制御綴の引数の個数について

要は,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のマクロは単純なテキストの置き換えしかできないので,そもそも引数の扱いが普通の言語と異なる.それを回避する方法として,今回は末尾を明記する手法を用いた.

これを実行すると,次の結果を得ることができる.
f:id:hak7a3:20150524201435p:plain
引数の個数に従って,マクロの動作が変化していることがわかる.

overload.styで提供されるマクロ展開部分は完全展開可能にしてあるので,\edefの内部でも利用可能である.実際,overload.styのテストコードでそのような使い方をしている.

制限

overload.styで定義したマクロの呼び出しには,いくつか制限がある.大きなものとしては,

  1. 各引数の先頭に\endinvokeが出現してはいけない
  2. 各引数にバランスの取れていない\fiが存在してはいけない

が挙げられる.これらの問題は引数の末尾か否かの判定処理のためのものである.完全展開可能にするため,どうしてもこのようにするしかなく,現状で解決の糸口はつかめていない.

また,内部でeTeX拡張を使っているので,古いTeX処理系では動作しない.もっとも,現在使用されているほとんどのTeXエンジンがeTeX拡張を認めているため,この制限に引っかかる可能性は低いだろう.

*1:これは上記の記事の回答にも示されている

*2:ようこそ

*3:一応注意しておくと,定義順は自由である

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

TeXだってテスト書きたい(続)

前回は,LaTeX単体テストを書くためのスタイルファイルであるqstest.styを紹介したが,今回はそれをJenkinsと連携させる方法を述べる.

Jenkinsからqstest.styの出力結果は直接読み取れない.テスト結果ならJUnitなどが吐き出すXML文書と同様の形式である必要がある.しかし,qstest.styを書き換えるのはもっと面倒である.なので,

  1. latexでqstest.styの形式で記述されたテストコードを処理し
  2. 結果をJUnitが吐き出すXML文書の形式に変換する

Pythonスクリプトを書いた.ここに公開している.変換自体は単純で,特筆することはないので省略.jenkins-qstest.pyがスクリプト本体で,次のように利用する.

python3 jenkins-qstest.py example.tex

実行するとexample.xmlができるので,これをテスト結果としてJenkinsに読ませればOK.標準でlatexコマンドを使用するようになっているが,その辺はオプションでコントロール可能にしてある.

次のテストファイ

\documentclass{article}
\usepackage{qstest}
\IncludeTests{*}
\LogTests{lgout}{*}{*}
\begin{document}
\begin{qstest}
\begin{qstest}{Example}{label}
 \Expect*{\the\numexpr1+2}{3}
\end{qstest}
\begin{qstest}{Example-fail}{label}
 \Expect*{\numexpr1+2}{3}
\end{qstest}
\end{qstest}
\end{document}

で実行した結果がこちら.



やったぜ.

ちなみに,これはLISP on TeXの今後の開発で利用するために作成したスクリプトで,実際に運用を開始している.