TeXと異世界の交わり

研究室のゼミでやった内容の加筆版.名称などはでっち上げなので注意.

はじめに

LISP on TeXがCTANに登録されてから,そろそろ一週間が経つ.TeXLiveやMikTeXに取り込まれるなどもした.更に,CTANのTopicsに「exec-foreign」が追加され,そこにlisp-on-texが配置された*1.しかし,これ以外にも,旧来からTeXと他の言語を組み合わせる試みは多数行われてきた.そこで,ここではそれらの紹介を行う.

まず,TeXと他の言語を組み合わせる手法は,TeXエンジンの中で解決するもの外部の処理系に頼るものに大別することができる*2.それぞれについて説明する.

TeXエンジンのみの中で解決する手法

これは,TeXエンジンやマクロの機能を用いて,TeXとして閉じたまま他の言語と交じり合う方式である.この手法の大きな特徴は,外部環境への依存性が少ないこと.そもそも外界との接触がないから当然だが,複数のOSで動くことを想定する際に,この性質は役に立つ.この手法は,インタプリタエンジン型に区別できる.

インタプリタ

インタプリタ型は,TeXのマクロを用いて,他の言語のインタプリタTeX上に実装してしまう形式である.既存のTeXエンジンの大半で動作するため,古い環境を使わざるを得ないユーザ*3でも簡単に導入できるメリットがある.問題は,実装するともれなくキチガイ呼ばわりされることと,TeXマクロの制約により,高度な文字列処理の実装が面倒であること.前者はともかく,後者は主な使用用途を考えると大問題である.
実装例としては,BASICほむほむ・Grassが挙げられる.もちろん,lisp-on-texもこれ.expl3は微妙な感じがするが,ここではインタプリタ型に分類する.

エンジン型

そもそも,TeXエンジンに他言語の実行機能をつけたもの.エンジンと密接に連携できる性質を持ち,マクロだけでは実現が困難なこともやってのける.実装例は言うまでもなくLuaTeX.この機能を用いて日本語組版の実現を行う取り組み(LuaTeX-ja)も有名.

外部の処理系に頼る手法

既存の言語処理系を用いて,TeXとその処理系を組み合わせる手法.利点は,実装がお手軽なところ.実際,スクリプト書いてLaTeXソース出力するとか位なら利用例は多い.この手法はプリプロセッサ外部ファイル型,および\write18型に分けることができる.

プリプロセッサ

Cプリプロセッサのように,TeXと違うプリプロセッサ言語を定義し,TeXエンジンにかける前に前処理を施す方式のこと.実装例はLISPで記述するものが挙げられる.

外部ファイル型

外部ファイルを通して,TeX処理系と他言語の処理系を連携させる方式.有名な例はもちろんPerlTeX.TeXLiveにも収録されているはず.PerlTeXの作者は論文?TeXと他言語の交わり方についてよく議論しているので,一度読むことをおすすめする.

\write18型

TeXにはなぜか,shell実行機能(\write18)がある.ファイルを通してなんてことをせずに,直接shellを叩いて他の言語とやり取りする方式が\write18型.問題は,悪意あるTeXソースによりシステム破壊が可能になる点.先の論文でもこの問題が指摘されている.CTANにPythonの実装がある.

*1:まさかのLISP on TeXのみのTopic

*2:私感.以下同様

*3:管理者が怠慢で,TeX環境が更新されない計算機を使うユーザとか

Load to CTAN(CTANに載るまで)

LISP on TeXのCTAN入り

全国のTeX Liveをお使いの皆さん.ようこそ,LISP on TeXへ.

というわけで,LISP on TeXがCTAN*1登録されました.TeX Liveで使えることは,

を見る限り確認できているので,使ってみてくれると嬉しいです.

CTANへの道標

で,ココからが本題.CTANに成果物をアップロードするまでの流れを,ここに残していきます.

1. 成果物を作る

最重要.まずは,Happy TeXingすることから始める.

