Clang之语法抽象语法树AST
语法分析器的任务是确定某个单词流是否能够与源语言的语法适配,即设定一个称之为上下文无关语言(context-free language)的语言集合,语法分析器建立一颗与(词法分析出的)输入单词流对应的正确语法树。语法分析树的建立过程主要有两种方法:自顶向下语法分析法和自底向上分析法。AST作为语法分析树(parse tree)的一种简写方式,它独立于具体编程语言(C++、Java、C等),而且与语法分析树的建立过程无关(自顶向下和自底向上逻辑等价),是联系编译器前端、后端的重要接口。Clang的AST树与其他一些AST有些区别,如前者括号表达式为未裁剪模式(in an unreduced form),后者一般会尽量省去多余的括号,这样方便建立重构工具(clang\docs\IntroductionToTheClangAST.rst中说明)。
一、AST的直观印象
可以使用clang –emit-ast input.cpp生成AST的二进制文件input.ast,也可以直接打印输出如下:
clang -Xclang -ast-dump -fsyntax-only input.cpp TranslationUnitDecl 0x5db3130 <<invalid sloc>> <invalid sloc> |-TypedefDecl 0x5db3670 <<invalid sloc>> <invalid sloc> implicit __int128_t '__int128' |-TypedefDecl 0x5db36d0 <<invalid sloc>> <invalid sloc> implicit __uint128_t 'unsigned __int128' |-TypedefDecl 0x5db3a90 <<invalid sloc>> <invalid sloc> implicit __builtin_va_list '__va_list_tag [1]' |-CXXRecordDecl 0x5db3ae0 <./test.h:1:1, line:7:1> line:1:7 class hello definition | |-CXXRecordDecl 0x5db3bf0 <col:1, col:7> col:7 implicit referenced class hello | |-AccessSpecDecl 0x5db3c80 <line:2:1, col:7> col:1 public | |-CXXConstructorDecl 0x5db3d20 <line:3:1, col:9> col:1 hello 'void (void)' | | `-CompoundStmt 0x5dfb108 <col:8, col:9> | |-CXXDestructorDecl 0x5dfafa0 <line:4:1, col:10> col:1 ~hello 'void (void)' | | `-CompoundStmt 0x5dfb120 <col:9, col:10> | |-AccessSpecDecl 0x5dfb050 <line:5:1, col:8> col:1 private | `-FieldDecl 0x5dfb090 <line:6:1, col:5> col:5 hello_private 'int' |-VarDecl 0x5dfb150 <input.cpp:8:1, col:5> col:5 innerDefiner 'int' |-VarDecl 0x5dfb1c0 <line:11:1, col:5> col:5 outDefiner 'int' |-FunctionDecl 0x5dfb2f0 <line:13:1, line:15:1> line:13:6 used testFunction 'void (int)' …
从上可以看出,每一行包括AST node的类型,行号、列号以及类型的信息。最顶部一般是TranslationUnitDecl【一个Cpp文件以及那些#include包括的文件称为翻译单元(TranslaitonUnit)】,如上面所示,test.h中的类也会进入AST树中。TypedefDecl、CXXRecordDecl、CompoundStmt等称为AST node,比较常见的有Stmt、Decl和Expr等。
二、AST树
AST树的所有信息都打包进了ASTContext(All information about the AST for a translation unit is bundled up in the class)。ASTContext中有一个重要的成员函数getTranslationUnitDecl,获取TranslationUnitDecl(其父类是Decl,DeclContext),这是AST树的顶层(top level)结构,可以通过其decls_begin()/decls_end()遍历其保存的nodes,下面代码打印Kind,查看保存的node类型,正与上命令行使用-emit-ast输出的一级目录相同。
TranslationUnitDecl *dc=Unit->getASTContext().getTranslationUnitDecl(); if(dc){ for(DeclContext::decl_iterator dit=dc->decls_begin() ; \ dit!= dc->decls_end();dit++){ std::cout<<dit->getDeclKindName()<<std::endl;}
AST树的本地化存储和读入借助ASTWriter和ASTReader,Clang还提供了一些高层次的类ASTUnit(Utility class for loading a ASTContext from an AST file),将AST树保存为二进制文件,也可以加载AST文件构建ASTContext。
- 加载AST文件构建ASTContext:
ASTUnit::LoadFromASTFile("input.ast",Diags,opts);
- 将AST树保存为二进制文件。
ASTUnit* Unit=ASTUnit::LoadFromCompilerInvocationAction(invocation, Diags1);
if(Unit&& !Unit->Save("output")){//这里的保存成功是返回false std::cout<<"save success!"<<std::endl; }
三、AST树的生成
构建AST树的核心类是ParseAST(Parse the entire file specified, notifying the ASTConsumer as the file is parsed),为了方便用户加入自己的actions,clang提供了众多的hooks。为更好的使用这些hooks,需弄清楚这几个类的关系—RecursiveASTVisitor,ASTConsumer,ParseAST, FrontendAction,CompilerInstance。 初始化CompilerInstance之后,调用其成员函数ExcutionAction, ExcutionAction会间接依次调用FrontendAction的6个成员函数(直接调用的是FrontendAction的三个public 接口,BeginSourceFile,Execute,EndSourceFile),而FrontendAction的ExecuteAction会 最终调用语法分析函数ParseAST(未强制要求ParseAST放入ExcuteAction,但ASTFrontendAction如此)。 ParseAST在分析过程中,又会插入ASTConsumer的多个句柄(用得最多是HandleTopLevelDecl和 HandleTranslationUnit)。FrontendAction是文件级别的动作,ASTConsumer则与一个Translation Unit内部处理过程相关。RecursiveASTVisitor是针对AST node的遍历,一般需要ASTConsumer中呈现的AST node(如TranslationUnitDecl)作为参数。
FrontendAction:Abstract base class for actions which can be performed by the frontend.FrontendAction 是抽象类,Clang还提供了几个继承子类 ASTFrontendAction,PluginASTAction,PreprocessorFrontendAction。 FrontendAction有三个public interface。
BeginSourceFile:该函数运行在options和FrontendAction初始化完成之后,每个文件Parse之前。如果该函数返回false,则后面的步骤不会执行。
Excute:Set the source manager's main input file, and run the action.
EndSourceFile():在parse完之后,做一些清理和内存释放工作。(Perform any per-file post processing, deallocate per-file objects, and run statistics and output file cleanup code)。
CreateASTConsumer(CompilerInstance &CI, StringRef InFile) |
在Compile之前,创建ASTConsumer。在建立AST的过程中,ASTConsumer提供了众多的Hooks。被FrontendAction的公共接口BeginSourceFile调用。 |
BeginInvocation(CompilerInstance &CI) |
在BeginSourceFileAction执行之前,该函数内还可以修改CompilerInvocation,即CompilerInstance编译参数选项。被FrontendAction的公共接口BeginSourceFile调用。 |
BeginSourceFileAction(CompilerInstance &CI,StringRef Filename) |
处理单个输入文件之前,做一些处理工作。被FrontendAction的公共接口BeginSourceFile调用。 |
ExecuteAction() |
执行动作。被FrontendAction的公共接口Execute调用。 |
EndSourceFileAction() |
Callback at the end of processing a single input,被FrontendAction的公共接口EndSourceFile调用。 |
shouldEraseOutputFiles() |
Determine if the output files should be erased or not. 被FrontendAction的公共接口EndSourceFile调用。 |
ASTConsumer:Abstract interface for reading ASTs,有两个重要的句柄HandleTopLevelDecl和HandleTranslationUnit。其他句柄有:HandleInlineMethodDefinition,HandleTagDeclDefinition,HandleTagDeclRequiredDefinition,HandleCXXImplicitFunctionInstantiation等。
CompilerInstance:是一个编译器实例,综合了一个Compiler需要的objects,如Preprocessor,ASTContext(真正保存AST内容的类),DiagnosticsEngine,TargetInfo等等。
CompilerInvocation:为编译器执行提供各种参数,它综合了TargetOptions 、DiagnosticOptions、HeaderSearchOptions、CodeGenOptions、DependencyOutputOptions、FileSystemOptions、PreprocessorOutputOptions等各种参数。如下从命令行解析成CompileInvocation。
int main(int argc,char **argv){ CompilerInvocation *invocation; if(argc>1){ IntrusiveRefCntPtr<clang::DiagnosticsEngine> Diags; invocation=clang::createInvocationFromCommandLine(llvm::makeArrayRef(argv+1,argv+argc),Diags) ;
四、RecursiveASTVisitor
AST nodes are modeled on a class hierarchy that does not have a common ancestor,AST nodes模型化的这些类包括Stmt,Type, Attr,Decl,DeclContext等,这些高度抽象的类又有众多子类,为了实现统一方式访问这些内部数据结构,RecursiveASTVisitor采用了“非严格意义”访问者设计模式(参见http://blog.csdn.net/zhengzhb/article/details/7489639),RecursiveASTVisitor是“抽象访问者”,“访问者”则是用户自己定义的RecursiveASTVisitor子类,“抽象元素类”是如上所述的Stmt,Type等。严格意义上的访问者设计模式,“元素类”都有一个统一的接口(如accept());而在这里,“元素类”没有统一的接口,发起访问只能通过“访问者”,而且没有统一的访问接口。
五、RecursiveASTVisitor功能
RecursiveASTVisitor主要完成以下三任务(称为#Task1,#Task2,#Task3),代码中原文(删除解释部分):
1、traverse the AST (i.e. go to each node); 2、at a given node, walk up the class hierarchy, starting from the node's dynamic type, until the top-most class (e.g. Stmt,Decl, or Type) is reached. 3、 given a (node, class) combination, where 'class' is some base class of the dynamic type of 'node', call a user-overridable function to actually visit the node.
#Task1要求给定一个root节点,深度优先方法递归遍历下去。#Task1只是一种dispatch过程,由TraverseDecl、TraverseStmt、TraverseType等Traverse*(*表示node类型)成员函数实现,具体访问node还需#Task2和#Task3完成。
#Task2,#Task3实现的是针对一个具体节点的user-overridable function,#Task2通过WalkUpFrom*实现,#Task3通过Visit*实现。下面通过例子简单说明。
class Visitor : public RecursiveASTVisitor<Visitor> { TraverseNamespaceDecl(decl); virtual bool VisitDecl(Decl * decl){ std::cout<<"Visit Decl!"<<std::endl; return true;} virtual bool VisitNamedDecl(NamedDecl *decl) { std::cout<<"VisitNamedDecl!"<<decl->getQualifiedNameAsString()<<std::endl; return true; } virtual bool VisitNamespaceDecl(NamespaceDecl *decl){ if(decl) std::cout<<"VisitNamespaceDecl:"<<decl->getQualifiedNameAsString()<<std::endl; return true;} }; Visitor vt; vt.TraverseNamespaceDecl(decl);//decl是NamespaceDecl指针
Test1:假设被编译文件包含Namespace In{}申明。打印如下:
Visit Decl! Visit NamedDecl!In Visit NamespaceDecl:In
Test2:假设被编译文件包含:Namespace In{int a;},打印如下:
Visit Decl! Visit NamedDecl!In Visit NamespaceDecl:In Visit Decl! Visit NamedDecl!In::a
(1)Test2在Test1基础上还遍历了Namespace内部的申明,所以TraverseNamespace是以Namespace为root深度遍历整棵树。查看RecursiveASTVisitor.h实现过程如下:
Derived &getDerived() { return *static_cast<Derived *>(this); } #define TRY_TO(CALL_EXPR) \ do { \ if (!getDerived().CALL_EXPR) \ return false; \ } while (0) template <typename Derived> bool RecursiveASTVisitor<Derived>::TraverseDecl(Decl *D) { if (!D) return true; if (!getDerived().shouldVisitImplicitCode() && D->isImplicit()) return true; switch (D->getKind()) { #define ABSTRACT_DECL(DECL) #define DECL(CLASS, BASE) \ case Decl::CLASS: \ if (!getDerived().Traverse##CLASS##Decl(static_cast<CLASS##Decl *>(D))) \ return false; \ break; #include "clang/AST/DeclNodes.inc"} template <typename Derived> bool RecursiveASTVisitor<Derived>::TraverseDeclContextHelper(DeclContext *DC) { if (!DC) return true; for (auto *Child : DC->decls()) { if (!isa<BlockDecl>(Child) && !isa<CapturedDecl>(Child)) TRY_TO(TraverseDecl(Child)); } #define DEF_TRAVERSE_DECL(DECL, CODE) \ template <typename Derived> \ bool RecursiveASTVisitor<Derived>::Traverse##DECL(DECL *D) { \ TRY_TO(WalkUpFrom##DECL(D)); \ { CODE; } \ TRY_TO(TraverseDeclContextHelper(dyn_cast<DeclContext>(D))); \ return true; \ } … DEF_TRAVERSE_DECL( NamespaceDecl, {})
在上面代码中,大量运用了宏(“##”是分隔强制连接标志),生成了许多成员函数。展开宏,合并函数,还原代码如下:
template <typename Derived> bool RecursiveASTVisitor<Derived>::TraverseNamespaceDecl(DECL *D) { Derived * temp1= static_cast<Derived *>(this);// getDerived() temp1-> WalkUpFromNamespaceDecl(D);//TRY_TO展开 DeclContext *DC= dyn_cast<DeclContext>(D); If(!DC) return true; //展开TraverseDeclContextHelper for (auto *Child : DC->decls()) { if (!isa<BlockDecl>(Child) && !isa<CapturedDecl>(Child)) //展开TraverseDecl if (!Child) return true; if (!temp1->shouldVisitImplicitCode() && Child->isImplicit()) return true; } switch (D->getKind()) { … case Decl::NamedDecl://test2中被编译文件定义了“int a”,需要用到该分支temp1->TraverseNamedDecl(static_cast<NamedDecl *>(D)); break; }} Return true;}
由上展开代码得,在Traverse某个node时,会for循环node中保存的Decls,然后每个Decls再调用对应的Traverse函数,所以Test2比Test1多遍历了”int a;”对应的node。
(2)在Traverse node之初,会调用WalkUpFrom*函数。其内部实现如下:
#define DECL(CLASS, BASE) \ bool WalkUpFrom##CLASS##Decl(CLASS##Decl *D) { \ TRY_TO(WalkUpFrom##BASE(D)); \ TRY_TO(Visit##CLASS##Decl(D)); \ return true; \ } \ bool Visit##CLASS##Decl(CLASS##Decl *D) { return true; } #include "clang/AST/DeclNodes.inc"
clang/AST/DeclNodes.inc定义了如下:
# define NAMESPACE(Type, Base) NAMED(Type, Base)
# define NAMED(Type, Base) DECL(Type, Base)
NAMESPACE(Namespace, NamedDecl)
NAMED(Named, Decl)
所以最终存在两个宏DECL(Namespace,NamedDecl),DECL(Named,Decl),还原代码得:
bool RecursiveASTVisitor<Derived>::WalkUpFromNameSpaceDecl(NameSpaceDecl *D) { Derived * temp1= static_cast<Derived *>(this);// getDerived() Temp1-> WalkUpFromNamedDecl(D); Temp1->VisitNameSpaceDecl(D); return true; } bool RecursiveASTVisitor<Derived>::WalkUpFromNamedDecl(NamedDecl *D) { Derived * temp1= static_cast<Derived *>(this);// getDerived() Temp1-> WalkUpFromDecl(D); Temp1->VisitNamedDecl(D); return true; } bool RecursiveASTVisitor<Derived>::WalkUpFromNamedDecl(NamedDecl *D) { Derived * temp1= static_cast<Derived *>(this);// getDerived() Temp1-> WalkUpFromDecl(D); Temp1->VisitNamedDecl(D); return true; } bool RecursiveASTVisitor<Derived>::WalkUpFromDecl(Decl *D) { return getDerived().VisitDecl(D); } bool VisitDecl(Decl *D) { return true; }
所以WalkUpFrom会不断递归调用父节点的WalkUpFrom函数,最终调用的是VisitDecl,VisitNamedDecl,VisitNamespaceDecl,这正是上面所说#task 2,如果用户实现了WalkUpFromXX可以阻断向上的递归。
六、如何实现RecursiveASTVisitor继承类
申明一个类A,时期继承模板类RecursiveASTVisitor<A>,当需要访问某种节点时,就重载函数VisitXXX(XXX b)【如VisitNameDecl(NameDecl)】。
七、示例代码
http://yunpan.cn/cdYYj7IEE7WYD下clang/AST测试.rar
提取码:919d