Thrift之代码生成器Compiler原理及源码详细解析1

时间:2022-09-25 23:43:06

我的新浪微博:http://weibo.com/freshairbrucewoo

欢迎大家相互交流,共同提高技术。

又很久没有写博客了,最近忙着研究GlusterFS,本来周末打算写几篇博客的,但是由于调试GlusterFS的一些新增功能就用了整整的一天,还有一天就陪老婆大人逛街去了!今晚浏览完微博发现时间还早就来博客一篇,本篇博客内容主要是前一段时间研究的Thrift的代码生成器的源码详细分析,没有具体分析语法解析,因为是工具字段生成的代码,人是没有办法阅读的----到处都是跳转表!由于Thrift支持N多种语言,但是生成代码原理都差不多,我主要分析了C++相关代码生成。关于Thrift的使用及原理、代码网上基本上都有,代码的注释很好,基本上都是英文注释。下面就是我之前分析写的文档,希望对学习使用代码生成代码的爱好者有一定帮助。

Thrift之代码生成器Compiler原理及源码详细解析1

这个功能是一个单独的工具程序,它会独立的生成一个可执行文件。

第一节 类关系图

本节主要展示了这个部分的整体类图,然后对这个类图做了简要的说明,有了这个类图让我在阅读这个部分源代码时不会找不到方向,让我更加清楚这个部分中的类是怎样协同工作的,类关系图如下所示:

Thrift之代码生成器Compiler原理及源码详细解析1

注意:实线代表继承关系;而虚线代表依赖关系。

由类关系图可以看出Compiler功能模块主要分为两个部分,一个是图的右边展示了各种语言代码生成的类。它们的共同基类都是t_generator类,但是面向对象的语言并不是直接从它继承,而是又把面向对象的共同特性提取出来用一个类来实现,这个类就是t_oop_generator,其他面向对象语言的生成类都是从这个类继承。总基类的实现是依赖于左边的t_program类,这个类表示一个程序代码需要的所有特征和要素。左边部分就是解决一个程序需要拥有的数据类型和函数,根据接口定义语言(IDL)解析和生成相应的数据和函数等。左边部分就显示thrift定义的中间语言(IDL)能够支持的数据类型,t_type类是所有数据类型类的基类。

第二节 程序流程图

这个部分整体的流程图如下图所示:

Thrift之代码生成器Compiler原理及源码详细解析1

以上流程图简要的说明了Compiler运行的一个过程,通过这个流程图可以让我们知道了根据中间定义语言生成各种程序代码的整体思路和流程。下一节将根据源代码详细分析整个过程的原理及实现方案,这个里面涉及到一些编译原理的知识,不深入分析这一部分,它里面的词法分析程序是用linux上的工具flex自动生成c语言程序,解析中间定义语言的时候直接调用yyparse函数即可。另一部分重点内容就是生成各种程序语言代码的功能,每一种语言用一个类来生成,后面不会详细分析每一种语言的生成实现的代码,主要分析三种主流语言(C++、java、python)。

第三节 源代码详细分析

1  Main.cc文件

这个文件是这个部分的入口文件,因为它定义main函数。除了main函数以外还定义了其它很多的全局函数和变量,其中比较重要的函数有:parse解析函数、generate生成代码的函数;比较重要的全局变量主要是:g_program程序类的变量和各种数据类型的类的变量。

(1)main函数

首先定义一个用于存放源代码输出路径的字符串变量:std::string out_path;然后生成一个基于时间的字符串保存到一个全局变量中,如下代码实现:

1 time_t now = time(NULL);
2
3 g_time_str = ctime(&now);

然后检查参数个数是否满足最低要求,不满足就调用使用说明函数,这个函数就是简单打印这个工具的使用说明,然后退出程序。代码如下:

1 if (argc < 2) {
2
3 usage();
4
5 }

下面定义一个用于保存需要生成语言的代表字符串的向量数组:vector<string> generator_strings;后面就根据参数的个数解析参数,然后根据参数的内容执行相应的功能。解析参数的时候用到了一个函数strtok,它需要两个参数,第一个是需要分割的字符串,不能是指向常量区的,第二个是分割字符串的分隔符字符串,首先返回第一个被分割后的字符串,下一次调用第一个参数用NULL就继续下一个被分割下来的字符串,如果没有了就返回NULL。上面说了根据参数内容执行相应的功能:主要包括查看版本信息、是否打印详细的执行过程信息、警告级别等,最主要的还是解析需要生产哪些语言的参数,然后将能够代表需要生产某种语言的字符串保存到generator_strings字符串数组当中。

下面的代码开始根据参数得到中间语言定义的文件,然后根据文件名生成一个t_program的对象来代表整个程序的解析树,接着根据文件名找到文件所在的目录并设置包含文件的目录,最后初始化一些全局变量(为这些变量分别内存资源),中间还设置了生成代码输出的路径。

