本文共 7174 字,大约阅读时间需要 23 分钟。
PyMacroParser 宏解析工具
PyMarcoParser宏解析工具
题目要求
题目描述
假定有CPP 源码文件(.cpp) 仅有如下内容
- 包含C/C++ 风格的注释: // 及 /**/
- 包含空白字符
- 只包含 #ifdef/#ifndef/#else/#endif/#define/#undef 这几个宏指令的应用
- #define 定义仅有如下定义情况: #define identifier token-stringopt
- 其中 token-stringopt 只有兼容如下几种C/C++指定基本类型常量表示内容:整型,浮点,布尔(true, false),字符(忽略宽字符),字符串,及各上述基本类型组成的聚合。
- 聚合:结构体 或者 数组初始化,均可用聚合初始化形式{}表示,聚合类型可以多维(嵌套).
- 除如上指定内容及适当的空格制表符换行等等等,不再有其他内容。不考虑字符映射及三元组序列, 不考虑行拼接 – 注释,空白字符,常量,和字符串的类型定义请参考如下相关 聚合初始化参考如下网址中关于聚合初始化的 预处理器相关参考如下,特别注意 “转换阶段” 与 “预处理器指令”
有如上特点的CPP源码文件,为了实现与Python脚本兼容对接,
请基于Python 2.7.X 封装实现一个可重用的类,要求如下:- 该类能读取 .cpp文件中的宏定义,并接收可变化的预定义宏串, 并根据两者,解析出当前所有的可用的宏定义。全部字符串都是ANSI编码。
- 可用宏定义可转为Python字典模式输出。 其中宏名转为字符串作为字典key, 若有与宏对应的常量定义转为对应python数据类型后作为字典的value。 类型对应关系见附表。若无任何常量则value为None
- 可用宏定义可再次导出成为只含有当前宏定义的CPP源文件。
- 请遵循CPP宏及常量类型的定义标准,确保相同常量变换后数值上保持一致,类型上与Python保持兼容(由于python转换过程中可能会损失掉C的具体类型信息,比如char 可能最终变为 int,具体表示方法也会有所变化,比如16进制最终表示为10进制, 故多次转换后能保持最终常量的值相等即可。 类型转换标准见“注意”)
- 只允许使用Python内置模块(如sys、math)和string模块,不允许使用其他标准模块及任何第三方开发库(包括但不限于re),不要使用 evel/exec 懒人解析, 不要使用copy;deepcopy等懒人复制。
- 独立完成作业,并代码中给予必要的注释。
- 代码采用UTF-8编码
该类的类名要求为PyMacroParser,所提供的方法约定如下:
- load(self, f) 从指定文件中读取CPP宏定义,存为python内部数据,以备进一步解析使用。 f为文件路径,文件操作失败抛出异常; 无返回值。若在初步解析中遇到宏定义格式错误 或 常量类型数据定义错误应该抛出异常。
- preDefine(self, s) 输入一堆预定义宏名串,宏名与宏名之间以”;” 分割。 比如串"mcname1;mcname2"相当于把 #define mcname1 #define mcname2 加在了CPP宏数据的最前面。 而空串"" 表示没有任何预定义宏。 显然,预定义宏会影响对CPP文件数据内的可用宏解析。 preDefine函数可被反复调用,每次调用自动清理掉之前的预定义宏序列。 preDefine 与 load的CPP宏定义数据,一起决定最终可用的宏。
- dumpDict(self) 返回一个dict, 结合类中存储的CPP宏定义与预定义的宏序列,解析输出所有的可用宏到一个字典,其中宏名转为字符串后作为字典的key, 若有与宏名对应的常量转为python数据对象,无常量则存为None, 注意不要返回类中内置的对象的引用。 解析过程若遇到宏定义格式错误 或 常量类型数据定义错误应该抛出异常;
- 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 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!