用python自动生成代码
如何用用python解析简单的C++的头文件提取类的元数据,并据此结合模板自动生成代码。这里给出了一个简单的工作实例,希望对有相关需求的同学可以有所帮助。
·
工作需要,要实现几十种事件的参数编辑界面,事件的数据结构如下:
// TsatEvent
struct TSATDATASHARED_EXPORT TsatContingencyEvent
{
virtual ~TsatContingencyEvent() {}
virtual TsatContingencyEvent* clone() const = 0;
virtual QString desc() const = 0;
};
// 母线三相短路
struct TSATDATASHARED_EXPORT TsatEventThreePhaseFaultAtBus : public TsatContingencyEvent
{
QString bus;
virtual TsatContingencyEvent* clone() const;
virtual QString desc() const;
};
// 线路三相短路
struct TSATDATASHARED_EXPORT TsatEventThreePhaseFaultOnLine : public TsatContingencyEvent
{
TsatEventThreePhaseFaultOnLine()
:distance(0)
{
}
QString fromBus;
QString toBus;
double distance;
virtual TsatContingencyEvent* clone() const;
virtual QString desc() const;
};
......
如何相关的编辑界面,那是另一个话题,光是这几十个类的成员函数和xml文件的读写就够写吐血的了。难度不是问题,问题是太烦了,作为一个有着多年偷懒经验的老程序员,自然很快想到为了代码自动生成。那就用可爱的python来搞吧,原因很简单,一是这玩意不需要编译;二是这玩意有文本处理的利器正则表达式。
友情提示,这里是基于Python3.4版本的,还在用Python2的同学就不要直接拷代码了。
作为一个职业程序员的基本素养,动手编码之前当前要先设计一下,主要功能如下:
- 文件的读写是最基本的,可以直接复用已有函数
- 从C++的头文件读取类的信息,包括类的名称和基类的名称,所有的类信息应该组织为列表,以保证顺序
- 从C++的头文件读取类中成员变量的信息,包括成员变量的类型和名称,成员变量的信息应该组织在相应的类信息下面,这实际上就是几十个类元数据信息
- 根据类的元数据信息结合代码模板自动生成相应的代码
好了,可以开工了,打开PyCharm,建立相关的py文件。当然,如果你愿意用文本编辑工具,也随便你。
先把python的文件读写函数拿过来吧,这没啥好说的:
def loadArray(path, records, encoding):
"""加载文件到列表"""
try:
file = open(path, "r", encoding=encoding) # open file in read mode
except IOError as message: # file open failed
print("read file error({0}:{1})".format(message, path))
sys.exit(1)
lines = file.readlines()
for line in lines:
records.append(line)
file.close()
def saveArray(path, records, encoding):
"""保存文件从列表"""
try:
file = open(path, "w", encoding=encoding) # open file in write mode
except IOError as message: # file open failed
print("write file error({0}:{1})".format(message, path))
sys.exit(1)
file.writelines(records)
file.close()
这里没有把头文件中的文字作为一个整理进行处理,而是分解为一行行的处理,效率当然会低一些,但是后面的处理可以更自由一些
然后定义两个类,分别代表类的元数据和类成员函数的元数据,也非常简单:
class Var:
def __init__(self, type, name):
self.type = type
self.name = name
class Class:
def __init__(self, name, desc, base):
self.name = name
self.desc = desc
self.base = base
self.vars = []
def appendVar(self, type, name):
var = Var(type, name)
self.vars.append(var)
下面就是重点了,定义cpp文件解析的类:
class ClassLib:
def __init__(self):
self.classes = []
self.classMaps = {}
其中classes存储类的元数据列表,classMap是为了搞一个从类名快速检索类元数据的结构,只是为了检索方便
既然要分析C++的头文件文件,自然要用到正则表达式了,因为这几个正则表达式只需要定义一次,就在类的构造函数中定义吧,当然更好一点的做法是做成属于类的静态变量,而不是属于对象的成员变量,不过也无所谓了,这个ClassLib基本上和单件也没啥区别的
reg = """
^ #开头
(struct|class) #捕获类或者结构的关键字
\s+ #空格
[\w_]+ #捕获导出标记
\s+ #空格
(\w+) #类名
(.*) #或许有基类,注释什么的
$ #结束
"""
self.classReg = re.compile(reg, re.VERBOSE)
reg = """
\s*:\s* #空格:空格
(public|private|protected) #捕获派生的关键字
\s+ #空格
(\w+) #基类
"""
self.baseClassReg = re.compile(reg, re.VERBOSE)
reg = """
^ #开头
\s+ #空格
(\w+) #变量类型
\s+ #空格
(\w+); #变量名称
.* #或许有注释什么的
$ #结束
"""
self.dataReg = re.compile(reg, re.VERBOSE)
reg = """
^ #开头
// #//
\s* #空格
(.+) #注释
$ #结束
"""
self.commentReg = re.compile(reg, re.VERBOSE)
这些正则表达式都没啥技术含量,基本上都是简单粗暴的直接翻译,注释也写得很清楚。唯一需要说明的是这里把捕获类名和捕获基类名分成了两个部分,因为
TsatContingencyEvent是没有基类的
下面就开始解析C++的头文件文件吧: def parse(self, lines):
object = None
comment = ""
for line in lines:
m = re.match(self.commentReg, line)
if m:
comment = m.group(1)
continue
m = re.match(self.classReg, line)
if m:
name = m.group(2)
base = None
if m.lastindex >= 3:
mm = re.match(self.baseClassReg, m.group(3))
if mm:
base = self.classMaps[mm.group(2)]
object = Class(name, comment, base)
self.classes.append(object)
self.classMaps[name] = object
continue
m = re.match(self.dataReg, line)
if m:
type = m.group(1)
name = m.group(2)
object.appendVar(type, name)
代码很简单,注释都没必要写,就是发现是注释行则保存注释;发现是类定义行则捕获类名和基类名,然后生成类的元数据;发现时类的成员变量行则捕获成员变量的类型和名称,然后加入到类的元数据中
解析出来的类的元数据基本上就是一个简单的两层树形结构,再写一个print函数吧,方便测试和调试:
def print(self):
for object in self.classes:
base = "None"
if object.base != None:
base = object.base.name
print("{0} : {1} // {2}".format(object.name, base, object.desc))
for var in object.vars:
print("\t{0} {1};".format(var.type, var.name))
好了,可以看主函数了:
srcPath = 'src.cpp'
srcs = []
loadArray(srcPath, srcs, 'utf-8')
classLib = ClassLib()
classLib.parse(srcs)
classLib.print()
没啥可说的,打印出来的结果是:
TsatContingencyEvent : None // TsatEvent
TsatEventThreePhaseFaultAtBus : TsatContingencyEvent // 母线三相短路
QString bus;
TsatEventThreePhaseFaultOnLine : TsatContingencyEvent // 线路三相短路
QString fromBus;
QString toBus;
double distance;
......
好了,可以开始第4步了,根据类的元数据信息结合代码模板自动生成相应的代码:
def writeCpp(classLib, path):
dsts = []
cpp = """
TsatContingencyEvent* $name$::clone() const
{
$name$ *ret = new $name$;
*ret = *this;
return ret;
}
QString $name$::desc() const
{
return QString();
}
"""
for object in classLib.classes:
if object.base == None:
continue
base = object.base.name
dsts.append(cpp.replace("$name$", object.name))
saveArray(path, dsts, 'utf-8')
基本上就是一个关键字替换,当然具体的模板可以随便写,所以也不要纠结于为什么我这里的desc函数是直接返回QString()了,因为自动生成代码也不是万能的,不要为了全自动生成代码而增加一堆额外的工作,除非是一个系统的基础数据结构,后面需要不断的被自动化,那还可以考虑,至于这里的小工具嘛,就算了吧。
调用一下吧:
writeCpp(classLib, 'dst.cpp')
自动生成的代码是这个样子的:
TsatContingencyEvent* TsatEventThreePhaseFaultAtBus::clone() const
{
TsatEventThreePhaseFaultAtBus *ret = new TsatEventThreePhaseFaultAtBus;
*ret = *this;
return ret;
}
QString TsatEventThreePhaseFaultAtBus::desc() const
{
return QString();
}
TsatContingencyEvent* TsatEventThreePhaseFaultOnLine::clone() const
{
TsatEventThreePhaseFaultOnLine *ret = new TsatEventThreePhaseFaultOnLine;
*ret = *this;
return ret;
}
QString TsatEventThreePhaseFaultOnLine::desc() const
{
return QString();
}
......
当然了,有的同学看得比较仔细,会一针见血的指出这个例子只用到类名嘛,那基类和类的成员函数的元数据根本没用到嘛。好吧,那就再举一个例子:
def writeFiler(classLib, path):
dsts = []
cpp = """
template<typename Filer>
inline void filerElem(Filer& filer, $name$* data, QDomElement& elem)
{
filerElem(filer, dynamic_cast<$base$*>(data), elem);
$vars$
}
"""
for object in classLib.classes:
if object.base == None:
continue
vars = []
for var in object.vars:
type = var.type.title()
if var.type == 'QString':
type = 'String'
vars.append('\tfiler.attribute{0}(elem, data->{1}, "{1}");'.format(type, var.name))
filer = cpp
filer = filer.replace('$name$', object.name)
filer = filer.replace('$base$', object.base.name)
filer = filer.replace('$vars$', "\n".join(vars)[1:])
dsts.append(filer)
saveArray(path, dsts, 'utf-8')
其实就是多了一层循环,多替换一些关键字而已,也没啥难度。
再调用一下吧:
writeFiler(classLib, 'filer.cpp')
这次自动生成的代码是这个样子的:
template<typename Filer>
inline void filerElem(Filer& filer, TsatEventRemoveLine* data, QDomElement& elem)
{
filerElem(filer, dynamic_cast<TsatContingencyEvent*>(data), elem);
filer.attributeString(elem, data->fromBus, "fromBus");
filer.attributeString(elem, data->toBus, "toBus");
}
template<typename Filer>
inline void filerElem(Filer& filer, TsatEventDisconnectGenerator* data, QDomElement& elem)
{
filerElem(filer, dynamic_cast<TsatContingencyEvent*>(data), elem);
filer.attributeString(elem, data->bus, "bus");
}
......
好了,怎么用python自动生成代码差多不也就这样了,没啥难度,但确实节约时间。我记得有人说过:珍爱生命,我用python。还有人说过,人生苦短,我用正则表达式。这两个东西这里都用到了,算是够怕死了吧,呵呵。
更多推荐
已为社区贡献1条内容
所有评论(0)