Thrift之代码生成器Compiler原理及源码详细解析1
 1 if (saferealpath(argv[i], rp) == NULL) {
2
3 failure("Could not open input file with realpath: %s", argv[i]);
4
5 }
6
7 string input_file(rp);
8
9 t_program* program = new t_program(input_file);
10
11 if (out_path.size()) {
12
13 program->set_out_path(out_path);
14
15 }
16
17 string input_filename = argv[i];
18
19 string include_prefix;
20
21 string::size_type last_slash = string::npos;
22
23 if ((last_slash = input_filename.rfind("/")) != string::npos) {
24
25 include_prefix = input_filename.substr(0, last_slash);
26
27 }
28
29 program->set_include_prefix(include_prefix);
30
31 g_type_void = new t_base_type("void", t_base_type::TYPE_VOID);
32
33 g_type_string = new t_base_type("string", t_base_type::TYPE_STRING);
34
35 g_type_binary = new t_base_type("string", t_base_type::TYPE_STRING);
36
37 ((t_base_type*)g_type_binary)->set_binary(true);
38
39 g_type_slist = new t_base_type("string", t_base_type::TYPE_STRING);
40
41 ((t_base_type*)g_type_slist)->set_string_list(true);
42
43 g_type_bool = new t_base_type("bool", t_base_type::TYPE_BOOL);
44
45 g_type_byte = new t_base_type("byte", t_base_type::TYPE_BYTE);
46
47 g_type_i16 = new t_base_type("i16", t_base_type::TYPE_I16);
48
49 g_type_i32 = new t_base_type("i32", t_base_type::TYPE_I32);
50
51 g_type_i64 = new t_base_type("i64", t_base_type::TYPE_I64);
52
53 g_type_double = new t_base_type("double", t_base_type::TYPE_DOUBLE);
Thrift之代码生成器Compiler原理及源码详细解析1

后然调用解析函数和代码生成函数如下:

1 parse(program, NULL);
2
3 generate(program, generator_strings);

最后删除申请到资源。

(2)parse函数

这个函数的主要功能就是调用词法分析程序来进行词法分析,后面会根据词法分析的结果来生产程序代码。下面将详细分析这个函数的功能。

首先从program得到中间文件的全路径并初始化当前文件路径的全局变量,然后根据文件的全路径得到目录来初始化当前目录全局变量,实现代码如下:

1 string path = program->get_path();
2
3 g_curdir = directory_name(path);
4
5 g_curpath = path;

接着根据上面得到的文件路径打开这个文件作为词法分析程序的分析对象:yyin = fopen(path.c_str(), "r");

下面开始第一次进行词法分析,这次词法分析的主要目的提取里面内嵌的IDL文件,所以设置解析的模式为INCLUDES,解析完成以后关闭文件。词法分析的结果都存放到program中。以便后面使用到的地方直接从program就可以得到。词法分析的时候可能发生异常,所以需要处理异常。实现代码如下:

Thrift之代码生成器Compiler原理及源码详细解析1
 1 g_parse_mode = INCLUDES;
2
3 g_program = program;
4
5 g_scope = program->scope();
6
7 try {
8
9 yylineno = 1;
10
11 if (yyparse() != 0) {
12
13 failure("Parser error during include pass.");
14
15 }
16
17 } catch (string x) {
18
19 failure(x.c_str());
20
21 }
22
23 fclose(yyin);
Thrift之代码生成器Compiler原理及源码详细解析1

分析出内嵌的IDL文件以后就对这些IDL文件进行递归调用parse函数来对每一个IDL文件进行词法分析,因为每一个IDL文件里面可能包括多个IDL文件,所以需要用一个for循环对没有一个IDL都进行递归词法分析,具体实现如下:

Thrift之代码生成器Compiler原理及源码详细解析1
1 vector<t_program*>& includes = program->get_includes();
2
3 vector<t_program*>::iterator iter;
4
5 for (iter = includes.begin(); iter != includes.end(); ++iter) {
6
7 parse(*iter, program);
8
9 }
Thrift之代码生成器Compiler原理及源码详细解析1

最后一部分是重点,将进行第二次词法分析,这次分析就是真正的对IDL文件中定义的数据类型和服务(函数)进行词法分析和语法分析,所以首先需要设置词法分析的模式为PROGRAM。还需要初始化一些全局变量,和第一次词法分析一样需要打开IDL文件为词法分析程序提供分析源、异常处理和最后关闭文件,实现的主要代码如下:

Thrift之代码生成器Compiler原理及源码详细解析1
 1 g_parse_mode = PROGRAM;
