注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

gmd20的个人空间

// 编程和生活

 
 
 

日志

 
 

Google Protocol Buffer 的complier代码阅读  

2012-09-20 14:52:31|  分类: 程序设计 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |


Google Protocl buffer不像 Facebook 开源的Thrift,Thrift是使用 flex 和 Bison 来实现 idl的解析和代码生成的。 protocol buffer不依赖这类的第三方库,完全是自己从头写了相应的解析和代

码生成的代码。 因为我不怎么懂 “编译原理”,不知道这类东西都是怎么去弄的,就大概浏览了一下compiler部分的代码。



关键代码生成路径
============
google\protobuf\compiler
main() ->
CommandLineInterface.run()
(1) 调用 Parser来解析文件,得到一个 FileDescriptor的内存里面树型结构。
-> Importer.Import()
- > Parser.Parse()
-> FileDescriptorProto
(2) 把上一步得到的FileDescriptor 传给generator,generator根据FileDescriptor 信息生成最后的头文件和c文件
generator.Generate(FileDescriptor, 需要输出的文件名)




1. 各种各样的Descriptor
================
描述proto文件里面支持的各种类型。 解析器解析proto文件,把解析出来的内容关联在一起,构建一个有详细描述信息的内存结构。

protobuf-2.4.1\src\google\protobuf\ 目录下的 descriptor.h descriptor.cc descriptor.proto

// Defined in this file.
class Descriptor;
class FieldDescriptor; // message最终的一个 属性 ?
class EnumDescriptor;
class EnumValueDescriptor;
class ServiceDescriptor;
class MethodDescriptor;
class FileDescriptor; /// 一个 proto文件对应一个 FileDescriptor, 下面的子类型什么的应该是用 树结构关联起来。
class DescriptorDatabase;
class DescriptorPool; // 统一管理?

// Defined in descriptor.proto
class DescriptorProto;
class FieldDescriptorProto;
class EnumDescriptorProto;
class EnumValueDescriptorProto;
class ServiceDescriptorProto;
class MethodDescriptorProto;
class FileDescriptorProto;
class MessageOptions;
class FieldOptions;
class EnumOptions;
class EnumValueOptions;
class ServiceOptions;
class MethodOptions;
class FileOptions;
class UninterpretedOption;


2. Importer
=======
负责 文件读取,文件依赖等逻辑,调用Parser 去实际解析文件。



3. Parser
=======
在 io::Tokenizer 的基础上面工作,


