如何计算一组 C 文件中有多少个#ifdef 子句至少有一个#elif 但没有#else?
How to count how many #ifdef clauses have at least an #elif but no #else in a set of C files?
我有一堆 C 文件,我需要计算这些文件中有多少 #ifdef 子句有 #elif 子句但没有 #else 子句,包括可能嵌套的 #ifdef 子句。例如,在第一个代码片段中没有匹配项,而在第二个代码片段中,有两个匹配项:
1:没有匹配项(#ifdef 包含一个#else 子句)
#ifdef A
...
#elif B
...
#else
...
#endif
2: 两个匹配项(有两个#ifdef 子句带有#elif 子句但没有对应的#else)
#ifdef X1
...
#elif X2
...
#endif
...
#ifdef Y1
...
#elif Y2
...
#elif Y3
...
#endif
我正在寻找一种使用一些命令行工具(例如 grep、awk 或 sed)来执行此操作的方法,但到目前为止还没有成功。所以,我仍然愿意接受更简单的替代方案,如果有的话。
我已经使用 grep 尝试了这个正则表达式:'^(?=.*#elif)((?!#elif|#else).)(?=.*\#endif).)*$'
(一个#elif 后面没有另一个#elif 或#else 并且有一个相应的#endif),但是它不起作用,因为子句在不同的行。
解决方案
除了假设#if
、#ifdef
等没有出现在字符串或注释中,和代码写在一个理智的方式,即没有疯狂的东西,如:
#i\
fdef
我至少做了另一个假设,即 if
、ifdef
必须紧跟在 #
之前,而两者之间可以有任意制表符或 space 字符。
下面的正则表达式已经过测试,适用于 PCRE 和 Perl 风格。
# Look-ahead to allow overlapping matches
(?=
(
# Just define patterns. Doesn't match anything.
(?(DEFINE)
(?<re>
# Match lines not ifdef, if, elif, else, endif macro
(?![ \t]* [#](?:if(?:def)?|elif|else|endif)) .*\R
|
# Recurse into another if or ifdef
(?1)
)
)
# Only match ifdef at top level, and allow if and ifdef nested
^[ \t]* [#](?(R)if(?:def)?|ifdef) .*\R
(?&re)*
# Match elif clause at least once at top level
(?(R)
|
(?:
[ \t]* [#]elif .*\R
(?&re)*
)
)
# Match 0 or more elif clauses
(?:
[ \t]* [#]elif .*\R
(?&re)*
)*
# Optional else clause nested
# else clause not allowed at top level
(?(R)
(?:
[ \t]* [#]else. *\R
(?&re)*
)?
)
# Match endif
[ \t]*[#]endif.*\R?+
)
)
必需的标志:m
(多行,用于 ^
)和 x
(自由间距语法和注释)。
构造 (?(R)...)
是一个条件构造,它测试我们当前是否在任何例程调用中。用于检查if
/ifdef
.
的当前嵌套层级
从技术上讲,调用 (?(DEFINE)...)
中定义的模式的 (?&re)
算作例程调用,但进入另一个嵌套 if
/[= 的 (?1)
除外17=],第一次交替只对没有if
/ifdef
的行进行操作,所以不影响最后的结果。
附录
通用版本
这是一般情况下的正则表达式,没有问题中要求的 else
和 elif
子句的限制。它更简单,因为我们不必考虑限制。
如果您难以理解上面的正则表达式,这可能是一个很好的起点。
(?=
(
(?(DEFINE)
(?<re>
(?![ \t]* [#](?:if(?:def)?|elif|else|endif)) .*\R
|
(?1)
)
)
^[ \t]* [#]if(?:def)? .*\R
(?&re)*
(?:
[ \t]* [#]elif .*\R
(?&re)*
)*
(?:
[ \t]* [#]else. *\R
(?&re)*
)?
[ \t]*[#]endif.*\R?+
)
)
测试用例
#ifdef X1
#elif X2
#endif
#ifdef Y1
#define DEF
#if defined(X) && U == 0
#elif
#endif
#elif Y2
#ifdef Y1
#elif Y2
#else
#endif
#elif Y3
#endif
#ifdef X
#ifdef Y
#else
#endif
#ifdef K
#elif
#ifdef N1
#elif
#endif
#ifdef N2
#elif
#endif
#endif
#elif defined Z
#ifdef T
#elif
#endif
#endif
#ifdef Y
#ifdef E1
#endif
#ifdef E2
#elif
#endif
#endif
#ifdef Y
#elif
#endif
您需要编写递归下降解析器,每次找到“#ifdef”时下降,returns每次找到“#endif”时下降。有关用 awk 编写的示例,请参阅 How to compare and substitute strings in different lines in unix。
您没有提供有用的示例输入或预期的输出,所以我不得不自己编写来测试它(因此它可能不完全是您需要的),但您会想要这样的东西:
$ cat tst.awk
function descend(cond, numElifs,numElses,gotEndif) {
while ( !gotEndif && (getline > 0) ) {
if ( /#ifdef/ ) { descend() }
else if ( /#elif/ ) { numElifs++ }
else if ( /#else/ ) { numElses++ }
else if ( /#endif/ ) { gotEndif++ }
}
print cond, numElses+0, numElifs+0, ((numElifs>0)&&(numElses==0) ? "UhOh" : "")
return
}
/#ifdef/ { descend() }
.
$ cat file
#ifdef A
#elif B
#else
#ifdef C
#elif D
#endif
#ifdef E
#elif F
#else
#endif
#ifdef G
#elif H
#ifdef I
#else
#endif
#elif J
#endif
#endif
.
$ awk -f tst.awk file
C 0 1 UhOh
E 1 1
I 1 0
G 0 2 UhOh
A 1 1
请注意,这是对 getline 的适当使用,但在其他地方使用它之前请参阅 http://awk.info/?tip/getline。
关于真正需要语言解析器(处理例如注释或字符串中的 #ifdef)而不是像这样的脚本的所有常见警告都适用。
如果您只想计算它们,这应该可行。
就我的测试而言,它应该可以很好地嵌套。
awk '/#ifdef/{x++}
/#elif/&&a[x]!="q"{a[x]="s"}
/#else/{a[x]="q"}
/#endif/{total+=a[x]=="s";delete a[x];x--}
END{print total}' file
对于 EdMortons 输入文件,这将导致
2
我有一堆 C 文件,我需要计算这些文件中有多少 #ifdef 子句有 #elif 子句但没有 #else 子句,包括可能嵌套的 #ifdef 子句。例如,在第一个代码片段中没有匹配项,而在第二个代码片段中,有两个匹配项:
1:没有匹配项(#ifdef 包含一个#else 子句)
#ifdef A
...
#elif B
...
#else
...
#endif
2: 两个匹配项(有两个#ifdef 子句带有#elif 子句但没有对应的#else)
#ifdef X1
...
#elif X2
...
#endif
...
#ifdef Y1
...
#elif Y2
...
#elif Y3
...
#endif
我正在寻找一种使用一些命令行工具(例如 grep、awk 或 sed)来执行此操作的方法,但到目前为止还没有成功。所以,我仍然愿意接受更简单的替代方案,如果有的话。
我已经使用 grep 尝试了这个正则表达式:'^(?=.*#elif)((?!#elif|#else).)(?=.*\#endif).)*$'
(一个#elif 后面没有另一个#elif 或#else 并且有一个相应的#endif),但是它不起作用,因为子句在不同的行。
解决方案
除了假设#if
、#ifdef
等没有出现在字符串或注释中,和代码写在一个理智的方式,即没有疯狂的东西,如:
#i\
fdef
我至少做了另一个假设,即 if
、ifdef
必须紧跟在 #
之前,而两者之间可以有任意制表符或 space 字符。
下面的正则表达式已经过测试,适用于 PCRE 和 Perl 风格。
# Look-ahead to allow overlapping matches
(?=
(
# Just define patterns. Doesn't match anything.
(?(DEFINE)
(?<re>
# Match lines not ifdef, if, elif, else, endif macro
(?![ \t]* [#](?:if(?:def)?|elif|else|endif)) .*\R
|
# Recurse into another if or ifdef
(?1)
)
)
# Only match ifdef at top level, and allow if and ifdef nested
^[ \t]* [#](?(R)if(?:def)?|ifdef) .*\R
(?&re)*
# Match elif clause at least once at top level
(?(R)
|
(?:
[ \t]* [#]elif .*\R
(?&re)*
)
)
# Match 0 or more elif clauses
(?:
[ \t]* [#]elif .*\R
(?&re)*
)*
# Optional else clause nested
# else clause not allowed at top level
(?(R)
(?:
[ \t]* [#]else. *\R
(?&re)*
)?
)
# Match endif
[ \t]*[#]endif.*\R?+
)
)
必需的标志:m
(多行,用于 ^
)和 x
(自由间距语法和注释)。
构造 (?(R)...)
是一个条件构造,它测试我们当前是否在任何例程调用中。用于检查if
/ifdef
.
从技术上讲,调用 (?(DEFINE)...)
中定义的模式的 (?&re)
算作例程调用,但进入另一个嵌套 if
/[= 的 (?1)
除外17=],第一次交替只对没有if
/ifdef
的行进行操作,所以不影响最后的结果。
附录
通用版本
这是一般情况下的正则表达式,没有问题中要求的 else
和 elif
子句的限制。它更简单,因为我们不必考虑限制。
如果您难以理解上面的正则表达式,这可能是一个很好的起点。
(?=
(
(?(DEFINE)
(?<re>
(?![ \t]* [#](?:if(?:def)?|elif|else|endif)) .*\R
|
(?1)
)
)
^[ \t]* [#]if(?:def)? .*\R
(?&re)*
(?:
[ \t]* [#]elif .*\R
(?&re)*
)*
(?:
[ \t]* [#]else. *\R
(?&re)*
)?
[ \t]*[#]endif.*\R?+
)
)
测试用例
#ifdef X1
#elif X2
#endif
#ifdef Y1
#define DEF
#if defined(X) && U == 0
#elif
#endif
#elif Y2
#ifdef Y1
#elif Y2
#else
#endif
#elif Y3
#endif
#ifdef X
#ifdef Y
#else
#endif
#ifdef K
#elif
#ifdef N1
#elif
#endif
#ifdef N2
#elif
#endif
#endif
#elif defined Z
#ifdef T
#elif
#endif
#endif
#ifdef Y
#ifdef E1
#endif
#ifdef E2
#elif
#endif
#endif
#ifdef Y
#elif
#endif
您需要编写递归下降解析器,每次找到“#ifdef”时下降,returns每次找到“#endif”时下降。有关用 awk 编写的示例,请参阅 How to compare and substitute strings in different lines in unix。
您没有提供有用的示例输入或预期的输出,所以我不得不自己编写来测试它(因此它可能不完全是您需要的),但您会想要这样的东西:
$ cat tst.awk
function descend(cond, numElifs,numElses,gotEndif) {
while ( !gotEndif && (getline > 0) ) {
if ( /#ifdef/ ) { descend() }
else if ( /#elif/ ) { numElifs++ }
else if ( /#else/ ) { numElses++ }
else if ( /#endif/ ) { gotEndif++ }
}
print cond, numElses+0, numElifs+0, ((numElifs>0)&&(numElses==0) ? "UhOh" : "")
return
}
/#ifdef/ { descend() }
.
$ cat file
#ifdef A
#elif B
#else
#ifdef C
#elif D
#endif
#ifdef E
#elif F
#else
#endif
#ifdef G
#elif H
#ifdef I
#else
#endif
#elif J
#endif
#endif
.
$ awk -f tst.awk file
C 0 1 UhOh
E 1 1
I 1 0
G 0 2 UhOh
A 1 1
请注意,这是对 getline 的适当使用,但在其他地方使用它之前请参阅 http://awk.info/?tip/getline。
关于真正需要语言解析器(处理例如注释或字符串中的 #ifdef)而不是像这样的脚本的所有常见警告都适用。
如果您只想计算它们,这应该可行。 就我的测试而言,它应该可以很好地嵌套。
awk '/#ifdef/{x++}
/#elif/&&a[x]!="q"{a[x]="s"}
/#else/{a[x]="q"}
/#endif/{total+=a[x]=="s";delete a[x];x--}
END{print total}' file
对于 EdMortons 输入文件,这将导致
2