Lab02_Visitor模式
SysYParser和ParseTree
ParseTree、ProgramContext、RuleNode都是从同一个类继承下来的,所以下面这行代码可以正确运行。
1 | ParseTree tree = sysYParser.program(); |
其中sysYParser.program()
返回了一个ProgramContext
对象,也就是语法分析器得到的抽象语法树的Program
节点,与我们所写的词法规则对应。
ParseTree
是抽象语法树的节点,sysYParser
是词法分析器,两个并不具备包含关系。
调用图如下:
- ProgramContext
classDiagram direction BT class ParseTree { <> } class ParserRuleContext class ProgramContext class RuleContext class RuleNode { < > } class SyntaxTree { < > } class Tree { < > } ParseTree --> SyntaxTree ParserRuleContext --> RuleContext ProgramContext --> ParserRuleContext RuleContext ..> RuleNode RuleNode --> ParseTree SyntaxTree --> Tree
- CompUnitContext
classDiagram direction BT class CompUnitContext class ParseTree { <> } class ParserRuleContext class RuleContext class RuleNode { < > } class SyntaxTree { < > } class Tree { < > } CompUnitContext --> ParserRuleContext ParseTree --> SyntaxTree ParserRuleContext --> RuleContext RuleContext ..> RuleNode RuleNode --> ParseTree SyntaxTree --> Tree
Visitor的运行机制
- SysYParserVisitor
classDiagram direction BT class ParseTreeVisitor~T~ { <> } class SysYParserVisitor~T~ { < > } SysYParserVisitor~T~ --> ParseTreeVisitor~T~
- Visitor模式需要定义两个类层次
- Node层次:接受操作的元素(即第一部分的
Parsertree
) - Visitor层次:定义对元素的操作
- Node层次:接受操作的元素(即第一部分的
在Main
中我们通过这行命令调用Visitor
遍历抽象语法树,visitor
确实只是一个入口。
1 | visitor.visit(tree); |
这个tree
的声明类型是ParseTree
,但显然它的实际类型是ProgramContext
,所以这个语句会调用SysyParser.ProgramContext
类中的accept
。
注意:ProgramContext
、CompUnitContext
等类都是SysyParser
内部的static
类,因此accept
方法并不能是SysyParser
中的,而是每个抽象语法树的节点里的。
1 |
|
这里的if
判断visitor
是否是SysYParserVisitor
的实例,若是则进行类型转换,然后调用对应的visit
方法。这里的this
是ProgramContext
,也就是抽象语法树的节点。这符合Visitor设计模式:accept以visitor为参数,节点内部实现accept时会回调visitor的的成员函数(以this为参数)。
visitProgram
的原始定义在接口SysYParserVisitor
中,Antlr4
自动生成的类SysYParserBaseVisitor
对其给出了默认实现。
1 | public class SysYParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implements SysYParserVisitor<T> |
1 | public T visitProgram(SysYParser.ProgramContext ctx) { return visitChildren(ctx); } |
SysYParserBaseVisitor
中每个节点的visitXXX
函数默认实现都是visitChildren(ctx)
。
如果我们在本次Lab中使用Visitor模式,我们需要继承SysYParserBaseVisitor
,所有这些方法都可以修改。因为本次Lab不需要对每个节点做特定处理,所以我们不需要修改每个节点的默认实现,只需要修改visitChildren
就可以实现抽象语法树的打印。
visitChildren
方法的存在只是为了具体实现时更方便,与Visitor设计模式关系不大。Visitor模式的核心应该是accept和visit的多分派,根据实际类型而不是声明类型来选择调用的方法(和C++中的虚函数类似)。
至于怎么打印语法树,也就是如何深度优先遍历,只要在我们自己的visitChildren
中实现递归即可。其实可以参考AbstractParseTreeVisitor
中的visitChildren
,其中的for
循环就实现了深度优先遍历,这里的accept
也会根据c
的实际类型动态选择实现,由于没有修改默认实现,最后一定会调用到visitChildren
方法。
我们只需要做一些修改,在适当的位置打印节点的相关信息就可以了。
1 | public T visitChildren(RuleNode node) { |
- 标题: Lab02_Visitor模式
- 作者: Charlie
- 创建于 : 2023-06-01 00:06:00
- 更新于 : 2024-07-05 12:55:04
- 链接: https://chillcharlie357.github.io/posts/5a39ddc7/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。