Pygments - lexerコンポーネント

初回公開:2018/10/14
最終更新:未

【 目次 】

lexerクラス

lexerクラスにアクセスするためのAPI(関数)およびlexerのベースクラスであるLexerクラスのソースコードはC:\Python27\Lib\site-packages\pygments\lexer.pyに格納されている。(window,python2.7の場合)
また、Lexerクラスを継承した各言語ごとのlexerクラスのソースコードはC:\Python27\Lib\site-packages\pygments\lexersデレクトリに格納されている。

lexerクラスのオプション

lexerクラスはコンスタントにて以下のオプションを指定することが可能。

stripnl
入力から先頭と末尾の改行を取り除く(デフォルト: True )
stripall
入力から先頭と末尾のすべての空白を削除します (デフォルト: False )。
ensurenl
入力が改行で終わることを確認する(デフォルト: True )。
これは、入力行ごとに処理をするレクサーによっては必要です。
tabsize
0より大きい値が指定されたなら、入力内のタブを展開します(デフォルト: 0 )。
encoding
指定されている場合、エンコーディング名("utf-8"など)でなければなりません。
このエンコーディングは、入力文字列をUnicodeに変換するために使用されます(まだUnicode文字列でない場合)。
デフォルトは「guess」(推測)です。
このオプションが「guess」に設定されている場合、単純なUTF-8かLatin-1かの検出に使われる。
「chardet」に設定されている場合、chardetライブラリが入力のエンコーディングを推測するために使われる。

すべてのlexerクラスはベースとなるLexerクラスから派生している。

lexerクラスのコンストラクタはオプションの**キーワード辞書を取ります。
すべてのLexerクラスのサブクラスは、最初に、独自のオプションを処理してから呼び出す必要があります。
Lexerクラスのサブクラスでは、にコンストラクタメソッド __init__で
最初に、独自のオプションを処理してから、stripnl、stripall、およびtabsizeオプションを処理するために、ベースクラスLexerのコンストラクタを呼び出さなければならない。

以下に独自のオプションcompressを取得した後にベースクラスLexerのコンストラクタにオプションを渡す例を示す。

def __init__(self, **options):
    self.compress = options.get('compress', '')
    Lexer.__init__(self, **options)

オプションはすべて文字列として指定できる必要があります。
そのためのオプション関数のAPIが用意されています。

lexerクラスのメソッド

get_tokens(text)
このメソッドは、lexerの基本インターフェイスです。
これはhighlight()関数によって呼び出されます。
これは、textを処理して iterableな(tokentype, value)のペアを返さなければなりません。

通常、このメソッドをオーバーライドする必要はありません。
デフォルトの実装では、stripnl、stripall、およびtabsize オプションを処理し、インデックスを削除してget_tokens_unprocessed()からすべてのトークンを生成します。

get_tokens_unprocessed(text)
このメソッドはtextを処理して、タプル(index、 tokentype、 value)のiterableを返します。
ここで、indexは入力テキスト内のトークンの開始位置です。

このメソッドは、サブクラスによってオーバーライドする必要があります。

static analyse_text(text)
lexerを推測するために呼び出される静的メソッド。
これは、textを分析し、0.0から1.0の範囲のfloat値を返します。
0.0が返られる場合には、lexerは最も可能性の高いものとして選択されません。
1.0が返されると、それが直ちに選択されます。

注意

このメソッドの定義に@staticmethodを追加する必要はありません。
lexerのメタクラスでサポートされています。
既知のトークンのリストについてはBuiltin Tokens — Pygmentsのページを参照してください。

lexerクラスの属性

また、lexerは、組み込みルックアップメカニズムによって使用される以下の属性を持つことができる(実際にはalias_filenames以外は必須です)。

name
人間が読める形式でlexerのフルネーム。
aliases
get_lexer_by_name()などを使用して、リストからlexerをルックアップするために使用できる短い一意の識別子のリスト。
filenames
このlexerの内容を含むファイル名と一致するfnmatchパターンのリスト。
このリストのパターンは、すべてのlexerの中で一意でなければなりません。
alias_filenames
このlexerのコンテンツを含む場合と含まない場合があるファイル名に一致するfnmatchパターンのリスト。
このリストはguess_lexer_for_filename()関数によって使用され、正しいレシピを推測するためにどのレクサーが含まれるかを決定します。
つまり、HTMLやテンプレート言語のすべてのレクサーは、このリストに*.htmlを含める必要があります。
mimetypes
このレクサーでレクチャーできるコンテンツのMIMEタイプのリスト。

Python言語のlexerクラスPythonLexerは公式ドキュメントには次のように記述されている。

class pygments.lexers.python.PythonLexer
Short names: python, py, sage
Filenames: .py, .pyw, .sc, SConstruct, SConscript, .tac, *.sage
MIME types: text/x-python, application/x-python

