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 <> 
|-TypedefDecl 0x5db3670 <>  implicit __int128_t '__int128'
|-TypedefDecl 0x5db36d0 <>  implicit __uint128_t 'unsigned __int128'
|-TypedefDecl 0x5db3a90 <>  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 1, col:7> col:7 implicit referenced class hello
| |-AccessSpecDecl 0x5db3c80 2:1, col:7> col:1 public
| |-CXXConstructorDecl 0x5db3d20 3:1, col:9> col:1 hello 'void (void)'
| | `-CompoundStmt 0x5dfb108 8, col:9>
| |-CXXDestructorDecl 0x5dfafa0 4:1, col:10> col:1 ~hello 'void (void)'
| | `-CompoundStmt 0x5dfb120 9, col:10>
| |-AccessSpecDecl 0x5dfb050 5:1, col:8> col:1 private
| `-FieldDecl 0x5dfb090 6:1, col:5> col:5 hello_private 'int'
|-VarDecl 0x5dfb150 8:1, col:5> col:5 innerDefiner 'int'
|-VarDecl 0x5dfb1c0 11:1, col:5> col:5 outDefiner 'int'
|-FunctionDecl 0x5dfb2f0 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<getDeclKindName()< 
 

    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!"<

 

三、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。Clang之语法抽象语法树AST_第1张图片      初始化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 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 {
           TraverseNamespaceDecl(decl);
    virtual bool VisitDecl(Decl * decl){
         std::cout<<"Visit Decl!"<<std::endl;
          return true;}
    virtual bool VisitNamedDecl(NamedDecl *decl) {
std::cout<<"VisitNamedDecl!"<getQualifiedNameAsString()<<std::endl;
      return true;
    }
   virtual bool VisitNamespaceDecl(NamespaceDecl *decl){
      if(decl)
std::cout<<"VisitNamespaceDecl:"<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(this); }

#define TRY_TO(CALL_EXPR)                                                      \
  do {                                                                         \
    if (!getDerived().CALL_EXPR)                                               \
      return false;                                                            \
  } while (0)

template 
bool RecursiveASTVisitor::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(D)))    \
      return false;                                                            \
    break;
#include "clang/AST/DeclNodes.inc"}

template 
bool RecursiveASTVisitor::TraverseDeclContextHelper(DeclContext *DC) {
  if (!DC)
    return true;
  for (auto *Child : DC->decls()) {
    if (!isa(Child) && !isa(Child))
      TRY_TO(TraverseDecl(Child));
  }

#define DEF_TRAVERSE_DECL(DECL, CODE)                                          \
  template                                                   \
  bool RecursiveASTVisitor::Traverse##DECL(DECL *D) {                 \
    TRY_TO(WalkUpFrom##DECL(D));                                               \
    { CODE; }                                                                  \
    TRY_TO(TraverseDeclContextHelper(dyn_cast(D)));               \
    return true;                                                               \
  }
…
DEF_TRAVERSE_DECL(
    NamespaceDecl,
    {})
View Code

     在上面代码中,大量运用了宏(“##”是分隔强制连接标志),生成了许多成员函数。展开宏,合并函数,还原代码如下:

template                                                   
  bool RecursiveASTVisitor::TraverseNamespaceDecl(DECL *D) {
  Derived * temp1= static_cast(this);// getDerived()
temp1-> WalkUpFromNamespaceDecl(D);//TRY_TO展开
  DeclContext *DC= dyn_cast(D);
  If(!DC) return true;
//展开TraverseDeclContextHelper
      for (auto *Child : DC->decls()) {
if (!isa(Child) && !isa(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(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::WalkUpFromNameSpaceDecl(NameSpaceDecl *D) {                            
Derived * temp1= static_cast(this);// getDerived()     
Temp1-> WalkUpFromNamedDecl(D);
Temp1->VisitNameSpaceDecl(D);
      return true;                                                               
  }      
bool RecursiveASTVisitor::WalkUpFromNamedDecl(NamedDecl *D) {                            
Derived * temp1= static_cast(this);// getDerived()     
Temp1-> WalkUpFromDecl(D);
Temp1->VisitNamedDecl(D);
      return true;                                                               
  }  
bool RecursiveASTVisitor::WalkUpFromNamedDecl(NamedDecl *D) {                            
Derived * temp1= static_cast(this);// getDerived()     
Temp1-> WalkUpFromDecl(D);
Temp1->VisitNamedDecl(D);
      return true;                                                               
  }  
  bool RecursiveASTVisitor::WalkUpFromDecl(Decl *D) { return getDerived().VisitDecl(D); }
  bool VisitDecl(Decl *D) { return true; }
View Code

    所以WalkUpFrom会不断递归调用父节点的WalkUpFrom函数,最终调用的是VisitDecl,VisitNamedDecl,VisitNamespaceDecl,这正是上面所说#task 2,如果用户实现了WalkUpFromXX可以阻断向上的递归。

六、如何实现RecursiveASTVisitor继承类

    申明一个类A,时期继承模板类RecursiveASTVisitor,当需要访问某种节点时,就重载函数VisitXXX(XXX b)【如VisitNameDecl(NameDecl)】。

七、示例代码

http://yunpan.cn/cdYYj7IEE7WYD下clang/AST测试.rar

提取码:919d

 

你可能感兴趣的:(Clang之语法抽象语法树AST)