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:前より遅いのは,精度を上げたから.