"Short Names"フィールドは、 後述するget_lexer_by_name()関数で使用できる識別子がリストである。
Available lexers — Pygmentsに記述されている各言語ごとのpygments組み込みのlexerクラスはpygments.lexersからインポートすることができる。
Short namesフィールド(longnameあるいはnamealiasのタプル),Filenamesフィールド(ファイル名パターンのタプル),MIME types(MIMEタイプのタプル)として定義されている。

これらの値をPythonLexerクラスの属性にアクセスして確認してみると

from pygments.lexers import PythonLexer

print(PythonLexer.name)
print(PythonLexer.aliases)
print(PythonLexer.filenames)
print(PythonLexer.alias_filenames)
print(PythonLexer.mimetypes)

実行結果

Python
['python', 'py', 'sage']
['*.py', '*.pyw', '*.sc', 'SConstruct', 'SConscript', '*.tac', '*.sage']
[]
['text/x-python', 'application/x-python']

lexerクラスのfilenames属性やalias_filenames属性にでてくるfnmatchって何?

Unixのワイルドカードを含むファイル名という事か。

ちなみにPythonLexerのFilenamesにあるSConstructとかSConscriptって何だろうか

PythonのMake処理に関係しているファイルらしい。
本筋とはあまり関係ないが気になってので。

pygments.lexersモジュールに定義されているAPI関数

lexerクラスにアクセスするためにはのpygments.lexersモジュールに定義されている次のAPI関数を呼び出すことになる。

pygmentsに組み込まれているすべてのlexerクラスを列挙する。

pygments.lexers.get_all_lexers()
登録されているすべてのレクサーに対して繰り返し可能な値を返し、次の形式のタプルを生成します。
(ロングネーム、エイリアスのタプル、ファイル名パターンのタプル、MIMEタイプのタプル)

get_all_lexers関数のコードの例を以下に示す。

from pygments.lexers import get_all_lexers
lexers = get_all_lexers()

print(type(lexers))

for longname, tuple_of_aliases, tuple_of_filename_patterns, tuple_of_mimetypes in  lexers:
    print()
    print(longname)
    print("aliases :",tuple_of_aliases)
    print("filename :",tuple_of_filename_patterns)
    print("mimetypes :",tuple_of_mimetypes)

実行結果

<class 'generator'>

XSLT
aliases : ('xslt',)
filename : ('*.xsl', '*.xslt', '*.xpl')
mimetypes : ('application/xsl+xml', 'application/xslt+xml')

... 途中略

C++
aliases : ('cpp', 'c++')
filename : ('*.cpp', '*.hpp', '*.c++', '*.h++', '*.cc', '*.hh', '*.cxx', '*.hxx', '*.C', '*.H', '*.cp', '*.CPP')
mimetypes : ('text/x-c++hdr', 'text/x-c++src')

... 途中略

Java
aliases : ('java',)
filename : ('*.java',)
mimetypes : ('text/x-java',)

... 途中略

C#
aliases : ('csharp', 'c#')
filename : ('*.cs',)
mimetypes : ('text/x-csharp',)

... 途中略

Ruby
aliases : ('rb', 'ruby', 'duby')
filename : ('*.rb', '*.rbw', 'Rakefile', '*.rake', '*.gemspec', '*.rbx', '*.duby', 'Gemfile')
mimetypes : ('text/x-ruby', 'application/x-ruby')

... 途中略

(pygmentsがどのような言語をサポートしているかの)参考のために、上記の実行結果のフル出力を以下のリンクに示す。

get_all_lexers関数の戻り値はgeneratorクラスのインスタンスを返す事がわかる。
generatorを反復処理する事で組み込みのlexerクラスがサポートしているaliase名,filename,mimetypesを知る事ができる。

get_all_lexers関数は、以前に述べたコマンドラインから以下のpygmentizeコマンドを実行する事に相当する。

pygmentize -L lexers

lexerクラスのインスタンスを取得する。

lexerクラスのインスタンスを取得するのに以下の関数を使う事ができる。

pygments.lexers.get_lexer_by_name(alias, **options)
エイリアスリストにエイリアスを持つLexerサブクラスのインスタンスを返します。
レクサーはインスタンス化時にオプションを与えられます。
そのエイリアスを持つレクサーが見つからない場合は、pygments.util.ClassNotFoundを送出します。
pygments.lexers.get_lexer_for_filename(fn, **options)
fnに一致するファイル名パターンを持つLexerサブクラスインスタンスを返します。
レクサーはインスタンス化時にオプションを与えられます。
もしファイル名に対応するレクサーが見つからない場合、pygments.util.ClassNotFoundを送出します。
pygments.lexers.get_lexer_for_mimetype(mime, **options)
mimetypeリストにmimeを持つLexerサブクラスインスタンスを返します。
レクサーはインスタンス化時にオプションを与えられます。
もしMIMEタイプに対応するレクサーが見つからない場合、pygments.util.ClassNotFoundを送出します。

