如何用正则表达式,匹配C语言的多行注释?

我们想要用正则表达式匹配C语言的注释,假设我们面对的是下面这样的C语言代码:

/**a*/b/*c**/

你可能会不假思索使用/\*.*\*/这样的正则表达式。即尝试用.*去匹配注释内容。

然而这样去匹配,我们得到的结果会是:

/**a*/b/*c**/

而不是预期中的:

/**a*/

究其原因,正则表达式骨子里是“贪婪”的,它总是会试图匹配更多。 但是现代的正则引擎,往往给正则表达式扩展了更加高级强大的功能,很多引擎支持“非贪婪”的匹配策略。

比如,JavaScript的正则表达式,就提供了一些“非贪婪”的量词,如*?, +?, {n,m}?等等。

使用“非贪婪”量词,匹配出注释倒是容易:

/\/\*.*?\*\//.exec("/**a*/b/*c**/")[0];

它运行的结果就是预期中的:

'/**a*/'

除了“非贪婪”的量词,高级正则的捕获功能,还支持“取反”操作,即(?!)。使用这个功能,可以有如下解法:

/\/\*([^*]|\*(?!\/))*\*\//.exec("/**a*/b/*c**/")[0];

它运行的结果也是预期中的:

'/**a*/'

但是这类方案的问题在于,很多重要的正则引擎,恰恰并不支持这些复杂的高级功能。

比如flexleex,只支持最基础的正则表达式写法。使用这些工具的时候,我们只能使用最基础的正则表达式。

可以查阅flex的相关文档leex的相关文档来验证是否有此限制。

那么,我们如何解决这个问题呢?

高兼容性的方案

最终方案可以使用下面的“伪正则表达式”来表示:

{START}({NOT_WORRYING}*{WORRYING}+{NOT_WORRYING_NOR_FINAL})*{NOT_WORRYING}*{WORRYING}+{FINAL}

使用此方案,上面的正则表达式可以写作:

/\*([^*]*\*+[^*/])*[^*]*\*+/

此方案的具体描述,可以在这个网站阅读。

我们把方案和具体使用示例切开并对应起来:

{START}                                                 /\*
({NOT_WORRYING}*{WORRYING}+{NOT_WORRYING_NOR_FINAL})*   ([^*]*\*+[^*/])*
{NOT_WORRYING}*                                         [^*]*
{WORRYING}+{FINAL}                                      \*+/

切分之后,理解此方案就变得容易了许多。大家可以一行一行仔细对照理解。

复杂示例

让我们写一个C语言文件a.c,里面包含一些随机的注释:

/*** this is c comment ** /
 **/

int blah(struct myobj **p) {
    return (*p)->f(p);
}

/* /* /*
 * other c comment
 */

JavaScript中处理这个文件:

/// 文件内容作为字符串被读入到了变量`c`中。
/\/\*([^*]*\*+[^*/])*[^*]*\*+\//s.exec(c)[0];

运行后返回的值:

'/*** this is c comment ** /\n **/'

可以看到,我们成功地匹配出了完整的注释。

sed命令中使用示例

我们可以将上面的正则,配合sed命令使用。比如我们想将源文件中所有的注释,替换成一个空行,那我们只需要:

sed -z -E "s#/\*([^*]*\*+[^*/])*[^*]*\*+/##g" a.c

运行结果:



int blah(struct myobj **p) {
    return (*p)->f(p);
}