juggle dsl语法介绍及codegen浅析
juggle语法规范如下:
类型:
bool -> in cpp bool int -> in cpp int64 float -> in cpp double string -> in cpp std::string array -> in cpp std::vector struct -> in cpp object
函数的定义则同c语言:void rpctest1(int argv1, bool argv2, string argv3, float argv4, array<int> argv5);
整体的juggle语法如下:
module juggle{ void rpctest1(int argv1, bool argv2, string argv3, float argv4, array<int> argv5); void rpctest2(int argv1, bool argv2, string argv3, float argv4, array<int> argv5); }
其中module对应c++中的class,并且在服务器端会被codegen实现为一个单件,无需用户定义句柄有codegen生成对应的create代码。
codegen会依据module中函数定义,生成如下代码:
#include <juggle.h> class juggle: public module{ public: juggle() : module(ch, juggleuuid::UUID()){ _service_handle->register_module_method(juggle_rpctest1,boost::bind(&juggle::call_rpctest1, this, _1)); _service_handle->register_module_method(juggle_rpctest2,boost::bind(&juggle::call_rpctest2, this, _1)); } ~juggle(){ } virtual void rpctest1(int64_t argv1,bool argv2,std::string argv3,double argv4,std::vector<int64_t> argv5) = 0; void call_rpctest1(boost::shared_ptr<channel> ch, boost::shared_ptr<object> v){ auto argv1 = (*v)["argv1"].asint(); auto argv2 = (*v)["argv2"].asbool(); auto argv3 = (*v)["argv3"].asstring(); auto argv4 = (*v)["argv4"].asfloat(); std::vector<int64_t> argv5; for(int i = 0; i < (*v)["argv5"].size(); i++){ v.push_back((*v)["argv5"][i].asint()); } auto ret = rpctest1(argv1, argv2, argv3, argv4, argv5); boost::shared_ptr<object> r = boost::make_shared<object>(); (*r)["suuid"] = (*v)["suuid"]; (*r)["method"] = (*value)["method"]; (*r)["ret"] = ret; ch->push(r); } virtual void rpctest2(int64_t argv1,bool argv2,std::string argv3,double argv4,std::vector<int64_t> argv5) = 0; void call_rpctest2(boost::shared_ptr<channel> ch, boost::shared_ptr<object> v){ auto argv1 = (*v)["argv1"].asint(); auto argv2 = (*v)["argv2"].asbool(); auto argv3 = (*v)["argv3"].asstring(); auto argv4 = (*v)["argv4"].asfloat(); std::vector<int64_t> argv5; for(int i = 0; i < (*v)["argv5"].size(); i++){ v.push_back((*v)["argv5"][i].asint()); } auto ret = rpctest2(argv1, argv2, argv3, argv4, argv5); boost::shared_ptr<object> r = boost::make_shared<object>(); (*r)["suuid"] = (*v)["suuid"]; (*r)["method"] = (*value)["method"]; (*r)["ret"] = ret; ch->push(r);
} };
可以看到,codegen实现了网络层面的消息响应、协议pack/unpack以及对rpc函数的调用,返回值封包发送的代码。用户只需要继承module并实现对应的rpc函数。
其中对于obejct的定义见 https://github.com/NetEase/fossilizid/blob/master/juggle/interface/object.h
我定义了一个纯虚类,用于规范一个通信协议参数入栈和访问的接口
然后定义了一个channel https://github.com/NetEase/fossilizid/blob/master/juggle/interface/channel.h
用于规范通信的接口
对于通信而言,push/pop是非常上层的一个接口,但是这样的设计目的在于提供一个宽泛的抽象,这里通信的可以是一个消息队列,一个基于共享内存的本地跨进程通信,同样也可以是socket。
btw:另一个原因是我自己封装的网络库的长相是这样的 https://github.com/NetEase/fossilizid/tree/master/remoteq, remotoq提供的通信句柄正是channel,而提供的访问接口则是push/pop。并且通过模板参数配置了网络协议的pack/unpack。我这么实现是为了方便代码复用。
然后是对dsl语言的编译:
juggle的语法定义的关键字,除了变量类型,就只有module和struct。对于一个module的定义,在module之后是是这个module的命名,之后是'{'表示此module定义开始,至'}'表示此module定义结束。module的分析代码如下:
class module(object): def __init__(self): self.keyworld = '' self.name = '' self.module = [] self.machine = None def push(self, ch): if ch == '}': self.machine = None return True if self.machine is not None: if self.machine.push(ch): self.module.append(self.machine.func) self.machine.clear() else: if ch == '{': self.name = deleteNoneSpacelstrip(self.keyworld) self.keyworld = '' self.machine = func() return False self.keyworld += ch return False
在检索到'{'之后开始对module定义的分析,至'}'结束这个module的定义。
因为dsl语言本身的特性,module中只有函数定义,struct中变量定义。所以在module中,只需要分析函数定义。
self.machine = func(),对函数分析器的定义如下:
class func(object): def __init__(self): self.keyworld = '' self.func = [] self.argvtuple = None def clear(self): self.keyworld = '' self.func = [] self.argvtuple = None def push(self, ch): if ch == ' ' or ch == '\0': self.keyworld = deleteNoneSpacelstrip(self.keyworld) if self.keyworld != '': if self.argvtuple is None: self.func.append(self.keyworld) else: self.argvtuple.append(self.keyworld) self.keyworld = '' return False if ch == ',': if self.keyworld != '': self.argvtuple.append(deleteNoneSpacelstrip(self.keyworld)) self.func.append(self.argvtuple) self.keyworld = '' self.argvtuple = [] return False if ch == '(': self.func.append(deleteNoneSpacelstrip(self.keyworld)) self.argvtuple = [] self.keyworld = '' return False if ch == ')': if self.keyworld != '': self.argvtuple.append(deleteNoneSpacelstrip(self.keyworld)) self.func.append(self.argvtuple) self.keyworld = '' return False if ch == ';': return True self.keyworld += ch return False
因为无需考虑其他的语法要素的区分,函数定义的分析只需要考虑依次提取返回值类型,函数名,(,参数定义,),;函数定义结束。符号表示如下:
rettype funcname(argvlist...);
之后是对struct的分析,与module类似,在struct之后的既是struct name的定义,之后是'{'开始struct的定义,之'}'结束此struct的定义,代码如下:
class struct(object): def __init__(self): self.keyworld = '' self.name = '' self.struct = [] self.argvdef = [] def push(self, ch): if ch == ' ' or ch == '\0': if self.keyworld != '': self.argvdef.append(self.keyworld) if ch == '{': self.name = deleteNoneSpacelstrip(self.keyworld) self.keyworld = '' return False if ch == ';': self.struct.append(self.argvdef) self.argvdef = [] if ch == '}': return True self.keyworld += ch return False
对于struct中的变量定义,同样以'type name;'的方式直接分割。
之后是对jeggle文件的整体分析:
class statemachine(object): Moduledefine = 0 Funcdefine = 1 def __init__(self): self.keyworld = '' self.module = {} self.struct = {} self.machine = None def push(self, ch): if self.machine is not None: if self.machine.push(ch): if isinstance(self.machine, module): self.module[self.machine.name] = self.machine.module self.machine = None if isinstance(self.machine, struct): self.struct[self.machine.name] = self.machine.struct self.machine = None else: self.keyworld += ch if self.keyworld == 'module': self.machine = module() self.keyworld = '' if self.keyworld == 'struct': self.machine = struct() self.keyworld = '' def getmodule(self): return self.module def getstruct(self): return self.struct def syntaxanalysis(self, genfilestr): for str in genfilestr: for ch in str: self.push(ch)
检索到module和struct之后分别进入对应分支。
之后是codegen的代码见:
https://github.com/NetEase/fossilizid/blob/master/juggle/rpcmake/codegen.py
和之前的http://www.cnblogs.com/qianqians/p/4184441.html对比可以看到精简之后的dsl语法要方便分析许多,实作代码也要清晰不少。
和之前为c++添加rpccall的计划相比,现在的dsl语言便于提供其他语言的扩展,同时编译器也会好些很多。
btw:现在的dsl语法非常之强类型,尤其是带模板参数的array<int>,有用过protobuf和thrift的同学应该可以对比去其中的区别,希望大家能对如何设计一个好用的dsl展开讨论。