在分布式系统开发或者跨语言数据交互场景中,自定义消息定义是常用的数据描述方式,手动编写对应的C++结构体不仅繁琐还容易出错,使用Lark解析自定义消息定义并生成C++结构体可以高效解决这个问题。

自定义消息定义格式设计
首先需要明确自定义消息定义的语法规则,这里我们设计一种简单的消息定义格式,支持消息名称、字段类型、字段名称的定义,示例如下:
message UserInfo {
int32 id
string name
float score
}
上述格式中,message是消息关键字,后面跟着消息名称,花括号内是字段列表,每个字段由类型、名称组成,每行一个字段。
Lark语法规则编写
Lark支持使用EBNF风格的语法规则定义解析逻辑,我们需要根据自定义消息格式编写对应的语法规则:
from lark import Lark
# 定义Lark语法规则
message_grammar = r"""
start: message_def+
message_def: "message" ID "{" field_list "}"
field_list: field*
field: TYPE ID
ID: /[a-zA-Z_][a-zA-Z0-9_]*/
TYPE: "int32" | "string" | "float" | "double" | "int64"
%import common.WS
%ignore WS
"""
上述语法规则中,start是根规则,支持多个消息定义;message_def匹配单个消息的完整结构;field匹配单个字段;ID和TYPE是终端规则,分别匹配标识符和字段类型。
解析自定义消息定义
使用编写好的语法规则初始化Lark解析器,然后解析自定义消息文本,得到结构化的解析树:
# 初始化Lark解析器
parser = Lark(message_grammar, parser="lalr")
# 待解析的自定义消息文本
message_text = """
message UserInfo {
int32 id
string name
float score
}
message OrderInfo {
int64 order_id
double price
string goods_name
}
"""
# 解析得到解析树
parse_tree = parser.parse(message_text)
遍历解析树生成C++结构体
解析树是嵌套的结构,我们需要编写遍历逻辑,提取消息名称和字段信息,然后拼接成C++结构体代码:
from lark import Transformer
class MessageTransformer(Transformer):
def start(self, items):
# 拼接所有消息的结构体代码
return "n".join(items)
def message_def(self, items):
msg_name = items[0]
fields = items[1]
# 拼接结构体头部
struct_code = f"struct {msg_name} {{n"
# 拼接字段
for field_type, field_name in fields:
# 类型映射:自定义类型转C++类型
type_map = {
"int32": "int32_t",
"int64": "int64_t",
"string": "std::string",
"float": "float",
"double": "double"
}
cpp_type = type_map.get(field_type, field_type)
struct_code += f" {cpp_type} {field_name};n"
struct_code += "};n"
return struct_code
def field_list(self, items):
return items
def field(self, items):
return (items[0], items[1])
def ID(self, token):
return str(token)
def TYPE(self, token):
return str(token)
# 使用Transformer遍历解析树生成代码
transformer = MessageTransformer()
cpp_code = transformer.transform(parse_tree)
print(cpp_code)
完整示例代码与输出结果
将上述逻辑整合为完整可运行的脚本,执行后可以得到生成的C++结构体代码:
from lark import Lark, Transformer
# 语法规则
message_grammar = r"""
start: message_def+
message_def: "message" ID "{" field_list "}"
field_list: field*
field: TYPE ID
ID: /[a-zA-Z_][a-zA-Z0-9_]*/
TYPE: "int32" | "string" | "float" | "double" | "int64"
%import common.WS
%ignore WS
"""
# Transformer类
class MessageTransformer(Transformer):
def start(self, items):
return "n".join(items)
def message_def(self, items):
msg_name = items[0]
fields = items[1]
struct_code = f"struct {msg_name} {{n"
type_map = {
"int32": "int32_t",
"int64": "int64_t",
"string": "std::string",
"float": "float",
"double": "double"
}
for field_type, field_name in fields:
cpp_type = type_map.get(field_type, field_type)
struct_code += f" {cpp_type} {field_name};n"
struct_code += "};n"
return struct_code
def field_list(self, items):
return items
def field(self, items):
return (items[0], items[1])
def ID(self, token):
return str(token)
def TYPE(self, token):
return str(token)
# 主逻辑
if __name__ == "__main__":
parser = Lark(message_grammar, parser="lalr")
message_text = """
message UserInfo {
int32 id
string name
float score
}
message OrderInfo {
int64 order_id
double price
string goods_name
}
"""
parse_tree = parser.parse(message_text)
transformer = MessageTransformer()
cpp_code = transformer.transform(parse_tree)
print("生成的C++结构体代码:")
print(cpp_code)
执行上述代码后,输出的C++结构体代码如下:
struct UserInfo {
int32_t id;
std::string name;
float score;
};
struct OrderInfo {
int64_t order_id;
double price;
std::string goods_name;
};
扩展优化方向
上述实现是基础版本,还可以根据需求进行扩展:
- 支持更多字段类型,比如数组、嵌套消息等,只需要在语法规则和类型映射中添加对应逻辑即可
- 添加字段默认值、注释的解析支持,丰富生成的结构体功能
- 增加代码输出到文件的功能,直接将生成的C++代码写入头文件,方便项目集成
- 添加语法错误校验,当自定义消息定义不符合规则时给出明确的错误提示