Clang之語法抽象語法樹AST

    語法分析器的任務是肯定某個單詞流是否可以與源語言的語法適配,即設定一個稱之爲上下文無關語言(context-free language)的語言集合,語法分析器創建一顆與(詞法分析出的)輸入單詞流對應的正確語法樹。語法分析樹的創建過程主要有兩種方法:自頂向下語法分析法和自底向上分析法。AST做爲語法分析樹(parse tree)的一種簡寫方式,它獨立於具體編程語言(C++、Java、C等),並且與語法分析樹的創建過程無關(自頂向下和自底向上邏輯等價),是聯繫編譯器前端、後端的重要接口。Clang的AST樹與其餘一些AST有些區別,如前者括號表達式爲未裁剪模式(in an unreduced form),後者通常會盡可能省去多餘的括號,這樣方便創建重構工具(clang\docs\IntroductionToTheClangAST.rst中說明)。前端

1、AST的直觀印象node

    能夠使用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等。後端

2、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。frontend

  • 加載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; }

 

3、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)做爲參數。                       ide

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) ;

 

4、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());而在這裏,「元素類」沒有統一的接口,發起訪問只能經過「訪問者」,並且沒有統一的訪問接口。

5、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能夠阻斷向上的遞歸。

6、如何實現RecursiveASTVisitor繼承類

    申明一個類A,時期繼承模板類RecursiveASTVisitor<A>,當須要訪問某種節點時,就重載函數VisitXXX(XXX b)【如VisitNameDecl(NameDecl)】。

7、示例代碼

http://yunpan.cn/cdYYj7IEE7WYD下clang/AST測試.rar

提取碼:919d

相關文章
相關標籤/搜索