所以我们知道
// This doesn't affect anything
/*
This doesn't affect anything either
*/
/*
/* /* /*
This doesn't affect anything
*/
This does because comments aren't recursive
/* /*
This doesn't affect anything
*/ */
This throws an error because the second * / is unmatched since comments aren't recursive
我听说它们不是递归的原因是它们会减慢编译器的速度,我想这是有道理的。然而,现在当我在高级语言(比如Python)中解析c++代码时,我可以简单地使用正则表达式
"/[/]+((?![n])[sS])*r*n"
匹配// single line comments
,并使用
"/*((?!*/)[sS])**/"
匹配/* multiline comments */
,然后循环遍历所有单行注释,删除它们,然后循环遍历所有多行注释并删除它们。反之亦然。但这就是我被困住的地方。只做其中之一似乎是不够的,因为:
// /*
An error is thrown because the /* is ignored
*/
/*
This doesn't affect things because of mysterious reasons
// */
和
/*
This throws an error because the second * / is unmatched
// */ */
这种行为的原因是什么?它也是编译器解析事物的方式的产物吗?明确地说,我不想改变c++的行为,我只是想知道第二组示例背后的原因。
编辑:所以,是的,更明确地说,我的问题是为什么以下三种(看似合理的)解释这种行为的方法不起作用:
直接忽略//之后的所有字符,不管它们是/*还是*/,即使你是在多行注释中。
允许/*或*/后面跟着//仍然有效。
我理解为什么不允许嵌套注释,因为它们需要堆栈和任意大量的内存。但这三种情况不会。
编辑:
如果有人感兴趣,下面的代码可以按照这里讨论的正确注释规则在python中提取c/c++文件的注释:
import re
commentScanner = re.Scanner([
(r"/[/]+((?![n])[sS])*r*(n{1})?", lambda scanner, token: ("//", token)),
(r"/*((?!*/)[sS])**/", lambda scanner, token: ("/* ... */", token)),
(r"[sS]", lambda scanner, token: None)
])
commentScanner.scan("fds a45fsa//kjl fds4325lkjfa/*jfds/nklj/*4532jlfds5342a l/*a/*b/*cn//fdsafanrn/*jfd//a*/fd// fs54fdsa3rr//r/*rn2annnois")
这不是不一致。现有的行为既容易指定也容易实现,并且你的编译器正在正确地实现它。参见标准中的[lex.comment]。
以
/*
开始注释,以*/
结束注释。这些评论不是巢。字符//
开始一个注释,它以下一个换行字符结束。如果有在这样的注释中,如果出现换行符或垂直制表符,则其之间只能出现空白字符结束注释的新行;不需要诊断。[注意:注释字符//
、/*
和*/
在//
注释中没有特殊的含义,它们与其他字符一样被处理。类似地,注释字符//
和/*
在/*
注释中没有特殊含义。
可以看到,//
可以用来注释掉/*
和*/
。只是注释不嵌套,所以如果//
已经在/*
中,那么//
根本没有影响。
就像注释中的所有内容都是文本一样,但是当你删除注释分隔符时,
暴露的文本可以再次被解析。
因此,如果该文本的一部分具有注释分隔符字面量,则它们可以作为新的注释分隔符进行解析。
而且总是先到先得的问题,即从左到右的顺序。
认为解析注释很简单可能有点过于简单。
事实上,必须同时解析引号(单引号/双引号),并且首先处理第一次遇到的注释/引号。
最后,跳过注释内的所有内容意味着如果您删除外部
注释层,除有效注释之外的所有内容都将被解析为
语言的一部分。这意味着任何公开的注释格式都不确定,
如果不是不可避免的话,获得解析错误的可能性也很大。
我相信c++也有//
样式注释的行延续形式。
例如:
// single line continuation
continuation
end here
code
用正则表达式解析c++注释的公式是
解析(匹配)文件中的每个字符。
如果你直接打开注释它会将匹配注入
找错地方了。
并稍微修改了单行注释和延续。
有了它,你可以删除评论或只是找到评论。
原始正则表达式:
# (/*[^*]**+(?:[^/*][^*]**+)*/|//(?:[^\]|\n?)*?n)|("(?:\[Ss]|[^"\])*"|'(?:\[Ss]|[^'\])*'|[Ss][^/"'\]*)
( # (1 start), Comments
/* # Start /* .. */ comment
[^*]* *+
(?: [^/*] [^*]* *+ )*
/ # End /* .. */ comment
|
// # Start // comment
(?: [^\] | \ n? )*? # Possible line-continuation
n # End // comment
) # (1 end)
|
( # (2 start), Non - comments
"
(?: \ [Ss] | [^"\] )* # Double quoted text
"
| '
(?: \ [Ss] | [^'\] )* # Single quoted text
'
| [Ss] # Any other char
[^/"'\]* # Chars which doesn't start a comment, string, escape,
# or line continuation (escape + newline)
) # (2 end)
增强(保留格式),主要用于删除注释。使用多行模式:
# ((?:(?:^[ t]*)?(?:/*[^*]**+(?:[^/*][^*]**+)*/(?:[ t]*r?n(?=[ t]*(?:r?n|/*|//)))?|//(?:[^\]|\(?:r?n)?)*?(?:r?n(?=[ t]*(?:r?n|/*|//))|(?=r?n))))+)|("(?:\[Ss]|[^"\])*"|'(?:\[Ss]|[^'\])*'|(?:r?n|[Ss])[^/"'\s]*)
( # (1 start), Comments
(?:
(?: ^ [ t]* )? # <- To preserve formatting
(?:
/* # Start /* .. */ comment
[^*]* *+
(?: [^/*] [^*]* *+ )*
/ # End /* .. */ comment
(?: # <- To preserve formatting
[ t]* r? n
(?=
[ t]*
(?: r? n | /* | // )
)
)?
|
// # Start // comment
(?: # Possible line-continuation
[^\]
| \
(?: r? n )?
)*?
(?: # End // comment
r? n
(?= # <- To preserve formatting
[ t]*
(?: r? n | /* | // )
)
| (?= r? n )
)
)
)+ # Grab multiple comment blocks if need be
) # (1 end)
| ## OR
( # (2 start), Non - comments
"
(?: \ [Ss] | [^"\] )* # Double quoted text
"
| '
(?: \ [Ss] | [^'\] )* # Single quoted text
'
| (?: r? n | [Ss] ) # Linebreak or Any other char
[^/"'\s]* # Chars which doesn't start a comment, string, escape,
# or line continuation (escape + newline)
) # (2 end)
当注释开始时,直到注释结束的所有内容都被视为注释。
因此,zero // one */ two
本身可以将zero // one */
作为前一行/* */
注释的结尾,两个two
在注释之外,或者它可以是一个新的单行注释,从// one */ two
开始,zero
在注释之外。
作为这样做的理论原因,//
不是一个有效的C标记或标记序列。所以没有程序在c语言的注释或字符串之外使用//
。
但是,注释中的//
是合法的。所以头文件包含:
/* this is a C style comment
// with some cool
// slashes */
如果我们将//
注释掉后面的*/
, 将会断开。在/* */
注释中,//
被忽略。不能无缘无故破坏与C语言的兼容性。
在//
中,所有内容都被忽略,直到行尾。没有偷偷摸摸的/*
或允许。
解析规则非常简单——开始注释,发出声音并丢弃,直到看到结束令牌(换行符或*/
取决于),然后继续解析。
由于c++不是为正则表达式解析而设计的,因此使用正则表达式解析它的困难要么没有被考虑,要么被认为不重要。