このとき,READMEを作ることを忘れないこと.READMEには,選択したライセンスに関する注釈を忘れない*2.READMEが日本語のものが見受けられたけど,私はつたない英語で書きました……
参考 : http://www.ctan.org/upload/

READMEはpdf形式でも構わないらしいが,その時は<<パッケージ名>>.pdfもしくは<<パッケージ名>>-doc.pdfにすべし.manual.pdfはやめろとここにある.

LaTeXのパッケージの場合,.dtxファイルや.insファイルを作るのが一般的とされているみたいだが,別に作らなくても通ります..styとかで平気.

2. アップロード形式に固める.

作成が終わったら,固めてCTAN teamへ送ります.ディレクトリ構成は,TDS*3がわかる人はそのように固めて送るといいらしい.わからなければ単一のディレクトリにまとめておけばOK.圧縮形式はzipまたはtar.gz.ただし,TDSの形式で送るときはtds.zip.

3. CTANのアップロードページヘ

さぁ,アップロードの時間だ.アップロードページヘ行き,必要事項を埋めてsubmitします.書く内容は,

  • 成果物の名前,version
  • 氏名,連絡先(メール)
  • 成果の要約
  • アナウンスメッセージ
    • ここは,よくわからないのでとりあえず「Release.」と書いてしまった……
  • 選択したライセンス

が必須です.オプションで,

  • TeXのディレクトリ構成のうち,どこが良いか
  • CTAN teamへのメッセージ

を記述できます.あとは,先ほど固めた成果物とともにアップロードします.

4. 待つ→登録

しばし待ちます.いつの間にか登録されてました.LISP on TeXの場合,登録が一日で完了しました.迅速な対応に感謝です*4
登録されると,ページがいつの間にか出来上がっています.ここで,あまりにも私の英語が汚かったのか,成果の要約が若干修正されていました.お手数おかけしました.
更にしばらくすると,TeX Liveで使えるようになってます.ちなみにこの間,メール等でのやり取りはありませんでした.

READMEが日本語でもいいあたり,実はCTANは身近な存在です.これで日本からのアップロードが増えてくれることを祈っています.

*1:Comprehensive TeX Archive Network

*2:私は忘れかけてた

*3:TeX Directory Standard

*4:ここで,かつ日本語で言っても届かないか……

LISP on TeX におけるパーサのフックな話

生きてます(唐突に).

LISP on TeX更新情報

最近,修士論文も無事に提出できたので,LISP on TeXに機能拡張とかしてみた.

パーサのフック機能.

LISP on TeXの整数型,dimen型,およびskip型にリテラルには,それぞれ決まったプレフィクスをつけることにしている.
しかし,ユーザが新しい型を追加できない*1という問題があった.また,これ以上型を増やすと適当なプレフィクスがなくなってしまうという問題も出てきた.

そこで,汎用的にパーサを操作できる構文を追加することとした.構文は,次で示される.

+{mod::foo}

modはモジュール名,fooはモジュールmodによってパースするトークン列である.

例えば,lisp-mod-stdout.styでは,stdoutモジュールを提供している.

\documentclass{article}
\usepackage{lisp}
\usepackage{lisp-mod-stdout}
\begin{document}
  \lispinterpl{+{stdout::orz}}
\end{document}

と書いてタイプセットすると,

$ latex hatena.tex
This is pdfTeX, Version 3.1415926-2.5-1.40.13 (TeX Live 2012/W32TeX)
 restricted \write18 enabled.
