PyMacroParser 宏解析工具
发布日期:2021-09-14 15:33:23 浏览次数:3 分类:技术文章

本文共 7174 字,大约阅读时间需要 23 分钟。

PyMacroParser 宏解析工具


PyMarcoParser宏解析工具

题目要求

题目描述

假定有CPP 源码文件(.cpp) 仅有如下内容

  1. 包含C/C++ 风格的注释: // 及 /**/
  2. 包含空白字符
  3. 只包含 #ifdef/#ifndef/#else/#endif/#define/#undef 这几个宏指令的应用
  4. #define 定义仅有如下定义情况:
    #define identifier token-stringopt
  5. 其中 token-stringopt 只有兼容如下几种C/C++指定基本类型常量表示内容:整型,浮点,布尔(true, false),字符(忽略宽字符),字符串,及各上述基本类型组成的聚合。
  6. 聚合:结构体 或者 数组初始化,均可用聚合初始化形式{}表示,聚合类型可以多维(嵌套).
  7. 除如上指定内容及适当的空格制表符换行等等等,不再有其他内容。不考虑字符映射及三元组序列, 不考虑行拼接
    注释,空白字符,常量,和字符串的类型定义请参考如下相关
    聚合初始化参考如下网址中关于聚合初始化的
    预处理器相关参考如下,特别注意 “转换阶段” 与 “预处理器指令”

有如上特点的CPP源码文件,为了实现与Python脚本兼容对接,

请基于Python 2.7.X 封装实现一个可重用的类,要求如下:

  1. 该类能读取 .cpp文件中的宏定义,并接收可变化的预定义宏串, 并根据两者,解析出当前所有的可用的宏定义。全部字符串都是ANSI编码。
  2. 可用宏定义可转为Python字典模式输出。 其中宏名转为字符串作为字典key, 若有与宏对应的常量定义转为对应python数据类型后作为字典的value。 类型对应关系见附表。若无任何常量则value为None
  3. 可用宏定义可再次导出成为只含有当前宏定义的CPP源文件。
  4. 请遵循CPP宏及常量类型的定义标准,确保相同常量变换后数值上保持一致,类型上与Python保持兼容(由于python转换过程中可能会损失掉C的具体类型信息,比如char 可能最终变为 int,具体表示方法也会有所变化,比如16进制最终表示为10进制, 故多次转换后能保持最终常量的值相等即可。 类型转换标准见“注意”)
  5. 只允许使用Python内置模块(如sys、math)和string模块,不允许使用其他标准模块及任何第三方开发库(包括但不限于re),不要使用 evel/exec 懒人解析, 不要使用copy;deepcopy等懒人复制。
  6. 独立完成作业,并代码中给予必要的注释。
  7. 代码采用UTF-8编码

该类的类名要求为PyMacroParser,所提供的方法约定如下:

  1. load(self, f) 从指定文件中读取CPP宏定义,存为python内部数据,以备进一步解析使用。 f为文件路径,文件操作失败抛出异常;
    无返回值。若在初步解析中遇到宏定义格式错误 或 常量类型数据定义错误应该抛出异常。
  2. preDefine(self, s) 输入一堆预定义宏名串,宏名与宏名之间以”;” 分割。
    比如串"mcname1;mcname2"相当于把
    #define mcname1
    #define mcname2
    加在了CPP宏数据的最前面。
    而空串"" 表示没有任何预定义宏。 显然,预定义宏会影响对CPP文件数据内的可用宏解析。
    preDefine函数可被反复调用,每次调用自动清理掉之前的预定义宏序列。 preDefine 与 load的CPP宏定义数据,一起决定最终可用的宏。
  3. dumpDict(self) 返回一个dict, 结合类中存储的CPP宏定义与预定义的宏序列,解析输出所有的可用宏到一个字典,其中宏名转为字符串后作为字典的key, 若有与宏名对应的常量转为python数据对象,无常量则存为None, 注意不要返回类中内置的对象的引用。 解析过程若遇到宏定义格式错误 或 常量类型数据定义错误应该抛出异常;
  4. dump(self, f) 结合类中的CPP宏定义数据与预定义宏序列,解析输出所有可用宏存储到新的CPP源文件,f为CPP文件路径,文件若存在则覆盖,文件操作失败抛出异常。 若遇到宏定义格式错误 或 常量类型数据定义错误应该抛出异常。 注意,转换后的常量数据表示应与Python对应类型兼容, 所以常量类型的长度存储信息可能丢失(例如 short 转为 int; float 转为 double 等), 允许特别表示方法信息丢失(例如原本16进制 统一变成10进制表示等)。 导出宏的顺序不做特别要求。

