在正文之前,突然想发一点小感慨。因为懂得不多,又想自己探索,所以走了很多很多弯路。以前总听别人说,不要怕走弯路,全当鸡汤听了。现在发现,确实如此。
参考:
主要是青木峰郎的《自制编译器》
还有很多网上的资料。
预备知识
虽然不见得光知道这些就够了,而且虽然我也许了解的也不够准确,但我觉得起码要大概知道。
1、AST和parse树的区别。
总的来说,AST树比parse树更简洁,只保留了必要的节点。我觉得既然只是做个工程,我也没有追究得那么准确,所以就想画个图意会一下。不过不知道是不是我姿势不对,网上找到的图只是表达式的图,并没有什么函数定义啊类定义啊还有for循环while循环等等的图,所以没办法,我只能按照我的想法设计了。
怎么说,图画的丑,就是举几个例子分享一下我是怎么搞的吧,主要是以后想怎么用,现在就怎么设计,并不保证这就是AST的模式,而且欢迎批评指正。
2、Java面向对象编程
对于我一个不太会Java的人来说,Java有些东西还是挺迷的。我当时找了网上教程,还去图书馆找了找书。
这里随便贴一个找到的教程,各位也可以自行寻找:
www.weixueyuan.net/java/rumen_1/
我觉得现在用到的知识大概如下:
(1)基本语法,当然这和C++很像了,没费什么力。
(2)package之类的组织结构。一个文件里只能有一个public类,类名与文件名相同。
(3)类,那些访问修饰符一定要搞懂,我一开始看到类前面还有public不public之分,就觉得很迷。如教程中所说:
修饰符 | 说明 |
---|---|
public | 共有的,对所有类可见。 |
protected | 受保护的,对同一包内的类和所有子类可见。 |
private | 私有的,在同一类内可见。 |
默认的 | 在同一包内可见。默认不使用任何修饰符。 |
(4)继承与多态。这个一定要搞的特别清楚才行。
(5)接口。这个虽然自己没写,但是因为要理解Visitor机制,要看它的源代码,所以接口要知道。
3、Listener和Visitor机制。
为了遍历parse tree,建立自己的AST,所以要搞清楚Listener和Visitor机制。这里我基本上还是看的上一篇博文推荐的博客:
https://abcdabcd987.com/notes-on-antlr4/
我自己为应该用Visitor写还是Listener写纠结了好久,最后发现网上看到的几个都用Visitor写的。我觉得Visitor好像更灵活一些,于是也用Visitor写了。
要用Visitor建立AST,就要自己写一个类BuildAstVisitor继承原有的类<GRAMMAR_NAME>BaseVisitor <Node>。Node是AST的结点类,为了表示不同的结点,它有很多很多子类。
public class Node { String id; TypeRef type; Location loc; List<Node> sons; Node() { sons = new ArrayList<Node>(); type = TypeRef.buildTypeRef("void"); loc = new location(); } void print(String indentation) {} }
其中,id用来记录类似于函数名、类名、运算符、变量名或者常量值,type现在只是记录一些常量或表达式类型,我觉得应该可以以后检查表达式类型,Location是表示位置的类。
这里还有一点点参考,可以用来感受一下怎么用Visitor建立ASThttp://stackoverflow.org.cn/front/ask/view?ask_id=81496。
我当时为了用嵌入文法写AST,特意学了好半天,结果发现ANTLR 4好像不像ANTLR 3那样支持那种特别简单的建立AST的方法了,所以用的Visitor,并没有用上嵌入文法。有兴趣的可以看一看这个博客还挺全面的:
https://www.cnblogs.com/6DAN_HUST/archive/2009/01/06/1370359.html
如果你要问我为什么不直接找《The Definitive ANTLR 4 Reference》来看,我能说我在网上没找着么,又不太想买,好不容易找着一本还是ANTLR 3的,看到里面那种建立AST的方法,大呼精妙然后看了半天,结果发现ANTLR 4不支持了。
写完以后如果想看一下构建出来的AST是不是你需要的,就需要写一个tester,dfs一下并打印出一些信息。
public class TempTestAst { private String delta = " "; void dfs(Node u, String indentation) { u.print(indentation + delta); for (int i = 0; i < u.sons.size(); ++i) dfs(u.sons.get(i), indentation + delta); } }
比如这样的程序:
int main() { while (true) { printf("HelloWorld!"); } return 0; }
打印出来就是这个样子:
<CodeNode> id: null type: void <FuncDefNode> id: main type: int <CompStatNode> id: null type: void <WhileStatNode> id: null type: void <LiteralNode> id: true type: bool <CompStatNode> id: null type: void <ExprStatNode> id: null type: void <FuncExprNode> id: printf type: void <LiteralNode> id: "HelloWorld!" type: string <RetStatNode> id: null type: void <LiteralNode> id: 0 type: int