entering extended mode
(./hatena.tex
LaTeX2e <2011/06/27>
Babel <v3.8m> and hyphenation patterns for english, dumylang, nohyphenation, ge
rman-x-2012-05-30, ngerman-x-2012-05-30, afrikaans, ancientgreek, ibycus, arabi
c, armenian, basque, bulgarian, catalan, pinyin, coptic, croatian, czech, danis
h, dutch, ukenglish, usenglishmax, esperanto, estonian, ethiopic, farsi, finnis
h, french, friulan, galician, german, ngerman, swissgerman, monogreek, greek, h
ungarian, icelandic, assamese, bengali, gujarati, hindi, kannada, malayalam, ma
rathi, oriya, panjabi, tamil, telugu, indonesian, interlingua, irish, italian,
kurmanji, latin, latvian, lithuanian, mongolian, mongolianlmc, bokmal, nynorsk,
 polish, portuguese, romanian, romansh, russian, sanskrit, serbian, serbianc, s
lovak, slovenian, spanish, swedish, turkish, turkmen, ukrainian, uppersorbian,
welsh, loaded.
(i:/W32TeX/share/texmf/tex/latex/base/article.cls
Document Class: article 2007/10/19 v1.4h Standard LaTeX document class
(i:/W32TeX/share/texmf/tex/latex/base/size10.clo)) (./lisp.sty (./lisp-read.tex
) (./lisp-arith.tex) (./lisp-string.tex) (./lisp-latexutil.tex)
(./lisp-prim.tex)) (./lisp-mod-stdout.sty)
No file hatena.aux.
orz
(./hatena.aux) )
No pages of output.
Transcript written on hatena.log.
[1]+  Done                    sublime_text.exe hatena.tex

コンソールにorzが表示される*2

また,現在のLISP on TeXでは,fpnumモジュールをlisp-mod-fpnum.styで提供している.これは,TeXのdimenを使って固定小数点数計算を行うためのモジュールで,

+{fpnum::1.0}

と記述すると,内部でfpnum型の1.0として扱われるオブジェクトを生成する.この型のオブジェクトに関する加算,減算,乗算および比較演算もlisp-mod-fpnum.styは提供している.

これを使って,マンデルブロ集合に再挑戦した.前回は,TeX on LaTeXあれに,実行時間,精度共に惨敗したが,fpnumがなかっただけで,今ならいけるはず!

ソースはこんな感じ

\documentclass{article}
\usepackage[dvipdfm,a3paper,margin=1pt,landscape]{geometry}
\newcount\mlength
\newcount\cstate
\newdimen\mandelunit
\mandelunit=0.5pt

\def\w{%
  \ifnum\cstate=1 \global\advance\mlength1
  \else \vrule width \mlength\mandelunit height \mandelunit depth 0pt \global\mlength1 \fi
  \global\cstate1}
\def\b{%
  \ifnum\cstate=-1 \global\advance\mlength1
  \else \hspace*{\mlength\mandelunit}\global\mlength1 \fi
  \global\cstate-1}
\def\r{%
  \ifnum\cstate=1\hspace*{\mlength\mandelunit}\else\vrule width \mlength\mandelunit height \mandelunit depth 0pt \fi
  \global\cstate0 \global\mlength0}

\usepackage{lisp}
\usepackage{lisp-mod-stdout}
\usepackage{lisp-mod-fpnum}
\lispinterpl{%
  (\define \maxloop :20)
  (\define \scale +{fpnum::0.002})
  (\define \isMandell
    (\lambda (\a \b \k \x \y)
      (\lispif (\< \maxloop \k) /t
        (\lispif (\fplt +{fpnum::4.0} (\fpplus (\fpmul \x \x) (\fpmul \y \y)))
          /f
          (\isMandell \a \b (\+ \k :1)
            (\fpplus \a (\fpmul \x \x) (\fpminus (\fpmul \y \y)))
            (\fpplus \b (\fpmul +{fpnum::2.0} \x \y)))))))
  (\define \drawMandell (\lambda (\a \b)
    (\begin
      (\lispif (\isMandell \a \b :0 +{fpnum::0} +{fpnum::0})
        (\texprint '\b') (\texprint '\w'))
      (\immediatewrite))))
  (\define \loopMandell (\lambda (\a \b)
    (\lispif (\fplt \b +{fpnum::-1.0}) ()
      (\begin
        (\drawMandell \a \b)
        (\lispif  (\fplt +{fpnum::0.5} \a)
          (\begin
            (\texprint '\r\\')
            (\immediatewrite)
            (\loopMandell +{fpnum::-1.5} (\fpminus \b \scale)))
          (\loopMandell (\fpplus \a \scale) \b))))))
}

