上文介绍了代码生成过程,成功的从erlang抽象码生成了erlang源文件,抽象码的替换步骤很少,这主要得益于模板文件pokemon_pb.erl的设计。这里将继续分析pokemon_pb.erl的执行过程,从中学习它的编程技巧。
编码过程:
pokemon_pb.erl
encode_pikachu(Record) when is_record(Record, pikachu) ->
encode(pikachu, Record).
encode(pikachu, Record) ->
iolist_to_binary(iolist(pikachu, Record)).
iolist(pikachu, Record) ->
[pack(1, required, with_default(Record#pikachu.abc, none), string, [])].
主要的编码工作交给了pack函数,因此iolist函数仅仅需要保存message各个域的域编号,操作类型(required等),默认值,域类型(string等)。
pack(_, optional, undefined, _, _) -> [];
pack(_, repeated, undefined, _, _) -> [];
pack(_, repeated_packed, undefined, _, _) -> [];
pack(_, repeated_packed, [], _, _) -> [];
pack(FNum, required, undefined, Type, _) ->
exit({error, {required_field_is_undefined, FNum, Type}});
pack(_, repeated, [], _, Acc) ->
lists:reverse(Acc);
pack(FNum, repeated, [Head|Tail], Type, Acc) ->
pack(FNum, repeated, Tail, Type, [pack(FNum, optional, Head, Type, [])|Acc]);
pack(FNum, repeated_packed, Data, Type, _) ->
protobuffs:encode_packed(FNum, Data, Type);
pack(FNum, _, Data, _, _) when is_tuple(Data) ->
[RecName|_] = tuple_to_list(Data),
protobuffs:encode(FNum, encode(RecName, Data), bytes);
pack(FNum, _, Data, Type, _) when Type=:=bool;Type=:=int32;Type=:=uint32;
Type=:=int64;Type=:=uint64;Type=:=sint32;
Type=:=sint64;Type=:=fixed32;Type=:=sfixed32;
Type=:=fixed64;Type=:=sfixed64;Type=:=string;
Type=:=bytes;Type=:=float;Type=:=double ->
protobuffs:encode(FNum, Data, Type);
pack(FNum, _, Data, Type, _) when is_atom(Data) ->
protobuffs:encode(FNum, enum_to_int(Type,Data), enum).
enum_to_int(pikachu,value) ->
1.
pack函数有五个地方值得注意:
1.对于optional的域,其值存在与否都不会有影响;
2.对于required的域,若值不存在,则应该引发异常;
3.对于repeated的域,由于其值是一个列表,因此在编码时,应该对列表的每一个元素都进行pack;
4.对于内建类型,直接调用protobuffs:encode进行编码,protobuffs:encode编码方式是protocol buffers编码方式的erlang实现,分析起来也比较枯燥,读者可以自行查阅;
5.对于枚举类型,需要先通过enum_to_int将枚举类型替换为具体的值,这个函数也是通过抽象码替换过程生成的,message的每个枚举定义都有一个独立的值。
解码过程:
decode_pikachu(Bytes) when is_binary(Bytes) ->
decode(pikachu, Bytes).
decode(pikachu, Bytes) when is_binary(Bytes) ->
Types = [{1, abc, int32, []}, {2, def, double, []}],
Decoded = decode(Bytes, Types, []),
to_record(pikachu, Decoded).
decode/2保存了解码时遇到的每一个域的处理方法,这相当于一个简单的语法定义,具体的解析过程由decode/3完成。
decode(<<>>, _, Acc) -> Acc;
decode(Bytes, Types, Acc) ->
{ok, FNum} = protobuffs:next_field_num(Bytes),
case lists:keysearch(FNum, 1, Types) of
{value, {FNum, Name, Type, Opts}} ->
{Value1, Rest1} =
case lists:member(is_record, Opts) of
true ->
{{FNum, V}, R} = protobuffs:decode(Bytes, bytes),
RecVal = decode(list_to_atom(string:to_lower(atom_to_list(Type))), V),
{RecVal, R};
false ->
case lists:member(repeated_packed, Opts) of
true ->
{{FNum, V}, R} = protobuffs:decode_packed(Bytes, Type),
{V, R};
false ->
{{FNum, V}, R} = protobuffs:decode(Bytes, Type),
{unpack_value(V, Type), R}
end
end,
case lists:member(repeated, Opts) of
true ->
case lists:keytake(FNum, 1, Acc) of
{value, {FNum, Name, List}, Acc1} ->
decode(Rest1, Types, [{FNum, Name, lists:reverse([int_to_enum(Type,Value1) | lists:reverse(List)])} | Acc1]);
false ->
decode(Rest1, Types, [{FNum, Name, [int_to_enum(Type,Value1)]}|Acc])
end;
false ->
decode(Rest1, Types, [{FNum, Name, int_to_enum(Type,Value1)}|Acc])
end;
false ->
exit({error, {unexpected_field_index, FNum}})
end.
int_to_enum(_,Val) ->
Val.
这个函数是最大的一个函数,Types即是语法定义,而该函数相当于一个词法分析器,通过protobuffs:decode/2得到的每一个“符号”,都将根据Types的语法定义进行相应的动作:
1.对于嵌套定义的message,将进行递归类型解码;
2.对于repeated的域,将合并到原先找到的域的值列表中;
3.对于枚举类型,需要先通过int_to_enum将枚举值替换为具体的atom,这个函数也是通过抽象码替换过程生成的,message的每个枚举定义都有一个独立的atom。
4.对于标量类型,将直接记录到message的域值中。
最终,decode/3将生成一个列表,记录了每一个message的各个域,一个message的所有域放置在一个列表内。
decode(pikachu, Bytes) when is_binary(Bytes) ->
Types = [{1, abc, int32, []}, {2, def, double, []}],
Decoded = decode(Bytes, Types, []),
to_record(pikachu, Decoded).
to_record(pikachu, DecodedTuples) ->
lists:foldl(
fun({_FNum, Name, Val}, Record) ->
set_record_field(record_info(fields, pikachu), Record, Name, Val)
end, #pikachu{}, DecodedTuples).
set_record_field(Fields, Record, Field, Value) ->
Index = list_index(Field, Fields),
erlang:setelement(Index+1, Record, Value).
list_index(Target, List) -> list_index(Target, List, 1).
list_index(Target, [Target|_], Index) -> Index;
list_index(Target, [_|Tail], Index) -> list_index(Target, Tail, Index+1);
list_index(_, [], _) -> 0.
to_record是一个语义分析过程,根据Types定义,遍历decode/3生成的语法结构,提取每个message的每个域及其值的绑定关系,然后填充到具体的record中去,这个填充的过程也较为巧妙,由于record实质也是一个元组,to_record将遍历一个message的域值绑定列表,由于每一个域在proto文件中都有顺序编号,取出域值绑定关系时也就得到了域在message里面的未知,继而也就得到了域在record元组中的位置,set_record_field即用于填充元组指定字段的值。
至此,erlang的protocol buffers实现protobuffs就已经分析完毕了,其设计思路较为新颖,从中可以学到很多编程技巧,感兴趣的读者可以深挖并应用。
抽象码是连接erlang代码与erlang虚拟机代码的纽带,抽象码的执行过程即代表了虚拟机的执行过程,分析抽象码可以有效帮助我们理解erlang代码的执行方式、性能等,利用抽象码替换(可参考smerl的实现),可以实现erlang代码trace、profile插桩,帮助我们找到代码的bug,调试代码的性能。
分享到:
相关推荐
1. **protobuf安装与编译**:首先,你需要在Erlang项目中安装protobuf的Erlang库,这通常通过rebar3或erlang.mk等构建工具完成。接着,你需要为protobuf消息定义一个`.proto`文件,例如`test.proto`,其中包含数据...
**protobuf与Erlang** `protobuf`,全称Protocol Buffers,是Google开发的一种数据序列化协议,类似于XML和JSON,但更小巧、快速且高效。它允许开发者定义数据结构,然后生成相应的代码来轻松地读写这些数据,支持...
标题中的“改进erlang版的protobuf代码”指的是在Erlang编程语言中对Protocol Buffers(protobuf)进行了优化和改进的代码实现。Protocol Buffers是一种数据序列化协议,由Google开发,它允许开发者定义数据结构,...
**enif_protobuf** 是一个基于 **Erlang NIF (Native Implemented Functions)** 的库,用于集成 **Google Protobuf**,使得在Erlang环境中能够方便地处理和序列化protobuf消息。Erlang NIF是一种机制,允许Erlang...
Erlang Protobuffs是一个用于Erlang的协议缓冲区(Protocol Buffers)实现,它允许Erlang程序与使用Google Protobuf编译器生成的消息格式进行交互。Google Protobuf是一种高效的数据序列化协议,广泛应用于跨语言的...
这两个模型由丹麦工程师Agner Krarup Erlang在20世纪初提出,至今仍广泛应用于现代通信网络的设计与优化。 Erlang B模型,也称为无阻塞模型,主要用于计算在给定呼叫到达率和系统容量下,系统不发生阻塞的概率。它...
read/1与Erlang源文件(即带有.erl后缀的文件)和Erlang二进制文件(即带有.beam后缀的文件)一起使用。 下面的行将从Erlang的内部lists模块中读取表单。 forms : read ( lists ). 同样,以下行将从开发人员提供...
总结来说,这个话题涵盖了Erlang编程、DSL设计与实现、元编程以及业务逻辑的动态执行。通过深入研究这些内容,你将能够构建自己的Erlang DSL,从而提升软件的可读性和可维护性,更好地适应变化的业务需求。
BEAM是Erlang的虚拟机,它执行Erlang字节码,并负责管理进程、内存和垃圾回收。BEAM的设计考虑了并发和实时性,确保了Erlang程序的高效运行。 10. **Erlang生态系统** Erlang有丰富的开源库和工具,如 Cowboy...
听说google proto buffer(以下简称protobuf)已经很久了,但是一直没有尝试使用它。其中一个原因是,项目组自己写了个打包和解包的工具,而且代码也简单,可以很方便的扩展到自动生成xml之类的配置文件,已经能很好...
它负责解释Erlang字节码,提供内存管理、垃圾回收和并发调度等功能。 ### 10. 语言特性 Erlang的语法简洁,支持模式匹配、函数式编程、列表处理和递归等特性。它的动态类型系统和强大的类型推断让代码更加灵活。 ...
在Erlang中,与MySQL对接的关键在于选择合适的驱动程序,文件名为"erlang-mysql-driver"很可能是指一个Erlang的MySQL驱动库。这样的驱动库允许Erlang应用程序通过标准的数据库接口与MySQL进行通信。在Erlang中,这种...
3. **环境变量**:安装过程中,安装程序可能会自动添加Erlang的bin目录到系统的PATH环境变量中,确保命令行可以识别`erl`等Erlang命令。 4. **验证**:安装完成后,打开命令行窗口并输入`erl`,如果Erlang成功安装,...
这个过程可能需要一些时间,因为它会编译整个Erlang虚拟机和标准库。编译完成后,你可以通过运行`make tests`来验证Erlang的正确性。 5. **安装**:最后,使用`make install`将编译好的Erlang二进制文件安装到你的...
【标题】: 使用rebar构建Erlang项目与发布流程详解 【描述】: 本文将详细介绍如何利用rebar工具来开发和发布Erlang工程,包括项目结构、配置文件的编写以及具体操作步骤。 【标签】: Erlang, rebar, 项目开发, ...
**RabbitMQ与Erlang版本匹配指南** RabbitMQ是一种广泛使用的开源消息代理和队列服务器,它基于Erlang编程语言构建。Erlang以其并发能力、容错性和分布式特性而闻名,是实现RabbitMQ的理想选择。正确地匹配RabbitMQ...
标题中的"C#与Erlang的群通信"指的是在编程领域中,使用C#和Erlang两种不同的编程语言进行集群或分布式系统间的通信。这两种语言各有特点,C#是微软开发的面向对象的编程语言,常用于Windows平台的开发,而Erlang则...
- **官方资源**: 通过阅读《Programming Erlang》这本书来深入了解 Erlang 的语法和特性,虽然书中某些部分可能较为抽象或难以理解,但它是了解 Erlang 的重要途径。 - **实践项目**: 结合实际案例进行练习,比如...
Erlang的下载过程中,你可以考虑以下几点: 1. 检查网络连接:确保网络稳定且速度足够。 2. 使用代理:如果你在公司或特定区域,可能需要通过代理服务器访问外部资源。 3. 更换下载时间:避开网络高峰期进行下载。 4...
3. **过程和模块**:Erlang的组织方式,包括如何定义和调用函数,以及模块的使用。 4. **错误调试**:Erlang的错误处理机制,如shell的使用、日志和调试工具。 5. **REPL(Read-Eval-Print Loop)**:Erlang shell...