LaTeX の listings パッケージや minted パッケージはソースコードをハイライト (色付け) したりしてみやすく表示するものである
ここで listings は TeX でぜんぶ実装されているのに対し、minted は pygments という外部ツールを使う (そのため、minted を使うには、latex の実行に -shell-escape オプションが必要になる)
出力は minted のほうがきれいだといわれているようである (たとえば、Ruby の文字列に埋め込む式は、listings では文字列の一部としてハイライトされるけれど、minted では式としてハイライトされる)
ところで、listings には literate という機能があって、文字列を置換することができる。 たとえば、forall という単語を $\forall$ にしたり、-> という ASCII文字の矢印を $\rightarrow$ にしたりできる。 これは出力がコンパクトになるし、数学の記法の矢印の代用として -> を使っているのが正しい文字になるのがよい
残念ながら minted には literate に相当するような機能は提供されていない (と思う)
しかし調べてみると、いくらかがんばれば、literate に相当することを実現できるようだ。 肝心なところは pygments の lexer (RegexLexer) に callback という機能があってソースとは異なる出力を生成できるというところである
まず、pygments は入力する言語を処理するための lexer というものと、 出力を行う formatter というものがいろいろ用意されている
今回は formatter として latex のみを考える
lexer は自分でいじる必要があるので、LaTeX 文書と同じディレクトリで管理したい
pygments にはそういうときのために、カレントディレクトリから lexer を探すという -x オプションがある
では minted で -x を指定するにはどうするかというと、普通は lexer の名前を書くところに mylexer.py -x というように書けばいいようだ (ひどい)
\begin{minted}[escapeinside=@@]{mylexer.py -x} ... \end{minted}
なお、ここの escapeinside=@@ というオプションは、@...@ の内側は LaTeX として扱うという指定で、 mylexer.py が生成する中で @...@ という出力を使うためである
まぁ、毎回こう書くのはつらいので、\newminted と \newmintinline で shortcut を定義しておけばいいだろう (ファイルから読み込む \newmintedfile もやってもいいかもしれない)
\newminted[mycode]{mylexer.py -x}{escapeinside=@@} \newmintinline[mycodeinline]{mylexer.py -x}{escapeinside=@@} \begin{mycode} ... \end{mycode} \mycodeinline|...|
で、mylexer.py を用意する。 おそらく、ふつうは対象の言語の lexer をもとにカスタマイズするのだろうが、 今回は literate 相当のことをする説明のために、それだけを行うものとした
# -*- coding: utf-8 -*- import re from pygments.lexer import RegexLexer, words from pygments.token import Text, Operator, Keyword __all__ = [' CustomLexer '] class CustomLexer (RegexLexer): literate_keyword = { 'forall' : '@\\ensuremath{\\forall}@' } literate_operator = { '->' : '@\\ensuremath{\\rightarrow}@', '=>' : '@\\ensuremath{\\Rightarrow}@', } def literate_keyword_callback(lexer, match): yield match.start(), Keyword, lexer.literate_keyword[match.group(0)] def literate_operator_callback(lexer, match): yield match.start(), Operator, lexer.literate_operator[match.group(0)] tokens = { 'root': [ (words(list(literate_keyword), prefix=r'\b', suffix=r'\b'), literate_keyword_callback), (r'(%s)' % '|'.join(list(literate_operator)[::-1]), literate_operator_callback), (r'.', Text), ], }
重要なのは、literate_keyword_callback (や literate_operator_callback) で、 ソースでマッチした match とは異なる文字列を yield している、というところである
ここでは、ソースの forall に対して @\ensuremath{\forall}@ を、-> に対して @\ensuremath{\rightarrow}@ を、=> に対して @\ensuremath{\Rightarrow}@ を、 生成している
(生成するところが数式モードでもそうでなくても、\ensuremath により \forall, \rightarrow, \Rightarrow は数式モードとして扱われる)
そうすると、LaTeX の出力では ∀ などと表示される
たとえば、以下の例を処理できる
t.tex:
\documentclass{article} \usepackage{minted} \newminted[mycode]{mylexer.py -x}{escapeinside=@@} \newmintinline[mycodeinline]{mylexer.py -x}{escapeinside=@@} \begin{document} \begin{mycode} nat -> nat. forall (T:Type), list T -> nat. \end{mycode} foo \mycodeinline|nat -> nat, forall (T:Type), list T -> nat| bar \end{document}
PDF生成:
pdflatex -shell-escape t.tex
[latest]