\begin{document}
  \noindent
  \leavevmode\baselineskip=\mandelunit
  \lispinterpl{(\loopMandell +{fpnum::-1.5} +{fpnum::1.0})}
\end{document}

これで勝つる.実行結果はこんな感じ.

実行時間はというと……

real    274m44.480s
user    0m0.000s
sys     0m0.031s

惨敗でしたー*3

追記
前回と同ループ回数,同メッシュでやったら37分だった.また,前回のメッシュ + ループ制限は今回のものでやったら10分で計算終了.

評価ルーチンのブラッシュアップ

これは拡張じゃないけども一応.\if系トークンの使用回数を減らしたし,若干早くなった+軽くなった,はず……

*1:そもそも誰もしないと思うが

*2:ちなみに,+{stdout::xxx}は常にnilを返す

*3:前より遅いのは,精度を上げたから.

アドベントカレンダー二日目 ― \newif\ifnextyear \ifnextyeartrue

TeX & LaTeX Advent Calendar TeX で騒げ、TeX で笑え
この記事は,12/2 担当分です.< 12/1 は ZR さん | | 12/3 はk16.shikano さん>

周りは \expandafter を叫んだりする記事みたいなので,私はマイナーな(?)プリミティブである \aftergroup と LISP on TeX での利用法を一例としてあげようと思う.

そもそも \aftergroup ってなによ

黄色(LaTeX2e マクロ & プラス プログラミング基礎解説)*1にも載っていない*2が,TeX の強力なトークン展開の制御機能ひとつである.\aftergroup は現在のグループが終わった地点での動作をフックできる.使い方は,

\aftergroup<トークン>

で,トークンがグループ終了時に挿入される.複数指定したときは,指定した順序でそのすべてのトークンが挿入される.

これだけでは直感に欠けるので,コード例を.

{\aftergroup\show}\expandafter

実行結果は次のようになる.

$ tex test.tex
This is TeX, Version 3.1415926 (TeX Live 2012/dev/W32TeX)
 encTeX v. Jun. 2004, reencoding enabled.
