如何计算一组 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

我至少做了另一个假设,即 ififdef 必须紧跟在 # 之前,而两者之间可以有任意制表符或 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(自由间距语法和注释)。

Demo on regex101

构造 (?(R)...) 是一个条件构造,它测试我们当前是否在任何例程调用中。用于检查if/ifdef.

的当前嵌套层级

从技术上讲,调用 (?(DEFINE)...) 中定义的模式的 (?&re) 算作例程调用,但进入另一个嵌套 if/[= 的 (?1) 除外17=],第一次交替只对没有if/ifdef的行进行操作,所以不影响最后的结果。

附录

通用版本

这是一般情况下的正则表达式,没有问题中要求的 elseelif 子句的限制。它更简单,因为我们不必考虑限制。

如果您难以理解上面的正则表达式,这可能是一个很好的起点。

(?=
  (

    (?(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?+

  )
)

Demon on regex101

测试用例

#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