语法分析器的任务是确定某个单词流是否能够与源语言的语法适配,即设定一个称之为上下文无关语言(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::, line::> line:: class hello definition
| |-CXXRecordDecl 0x5db3bf0 <col:, col:> col: implicit referenced class hello
| |-AccessSpecDecl 0x5db3c80 <line::, col:> col: public
| |-CXXConstructorDecl 0x5db3d20 <line::, col:> col: hello 'void (void)'
| | `-CompoundStmt 0x5dfb108 <col:, col:>
| |-CXXDestructorDecl 0x5dfafa0 <line::, col:> col: ~hello 'void (void)'
| | `-CompoundStmt 0x5dfb120 <col:, col:>
| |-AccessSpecDecl 0x5dfb050 <line::, col:> col: private
| `-FieldDecl 0x5dfb090 <line::, col:> col: hello_private 'int'
|-VarDecl 0x5dfb150 <input.cpp::, col:> col: innerDefiner 'int'
|-VarDecl 0x5dfb1c0 <line::, col:> col: outDefiner 'int'
|-FunctionDecl 0x5dfb2f0 <line::, line::> line:: 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>){
IntrusiveRefCntPtr<clang::DiagnosticsEngine> Diags;
invocation=clang::createInvocationFromCommandLine(llvm::makeArrayRef(argv+,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),代码中原文(删除解释部分):

、traverse the AST (i.e. go to each node);

、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.

、 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 () 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

Clang之语法抽象语法树AST的更多相关文章

  1. javascript编写一个简单的编译器(理解抽象语法树AST)

    javascript编写一个简单的编译器(理解抽象语法树AST) 编译器 是一种接收一段代码,然后把它转成一些其他一种机制.我们现在来做一个在一张纸上画出一条线,那么我们画出一条线需要定义的条件如下: ...

  2. 理解Babel是如何编译JS代码的及理解抽象语法树(AST)

    Babel是如何编译JS代码的及理解抽象语法树(AST) 1. Babel的作用是?   很多浏览器目前还不支持ES6的代码,但是我们可以通过Babel将ES6的代码转译成ES5代码,让所有的浏览器都 ...

  3. 抽象语法树(AST)

    AST描述 在计算机科学中,抽象语法树(AST)或语法树是用编程语言编写的源代码的抽象语法结构的树表示.树的每个节点表示在源代码中出现的构造.语法是“抽象的”,因为它不代表真实语法中出现的每个细节,而 ...

  4. 从零写一个编译器(九):语义分析之构造抽象语法树(AST)

    项目的完整代码在 C2j-Compiler 前言 在上一篇完成了符号表的构建,下一步就是输出抽象语法树(Abstract Syntax Tree,AST) 抽象语法树(abstract syntax ...

  5. Java抽象语法树AST,JCTree 分析

    JCTree简要分析文章目录JCTree简要分析JCAnnotatedTypeJCAnnotationJCArrayAccessJCArrayTypeTreeJCAssertJCAssignJCAss ...

  6. AST抽象语法树 Javascript版

    在javascript世界中,你可以认为抽象语法树(AST)是最底层. 再往下,就是关于转换和编译的"黑魔法"领域了. 现在,我们拆解一个简单的add函数 function add ...

  7. 五分钟了解抽象语法树(AST)babel是如何转换的?

    抽象语法树 什么是抽象语法树? It is a hierarchical program representation that presents source code structure acco ...

  8. AST抽象语法树

    抽象语法树简介 (一)简介 抽象语法树(abstract syntax code,AST)是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构,这所以说是抽象的,是因为抽象语法树并 ...

  9. JavaScript的工作原理:解析、抽象语法树(AST)+ 提升编译速度5个技巧

    这是专门探索 JavaScript 及其所构建的组件的系列文章的第 14 篇. 如果你错过了前面的章节,可以在这里找到它们: JavaScript 是如何工作的:引擎,运行时和调用堆栈的概述! Jav ...

随机推荐

  1. Linux学习笔记(二)——文件/目录/VIM

    文件和目录管理 及 VI编辑器的使用 文件和目录管理,刚开始学这块的时候感觉内容很多很杂,但是学完进行总结后,发现其实很有条理的而且没什么难度,只是熟练掌握这些常用的命令就行了.至于Vim编辑器,不得 ...

  2. 在Linux环境如何在不解压情况下搜索多个zip包中匹配的字符串内容

    今天有个生产文件需要查日志,但因为是比较久远的故障,日志已经被归档为zip包放到某个目录下了,在不知道具体日期时间的情况下,总不能一个一个解压搜索吧.于是就研究一下怎么在多个压缩包里搜索字符串了.目前 ...

  3. Python学习笔记——import模块

    OS模块 直接输出系统命令到屏幕,该方法获取的命令返回值不可被赋值给变量,输出结果受编码影响会乱码: import os os.system("ipconfig") 将执行获取的系 ...

  4. 使用joda-time工具类 计算时间相差多少 天,小时,分钟,秒

    下面程序使用了两种方法计算两个时间相差 天,小时,分钟,秒 package jodotest; import java.text.ParseException; import java.text.Si ...

  5. poj3320 (尺取法)

    n个数,求最小区间覆盖着n个数中所有的不相同的数字. 解题思路: AC代码: import java.util.HashMap; import java.util.HashSet; import ja ...

  6. 从源码的角度看Service是如何启动的

    欢迎访问我的个人博客 ,原文链接:http://wensibo.top/2017/07/16/service/ ,未经允许不得转载! 七月中旬了,大家的实习有着落了吗?秋招又准备的怎么样了呢?我依旧在 ...

  7. Ubuntu16.04修改内核启动

    写这篇文章一是为了对遇到同样问题的人提供一个参考,二来也是为了自己便于总结和查阅.希望大神勿喷. 好了,废话不多说了,转入正题. 前几天给自己的电脑装了个Ubuntu16.04LTS,自己顺手就把里边 ...

  8. PXE+Kickstart无人值守安装操作系统

    1.PXE的工作过程: 1. PXE Client 从自己的PXE网卡启动,向本网络中的DHCP服务器索取IP: 2. DHCP 服务器返回分配给客户机的IP 以及PXE文件的放置位置(该文件一般是放 ...

  9. RxSwift 系列(九) -- 那些难以理解的概念

    前言 看完本系列前面几篇之后,估计大家也还是有点懵逼,本系列前八篇也都是参考RxSwift官方文档和一些概念做的解读.上几篇文章概念性的东西有点多,一时也是很难全部记住,大家脑子里面知道有这么个概念就 ...

  10. python 图形界面开发

    用python来开发图形界面,确实不是很方便,没有c#,Java,甚至VB来得容易.几个控件拖拽,然后响应事件. 用python写脚本,或者web service来处理一般工作,绰绰有余.但有的时候, ...