(./test.tex
> \expandafter=\expandafter.
l.1 {\aftergroup\show}\expandafter

? x
No pages of output.
Transcript written on test.log.

グループの終わり,すなわち \expandafter の前に \show が挿入され,\show\expandafter となりこのような表示がなされる.

で,なにに使うの?

私も最初はよくわからなかった.しかし,TeX で言語処理系を実装するよくある状況*3では非常に有用なことがわかった.末尾呼び出しの最適化である.

末尾呼び出しの最適化とは?

末尾呼び出しの最適化とは,再帰呼び出しを安心してループのように使うために必要な最適化技術である.次の Scheme プログラムを例にして説明する.

(define (fact n acc) (if (= n 0) acc (fact (- n 1) (* acc n))))
(fact 10 1)

階乗を計算する末尾再帰になっているプログラムである.最適化しないナイーブな実装では,関数の呼び出しスタックは

[(fact 10 1)][(fact 9 10)][(fact 8 90)][(fact 7 720)].........[(fact 0 3628800)]

となる.ここで fact 関数の末尾での動作は,呼び出した関数の結果を返すだけというものであり,わざわざスタックを消費する必要がない.そんなときを検知して,スタックを潰すのが末尾呼び出しの最適化である.

で,\aftergroup との関連性は?

TeXインタプリタを作成する場合,この呼び出しスタックをグループで表現するのがよくあるデザインである.グループのネストには限界があるので,末尾呼び出しの最適化は重要な課題となる*4.ここで,\aftergroup である.\aftergroup で「次にやるべき計算」*5をグループが終わったあとに挿入させるようにし,現在のグループを閉じてしまえば,不必要な要素をたたんで,新たな要素をスタックに積むという動作を実現することができる.

具体的に,LISP on Tex では \@@tco という名前のトークンに「次にやるべき計算」を束縛し,\aftergroup\@@tco とすることで末尾呼び出しを最適化している.

おまけ -- TeX におけるフック機能について

TeX には \aftergroup 以外にも,様々な場所をフックできるキチガイ言語である.一覧にしてみよう.

\output 出力ルーチンそのもの*6
\par 段落の間に挿入される.これを書き換えると,段落の終わりをフックできる.しかし,オリジナルの \par を実行しないと段落が変わらないため,初心者にはおすすめしない
\everypar 段落の先頭に挿入されるトークンリスト.行頭に行番号を入れるなどのマクロは,これを使うと実装できる.
\everymath 文章内の数式をはじめる時のフック.
\everydisplay ディスプレイ数式のフック.
\everyvbox 垂直ボックスの開始.
\everyhbox 水平ボックスの開始.
\everycr 表組みでの \cr の後ろ.TeX の表組みプリミティブである \halign と \valign を知ってないと使いづらいかもしれない.
\afterassignment レジスタへの代入やマクロ定義のフック.

*1:TeX プログラミングするときの有名な参考書のひとつ.絶版

*2:索引見て確認した

*3:BASIC とか Grass とか LISP とか

*4:LISP on TeX の初期実装では,処理系が簡単に落ちたという悲惨な歴史もあったりする.

*5:継続みたいなもの.

*6:フック -> 修正

LISP on TeX で千反田えるな話(3)

元ネタ:"LaTeXで "千反田える" してみた(1):概略"

さて,LISP on TeX の時間である.
といっても,プログラム的には簡単なので,今回はpTeX限定な命令を使ってやってみたがメイン.漢数字にするマクロ部分に,pTeXプリミティブ\kansujiを使ってみた.

\kansujiは

 \kansuji 1234

のようにして使う.結果は「一二三四」に展開される.TeXでいうところの\romannumeralとか\numberと同じ用法.
0,1,...,9に使う文字は設定可能で,デフォルトは〇,一,…,九.この出力に「千」や「百」,「十」を入れるようなマクロを書いた.

結果はこちら

\documentclass{jsarticle}
\pagestyle{empty}
\usepackage{lisp}
\lispinterpl{%
(\define \alphlist
  (\quote ( 'ぜっと' 'えー' 'びー' 'しー' 'でぃー' 'いー' 'えふ' 'じー' 'えいち' 'あい'
 'じぇー' 'けー' 'える' 'えむ' 'えぬ' 'おー' 'ぴー' 'きゅー' 'あーる' 'えす' 
  'てぃー' 'ゆー' 'ぶい' 'だぶりゅー' 'えっくす' 'わい')))
(\define \strofkananum (\lambda (\n) (\ith \n \alphlist)))
(\define \ith (\lambda (\n \lst) 
  (\lispif (\= \n :0)
    (\car \lst) (\ith (\- \n :1) (\cdr \lst)))))
(\define \erutaso (\lambda () (\erutasobody :1)))
(\define \erutasobody (\lambda (\n)
(\lispif (\= \n :1001) ()
    (\begin
      (\texprint '\tanda')
      (\texprint (\group (\strOfInt \n)))
      (\texprint (\strofkananum (\modulo \n :26)))
      (\lispif (\= \n :1000) () (\texprint '\\'))
      (\immediatewrite)
      (\erutasobody (\+ \n :1))))))
(\define \modulo (\lambda (\n \m) (\- \n (\* \m (\/ \n \m)))))
}
\newcount\cnttanda
\def\tanda#1{%
  \cnttanda#1\relax \advance\cnttanda10000\relax
  \expandafter\tandawrite\the\cnttanda}
\def\tandawrite#1#2#3#4#5{%
  \if0#2\else\if1#2\else\kansuji#2\fi\fi
  \if0#3\else\if1#3\else\kansuji#3\fi\fi
  \if0#4\else\if1#4\else\kansuji#4\fi\fi
  \if0#5\else\kansuji#5\fi
  反田}
\begin{document}
\noindent\lispinterpl{(\erutaso)}
\end{document}