2
3 g_program = program;
4
5 g_scope = program->scope();
6
7 g_parent_scope = (parent_program != NULL) ? parent_program->scope() : NULL;
8
9 g_parent_prefix = program->get_name() + ".";
10
11 g_curpath = path;
12
13 yyin = fopen(path.c_str(), "r");
14
15 yylineno = 1;
16
17 try {
18
19 if (yyparse() != 0) {
20
21 failure("Parser error during types pass.");
22
23 }
24
25 } catch (string x) {
26
27 failure(x.c_str());
28
29 }
30
31 fclose(yyin);
Thrift之代码生成器Compiler原理及源码详细解析1

到此这个函数完成了所有自己的功能,所有词法分析的结果都保存到program中了,同时g_program也保存同样的一份内容,以便后面的生成代码函数使用。

(3)generate函数

本函数主要功能就是根据parse函数生成的各种数据类型和服务生成各种代码文件。首先递归调用每一个内嵌的t_program来生成代码,实现代码如下:

Thrift之代码生成器Compiler原理及源码详细解析1
1 const vector<t_program*>& includes = program->get_includes();
2
3 for (size_t i = 0; i < includes.size(); ++i) {
4
5 includes[i]->set_out_path(program->get_out_path());
6
7 generate(includes[i], generator_strings);
8
9 }
Thrift之代码生成器Compiler原理及源码详细解析1

后面部分就是真正生成代码文件的功能了。首先为每一个结构体、枚举和异常生成一个在thrift中全球唯一的识别指纹(其实就是字符串,这个字符串是根据具体类型信息的字符串经过MD5处理后的字符串,如枚举就是根据”enum”生成的)。然后根据需要决定是否打印所有的调试信息。接着根据需要生成的语言循环生成每一种语言的代码,这个是根据在main函数中存放代表语言的字符串(generator_strings)来决定,根据t_program和代表语言的字符串得到一个代码生成器的对象(每一种语言都有一个独立的生成语言的类),然最后就调用这个代码生成器对象的代码生成函数生成具体的代码文件,代码如下:

Thrift之代码生成器Compiler原理及源码详细解析1
 1 generate_all_fingerprints(program);
2
3 if (dump_docs) {
4
5 dump_docstrings(program);
6
7 }
8
9 vector<string>::const_iterator iter;
10
11 for (iter = generator_strings.begin(); iter != generator_strings.end(); ++iter) {
12
13 t_generator* generator = t_generator_registry::get_generator(program, *iter);
14
15 if (generator == NULL) {
16
17 pwarning(1, "Unable to get a generator for \"%s\".\n", iter->c_str());
18
19 } else {
20
21 pverbose("Generating \"%s\"\n", iter->c_str());
22
23 generator->generate_program();
24
25 delete generator;
26
27 }
28
29 }
Thrift之代码生成器Compiler原理及源码详细解析1

本函数实现的功能就是以上这些功能,至于具体生成语言代码的功能在各种语言生成的类中实现,后面会详细分析java、C++和python的生成实现。

(4)其它函数

这个主程序文件中还有其他许多函数,下面简单介绍每一个函数的功能,就不分析详细的实现了,具体的实现可以查看源代码。

函数名称

函数功能

saferealpath

根据文件的相对路径得到文件真实而安全的文件绝对路径

yyerror

词法分析程序的错误信息输出程序

pdebug

解析器打印调试信息

pverbose

打印一个详细的输出模式的消息

pwarning

打印警告消息

failure

打印失败的消息并且退出程序

program_name

转换一个字符串的文件名为thrift的program名称

directory_name

根据一个文件的路径得到目录路径

include_file

从给定的文件名查找相应的文件路径

clear_doctext

清除任何以前存储的文档的文本字符串。

clean_up_doctext

清理文本通常类似doxygen的注释

dump_docstrings

输出程序文档字符串到stdout

generate_all_fingerprints

为program的每一个结构体和枚举生成唯一的“指纹”

version

打印thrift的版本信息

usage

打印使用信息并且退出程序

validate_const_rec

验证常量类型是否有效

validate_const_type

检查常量类型的声明类型

validate_field_value

检查分配给一个字段默认值的类型。

validate_throws

检查所有元素抛出的异常是真实的异常