示例

Input

假设源文件 a.cpp 中有如下内容

#ifndef MCTEST#define MCTEST#ifdef MC1#define data1 0x20/*cmment start*/#define /*this is comment*/ data2 2.5f#define data3 L"this is a data"#define data4 true#ifdef MC2#define data5 'a'#define data6 { {2.0, "abc"}, {1.5, "def"}, {5.6f, "7.2"}} // 浮点与字符串组成的结构体初始化聚合, 再进一步聚合组成了数组#else#define data5 {5.0, 7.5, 3.8}#define data6 'c'#endif //end MC2#else#define data1 1.0f /* this is floatmay be changed*/#define data2 2#define data3 false#define data4 "this is a data"#ifdef MC2#define data5 'B'#define data6 {1, 6, 3}#define data7 0xa#else#define data5 'D'#define data6 {1, 6}#endif //end MC2#endif //MC1#ifdef MC2#undef MC2#endif#endif // !MC_TEST

python 测试代码:

a1 = PyMacroParser()a2 = PyMacroParser()a1.load("a.cpp")filename = "b.cpp"a1.dump(filename) #没有预定义宏的情况下,dump cppa2.load(filename)a2.dumpDict()a1.preDefine("MC1;MC2") #指定预定义宏,再dumpa1.dumpDict()a1.dump("c.cpp")

Output

则b.cpp输出

#define data1 1.0 //浮点精度信息消失,统一转成了double 正式输出没有这个注释#define data2 2#define data3 false#define data4 "this is a data"#define data5 68 //注意:这里本是'D' 转换后成为整型十进制表示,正式输出没有这个注释#define data6 {1, 6}#define MCTEST //空宏,但是被定义了, 正式输出没有这个注释

a2.dump字典

