ANTLR解析器的AST塑造
项目描述
antlr-ast
此软件包允许您使用ANTLR语法,并使用解析器输出生成抽象语法树(AST)。
安装
pip install antlr-ast
注意:此软件包与python2不兼容。
运行测试
# may need:
# pip install pytest
py.test
用法
使用antlr-ast
涉及四个步骤
- 使用ANTLR定义语法并生成解析此语法的必要Python文件
- 使用
parse
根据生成的语法文件获取ANTLR运行时输出 - 对上一步的输出使用
process_tree
BaseAstVisitor
(可通过提供子类进行自定义)将ANTLR输出转换为可序列化的BaseNode
树,该树根据ANTLR语法中的规则动态创建- 可以使用
BaseNodeTransformer
子类来转换每种类型的节点 - 可以使用简化选项通过跳过只有单个子节点的节点来缩短树中的路径
- 使用生成的树
下一节将详细介绍这些步骤。
为了可视化创建和转换这些解析树的过程,您可以使用 这个ast-viewer。
使用ANTLR
注意:在本教程的这一部分,您需要了解如何解析代码
如果您从未安装过ANTLR,请参阅ANTLR 入门指南。
ANTLR Mega Tutorial 中有有用的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
这将生成多个文件在 /tests/
目录中,包括一个词法分析器(ExprLexer.py
)、一个解析器(ExprParser.py
)和一个访问者(ExprVisitor.py
)。
您可以直接在Python中使用并导入这些文件。例如,从本仓库的根目录
from tests import ExprVisitor
为了方便使用生成的文件,它们被放在了 antlr_py
包中。在 __init__.py
文件中,导出的生成文件使用不包含语法名称的别名。
基本节点
BaseNode
子类具有其对应语法规则中所有规则元素的字段,以及所有规则元素标签的标签。这两个字段和标签都作为 BaseNode
实例上的属性可用。如果名称发生冲突,标签优先于字段。
BaseNode
的名称是相应的ANTLR语法规则的名称,但以大写字母开头。如果ANTLR规则中指定了规则可选标签,则使用这些标签而不是规则名称。
转换节点
通常,ANTLR规则与语言的构思之间没有一对一的映射:规则层次结构更为嵌套。可以使用转换来使基于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"]
这段代码定义了一个自定义节点 NotExpr
,具有 expr
和 op
字段。
字段规范
_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
名称,第二个项是自定义节点的一个类方法的名称。
在上述示例中,这没有用,但它与以下内容等价
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
,则代码可能会因为添加了替换这些节点的某些字段的AliasNode
而导致中断。
项目详情
antlr-ast-0.8.1.tar.gz的散列
算法 | 散列摘要 | |
---|---|---|
SHA256 | 534ff94889de3561de0a126e76e003bc3a992e9ac0c2f4c7ea8563e651e695ff |
|
MD5 | 55393dcd1342974e1d47bc8e6810cc02 |
|
BLAKE2b-256 | 060e85ce7fe4d672995947e6582f22cd76841044fe0faaa1960bd07ab401acf9 |