語法分析器的任務是肯定某個單詞流是否可以與源語言的語法適配,即設定一個稱之爲上下文無關語言(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
ASTUnit::LoadFromASTFile("input.ast",Diags,opts);
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, {})
在上面代碼中,大量運用了宏(「##」是分隔強制鏈接標誌),生成了許多成員函數。展開宏,合併函數,還原代碼以下:
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能夠阻斷向上的遞歸。
6、如何實現RecursiveASTVisitor繼承類
申明一個類A,時期繼承模板類RecursiveASTVisitor<A>,當須要訪問某種節點時,就重載函數VisitXXX(XXX b)【如VisitNameDecl(NameDecl)】。
7、示例代碼
http://yunpan.cn/cdYYj7IEE7WYD下clang/AST測試.rar
提取碼:919d