ほぼLISP(またか).出力は元ネタと同じ.

なお,このコードはいつもどおりリポジトリにおいておいた.

LISP on TeX でマンデルブロな話

いや,e-pLaTeX でもいけるはずだ.もっと言えば,LISP on TeX で記述できるはずだ.
というわけで書いてみた.精度は時間がかかるのでだいぶ抑えているけど.
以下ソース

\documentclass[a3paper,landscape]{jsarticle}
\usepackage[dvipdfm,margin=1pt]{geometry}
\usepackage{lisp}
\lispinterpl{%
(\define \maxloop :100)
(\define \scale :100)
(\define \limitr (\* :2 \scale \scale))
(\define \sq (\lambda (\x) (\/ (\* \x \x) \scale)))
(\define \isMandell 
   (\lambda (\a \b \k \x \y)
     (\lispif (\< \maxloop \k)
        /t
        (\lispif (\< \limitr (\+ (\sq \x) (\sq \y)))
           /f
           (\isMandell \a \b (\+ \k :1)
                      (\+ \a (\sq \x) (\- (\sq \y))) 
                      (\+ \b (\/ (\* :2 \x \y) \scale)))))))
(\define \drawMandell 
  (\lambda (\a \b) 
    (\lispif (\isMandell \a \b :0 :0 :0)
      (\begin
        (\texprint '*')
        (\immediatewrite)
      ) 
      (\begin
        (\texprint '-')
        (\immediatewrite)
      ))))
(\define \modulo (\lambda (\n \m) (\- \n (\* \m (\/ \n \m)))))
(\define \loopMandell
  (\lambda (\n)
    (\lispif (\= \n (\* :4 \scale \scale)) % test (\* :4 \scale \scale)
      ()
      (\begin
         (\drawMandell 
            (\- (\modulo \n (\* :2 \scale)) \scale) 
            (\- (\/ \n (\* :2 \scale)) \scale))
         (\lispif (\= (\modulo \n (\* :2 \scale)) (\- (\* :2 \scale):1))
             (\texprint '\\') ())
         (\loopMandell (\+ \n :1))))))
}
\begin{document}
\fontsize{5pt}{0pt}\selectfont\ttfamily\noindent
 \lispinterpl{(\loopMandell :0)} 
\end{document}

LISPじゃねーかw
ソース自体は,リポジトリに置いてある.勇気のあるひとは実行して欲しい.
実行結果はこちら*1.アスキーアート出力なので,アスペクト比がおかしいけど,計算自体はできている.

一応,ログファイルのメモリ使用量もつけてみる.

Here is how much of TeX's memory you used:
 1523 strings out of 494059
 15752 string characters out of 3159691
 112278 words of memory out of 3000000
 5008 multiletter control sequences out of 15000+200000
 9102 words of font info for 41 fonts, out of 3000000 for 9000
 750 hyphenation exceptions out of 8191
 39i,4n,80p,192b,229s stack positions out of 5000i,500n,10000p,500000b,50000s

ちなみに,私の実行環境(W32TeX (2012/2/4),Core 2 Quad 2.4GHz,4GB memory)での計算時間は 130 [min] ほどでした.

*1:ページ番号入れっぱなしだったのでトリミングしてある

これでいいのかなぁ?

TeX芸人検定への解答.

\catcode`@=11
\def\xx@q@end{\xx@q@@end}
\def\xx@q@head{\afterassignment\xx@q@calc\let\xx@q@temp= }
\def\length#1{\xx@q@head#1\xx@q@end}
\def\xx@q@calc{%
  \show\xx@q@temp
  \ifx\xx@q@temp\xx@q@end
     \let\xx@q@next\relax
  \else
     *\let\xx@q@next\xx@q@head
  \fi\xx@q@next}
\tracingmacros=1
\length{hoge \fuga  {group\piyo}} %**************
\bye

こーゆー事なのだろうか?

修正その一

そっこーで違うと把握(\edefでこれを使えない.).まじか……