lexerクラスのインスタンスを取得するにはPygmentsの簡単なサンプルコードで示したように直接、lexerクラスのコンストラクタを使う事もできるが、上記の関数群を使う事により、nameやaliases,filenames,alias_filenames,mimetypesを指定する事もできる。

以下に、lexerクラスのインスタンスを取得する複数の例を示す。

import pygments.lexers

# 直接lexerクラスのコンストラクタを使ってlexerのインスタンスを取得

lexer=pygments.lexers.PythonLexer();                        print(type(lexer))

# pygments.lexers.get_lexer_by_name => nameおよびaliasよりlexerのインスタンスを取得

lexer=pygments.lexers.get_lexer_by_name('python');          print(type(lexer))
lexer=pygments.lexers.get_lexer_by_name('py');              print(type(lexer))
lexer=pygments.lexers.get_lexer_by_name('sage');            print(type(lexer))

# pygments.lexers.get_lexer_for_filename => filenameよりlexerのインスタンスを取得

lexer=pygments.lexers.get_lexer_for_filename('*.py');       print(type(lexer))
lexer=pygments.lexers.get_lexer_for_filename('*.pyw');      print(type(lexer))
lexer=pygments.lexers.get_lexer_for_filename('*.sc');       print(type(lexer))
lexer=pygments.lexers.get_lexer_for_filename('SConstruct'); print(type(lexer))
lexer=pygments.lexers.get_lexer_for_filename('SConscript'); print(type(lexer))
lexer=pygments.lexers.get_lexer_for_filename('*.tac');      print(type(lexer))
lexer=pygments.lexers.get_lexer_for_filename('*.sage');     print(type(lexer))

# ※.filenamはワイルドカード(*)で無くても、特定のファイル名でもよい

lexer=pygments.lexers.get_lexer_for_filename('xxx.py');     print(type(lexer))

# pygments.lexers.get_lexer_for_mimetype => mimetypeよりlexerのインスタンスを取得

lexer=pygments.lexers.get_lexer_for_mimetype('text/x-python');        print(type(lexer))
lexer=pygments.lexers.get_lexer_for_mimetype('application/x-python'); print(type(lexer))

実行結果

<class 'pygments.lexers.python.PythonLexer'>
<class 'pygments.lexers.python.PythonLexer'>
<class 'pygments.lexers.python.PythonLexer'>
<class 'pygments.lexers.python.PythonLexer'>
<class 'pygments.lexers.python.PythonLexer'>
<class 'pygments.lexers.python.PythonLexer'>
<class 'pygments.lexers.supercollider.SuperColliderLexer'>
<class 'pygments.lexers.python.PythonLexer'>
<class 'pygments.lexers.python.PythonLexer'>
<class 'pygments.lexers.python.PythonLexer'>
<class 'pygments.lexers.python.PythonLexer'>
<class 'pygments.lexers.python.PythonLexer'>
<class 'pygments.lexers.python.PythonLexer'>
<class 'pygments.lexers.python.PythonLexer'>

上記の実行結果をみるとほとんどがPythonLexerクラスのインスタンスを返しているが、get_lexer_for_filename('*.sc')を実行した場合だけPythonLexerクラスではなくSuperColliderLexerクラスのインスタンスが返されている。
これは、拡張子が.scがpythonの他にSuperColliderという言語でも使われており、supercolliderの方が優先されたためと思われる。

SuperColliderについては

これらの関数はもちろんクラスのoptions引数を渡す事ができる。
lexerにtabsizeオプションを指定した次のコードの結果は

lexer=pygments.lexers.PythonLexer(tabsize=20)

次のコードと同じである。

lexer=pygments.lexers.get_lexer_by_name('python',tabsize=20)

コードやファイル名から推測してlexerクラスのインスタンスを取得する。

pygments.lexers.guess_lexer(text, **options)
text引数内のテキストから推測されるLexerサブクラスインスタンスを 返します。
そのために、既知のすべてのlexerクラスのanalyse_text()メソッドがテキストを引数として呼び出され、最も高い値を返すlexerがインスタンス化されて返されます。
pygments.lexers.guess_lexer_for_filename (filename、text、** options )
guess_lexer()内のパターン持っているが、唯一のレクサーファイル名 またはalias_filenames一致するファイル名が考慮されます。

guess_lexer()は、与えられたコンテンツをレクサークラスの analyse_text()メソッドに渡し、最も高い数値を返すメソッドを返します。

