Thrift之代码⽣成器Compiler原理及源码详细解析1
欢迎⼤家相互交流,共同提⾼技术。
⼜很久没有写博客了,最近忙着研究GlusterFS,本来周末打算写⼏篇博客的,但是由于调试GlusterFS的⼀些新增功能就⽤了整整的⼀天,还有⼀天就陪⽼婆⼤⼈逛街去了!今晚浏览完微博发现时间还早就来博客⼀篇,本篇博客内容主要是前⼀段时间研究的Thrift的代码⽣成器的源码详细分析,没有具体分析语法解析,因为是⼯具字段⽣成的代码,⼈是没有办法阅读的----到处都是跳转表!由于Thrift⽀持N多种语⾔,但是⽣成代码原理都差不多,我主要分析了C++相关代码⽣成。关于Thrift的使⽤及原理、代码⽹上基本上都有,代码的注释很好,基本上都是英⽂注释。下⾯就是我之前分析写的⽂档,希望对学习使⽤代码⽣成代码的爱好者有⼀定帮助。
Thrift之代码⽣成器Compiler原理及源码详细解析1
这个功能是⼀个单独的⼯具程序,它会独⽴的⽣成⼀个可执⾏⽂件。
第⼀节类关系图
本节主要展⽰了这个部分的整体类图,然后对这个类图做了简要的说明,有了这个类图让我在阅读这个部分源代码时不会不到⽅向,让我更加清楚这个部分中的类是怎样协同⼯作的,类关系图如下所⽰:
注意:实线代表继承关系;⽽虚线代表依赖关系。
由类关系图可以看出Compiler功能模块主要分为两个部分,⼀个是图的右边展⽰了各种语⾔代码⽣成的类。它们的共同基类都是
t_generator类,但是⾯向对象的语⾔并不是直接从它继承,⽽是⼜把⾯向对象的共同特性提取出来⽤⼀个类来实现,这个类就是
t_oop_generator,其他⾯向对象语⾔的⽣成类都是从这个类继承。总基类的实现是依赖于左边的t_pro
gram类,这个类表⽰⼀个程序代码需要的所有特征和要素。左边部分就是解决⼀个程序需要拥有的数据类型和函数,根据接⼝定义语⾔(IDL)解析和⽣成相应的数据和函数等。左边部分就显⽰thrift定义的中间语⾔(IDL)能够⽀持的数据类型,t_type类是所有数据类型类的基类。
第⼆节程序流程图
这个部分整体的流程图如下图所⽰:
以上流程图简要的说明了Compiler运⾏的⼀个过程,通过这个流程图可以让我们知道了根据中间定义语⾔⽣成各种程序代码的整体思路和流程。下⼀节将根据源代码详细分析整个过程的原理及实现⽅案,这个⾥⾯涉及到⼀些编译原理的知识,不深⼊分析这⼀部分,它⾥⾯的词法分析程序是⽤linux上的⼯具flex⾃动⽣成c语⾔程序,解析中间定义语⾔的时候直接调⽤yyparse函数即可。另⼀部分重点内容就是⽣成各种程序语⾔代码的功能,每⼀种语⾔⽤⼀个类来⽣成,后⾯不会详细分析每⼀种语⾔的⽣成实现的代码,主要分析三种主流语⾔(C++、java、python)。
第三节源代码详细分析
1 ⽂件
这个⽂件是这个部分的⼊⼝⽂件,因为它定义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的对象来代表整个程序的解析树,接着根据⽂件名到⽂件所在的⽬录并设置包含⽂件的⽬录,最后初始化⼀些全局变量(为这些变量分别内存资源),中间还设置了⽣成代码输出的路径。
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
thrift29 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);
后然调⽤解析函数和代码⽣成函数如下:
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就可以得到。词法分析的时候可能发⽣异常,所以需要处理异常。实现代码如下:
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);
分析出内嵌的IDL⽂件以后就对这些IDL⽂件进⾏递归调⽤parse函数来对每⼀个IDL⽂件进⾏词法分析,因为每⼀个IDL⽂件⾥⾯可能包括多个IDL⽂件,所以需要⽤⼀个for循环对没有⼀个IDL都进⾏递归词法分析,具体实现如下:
1 vector<t_program*>& includes = program->get_includes();
2
3 vector<t_program*>::iterator iter;
4
5 for (iter = includes.begin(); iter != d(); ++iter) {
6
7 parse(*iter, program);
8
9 }
最后⼀部分是重点,将进⾏第⼆次词法分析,这次分析就是真正的对IDL⽂件中定义的数据类型和服务(函数)进⾏词法分析和语法分析,所以⾸先需要设置词法分析的模式为PROGRAM。还需要初始化⼀些全局变量,和第⼀次词法分析⼀样需要打开IDL⽂件为词法分析程序提供分析源、异常处理和最后关闭⽂件,实现的主要代码如下:
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);
到此这个函数完成了所有⾃⼰的功能,所有词法分析的结果都保存到program中了,同时g_program也保存同样的⼀份内容,以便后⾯的⽣成代码函数使⽤。
(3)generate函数
本函数主要功能就是根据parse函数⽣成的各种数据类型和服务⽣成各种代码⽂件。⾸先递归调⽤每⼀个内嵌的t_program来⽣成代码,实现代码如下:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论