python - 使用 RegEx 从 .h 文件中提取结构定义以根据 [XXX] 标准检查代码一致性
问题描述
我在我的项目中在不同的 .h 中定义了 C 结构,我正在寻找一种方法来检查每个结构是否满足我的编码要求。
例如:我希望我的所有位域结构类型都是相同的基本类型,例如:
typedef union {
uint8_t data;
struct {
uint8_t LSB:4;
uint8_t MSB:4;
} bit;
} MyType_t;
我原来的想法是使用正则表达式从 .h 头文件中提取所有结构/联合定义,并“播放”结果数据(即匹配)。然后提出其他 RegEx(或任何其他方式)来断言类型是一致的,并且我的所有要求都得到了满足。我不确定这是最好的方法,我可以手动检查它,但整个目的是拥有一个自主的代码检查器或类似的东西。我发现它也是 RegEx 和解析的一个很好的练习。(我认为)
为此,我只是尝试创建 RegEx 以匹配以下代码。所以我想出了以下几点:
reg_A = r'((typedef )(union|struct)\n)([\t ]*\{\n)((([\t ]*(void|\w+_t) \w+[\t ]*(:\d)?;).*)\n)+([\t ]*((union|struct)\n)([\t ]*\{\n)((([\t ]*(void|\w+_t) \w+[\t ]*(:\d)?;).*)\n)+([\t ]*\} \w+;)\n)?(\} \w+;)\n'
reg_B = r'([\t ]*((((typedef )?(struct|union))|\{)|(((volatile|static|const|FAR|NEAR|INTERRUPT) )*(void|\w+_t)\*?[\t ]*\w+([\t ]*:\d+)?;.*)|(\} \w+ ?;))(\n+|$))+'
reg_C = r'([\t ]*typedef (struct|union))\n[\t ]*\{(([\n\t ]*(struct[\n\t ]*\{)([\n\t ]*(((volatile|static|const|FAR|NEAR|INTERRUPT) )*(void|\w+_t)\*?[\t ]*\w+([\t ]*:\d+)?;.*))+[\n\t ]*\} \w+[\t ]*;)|[\n\t ]*(((volatile|static|const|FAR|NEAR|INTERRUPT) )*(void|\w+_t)\*?[\t ]*\w+([\t ]*:\d+)?;.*))+[\n\t ]*\} \w+[\t ]*;'
reg_D = r'([\t ]*typedef (struct|union))\n[\t ]*\{(([\n\t ]*(struct[\n\t ]*\{)([\n\t ]*(((volatile|static|const|FAR|NEAR|INTERRUPT) )*(void|\w+_t)\*?[\t ]*\w+([\t ]*:\d+)?;.*)|([\t\n ]*\/\/.*))+[\n\t ]*\} \w+[\t ]*;)|[\n\t ]*(((volatile|static|const|FAR|NEAR|INTERRUPT) )*(void|\w+_t)\*?[\t ]*\w+([\t ]*:\d+)?;.*)|([\t\n ]*\/\/.*))+[\n\t ]*\} \w+[\t ]*;'
reg_E = r'(\s*typedef (struct|union))\n\s*\{((\s*(struct\s*\{)(\s*(((volatile|static|const|FAR|NEAR|INTER{2}UPT) )*(void|\w+_t)\*?\s*\w+(\s*:\d+)?;.*)|(\s*\/\/.*))+\s*\} \w+\s*;)|\s*(((volatile|static|const|FAR|NEAR|INTER{2}UPT) )*(void|\w+_t)\*?\s*\w+(\s*:\d+)?;.*)|(\s*\/\/.*))+\s*\} \w+\s*;'
它们都遵循相同的一般理念,并且可能或多或少地针对任务和/或大文件进行了优化。
顺便说一句,我正在使用 python 和一个“简单”的函数:
out = open('path/to/output/file.txt', 'w')
for file in self.header_files:
with open(file) as f:
whole = f.read()
print(file)
for match in re.finditer(reg_X, whole):
rslt.append(match.group())
group = match.group()
out.write(group) # all available structure definition from .h files
这self.header_files
是我查看的所有文件的列表。并且可以很容易地替换为特定文件的路径并删除for
循环语句。
reg_X
这意味着您可以使用上面定义的任何正则表达式
现在我如何构建正则表达式(regex_D):
(
[\t ]*typedef (struct|union) <= 'OUTER' DEFINITION
)
\n[\t ]*\{ <= SPACING & BRACKETS
(
(
[\n\t ]*(struct[\n\t ]*\{) <= 'INNER' DEFINITION
(
[\n\t ]* <= SPACING
(
((volatile|static|const|FAR|NEAR|INTERRUPT) )* <= TYPE
(void|\w+_t)\*?[\t ]*\w+ <= 'FINAL' TYPE + NAME
([\t ]*:\d+)? <= BITFIELD SPECIFICATION (optional)
;.* <= EOL + whatever
)
| || OR
(
[\t\n ]*\/\/.* <= LINE STARTING WITH A COMMENT
)
)+ <= Variable definition + comment line could occurs multiple time
[\n\t ]*\} \w+[\t ]*; <= END OF 'INNER' definition1
)
| || OR
[\n\t ]* <= SPACING
(
(
(volatile|static|const|FAR|NEAR|INTERRUPT) <= TYPE
)*
(void|\w+_t) <= FINAL TYPE
\*?[\t ]*\w+ <= VAR NAME
([\t ]*:\d+)? <= Bitfield specification
;.*
)
| || OR
(
[\t\n ]*\/\/.* <= Line starting with a comment
)
)+
[\n\t ]*\} \w+[\t ]*; <= End of outer definition
很多表情都是加倍的。我试图有一个“更好”的正则表达式(regex_B)
(
[\t ]*
(
(
(
(typedef )?(struct|union)
)
|
\{
)
|
(
((volatile|static|const|FAR|NEAR|INTERRUPT) )*
(void|\w+_t)
\*?[\t ]*\w+
([\t ]*:\d+)?
;.*
)
|
(
\} \w+ ?;
)
)
(
\n+
|
$
)
)+
它包含相同的“信息”机器人,但顺序不同,并且具有不同的“要求”,例如第二个在任何行上给出结果,例如:
extern FAR varType_t var;
这只是一个简单的变量定义。
澄清:我选择了正则表达式,因为我对解析和实践知之甚少。我正在寻找完成任务的“最佳方式”。正如答案所指出的那样,编译器可能使用的代码解析器是(唯一的)最佳解决方案。
但这个问题有两个目标。第一个已经回答了。
这篇文章的第二个目标是更多地了解一般的正则表达式(和优化)。例如有一个匹配组与 the 相同regex_B
但避免任何表达式重复。正如您所看到的,“外部”和“内部”的定义是相同的(只有一两件事),但可以匹配我想要的(仅此而已)。最后一个匹配相同的东西,但由于它更“灵活”,它匹配不应该的地方。
谢谢大家的时间:)
注意:如果您有任何资源可供我查看并从中获得一些知识/经验,请随时分享任何事情/想法。
[使用:https://regex101.com/ 测试正则表达式]
解决方案
唯一正确的方法是改用解析器,例如用于 Python的解析器。其中有很多/examples
...
将“正确处理语言本身的复杂性”的整个问题交给这个已经存在并经过测试的解析器,而不是尝试使用正则表达式来“自己创建一种解析器”。这是愚蠢的差事。
解析器以各种方式工作,但本质上它们通过扫描源代码以在内部创建抽象语法树或AST 来工作。 然后,它们允许您探索该树或通过使用所谓的“访问者”对其进行迭代。
树本身不是“抽象的”。该术语来自这样一个事实,即其内容是源代码语法的抽象,代表源代码所说的内容,而不是它的实际文本。
然后,您可以创建一个“访问者”,只要在“行走”期间遇到感兴趣的 AST 节点,就会调用该访问者。这将是收集您需要的任何信息的“正确地点和时间”——但您不必担心您是如何到达那里的。这就是解析器的工作。
例如,您“访问”某个与typedef struct
声明相对应的节点。该节点的子节点将代表您现在正在查看的结构的抽象本质,无论它是如何在源代码文本中编写的,这早已不复存在,现在完全无关紧要。这些子节点的组织和内容是已知的并且可以依赖。该节点的父节点代表“包含” this 的任何内容typedef
。所有这些繁重的工作都是由解析器自动为您完成的。
脚注: “当然,这就是每种语言解释器或编译器本身在前端的实际工作方式。” 毫无疑问,这个包是使用C99 语言的官方所谓“语法”构建的,该语言的每个编译器也将使用它。该技术(包括构建此类解析器的方法)已经过验证、快速且高效。
推荐阅读
- netsuite - 在高级 pdf/html 中打印图像字段
- android - AndroidRuntime: FATAL EXCEPTION, java.lang.OutOfMemoryError 发送多张图片时
- powerbi - 错误'表达式引用了多个列
- css - Why don't the elements in the navbar darken like the rest of the page when opening modal? (bootstrap)
- python - Intersection from different keys inside Python dictionary
- r - 如何将数据框划分为新的数据框(如新数据1、数据2、数据3 ..等等),以便我可以分析它们中的每一个(如T检验)
- gatsby - 在数组中传递静态查询数据
- sql-server - 哪个更适合托管在 Azure 上的 MSSQL,CPU 或 RAM 的成本更高?
- typescript - Ionic 4 在从 http 请求调用返回的对象键上抛出错误
- java - 我在向服务器发送凌空请求时遇到了一些错误......任何人都可以帮助我