
本教程旨在详细阐述如何利用#%#$#%@%@%$#%$#%#%#$%@_23eeeb4347bdd26bfc++6b7ee9a3b755dd的lark库解析自定义消息定义文件,并通过lark的interpreter功能,结合python f-string模板,自动化生成c++语言的消息结构体代码。通过构建领域特定语言(dsl)的语法规则、实现语法解析器以及定义代码生成逻辑,开发者可以高效地从简洁的消息定义中生成复杂的c++样板代码,显著提升开发效率并减少手动编写重复代码的工作量。
在无线通信协议或嵌入式系统开发中,定义消息结构体往往涉及大量的重复性代码编写,例如为每个消息创建对应的C++结构体、定义成员变量、构造函数以及ID等。为了解决这一痛点,我们可以借鉴ROS/ROS2等框架的思路,通过定义一种简洁的消息描述语言(DSL),然后利用Lark这样的解析器生成器工具,自动将这些描述转换为目标语言(如C++)的代码。
首先,我们需要设计一个简洁明了的消息定义文件格式。本例中,我们采用以下格式来定义一个消息:
name TWIST id 123 float variableone float variabletwo
这个示例定义了一个名为 TWIST 的消息,其ID为 123,包含两个浮点型成员变量 variableone 和 variabletwo。这种格式清晰地表达了消息的关键信息:消息名称、ID以及其成员的类型和名称。
Lark是一个强大的Python解析器生成器,支持EBNF(扩展巴科斯范式)语法。我们将使用Lark来解析上述自定义消息定义文件。以下是用于解析的Lark语法定义:
立即学习“Python免费学习笔记(深入)”;
from lark import Lark message_grammar = """ start: message+ // 一个文件可以包含一个或多个消息定义 message: msgname msgid member+ // 一个消息定义包含名称、ID和至少一个成员 msgname: "name" MSG_NAME // 消息名称以"name"关键字开头 msgid: "id" MSG_ID // 消息ID以"id"关键字开头 member: DATATYPE MEMBER_NAME // 成员定义包含数据类型和成员名称 DATATYPE: "float"|"int"|"bool" // 支持的数据类型 MSG_NAME: WORD // 消息名称由单词组成 MEMBER_NAME: WORD // 成员名称由单词组成 MSG_ID: INT // 消息ID为整数 %import common (INT, WORD, WS) // 导入Lark内置的常用规则:整数、单词、空白符 %ignore WS // 忽略空白符 """ # 创建Lark解析器实例 parser = Lark(message_grammar)
语法规则说明:
在生成C++代码之前,我们需要定义一个C++结构体的模板。Python的f-string非常适合用于这种简单的模板需求。
ctemplate = """
struct {name} {{
{name}(const Packet&);
static constexpr const int id={id};
{cmembers}
}};
"""这个模板定义了一个C++结构体的基本框架。其中,{name}、{id} 和 {cmembers} 是占位符,将在代码生成阶段被实际数据填充。{cmembers} 将用于插入所有成员变量的定义。注意,这里将 id 定义为 static constexpr const int,这是一种更现代和高效的C++常量定义方式。
Lark解析器会将输入文本转换为一个抽象语法树(AST)。为了从这个AST中提取信息并生成C++代码,我们可以使用Lark提供的 Interpreter 模式。Interpreter 允许我们遍历AST,并在访问每个节点时执行自定义逻辑。
from lark.visitors import Interpreter
class CGen(Interpreter):
def __init__(self):
super().__init__()
self.generated_sources = [] # 用于存储所有生成的消息代码
def start(self, tree):
# 遍历所有消息定义
self.visit_children(tree)
def message(self, tree):
# 初始化当前消息的数据结构
self.current_msg = { "members": {} }
# 访问子节点以填充 current_msg
self.visit_children(tree)
# 处理完一个消息后,生成其C++代码并添加到列表中
self.generated_sources.append(CGen._process_message(self.current_msg))
@staticmethod
def _process_message(msg_data):
# 拼接成员变量的C++定义
members_str = ""
for _name, _type in msg_data["members"].items():
if members_str:
members_str += "\n " # 换行并缩进
members_str += f"{_type} {_name};"
msg_data["cmembers"] = members_str # 将拼接好的成员字符串添加到数据中
# 使用模板和收集到的数据生成C++代码
return ctemplate.format(**msg_data)
def msgname(self, tree):
# 提取消息名称
self.current_msg["name"] = tree.children[0].value
def msgid(self, tree):
# 提取消息ID,并转换为整数
self.current_msg["id"] = int(tree.children[0].value)
def member(self, tree):
# 提取成员的数据类型和名称
member_type = None
member_name = None
for child in tree.children:
if child.type == 'DATATYPE':
member_type = child.value
if child.type == 'MEMBER_NAME':
member_name = child.value
# 将成员添加到当前消息的成员字典中
if member_name and member_type:
self.current_msg["members"][member_name] = member_type
CGen 类说明:
现在,我们将所有部分整合起来,演示如何解析一个消息定义并生成对应的C++代码。
# 示例消息定义文件内容
example_msg_content = """
name TWIST
id 123
float variableone
float variabletwo
name STATUS
id 456
bool is_active
int error_code
"""
# 使用Lark解析器解析消息定义内容
parse_tree = parser.parse(example_msg_content)
# 实例化CGen解释器并访问解析树
cgen = CGen()
cgen.visit(parse_tree)
# 打印所有生成的C++代码
for source_code in cgen.generated_sources:
print(source_code)
输出结果:
struct TWIST {
TWIST(const Packet&);
static constexpr const int id=123;
float variableone;
float variabletwo;
};
struct STATUS {
STATUS(const Packet&);
static constexpr const int id=456;
bool is_active;
int error_code;
};通过Lark和Python的结合,我们成功地构建了一个自动化代码生成工具,能够将自定义的简洁消息定义转换为结构化的C++代码。这种方法极大地减少了样板代码的编写,提高了开发效率,并使得消息定义的管理更加集中和标准化。这不仅适用于C++,同样可以推广到其他需要从DSL生成代码的场景。
以上就是基于Lark和Python自动生成C++消息定义结构的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号