이 패키지를 사용하면 ANTLR 문법을 사용하고 파서 출력을 사용하여 추상 구문 트리(AST)를 생성할 수 있습니다.
pip install antlr-ast
참고: 이 패키지는 Python2와 호환되지 않습니다.
# may need:
# pip install pytest
py.test
antlr-ast
사용하려면 다음 네 단계가 필요합니다.
parse
사용하여 생성된 문법 파일을 기반으로 ANTLR 런타임 출력 가져오기process_tree
사용BaseAstVisitor
(하위 클래스를 제공하여 사용자 정의 가능)는 ANTLR 출력을 ANTLR 문법의 규칙에 따라 동적으로 생성된 직렬화 가능한 BaseNode
트리로 변환합니다.BaseNodeTransformer
하위 클래스를 사용하여 각 종류의 노드를 변환할 수 있습니다.다음 섹션에서는 이러한 단계에 대해 자세히 설명합니다.
이러한 구문 분석 트리를 생성하고 변환하는 프로세스를 시각화하려면 이 ast-viewer를 사용할 수 있습니다.
참고: 이 튜토리얼의 이 부분에서는 코드를 구문 분석하는 방법을 알아야 합니다.
ANTLR을 설치한 적이 없다면 ANTLR 시작 가이드를 참조하세요.
ANTLR Mega Tutorial에는 유용한 Python 예제가 있습니다.
이 페이지에서는 ANTLR 파서 규칙을 작성하는 방법을 설명합니다.
아래 규칙 정의는 중요한 ANTLR 파서 문법 요소에 대한 설명 이름이 포함된 예입니다.
rule_name : rule_element? rule_element_label= ' literal ' #RuleAlternativeLabel
| TOKEN + #RuleAlternativeLabel
;
규칙 요소 및 대체 레이블은 선택 사항입니다.
+
, *
, ?
, |
및 ()
RegEx와 동일한 의미를 갖습니다.
아래에서는 간단한 문법을 사용하여 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
그러면 Lexer( 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 규칙을 기반으로 하는 BaseNodes의 초기 트리를 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
의 필드 이름이고 오른쪽은 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
이름이고 두 번째 항목은 사용자 정의 노드의 클래스 메서드 이름입니다.
위의 예에서는 유용하지 않지만 다음과 동일합니다.
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
를 추가하면 코드가 중단될 수 있습니다.