Thrift之代码生成器Compiler原理及源码详细解析1的更多相关文章

  1. Thrift之代码生成器Compiler原理及源码详细解析2

    我的新浪微博:http://weibo.com/freshairbrucewoo. 欢迎大家相互交流,共同提高技术. 2  t_generator类和t_generator_registry类 这个两 ...

  2. Thrift之TProcess类体系原理及源码详细解析

    我的新浪微博:http://weibo.com/freshairbrucewoo. 欢迎大家相互交流,共同提高技术. 之前对Thrift自动生成代码的实现细节做了详细的分析,下面进行处理层的实现做详细 ...

  3. thrift之TTransport类体系原理及源码详细解析1-类结构和抽象基类

    本章主要介绍Thrift的传输层功能的实现,传输的方式多种多样,可以采用压缩.分帧等,而这些功能的实现都是相互独立,和上一章介绍的协议类实现方式比较雷同,还是先看看这部分的类关系图,如下: 由上面的类 ...

  4. 并发编程(十五)——定时器 ScheduledThreadPoolExecutor 实现原理与源码深度解析

    在上一篇线程池的文章<并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)>中从ThreadPoolExecutor源码分析了其运行机制.限于篇幅,留下了Scheduled ...

  5. 并发编程(十二)—— Java 线程池 实现原理与源码深度解析 之 submit 方法 (二)

    在上一篇<并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)>中提到了线程池ThreadPoolExecutor的原理以及它的execute方法.这篇文章是接着上一篇文章 ...

  6. JVM CPU Profiler技术原理及源码深度解析

    研发人员在遇到线上报警或需要优化系统性能时,常常需要分析程序运行行为和性能瓶颈.Profiling技术是一种在应用运行时收集程序相关信息的动态分析手段,常用的JVM Profiler可以从多个方面对程 ...

  7. 并发编程(十三)—— Java 线程池 实现原理与源码深度解析 之 Executors(三)

    前两篇文章讲了线程池的源码分析,再来看这篇文章就比较简单了, 本文主要讲解 Executors 这个工具类,看看长江创建线程池的几种方法. newFixedThreadPool 生成一个固定大小的线程 ...

  8. 并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)

    史上最清晰的线程池源码分析 鼎鼎大名的线程池.不需要多说!!!!! 这篇博客深入分析 Java 中线程池的实现. 总览 下图是 java 线程池几个相关类的继承结构:    先简单说说这个继承结构,E ...

  9. Vue源码详细解析&colon;transclude&comma;compile&comma;link&comma;依赖&comma;批处理&period;&period;&period;一网打尽,全解析&excl;

    用了Vue很久了,最近决定系统性的看看Vue的源码,相信看源码的同学不在少数,但是看的时候却发现挺有难度,Vue虽然足够精简,但是怎么说现在也有10k行的代码量了,深入进去逐行查看的时候感觉内容庞杂并 ...

随机推荐

  1. Spring学习记录&lpar;四&rpar;---bean之间的关系:继承、依赖

         继承 这里说的继承和java的继承是不一样的,不是父类子类.但思想很相似,是父bean和子bean 1.父bean是一个实例时.它本身是一个完整的bean 2.父bean是模板,抽象bean ...

  2. android SDK 更新问题完美解决 http&colon;&sol;&sol;dl-ssl&period;google&period;com refused

    现在由于GWF,google基本和咱们说咱见了,就给现在在做Android  或者想学习Android 的朋友带来了诸多的不便,最简单的就是Android SDK Manager 你无法更新了. 现在 ...

  3. hadoop1&period;2&period;1的namenode格式化失败的问题

    最近要开始找工作,就在原来搭建好的hadoop1.2.1的伪分布式跑跑mapreduce 很久没用,就想着格式化一下namode,结果: Format aborted in /uar/local/ha ...

  4. 在Mac OS X中搭建STM32开发环境(2)

       本文原创于http://www.cnblogs.com/humaoxiao,非法转载者请自重!     在上一篇文章中,我们在OSX中编译了ST-Link2调试工具,并且简单的对其功能进行了测试 ...

  5. Linux 环境进程间通信&lpar;六&rpar;:

    linux下进程间通信的几种主要手段: 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允 ...

  6. 深入了解Android蓝牙Bluetooth——《进阶篇》

    在 [深入了解Android蓝牙Bluetooth--<基础篇>](http://blog.csdn.net/androidstarjack/article/details/6046846 ...

  7. JavaFX&lpar;Maven 方式&rpar;

    运行界面第一种方式 运行界面第二种方式 参考文献 https://github.com/AlmasB/JavaFX11-example

  8. SpringBoot入门笔记&lpar;二&rpar;、使用fastjson

    1.添加fastjson配置 <dependency> <groupId>com.alibaba</groupId> <artifactId>fastj ...

  9. Docker修改本地镜像与容器的存储位置

    这个方法里将通过软连接来实现. 首先停掉Docker服务: systemctl restart docker或者service docker stop 然后移动整个/var/lib/docker目录到 ...

  10. 2017-2018-1 20155330《信息安全技术》实验二——Windows口令破解

    2017-2018-1 20155330<信息安全技术>实验二--Windows口令破解 姓名:朱玥 学号:20155330 班级:201553 日期:2017.10.24 实验环境 系统 ...