TeXと異世界の交わり
研究室のゼミでやった内容の加筆版.名称などはでっち上げなので注意.
はじめに
LISP on TeXがCTANに登録されてから,そろそろ一週間が経つ.TeXLiveやMikTeXに取り込まれるなどもした.更に,CTANのTopicsに「exec-foreign」が追加され,そこにlisp-on-texが配置された*1.しかし,これ以外にも,旧来からTeXと他の言語を組み合わせる試みは多数行われてきた.そこで,ここではそれらの紹介を行う.
まず,TeXと他の言語を組み合わせる手法は,TeXエンジンの中で解決するものと外部の処理系に頼るものに大別することができる*2.それぞれについて説明する.
TeXエンジンのみの中で解決する手法
これは,TeXエンジンやマクロの機能を用いて,TeXとして閉じたまま他の言語と交じり合う方式である.この手法の大きな特徴は,外部環境への依存性が少ないこと.そもそも外界との接触がないから当然だが,複数のOSで動くことを想定する際に,この性質は役に立つ.この手法は,インタプリタ型とエンジン型に区別できる.
Load to CTAN(CTANに載るまで)
祝 LISP on TeXのCTAN入り
全国のTeX Liveをお使いの皆さん.ようこそ,LISP on TeXへ.
というわけで,LISP on TeXがCTAN*1に登録されました.TeX Liveで使えることは,
@zr_tex8r: あわわわわわ、#texlive を update したら lisp-on-tex とかいう怖そうなパッケージが乗り込んできた。 #TeX
2013-03-05 22:58:24 via web
@PowerPC7450: > sudo tlmgr update --all...[6/7, 00:14/00:14] auto-install: lisp-on-tex (29291) [108k] ... done!?
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.
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系トークンの使用回数を減らしたし,若干早くなった+軽くなった,はず……
アドベントカレンダー二日目 ― \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 | レジスタへの代入やマクロ定義のフック. |
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 でマンデルブロな話
LuaTeXならマンデルブロ集合も書ける。
いや,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でこれを使えない.).まじか……