Clang中包含了非常多的关于抽象语法树(AST)的访问和操作的类和接口。我们程序开发人员可以直接通过继承其中的某些类,重写其中的关键成员方法,从而形成我们自己的对抽象语法树的操作。
那么,首先我们简要介绍几个概念:
抽象语法树(AST):抽象语法树是源代码的抽象语法结构的树状表现形式。树上的每个节点都表示源代码中的一种结构。之所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。一般的,在源代码的翻译和编译过程中,语法分析之后会创建出抽象语法树。一旦AST被创建出来,在后续的处理过程中,比如语义分析阶段,会添加很多语义信息帮助进行后续的语义翻译工作。AST其实就是一个程序的静态模型,它抽象出了一个程序在静态时结构状态,我们可以通过对AST的分析从而了解一个程序的相关静态信息。
Source to Source转换:我所理解的源到源的转换可以简单的当做是从一种源代码的形式转换到另一种源代码的形式。这其中,形式的定义很宽泛,包括了最简单的源代码风格、变量函数的命名到不同的编程语言。这些的转换都可以成为source to source transformation。
那么,如果要进行源到源的转换,最直接的思路和方式就是通过获得一个程序代码片段的抽象语法树(AST),然后通过修改AST的若干子树或若干结点,然后将AST转换成源代码,从而完成源到源的转换。
既然,我们已经明白了我们的目标和途径,那么接下来就介绍一下Clang中的基于AST的操作以及它们的设计模式。
Clang中的AST部分操作和表示的设计和实现比较类似于设计模式中的访问者模式。
Stmt
Stmt是表示程序语言语法成分的最原始的抽象基类接口,而我们的其他各种语法类型则是继承Stmt,如IfStmt,NullStmt,DeclStmt等等。它们相当于是访问者模式中的Element和ConcreteElement。元素类和抽象元素类。
RecursiveASTVisitor
RecursiveASTVisitor类似于访问者模式中的访问者。 我们在实现自己的操作AST的方法时需要继承自RecursiveASTVisitor类,并重写其中的多个方法,一般为bool VisitXXX(Stmt* stmt)方法。每一个VisitXXX方法都是访问某个具体对应类型的Stmt结点并对它进行操作的函数。 所以RecursiveASTVisitor和我们写的Visitor类就相当于抽象访问者类和访问者类。
ASTConsumer
ASTConsumer类的主要功能是提供一种自顶向下的对抽象语法树进行访问的入口。因为AST中包含了各种各样的Stmt,所以也可以认为ASTConsumer类似于提供了访问这个包含多种类型Stmt的容器的入口。因此我们可以将它对应到访问者模式中的ObjectStructure和Client。在这其中有多种方法来遍历当前程序生成的抽象语法树AST,从而获得各种各样类型的AST Node。因此,我们需要自己实现一个继承自ASTConsumer得类,并重写其中的遍历AST的方法,如:HandleTopLevelDecl,HandleTranslationUnit等等。
最后,我们还可以加入了FrontendAction等类,让我们绑定相关的编译器CompilerInstance信息等等,这些具体的部分我们放在下一篇文章中进行分析。
因此,我们在使用Clang进行AST操作时的主要流程是:分别继承RecursiveASTVisitor类和ASTConsumer类并重写其中对应的方法,如图中红色标识的类。
整个过程的表示如下:
根据我们设定好的前序或后序深度优先的访问方式,对于一个已经构建好的AST,完成以下工作:
1、从抽象语法树的根节点开始,当遇到一个 AST节点时,根据它的类型调用对应的TraverseXXX()方法;
2、接着,调用对应的VisitXXX()方法,进行具体的操作;
3、最后,在遇到接下来的语句再调用对应的TraverseDecl、TraverseStmt等等,递归执行。
因为基于C++语言的基本的语法类型已经不会怎么变动了(如if语句、class声明语句、循环语句等等),而对于每一个AST结点的操作确实需要随时按需求修改的,需要将数据结构的抽象和对数据结构的操作进行分离,所以比较满足了访问者模式的基本要求。
有人说,访问者模式比较适用于对已有功能的重构,或者说对一个项目已经完成,它的元素类型、数据结构已经定的差不多,而对数据的操作还有可能后序会改变。这样,可以使用访问者模式对原有的代码进行重构一遍。