bool Parser::ParseMessageStatement(DescriptorProto* message,
const LocationRecorder& message_location) {
if (TryConsume(";")) {
// empty statement; ignore
return true;
} else if (LookingAt("message")) {
LocationRecorder location(message_location,
DescriptorProto::kNestedTypeFieldNumber,
message->nested_type_size());
return ParseMessageDefinition(message->add_nested_type(), location);
} else if (LookingAt("enum")) { /// 碰到 enum字符串
LocationRecorder location(message_location,
DescriptorProto::kEnumTypeFieldNumber,
message->enum_type_size());
return ParseEnumDefinition(message->add_enum_type(), location); //调用 ParseEnumDefinition 去解析
} else if (LookingAt("extensions")) {


-----------------------------------------

bool Parser::ParseEnumDefinition(EnumDescriptorProto* enum_type, //解析出来的结构放到(EnumDescriptorProto* 里面去
const LocationRecorder& enum_location) {
DO(Consume("enum")); //消费掉 enum字符

{
LocationRecorder location(enum_location,
EnumDescriptorProto::kNameFieldNumber);
location.RecordLegacyLocation(
enum_type, DescriptorPool::ErrorCollector::NAME);
DO(ConsumeIdentifier(enum_type->mutable_name(), "Expected enum name.")); // 解析 enum的变量的名字。
}

DO(ParseEnumBlock(enum_type, enum_location)); // 解析剩下的 enum 定义块
return true;
}
--------------------------

bool Parser::ParseEnumBlock(EnumDescriptorProto* enum_type,
const LocationRecorder& enum_location) {
DO(Consume("{")); /// 消费了 开始的大括号

while (!TryConsume("}")) { ///匹配 大括号
if (AtEnd()) {
AddError("Reached end of input in enum definition (missing '}')."); 提示错误
return false;
}

if (!ParseEnumStatement(enum_type, enum_location)) { ParseEnumStatement 继续展开,解析 enum的常量定义
// This statement failed to parse. Skip it, but keep looping to parse
// other statements.
SkipStatement();
}
}

return true;
}

后面继续有
ParseEnumStatement -》 ParseEnumConstant -》 ParseEnumConstantOptions
等函数。
在 parser.cc 里面,解析同一种类型的函数都放在一块。比如 解析 message 结构的有

ParseMessageDefinition
ParseMessageBlock
ParseMessageStatement
ParseMessageField
ParseFieldOptions
等等

Parser使用的两个辅助类
LocationRecorder
io::Tokenizer::Token
通过这两个来访问文件,不直接操作文件内容。



4. 代码生成,以 c++ 代码生成为例
======================
protobuf-2.4.1\src\google\protobuf\compiler\cpp


文件命名很好啊,总的 代码生成器 应该在这里定义。
cpp_generator.h
cpp_generator.cc


各种类型、结构啊对应的代码生成,比如
cpp_generator.cc
cpp_message.cc 对应Message
cpp_message_field.h 对应 Message的子成员
cpp_message_field.cc
cpp_enum_field.cc
cpp_enum_field.h
cpp_field.cc
cpp_field.h
cpp_string_field.h
cpp_string_field.cc
cpp_file.cc 对应 FileDescriptor 类型
cpp_file.h


---------------------------
bool CppGenerator::Generate(const FileDescriptor* file, //上一步解析出来的类型信息全部在这里了。
const string& parameter,
GeneratorContext* generator_context,
string* error) const {



string basename = StripProto(file->name());
basename.append(".pb");

FileGenerator file_generator(file, dllexport_decl);

// Generate header.
{
scoped_ptr<io::ZeroCopyOutputStream> output(
generator_context->Open(basename + ".h"));
io::Printer printer(output.get(), '$');
file_generator.GenerateHeader(&printer); ///调用 file_generator 来根据 FileDescriptor 生成头文件
}

// Generate cc file.
{
scoped_ptr<io::ZeroCopyOutputStream> output(
generator_context->Open(basename + ".cc"));
io::Printer printer(output.get(), '$');
file_generator.GenerateSource(&printer); ///调用 file_generator 来根据 FileDescriptor 生成源文件
}

return true;
}

--------------------------
FileGenerator的构造函数里面,为自己子类型 建立 相应的代码生成器对象,比如
for (int i = 0; i < file->message_type_count(); i++) {
message_generators_[i].reset(
new MessageGenerator(file->message_type(i), dllexport_decl));
}



void FileGenerator::GenerateHeader(io::Printer* printer)
void FileGenerator::GenerateSource(io::Printer* printer) {

遍历自己包含的 所有子成员, 调用子成员对应的类,生成代码,比如

// Generate classes.
for (int i = 0; i < file_->message_type_count(); i++) {
printer->Print("\n");
printer->Print(kThickSeparator);
printer->Print("\n");
message_generators_[i]->GenerateClassMethods(printer); ///展开 ,继续 调用Message generator来生成对应类的代码
}




--------------------------
下面的就是具体的代码生成了,比如 message_generators 生成类定义的, 在cpp_message.cc 文件里面有很多辅助函数,和io::Printer类的帮助。


void MessageGenerator::
GenerateClassDefinition(io::Printer* printer) {
for (int i = 0; i < descriptor_->nested_type_count(); i++) {
nested_generators_[i]->GenerateClassDefinition(printer);
printer->Print("\n");
printer->Print(kThinSeparator);
printer->Print("\n");
}

map<string, string> vars;
vars["classname"] = classname_;
vars["field_count"] = SimpleItoa(descriptor_->field_count());
if (dllexport_decl_.empty()) {
vars["dllexport"] = "";
} else {
vars["dllexport"] = dllexport_decl_ + " ";
}
vars["superclass"] = SuperClassName(descriptor_);

printer->Print(vars,
"class $dllexport$$classname$ : public $superclass$ {\n"
" public:\n");
printer->Indent();

printer->Print(vars,
"$classname$();\n"
"virtual ~$classname$();\n"
"\n"
"$classname$(const $classname$& from);\n"
"\n"
"inline $classname$& operator=(const $classname$& from) {\n"
" CopyFrom(from);\n"
" return *this;\n"
"}\n"
"\n");


--------------------------------------------------------------------------





5. 总结
====
看起来,弄一个简单的 proto文件的代码生成器,看起来也不是那么的复杂。

Descriptor 为每种类型定义一个Descriptor,然后构建一个内存里面的描述结构,通过树一样模型关联起来。
Tokenizer 词法解析器 ??
Parser 解析 Tokenizer 流, 构建Descriptor 树。
Generator 根据Descriptor 树,生成代码。






=========================================


2012/09/25 补充

大概浏览了一下 apache thrift的代码框架
http://svn.apache.org/viewvc/thrift/trunk/compiler/cpp/src/parse/
http://svn.apache.org/viewvc/thrift/trunk/compiler/cpp/src/generate/t_cpp_generator.cc?view=markup

除去了Bison 的应用,会辅助解析代码,保存到 一个 “paser table” 便于解析之外,其他的代码和 google protocol buffer的compiler的代码是很类似的,用不同的类型类表示 相应的类型,组织一个 内存里面的管理结构“树型结构”,然后代码生成部分代码根据这个“内存表示”结构为每种类型生成代码。

定义了
t_base_type.h 基类
t_enum.h 表示 idl 里面 enum
t_field.h
t_struct.h
t_function.h
等类型,


定义了相对应的生成函数
generate_typedef
generate_enum
generate_struct
等等

上次微博看人提到《C++ 沉思录》 上面 表达式计算的例子, 看了一下,模型也是比较类似的,可以参考学习一下。


======================================

2012 09 28 补充


复习了一下 编译原理,以前在学校时自学过,大概瞄过两眼,不过都忘了。在网上找了这本书,又大概看了一下
"现代编译原理--C语言描述。Appel,普林斯顿.pdf "

前面提到的内存里面的树型结构,其实术其实叫做 "抽象语法分析树", 参考书的 ”第4 章抽象语法“, 利用flex和bison是可以帮助生成 ”抽象语法树“的。只要给bison定义相关的语法和嵌入的c嵌入代码,就可以让bison在解析相应的节点的时候,调用你相关的代码,生成 ”抽象语法分析树“。 书里面大概讲了一下。

不过网上有人写了一个更好的例子,教你怎么用 flex bison还有llvm来实现自定义语言的编译器。对生成抽象语法树的,然后根据抽象语法树的生成汇编代码的全过程都做了讲解。非常不错的例子,先看一下书了解一下相关概念在来看看这个很不错。估计学计算机的同学,课程作业的时候都弄过这样的例子了吧。

使用Flex Bison 和LLVM编写自己的编译器
http://coolshell.cn/articles/1547.html

英文原文
Writing Your Own Toy Compiler Using Flex, Bison and LLVM
http://gnuu.org/2009/09/18/writing-your-own-toy-compiler/

例子带的源码都在github上面 https://github.com/lsegal/my_toy_compiler



  评论这张
 
阅读(1442)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017