BITMINICC生成AST
首先请花一定的时间阅读下面的参考方法,本文是我对下面文章的一个理解和踩坑记录。
参考方法:https://zhuanlan.zhihu.com/p/369250644?utm_source=qq&utm_medium=social&utm_oi=732535204933025792
1.一个好的文法
因为要用Antlr生成Lexer以及Listener,所以一个定义完整的文法是最重要的。因为当你用一个缺陷的文法,重写好了Listener,分析好了上一个实验的测试用例,结果到了下个实验直接连语法树都没办法生成的时候,就得一切重来(没错,这就是我写了3次语法分析的原因)
2.Antlr的Listener遍历方式
打开你使用Antlr工具生成的BaseListener.java,里面是对你文法的每一个节点的Enter和Exit函数,当调用Listener遍历你的语法树时,Walker会调用语法树的每一个节点的Enter函数直到Terminalnode,再从下往上调用Exit函数退出。这也是参考方法的原理。
怎么使用Listener方法
Listener需要与walker一起使用
ParseTree tree = parser.compilationUnit(); ParseTreeWalker walker = new ParseTreeWalker(); MyListener listener = new MyListener();//你重载的类 walker.walk(listener, tree);
3.要挂载哪些节点?
BITMINICC中定义好了实验所需的AST类,总结来说其中包括:
程序定义ASTCompilationUnit,声明语句ASTDeclaration,函数定义ASTFunctionDefine,声明ASTDeclarator,表达式ASTExpression,句子ASTStatement。其中ASTDeclarator,ASTExpression,ASTStatement为抽象类,被其它具体的节点所继承,详细如下。
/**
* Declarator
*@JsonSubTypes.Type(value = ASTArrayDeclarator.class,name = "ArrayDeclarator"),
@JsonSubTypes.Type(value = ASTVariableDeclarator.class,name = "VariableDeclarator"),
@JsonSubTypes.Type(value = ASTFunctionDeclarator.class,name="FunctionDeclarator"),
@JsonSubTypes.Type(value = ASTParamsDeclarator.class,name="ASTParamsDeclarator"),
*/
/**
* Expression
* @JsonSubTypes.Type(value = ASTIdentifier.class,name = "Identifier"),
* @JsonSubTypes.Type(value = ASTArrayAccess.class,name = "ArrayAccess"),
* @JsonSubTypes.Type(value = ASTBinaryExpression.class,name="BinaryExpression"),
* @JsonSubTypes.Type(value = ASTCastExpression.class,name = "CastExpression"),
* @JsonSubTypes.Type(value = ASTCharConstant.class,name = "CharConstant"), UNDO
* @JsonSubTypes.Type(value = ASTConditionExpression.class,name = "ConditionExpression"), UNDO
* @JsonSubTypes.Type(value = ASTFloatConstant.class,name = "FloatConstant"), UNDO
* @JsonSubTypes.Type(value = ASTFunctionCall.class,name = "FunctionCall"),
* @JsonSubTypes.Type(value = ASTIntegerConstant.class,name = "IntegerConstant"),
* @JsonSubTypes.Type(value = ASTMemberAccess.class,name = "MemberAccess"), UNDO
* @JsonSubTypes.Type(value = ASTPostfixExpression.class,name = "PostfixExpression"),
* @JsonSubTypes.Type(value = ASTStringConstant.class,name = "StringConstant"),
* @JsonSubTypes.Type(value = ASTUnaryExpression.class,name = "UnaryExpression"),
* @JsonSubTypes.Type(value = ASTUnaryTypename.class,name = "UnaryTypename") UNDO
*/
/**
* Statement
* @JsonSubTypes.Type(value = ASTBreakStatement.class,name = "BreakStatement"),
* @JsonSubTypes.Type(value = ASTCompoundStatement.class,name = "CompoundStatement"),
* @JsonSubTypes.Type(value = ASTContinueStatement.class,name="ContinueStatement"),
* @JsonSubTypes.Type(value = ASTExpressionStatement.class,name = "ExpressionStatement"),
* @JsonSubTypes.Type(value = ASTGotoStatement.class,name = "GotoStatement"),
* @JsonSubTypes.Type(value = ASTIterationDeclaredStatement.class,name = "IterationDeclaredStatement"),
* @JsonSubTypes.Type(value = ASTIterationStatement.class,name = "IterationStatement"),
* @JsonSubTypes.Type(value = ASTLabeledStatement.class,name = "LabeledStatement"),
* @JsonSubTypes.Type(value = ASTReturnStatement.class,name = "ReturnStatement"),
* @JsonSubTypes.Type(value = ASTSelectionStatement.class,name = "SelectionStatement")
*/
所以其实就5类节点需要挂载,相同类的实现基本相同。
4.怎么挂载?
如参考文章中所说,我们需要用一个栈,在访问到某一个具体节点时压入,退出某一个具体节点时弹出并挂载。那么最大的问题就是从哪一个节点访问和退出?毕竟语法分析树存在的递归实在太多了。
我的方法也是和参考文章一样,将递归的节点定义为pass,具有具体意义的节点定义为需要实现的。
如
postfixExpression
: primaryExpression #postfixExpression_pass
| postfixExpression '[' expression ']' #postfixExpression_arrayaccess
| postfixExpression '(' argumentExpressionList? ')' #postfixExpression_funccall
| postfixExpression '.' Identifier #postfixExpression_member
| postfixExpression '->' Identifier #postfixExpression_point
| postfixExpression '++' #postfixExpression_
| postfixExpression '--' #postfixExpression_
| '(' typeName ')' '{' initializerList '}' #postfixExpression_pass
| '(' typeName ')' '{' initializerList ',' '}' #postfixExpression_pass
| '__extension__' '(' typeName ')' '{' initializerList '}' #postfixExpression_pass
| '__extension__' '(' typeName ')' '{' initializerList ',' '}' #postfixExpression_pass
;
这是一个后缀表达式的文法,最开始的primaryExpression很明显就是一个递归节点定义为pass,而后面的4个是不会用到的,也定义为pass。
(忘了说了,生成的Listener如果你有定义的话,就会生成对应的函数,比如这个例子就会生成如下的函数接口,但是其实用到的也就arrayaccess,funcall,_,3个)
@Override public void enterPostfixExpression_arrayaccess(MyCGrammerParser.PostfixExpression_arrayaccessContext ctx) { }
@Override public void exitPostfixExpression_arrayaccess(MyCGrammerParser.PostfixExpression_arrayaccessContext ctx) { }
@Override public void enterPostfixExpression_point(MyCGrammerParser.PostfixExpression_pointContext ctx) { }
@Override public void exitPostfixExpression_point(MyCGrammerParser.PostfixExpression_pointContext ctx) { }
@Override public void enterPostfixExpression_funcall(MyCGrammerParser.PostfixExpression_funcallContext ctx) { }
@Override public void exitPostfixExpression_funcall(MyCGrammerParser.PostfixExpression_funcallContext ctx) { }
@Override public void enterPostfixExpression_pass(MyCGrammerParser.PostfixExpression_passContext ctx) { }
@Override public void exitPostfixExpression_pass(MyCGrammerParser.PostfixExpression_passContext ctx) { }
@Override public void enterPostfixExpression_member(MyCGrammerParser.PostfixExpression_memberContext ctx) { }
@Override public void exitPostfixExpression_member(MyCGrammerParser.PostfixExpression_memberContext ctx) { }
@Override public void enterPostfixExpression_(MyCGrammerParser.PostfixExpression_Context ctx) { }
@Override public void exitPostfixExpression_(MyCGrammerParser.PostfixExpression_Context ctx) { }
说回怎么挂载,这样定义之后,在你生成的语法树中,如果是形如postfixExpression '++'(a++)的节点,在语法树中会是下面这样
"assignmentExpression_pass": [
{
"conditionalExpression": [
{
"logicalOrExpression_pass": [
{
"logicalAndExpression_pass": [
{
"inclusiveOrExpression_pass": [
{
"exclusiveOrExpression_pass": [
{
"andExpression_pass": [
{
"equalityExpression_pass": [
{
"relationalExpression_pass": [
{
"shiftExpression_pass": [
{
"additiveExpression_pass": [
{
"multiplicativeExpression_pass": [
{
"castExpression_pass": [
{
"unaryExpression_pass": [
{
"postfixExpression_": [
{
"postfixExpression_pass": [
{
"primaryExpression": [
{
"tokenId": [
{
"value": "a1",
"tokenId": 101
}
]
}
]
}
]
},
{
"type": "BlockComment",
"value": "++",
"tokenId": 68
}
可以观察到除了有一个节点是"postfixExpression_"之外,其余的都是pass。("conditionalExpression"和"primaryExpression"是因为我没有对这两个文法重新定义)
这个时候,你应该已经懂了,我们只需要重写EnterpostfixExpression_和ExitpostfixExpression_即可。
在Enter时创建一个节点,在Exit时把它挂到正确的ParentNode上去。
5.挂到哪?
正如我说的,BITMINICC中定义的AST类其实就5类,同类之间的挂载是相同的,所以你只需要比对每一个节点它的成员需要哪一类,就能把哪一类挂到它上面去。
如
@JsonTypeName("PostfixExpression")
public class ASTPostfixExpression extends ASTExpression{
public ASTExpression expr;
public ASTToken op;
public ASTPostfixExpression() {
super("PostfixExpression");
}
public ASTPostfixExpression(ASTExpression expr,ASTToken op){
super("PostfixExpression");
this.expr = expr;
this.op = op;
}
@Override
public void accept(ASTVisitor visitor) throws Exception {
visitor.visit(this);
}
}
"PostfixExpression"节点需要的就是ASTExpression和ASTToken,当你退出一个ASTExpression节点时,检测栈里的上一个节点的类型是不是"PostfixExpression",如果是,就挂到它的上面去。
if(ParentNode.getClass()==ASTPostfixExpression.class){
if(((ASTPostfixExpression) ParentNode).expr==null){
((ASTPostfixExpression) ParentNode).expr=(ASTExpression) node;
}
}
6.踩的一些坑
AST类中,有的是List,有的是变量,List如果是null需要先分配空间。
AST类中自带了.children.add()和.children.addAll()方法,在退出节点时,需要将节点的成员都挂到children上才能生成JSON
可以考虑在挂载节点时,顺带把node.parent也挂上,方便以后访问树。
7.
最后,语法分析生成AST树是语义分析和中间代码生成的先决条件,做好语法分析需要一个好的文法。
我写了3次语法分析,第1次没有生成AST,第2次生成的AST只能适用于实验5的例子,第3次写的时候我就将实验6,7,8的例子作为用例来进行测试,至少现在是符合要求了,所以你在写的时候,最好也把实验8的例子加进来测试。
至于这次的实验,在你有一颗正确的AST树时,就是对这棵树进行访问和取你需要的东西,虽然也很恶心,但至少不会没有思路,以上。
(肝了2天3000行的屎山)