Lab02_Visitor模式

Charlie

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层次:定义对元素的操作

Main中我们通过这行命令调用Visitor遍历抽象语法树,visitor确实只是一个入口。

1
visitor.visit(tree);

这个tree的声明类型是ParseTree,但显然它的实际类型是ProgramContext,所以这个语句会调用SysyParser.ProgramContext类中的accept

注意:ProgramContextCompUnitContext等类都是SysyParser内部的static类,因此accept方法并不能是SysyParser中的,而是每个抽象语法树的节点里的。

1
2
3
4
5
6
7
@Override  
public <T> T accept(ParseTreeVisitor<? extends T> visitor) {
if ( visitor instanceof SysYParserVisitor )
return ((SysYParserVisitor<? extends T>)visitor).visitProgram(this);
else
return visitor.visitChildren(this);
}

这里的if判断visitor是否是SysYParserVisitor的实例,若是则进行类型转换,然后调用对应的visit方法。这里的thisProgramContext,也就是抽象语法树的节点。这符合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
2
3
4
5
6
7
8
9
10
11
12
public T visitChildren(RuleNode node) {  
T result = this.defaultResult();
int n = node.getChildCount();

for(int i = 0; i < n && this.shouldVisitNextChild(node, result); ++i) {
ParseTree c = node.getChild(i);
T childResult = c.accept(this);
result = this.aggregateResult(result, childResult);
}

return result;
}
  • 标题: 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 进行许可。
评论
此页目录
Lab02_Visitor模式