zoukankan      html  css  js  c++  java
  • Clang之语法抽象语法树AST

        语法分析器的任务是确定某个单词流是否能够与源语言的语法适配,即设定一个称之为上下文无关语言(context-free language)的语言集合,语法分析器建立一颗与(词法分析出的)输入单词流对应的正确语法树。语法分析树的建立过程主要有两种方法:自顶向下语法分析法和自底向上分析法。AST作为语法分析树(parse tree)的一种简写方式,它独立于具体编程语言(C++、Java、C等),而且与语法分析树的建立过程无关(自顶向下和自底向上逻辑等价),是联系编译器前端、后端的重要接口。Clang的AST树与其他一些AST有些区别,如前者括号表达式为未裁剪模式(in an unreduced form),后者一般会尽量省去多余的括号,这样方便建立重构工具(clangdocsIntroductionToTheClangAST.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,
        {})
    View Code

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

    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; }
    View Code

        所以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

  • 相关阅读:
    DataItem 的使用[转帖]
    xmpp协议阅读总结
    smart pointer shared_from_this的使用
    std IO库, stringstream, 简看1
    const成员函数, const member function
    enum 随笔
    分隔和截断字符串, boost string algorithm library中的split和trim
    C++中异常处理
    boost::thread中的锁
    函数对象function object 以及boost::bind的一点了解
  • 原文地址:https://www.cnblogs.com/zhangke007/p/4714245.html
Copyright © 2011-2022 走看看