用python自动生成代码

来源:互联网 发布:淘宝权在哪里直播现在 编辑:程序博客网 时间:2024/05/29 19:41

工作需要,要实现几十种事件的参数编辑界面,事件的数据结构如下:

// TsatEventstruct 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的同学就不要直接拷代码了。

作为一个职业程序员的基本素养,动手编码之前当前要先设计一下,主要功能如下:

  1. 文件的读写是最基本的,可以直接复用已有函数
  2. 从C++的头文件读取类的信息,包括类的名称和基类的名称,所有的类信息应该组织为列表,以保证顺序
  3. 从C++的头文件读取类中成员变量的信息,包括成员变量的类型和名称,成员变量的信息应该组织在相应的类信息下面,这实际上就是几十个类元数据信息
  4. 根据类的元数据信息结合代码模板自动生成相应的代码

好了,可以开工了,打开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 = nameclass 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 // TsatEventTsatEventThreePhaseFaultAtBus : 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。还有人说过,人生苦短,我用正则表达式。这两个东西这里都用到了,算是够怕死了吧,呵呵。

0 1
原创粉丝点击