上文介绍了rds_la.proto生成的头文件rds_la_pb.hrl和源文件rds_la_pb.erl的内容,这里将继续分析生成它们的原理。
首先看看protoc-erl的内容:
#!/usr/bin/env escript
%% -*- erlang -*-
%%! -sasl errlog_type error -boot start_sasl -noshell
main ([File]) ->
protobuffs_compile:generate_source (File);
main (_) ->
io:format ("usage: ~s <protofile>~n",
[filename:basename (escript:script_name())]),
halt (1).
这是一个escript,调用核心函数protobuffs_compile:generate_source/1生成文件。
protobuffs_compile.erl
generate_source(ProtoFile,Options) when is_list (ProtoFile) ->
Basename = filename:basename(ProtoFile, ".proto") ++ "_pb",
{ok,FirstParsed} = parse(ProtoFile),
ImportPaths = ["./", "src/" | proplists:get_value(imports_dir, Options, [])],
Parsed = parse_imports(FirstParsed, ImportPaths),
Collected = collect_full_messages(Parsed),
Messages = resolve_types(Collected#collected.msg,Collected#collected.enum),
output_source (Basename, Messages, Collected#collected.enum, Options).
generate_source首先计算生成文件的basename,即在proto文件的basename后加上"_pb",然后开始一个parse过程:
parse(FileName) ->
{ok, InFile} = file:open(FileName, [read]),
Acc = loop(InFile,[]),
file:close(InFile),
protobuffs_parser:parse(Acc).
loop(InFile,Acc) ->
case io:request(InFile,{get_until,prompt,protobuffs_scanner,token,[1]}) of
{ok,Token,_EndLine} ->
loop(InFile,Acc ++ [Token]);
{error,token} ->
exit(scanning_error);
{eof,_} ->
Acc
end.
可以看出,这里利用io:request读取proto文件的内容,读取时使用protobuffs_scanner:token/1进行词法分析,得到token后,交由protobuffs_parser:parse/1进行语法分析。
protobuffs_scanner与protobuffs_parser分别由leex和yecc生成,因此可以直接分析其对应的protobuffs_scanner.xrl和protobuffs_parser.yrl文件。
protobuffs_scanner.xrl
Definitions.
L = [A-Za-z_\.]
D = [0-9]
F = (\+|-)?[0-9]+\.[0-9]+((E|e)(\+|-)?[0-9]+)?
HEX = 0x[0-9]+
WS = ([\000-\s]|%.*)
S = [\(\)\]\[\{\};=]
Rules.
{L}({L}|{D})* : {token, {var, TokenLine,list_to_atom(TokenChars)}}.
'({L}|{D})+' : S = strip(TokenChars,TokenLen),
{token,{string,TokenLine,S}}.
"({L}|{D}|/)+" : S = strip(TokenChars,TokenLen),
{token,{string,TokenLine,S}}.
{S} : {token, {list_to_atom(TokenChars),TokenLine}}.
{WS}+ : skip_token.
//.* : skip_token.
/\*([^\*]|\*[^/])*\*/ : skip_token.
{D}+ : {token, {integer, TokenLine, list_to_integer(TokenChars)}}.
{F} : {token, {float, TokenLine, list_to_float(TokenChars)}}.
{HEX} : {token, {integer, TokenLine, hex_to_int(TokenChars)}}.
...
可以看出://开头和/* */内部是注释,()[]{};=都将作为原子,'...'和"..."都将作为字符串,以[A-Za-z_\.]开头的[A-Za-z_\.]|[0-9]的字符串作为标识符,其余的读者可自行分析,值得注意的是,标识符都将被转换为atom。
protobuffs_parser.yrl
Nonterminals
g_protobuffs g_header g_message g_rpcs g_rpc g_element g_elements g_var g_value g_default.
Terminals ';' '=' '{' '}' '[' ']' '(' ')' string integer float var.
Rootsymbol g_protobuffs.
Endsymbol '$end'.
g_protobuffs -> '$empty' : [].
g_protobuffs -> g_header g_protobuffs : ['$1'|'$2'].
g_protobuffs -> g_message g_protobuffs : ['$1'|'$2'].
g_header -> g_var string ';' : {'$1', unwrap('$2')}.
g_header -> g_var g_var ';' : {'$1', safe_string('$2')}.
g_header -> g_var g_var '=' g_value ';' : {'$1', '$2', '$4'}.
g_message -> g_var g_var '{' g_elements '}' : {'$1', safe_string('$2'), '$4'}.
g_message -> g_var g_var '{' g_rpcs '}' : {'$1', safe_string('$2'), '$4'}.
g_rpcs -> g_rpc : ['$1'].
g_rpcs -> g_rpc g_rpcs : ['$1' | '$2'].
g_rpc -> g_var g_var '(' g_var ')' g_var '(' g_var ')' ';' : {'$1', safe_string('$2'), safe_string('$4'),
safe_string('$8')}.
g_elements -> g_element : ['$1'].
g_elements -> g_element g_elements : ['$1' | '$2'].
g_element -> g_var g_var g_var '=' integer g_default ';' : {unwrap('$5'),
pack_repeated('$1','$6'),
safe_string('$2'),
safe_string('$3'),
default('$6')}.
g_element -> g_var '=' integer ';' : {'$1', unwrap('$3')}.
g_element -> g_var integer g_var integer ';' : {'$1', unwrap('$2'), unwrap('$4')}.
g_element -> g_var integer g_var g_var ';' : {'$1', unwrap('$2'), '$4'}.
g_element -> g_var g_var '=' g_value ';' : {'$1', '$2', '$4'}.
g_element -> g_message : '$1'.
g_var -> var : unwrap('$1').
g_value -> g_var : '$1'.
g_value -> integer : unwrap('$1').
g_value -> string : unwrap('$1').
g_value -> float : unwrap('$1').
g_default -> '$empty' : none.
g_default -> '[' g_var '=' g_value ']' : {'$2', '$4'}.
safe_string(A) -> make_safe(atom_to_list(A)).
reserved_words() ->
["after", "and", "andalso", "band", "begin", "bnot", "bor", "bsl", "bsr", "bxor", "case", "catch", "cond", "div", "end", "fun",
"if", "let", "not", "of", "or", "orelse", "query", "receive", "rem", "try", "when", "xor"].
make_safe(String) ->
case lists:any(fun(Elem) -> string:equal(String,Elem) end, reserved_words()) of
true -> "pb_"++String;
false -> String
end.
unwrap({_,_,V}) -> V;
unwrap({V,_}) -> V.
default({default,D}) ->
D;
default(_) ->
none.
pack_repeated(repeated,{packed,true}) ->
repeated_packed;
pack_repeated(Type,_) ->
Type.
parse的语法定义与通常的yacc/yecc定义有点不同,因为它的语法定义中没有包含任何通过标识符定义的终结符,仅仅通过()[]{}=;来区分不同的语法,诸如message等protocol buffers的保留字,一概没有出现,而是直接转换成了atom,由将来的语义分析过程进行处理。
今天的主角是message,因此着重观察与它相关的语法,类似于
required string name = 1;
optional int32 id = 2;
repeated int64 ts = 3;
等的语法,都将被
g_element -> g_var g_var g_var '=' integer g_default ';' : {unwrap('$5'),
pack_repeated('$1','$6'),
safe_string('$2'),
safe_string('$3'),
default('$6')}.
匹配,生成一条记录FieldRecord = {FieldID,required/optional/repeated/repeated_packed,FieldType,FieldName,DefaultValue},
并最终归并到message体中,生成message的原始语法树:
[Message1 = {message,MessageName,[FieldRecord1,FieldRecord2,...,FieldRecordn]},Message2,...,Messagen]。
protobuffs_compile.erl。
此时需要注意两点:
1任何proto文件中的保留字,如message、import、package等,都已经被转换为atom了,将来可以直接使用;
2如果用户定义的标识符"Identifier"与erlang的保留字冲突,则将被替换为"pb_Identifier",la_record的query域便与erlang的query保留字冲突,因此被替换成了pb_query。
generate_source(ProtoFile,Options) when is_list (ProtoFile) ->
Basename = filename:basename(ProtoFile, ".proto") ++ "_pb",
{ok,FirstParsed} = parse(ProtoFile),
ImportPaths = ["./", "src/" | proplists:get_value(imports_dir, Options, [])],
Parsed = parse_imports(FirstParsed, ImportPaths),
Collected = collect_full_messages(Parsed),
Messages = resolve_types(Collected#collected.msg,Collected#collected.enum),
output_source (Basename, Messages, Collected#collected.enum, Options).
parse_imports(Parsed, Path) ->
parse_imports(Parsed, Path, []).
parse_imports([], _Path, Acc) ->
lists:reverse(Acc);
parse_imports([{import, File} = Head | Tail], Path, Acc) ->
case file:path_open(Path, File, [read]) of
{ok, F, Fullname} ->
file:close(F),
{ok,FirstParsed} = parse(Fullname),
Parsed = lists:append(FirstParsed, Tail),
parse_imports(Parsed, Path, [Head | Acc]);
{error, Error} ->
error_logger:error_report([
"Could not do import",
{import, File},
{error, Error},
{path, Path}
]),
parse_imports(Tail, Path, [Head | Acc])
end;
parse_imports([Head | Tail], Path, Acc) ->
parse_imports(Tail, Path, [Head | Acc]).
在parse完目标proto文件后,转而对proto文件中的任何import声明进行处理,对import指明的文件也进行parse,然后合并到已经parse的语法树中。
至此proto文件的scan和parse过程也分析完了,此处已经收集到了所有proto文件及其import文件的语法树。
未完待续...
分享到:
相关推荐
标题中的“在erlang项目中使用protobuf例子”指的是在Erlang编程环境中使用Protocol Buffers(protobuf)这一数据序列化工具。protobuf是由Google开发的一种高效、跨语言的数据表示和序列化格式,它允许开发者定义...
标题中的“改进erlang版的protobuf代码”指的是在Erlang编程语言中对Protocol Buffers(protobuf)进行了优化和改进的代码实现。Protocol Buffers是一种数据序列化协议,由Google开发,它允许开发者定义数据结构,...
**protobuf与Erlang** `protobuf`,全称Protocol Buffers,是Google开发的一种数据序列化协议,类似于XML和JSON,但更小巧、快速且高效。它允许开发者定义数据结构,然后生成相应的代码来轻松地读写这些数据,支持...
**enif_protobuf** 是一个基于 **Erlang NIF (Native Implemented Functions)** 的库,用于集成 **Google Protobuf**,使得在Erlang环境中能够方便地处理和序列化protobuf消息。Erlang NIF是一种机制,允许Erlang...
Erlang Protobuffs是一个用于Erlang的协议缓冲区(Protocol Buffers)实现,它允许Erlang程序与使用Google Protobuf编译器生成的消息格式进行交互。Google Protobuf是一种高效的数据序列化协议,广泛应用于跨语言的...
虽然描述是空的,但我们可以推断出这个过程可能涉及下载Erlang的语法规则文件(如`.stx`文件),然后在EditPlus中配置这些规则,以便在编辑Erlang代码时能够享受到语法高亮、代码折叠等特性,从而提高编程效率和代码...
《远古封神》与《英雄远征》是两款受欢迎的网络游戏,它们的后端服务器采用了ERLANG这一编程语言来构建。ERLANG是一种为并发、分布式和容错系统设计的函数式编程语言,因其在实时系统和大规模并发处理中的优秀性能而...
Erlang深度分析所包含的内容非常广泛,涵盖了从Erlang虚拟机的工作原理、性能优化、最佳编程实践,到最新的Erlang版本中新增特性的介绍。Erlang语言因其独特的并发模型、高效的分布式计算能力和灵活的网络编程接口,...
1. **准备新版本**:编写并编译新的Erlang模块代码,生成`.beam`文件。 2. **加载新代码**:使用`code:load_file/1`函数加载新版本的模块到代码服务器,但此时旧代码仍然在运行。 3. **检查类型签名**:Erlang会对比...
本项目提供了一个使用Erlang编写的聊天室服务器端代码以及Java编写的客户端代码,这为我们深入理解Erlang的并发特性和Java与Erlang的交互提供了实践案例。 一、Erlang聊天室服务器端 1. 并发处理:Erlang的轻量级...
read/1与Erlang源文件(即带有.erl后缀的文件)和Erlang二进制文件(即带有.beam后缀的文件)一起使用。 下面的行将从Erlang的内部lists模块中读取表单。 forms : read ( lists ). 同样,以下行将从开发人员提供...
其中一个原因是,项目组自己写了个打包和解包的工具,而且代码也简单,可以很方便的扩展到自动生成xml之类的配置文件,已经能很好的符合项目的需要。但是最近发现protobuf有个很不错的功能,就是可以向已有的协议中...
BEAM是Erlang的字节码解释器,模拟器可以用于开发阶段模拟运行和测试Erlang代码,有助于调试程序和优化性能。 #### 7. 内存管理 Erlang的内存管理是非常高效的,它使用了一种特殊的垃圾回收机制来确保低延迟。了解...
### Erlang深度分析知识点概述 #### 1. Erlang虚拟机(VM)分析 - **概念**: Erlang VM,也称为BEAM (Bytecode for the Erlang Abstract Machine),是Erlang语言的基础运行环境。 - **特性**: - **轻量级进程**: ...
《英雄远征》是一款基于Erlang编程语言开发的网络游戏,其源码的公开为开发者提供了深入了解游戏服务端架构和Erlang在大型分布式系统应用的机会。Erlang是一种为并发、容错和实时系统设计的函数式编程语言,以其在高...
"源码"和"工具"这两个标签暗示了PPT可能涵盖了Erlang的源代码解析以及它作为开发工具的使用方法。源码部分可能涉及Erlang的模块结构、函数定义、并发处理机制等;而“工具”可能涉及Erlang的开发环境如Eshell、REPL...
Erlang语言的语法有其独特性,它的并发编程风格与传统语言不同,需要一定的学习成本。 在并发模型方面,Go语言引入了goroutine,它是一种轻量级线程,可以通过channel进行通信。这种模型非常适合在现代多核处理器上...
12. **Erlang模拟器工作原理分析**:深入理解BEAM模拟器的工作原理有助于编写更高效、更稳定的Erlang代码,书中对此进行了详细的理论和实践分析。 通过以上内容,读者可以全面了解Erlang的高级特性和实战技巧,无论...
erlang otp in action 代码
二、Erlang热代码替换 热代码替换是Erlang的一项核心特性,允许在不中断运行服务的情况下更新应用程序代码。它分为三个阶段:停止旧进程、加载新代码和启动新进程。 1. **加载新代码**:使用`code:load_file/1`或`...