すべてのレクサーには、プライマリとセカンダリの2つの異なるファイル名パターンリストがあります。
get_lexer_for_filename()関数は、そのエントリのすべてのレクサーの中で一意であると考えられている主なリストを、使用しています。
しかし、guess_lexer_for_filename()は最初にすべてのレクサーをループし、ファイル名が一致する場合はプライマリとセカンダリのファイル名パターンを調べます。
1つのレクサーだけが一致すれば、それが返されます。
そうでなければ、一致するレクサーでguess_lexer()の推測メカニズム が使用されます。

いつものように、これらの関数のキーワード引数は、作成されたレクサーにオプションとして与えられます。

import pygments.lexers

code = '#!/usr/bin/python\nprint "Hello World!"'

# pygments.lexers.guess_lexer => ファイル名とcodeより推察してlexerのインスタンスを取得

lexer=pygments.lexers.guess_lexer(code);                          print(type(lexer))
lexer=pygments.lexers.guess_lexer_for_filename('xxx.sc', 'abc');  print(type(lexer))
lexer=pygments.lexers.guess_lexer_for_filename('xxx.sc', code);   print(type(lexer))

実行結果

<class 'pygments.lexers.python.PythonLexer'>
<class 'pygments.lexers.supercollider.SuperColliderLexer'>
<class 'pygments.lexers.python.PythonLexer'>

上記実行結果の最初の例ではコードの最初にshebangが記述されていたためlexerとしてPythonLexerを推測する事ができる。
2番目の例ではguess_lexer_for_filename関数を使ってファイル名とコードからlexerを推察しようとしているがコードからはpythonのコードとは認められないためSuperColliderLexerが推測されている。
これに対して2番目の例ではコードの最初にshebangが記述されていたため拡張子が.scであってもPythonLexerが選択される事になるようだ。

shebangについては

lexerクラスのクラスオブジェクト取得する。

pygments.lexers.find_lexer_class_by_name(alias)
エイリアスリストにエイリアスを持つLexerサブクラスを返します。
Lexerサブクラスのインスタンスを返すのではなく、Lexerサブクラスそのものを返すところがget_lexer_by_nameと異なる点。
pygments.lexers.find_lexer_class(name)
引数nameで指定されたname属性を持つLexerのサブクラスを返す。

コードの例を以下に示す。

import pygments.lexers

# find_lexer_class_by_name => 名前によるlexerクラスの取得

lexer_cls=pygments.lexers.find_lexer_class_by_name('python')
print(type(lexer_cls()))
print(lexer_cls.name)

# find_lexer_class => 名前によるlexerクラスの取得

print(pygments.lexers.find_lexer_class(lexer_cls.name))

実行結果

<class 'pygments.lexers.python.PythonLexer'>
Python
<class 'pygments.lexers.python.PythonLexer'>

load_lexer_from_file

pygments.lexers.load_lexer_from_file(filename, lexername="CustomLexer", **options)
現在のディレクトリを基準にして、指定されたファイルから読み込まれたLexerのサブクラスのインスタンスを返します。
Lexerクラスはlexername(デフォルトでは CustomLexer)で指定される。
このメソッドは注意して使用してください。
なぜなら、このメソッドは、入力ファイルに対してevalを実行するのと同じだからです。

これだけではわかりずらいので以下のコードで試してみる。

Lexerクラスを継承した空のクラスMyLexerを記述したmy_lexer_class.pyというpythonのソースファイルを用意する。

print("xxx")

from pygments.lexer import Lexer

class MyLexer(Lexer):
  pass

print("yyy")

このコードの中のprint関数はコードが評価される順番を確認するために入れてある。

load_lexer_from_file関数を使ってMyLexerクラスのインスタンスを取得してみる。

my_lexer=pygments.lexers.load_lexer_from_file('my_lexer_class.py', lexername="MyLexer_")
print(my_lexer)

実行結果

xxx
yyy
<pygments.lexers.MyLexer>

要するにload_lexer_from_file関数が何をしているかというと、lexerクラスを記述してあるpythonのソースファイルからコードを読み込みeval関数を使ってコードを評価してlexername引数で指定されたクラスのインスタンスを返してくれるという事になる。

lexerの自作

lexerの自作までは考えていないが、参考になりそうな記事があったのでリンクをはっておく。

  • Pygmentsドキュメント「じぶんで字句解析器をつくる」 - M12i.

    以下の例は、Pygmentsに組み込みのDiffLexerです。
    name、aliasesそしてfilenamesという追加の属性が含まれている点に注意してください。
    これらはlexerに必須のものではありません。
    これらの属性は組み込みのlexerを検索する機能のために用意されているものです。

  • Pygmentsの自作字句解析(入門) - ikikko.py - fukuoka.pyのはてなグループ

    name属性は無くても構いません。設定しておくと、組み込みのlexer機能側で何かいいことをしてくれるみたいですw
    また、レクサーは、組み込みルックアップメカニズムによって使用される以下の属性を持つことができます(実際にはalias_filenames以外は必須です)。

ページのトップへ戻る