このパッケージを使用すると、ANTLR 文法を使用し、パーサー出力を使用して抽象構文ツリー (AST) を生成できます。
pip install antlr-ast
注:このパッケージは Python2 と互換性がありません。
# may need:
# pip install pytest
py.test
antlr-ast
の使用には、次の 4 つの手順が必要です。
parse
使用して、生成された文法ファイルに基づいて ANTLR ランタイム出力を取得するprocess_tree
使用するBaseAstVisitor
(サブクラスを提供することでカスタマイズ可能) は、ANTLR 出力を、ANTLR 文法のルールに基づいて動的に作成されるBaseNode
の直列化可能なツリーに変換します。BaseNodeTransformer
サブクラスを使用して、各種類のノードを変換できます。次のセクションでは、これらの手順について詳しく説明します。
これらの解析ツリーの作成と変換のプロセスを視覚化するには、この ast-viewer を使用できます。
注: このチュートリアルのこの部分では、コードを解析する方法を知る必要があります。
ANTLR をインストールしたことがない場合は、ANTLR スタート ガイドを参照してください。
ANTLR Mega チュートリアルには、役立つ Python の例が含まれています。
このページでは、ANTLR パーサー ルールの記述方法を説明します。
以下のルール定義は、重要な ANTLR パーサー文法要素にわかりやすい名前を付けた例です。
rule_name : rule_element? rule_element_label= ' literal ' #RuleAlternativeLabel
| TOKEN + #RuleAlternativeLabel
;
ルール要素と代替ラベルはオプションです。
+
、 *
?
、 |
および()
正規表現と同じ意味を持ちます。
以下では、簡単な文法を使用してantlr-ast
どのように機能するかを説明します。この文法は/tests/Expr.g4
にあります。
grammar Expr;
// parser
expr : left=expr op=( ' + ' | ' - ' ) right=expr #BinaryExpr
| NOT expr #NotExpr
| INT #Integer
| ' ( ' expr ' ) ' #SubExpr
;
// lexer
INT : [0-9]+ ; // match integers
NOT : ' not ' ;
WS : [ t]+ -> skip ; // toss out whitespace
ANTLR は上記の文法を使用して、さまざまな言語でパーサーを生成できます。 Python パーサーを生成するには、次のコマンドを使用できます。
antlr4 -Dlanguage=Python3 -visitor /tests/Expr.g4
これにより、レクサー ( ExprLexer.py
)、パーサー ( ExprParser.py
)、およびビジター ( ExprVisitor.py
) を含む多数のファイルが/tests/
ディレクトリに生成されます。
これらを Python で直接使用およびインポートできます。たとえば、このリポジトリのルートから:
from tests import ExprVisitor
生成されたファイルを簡単に使用できるように、それらはantlr_py
パッケージに入れられます。 __init__.py
ファイルは、文法名を含まないエイリアスで生成されたファイルをエクスポートします。
BaseNode
サブクラスには、対応する文法規則内のすべてのルール要素のフィールドとすべてのルール要素ラベルのラベルがあります。フィールドとラベルは両方とも、 BaseNode
インスタンスのプロパティとして使用できます。名前が衝突する場合は、ラベルがフィールドより優先されます。
BaseNode
の名前は、対応する ANTLR 文法規則の名前ですが、大文字で始まります。ルール代替ラベルが ANTLR ルールに指定されている場合、これらはルール名の代わりに使用されます。
通常、ANTLR ルールと言語の概念の間に 1 対 1 のマッピングはありません。ルール階層はさらに入れ子になっています。変換を使用すると、ANTLR ルールに基づいて BaseNode の初期ツリーを AST に近づけることができます。
BaseNodeTransformer
、ルート ノードからリーフ ノードまでツリー上を移動します。ノードにアクセスすると、ノードを変換することができます。ツリー上のウォークを続行する前に、変換されたノードでツリーが更新されます。
ノード変換を定義するには、 process_tree
に渡されるBaseNodeTransformer
サブクラスに静的メソッドを追加します。
visit_<BaseNode>
というパターンに従います。 <BaseNode>
は、変換するBaseNode
サブクラスの名前に置き換える必要があります。これは簡単な例です。
class Transformer ( BaseNodeTransformer ):
@ staticmethod
def visit_My_antlr_rule ( node ):
return node . name_of_part
カスタム ノードは、AST 内に存在するノードのタイプである、解析された言語の一部を表すことができます。
カスタム ノードを簡単に返すために、 AliasNode
サブクラスを定義できます。通常、 AliasNode
のフィールドは、 BaseNode
のツリー内を移動するためのシンボリックリンクのようなものです。
カスタム ノードのインスタンスはBaseNode
から作成されます。ソースBaseNode
のフィールドとラベルはAliasNode
でも使用できます。 AliasNode
フィールド名がこれらと衝突する場合、そのプロパティにアクセスするときに優先されます。
カスタム ノードは次のようになります。
class NotExpr ( AliasNode ):
_fields_spec = [ "expr" , "op=NOT" ]
このコードは、 expr
フィールドとop
フィールドを持つカスタム ノードNotExpr
定義します。
_fields_spec
クラス プロパティは、カスタム ノードに必要なフィールドを定義するリストです。
BaseNode
(ソース ノード) からカスタム ノードを作成するときに、このリストのフィールド仕様がどのように使用されるかは次のとおりです。
None
に設定されます。None
ではない最初のフィールド仕様が使用されます。AliasNode
上のフィールドの名前で、右側はソース ノードから開始して、そのフィールドの値となるノードを取得するために取得するパスです。カスタムノード。このパスの一部は を使用して区切られます.
このカスタム ノードを使用するには、トランスフォーマーにメソッドを追加します。
class Transformer ( BaseNodeTransformer ):
# ...
# here the BaseNode name is the same as the custom node name
# but that isn't required
@ staticmethod
def visit_NotExpr ( node ):
return NotExpr . from_spec ( node )
カスタム ノードを使用するためにトランスフォーマー クラスでメソッドを定義する代わりに、これを自動的に行うことができます。
Transformer . bind_alias_nodes ( alias_nodes )
これを機能させるには、リスト内のAliasNode
クラスに、変換するBaseNode
名のリストを含む_rules
クラス プロパティが必要です。
結果は次のとおりです。
class NotExpr ( AliasNode ):
_fields_spec = [ "expr" , "op=NOT" ]
_rules = [ "NotExpr" ]
class Transformer ( BaseNodeTransformer ):
pass
alias_nodes = [ NotExpr ]
Transformer . bind_alias_nodes ( alias_nodes )
_rules
内の項目はタプルにすることもできます。この場合、タプル内の最初の項目はBaseNode
名で、2 番目の項目はカスタム ノードのクラス メソッドの名前です。
上の例では役に立ちませんが、これは次と同等です。
class NotExpr ( AliasNode ):
_fields_spec = [ "expr" , "op=NOT" ]
_rules = [( "NotExpr" , "from_not" )]
@ classmethod
def from_not ( cls , node ):
return cls . from_spec ( node )
class Transformer ( BaseNodeTransformer ):
pass
alias_nodes = [ NotExpr ]
Transformer . bind_alias_nodes ( alias_nodes )
AliasNode
と動的BaseNode
が混在するツリーを使用するのは簡単です。ツリー全体は単なるネストされた Python オブジェクトです。
ツリー内のノードを検索するとき、ノードの優先順位を考慮できます。デフォルトでは、 BaseNode
の優先順位は 3、 AliasNode
の優先順位は 2 です。
ツリーを操作するコードを作成する場合、文法、変換、カスタム ノードの変更の影響を受ける可能性があります。文法が最も変更される可能性が高いです。
文法の更新がコードに影響を与えないようにするには、 BaseNode
に依存しないでください。 BaseNode
のAliasNode
親ノードに正しいフィールドが設定されているかどうかを確認したり、サブツリー内でネストされたAliasNode
を検索したりすることはできます。
BaseNode
に依存する場合、フィールド名が使用されているBaseNode
のフィールド名と衝突した場合、これらの一部を置き換えるAliasNode
の追加によってコードが中断される可能性があります。