如何用正则表达式,匹配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*/'
但是这类方案的问题在于,很多重要的正则引擎,恰恰并不支持这些复杂的高级功能。
比如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);
}