語法分析器的任務是確定某個單詞流是否能夠與源語言的語法適配,即設定一個稱之為上下文無關語言(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