{
"data1" : 1.0,"data2" : 2,"data3" : False,"data4" : "this is a data","data5" : 68,"data6" : (1, 6),"MCTEST" : None, #空宏,但被定义了。 正式输出没有这个注释}

a1.dump字典:

{
"data1" : 32,"data2" : 2.5, #2.5f的float标记消失,正式输出没有这个注释"data3" : u"this is a data", #宽字符串成为 unicode 正式输出没有这个注释"data4" : True,"data5" : 97, #注意 这里是'a'转int。 正式输出没有这个注释"data6" : ((2.0, "abc"), (1.5, "def"), (5.6, "7.2")) , #python数据对象与源数据类型按规则对应即可, 正式输出没有这个注释"MC1" : None, #预定义的空宏,而MC2最终被undef了,所以不存在MC2"MCTEST" : None,}

c.cpp 输出

#define data1 32 //16进制表示消失。 正式输出没有这个注释#define data2 2.5#define data3 L"this is a data" //unicode 转回宽字符 正式输出没有这个注释#define data4 true#define data5 97 //'a', 正式输出没有这个注释#define data6 {
{2.0, "abc"}, {1.5, "def"}, {5.6, "7.2"}} #tuple转回聚合, 正式输出没有这个注释#define MC1#define MCTEST

解题思路

1.load函数

功能说明:load(self, f) 从指定文件中读取CPP宏定义,存为python内部数据,以备进一步解析使用。 f为文件路径,文件操作失败抛出异常;无返回值。若在初步解析中遇到宏定义格式错误 或 常量类型数据定义错误应该抛出异常。

需求分析:读取cpp宏定义,存为python内部数据

解题思路:按行读取cpp文件,去除不必要的换行符、Tab、转义、注释,留下有用的宏定义。将宏定义用函数进行解析。解析函数应当对宏定义进行分割和处理。并保存为python的List数据。

宏的定义一般是2个词,或3个词,中间空格分隔,常见的思路是split,这里有个小坑,如果定义的是字符串,字符串中间有空格,这样split会把第三个词割裂了。

关键函数:去除换行符、去除Tab、去除注释、处理转义字符、解析宏定义并保存。

2.preDefine函数

功能说明: preDefine(self, s) 输入一堆预定义宏名串,宏名与宏名之间以; 分割。 preDefine函数可被反复调用,每次调用自动清理掉之前的预定义宏序列。 preDefine 与 load的CPP宏定义数据,一起决定最终可用的宏。

需求分析:读取输入的以;分割的宏名串,存为python内部数据

解题思路:读取以;分割的宏名串,处理转义字符,保存为python的List数据。

关键函数:转义字符处理

3.dumpDict函数

功能说明:dumpDict(self) 返回一个dict, 结合类中存储的CPP宏定义与预定义的宏序列,解析输出所有的可用宏到一个字典,其中宏名转为字符串后作为字典的key, 若有与宏名对应的常量转为python数据对象,作为字典的value,无常量则存为None, 注意不要返回类中内置的对象的引用。 解析过程若遇到宏定义格式错误 或 常量类型数据定义错误应该抛出异常;

需求分析:得到可用宏,输出到字典中

解题思路:将存储的cpp宏定义和预定义的宏都作为可用宏。解析所有的可用宏,用字典来存储,其中key为宏名转化成的字符串,value为对应的常量或None。

关键函数:解析宏(处理#ifdef/#ifndef/#else/#endif/#define/#undef 这几个宏指令)

4.dump函数

功能说明:dump(self, f) 结合类中的CPP宏定义数据与预定义宏序列,解析输出所有可用宏存储到新的CPP源文件,f为CPP文件路径,文件若存在则覆盖,文件操作失败抛出异常。 若遇到宏定义格式错误 或 常量类型数据定义错误应该抛出异常。 注意,转换后的常量数据表示应与Python对应类型兼容, 所以常量类型的长度存储信息可能丢失(例如 short 转为 int; float 转为 double 等), 允许特别表示方法信息丢失(例如原本16进制 统一变成10进制表示等)。 导出宏的顺序不做特别要求。

需求分析:将输出的可用宏存储到cpp源文件

解题思路:首先将存储的cpp宏定义和预定义的宏都作为可用宏。解析所有的可用宏,用字典来存储,其中key为宏名转化成的字符串,value为对应的常量或None。之后,将字典数据存成cpp源文件,需要进行类型转换。

关键函数:解析宏、字典里的宏定义存为C类型的文件


关键代码

1.主要函数

class PyMacroParser:    def __init__(self):        self.myMacro=[] # 保存的宏定义        self.preDefineMacro=[] #预定义的宏            #读取cpp宏定义,存为python内部数据    def load(self,f):        try:            with open(f,'r')as f:                initLines = f.readlines()                #去除注释                macroLines = self.delComment(initLines)                #解析宏定义,存为python内部数据                self.myMacro = self.parseMacro(macroLines)        except IOError:            print("ERROR: %s 文件打开失败或文件不存在!" %f)        else:            print("%s 文件打开成功!" %f)    #预定义宏序列    def preDefine(self,s):        #处理转义字符        self.proEscapeChar(s)        # 清空之前的预定义宏序列        self.preDefineMacro = []        temp = s.strip().split(';')        for item in temp:            if item != "":                self.preDefineMacro.append(item.strip())    #输出所有的可用宏到字典里    def dumpDict(self):        macroDict = {
} for macroName in self.preDefineMacro: macroDict[macroName] = None self.parseDict(self.myMacro, macroDict) res = {
} for key in macroDict: res[key] = macroDict[key] print(res) return res #输出所有的可用宏到cpp文件里 def dump(self,f): macroDict = {
} for macroName in self.preDefineMacro: macroDict[macroName] = None self.parseDict(self.myMacro, macroDict) try: with open(f, 'w') as f: for key in macroDict: if macroDict[key] is not None: f.write("#define {0} {1}\n".format(key, self.dictToCpp(macroDict[key]))) else: f.write("#define {}\n".format(key)) except IOError: print("Error: %s 文件写入失败!" %f) else: print("%s 文件写入成功!" %f)

2.关键函数


测试用例分析

我在test case 02_1和test case 02_6中都出现了问题。

第一次出错的原因是删除注释时出现了问题,第二次出错是对于宏命令格式解析出错。

出错原因无非以下几种:

1.删除注释时出错(如遇到TAB键、空格之类,以及对转义字符的处理)
2.宏定义解析时出错(特别注意# define也是有效定义,以及遇到型如{
{1},{2,},}
这种聚合类型定义时)
3.类型转换时出错(对字符串类型、字符类型、浮点类型、整型的分别处理)


欢迎交流😄

转载地址:https://blog.csdn.net/weixin_42490152/article/details/103817482 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:英宝通Unity4.0公开课学习笔记Vol.0
下一篇:腾讯游戏学院:实时图形渲染管道

发表评论

最新留言

留言是一种美德,欢迎回访!
[***.207.175.100]2024年04月01日 13时10分44秒