7

我正在尝试在一些 Java 代码(使用 Python 脚本)中查找关键字“public”的所有实例,这些实例不在注释或字符串中,也就是//在 a/*和 a之间*/,而不是在 double 或 single 之间引号,并且它们不是变量名的一部分——即它们必须以空格、制表符或换行符开头,并且必须跟在相同的后面。

所以这就是我现在所拥有的——

//.*\spublic\s.*\n
/\*.*\spublic\s.*\*/
".*\spublic\s.*"
'.*\spublic\s.*'

我把这搞砸了吗?

但这正是我不想要的。我怎样才能把它转过来并搜索这四个表达式之和的倒数,作为一个单一的正则表达式

我发现这可能使用了负前瞻和后视,但我仍然无法将它拼凑在一起。此外,对于 /**/ 正则表达式,我担心它.*与换行符不匹配,因此它无法识别这public是在评论中:

/*
public
*/

低于这一点的一切都是我在纸上思考的,可以忽略不计。这些想法并不完全准确。


编辑:

我敢说(?<!//).*public.*会匹配任何不在单行注释中的内容,所以我掌握了窍门。我认为。但仍然不确定如何结合一切。

编辑2:

那么——按照这个想法,我|把它们都编成——

(?<!//).*public.*|(?<!/\*).*public.\*/(?!\*/)|(?<!").*public.*(?!")|(?<!').*public.*(?!')

但我不确定。//public不会被第一个替补匹配,但会被第二个匹配。我需要与前瞻和后视,而不是或整个事情。

4

4 回答 4

5

对不起,我得告诉你一个消息,你想做的事是不可能的。原因主要是因为 Java 不是常规语言。我们现在都知道,大多数正则表达式引擎都提供非常规的功能,但 Python 尤其缺乏可以解决问题的递归 (PCRE) 或平衡组 (.NET) 之类的东西。但让我们更深入地研究一下。

首先,为什么你的模式没有你想象的那么好?(用于匹配这些文字public 内部的任务;类似的问题将适用于反转逻辑)

正如您已经认识到的那样,您将遇到换行问题(在 的情况下/*...*/)。这可以通过使用修饰符/选项/标志re.S(改变 的行为.)或使用[\s\S]代替.(因为前者匹配任何字符)来解决。

但还有其他问题。您只想查找周围出现的字符串或注释文字。您实际上并没有确保它们是专门围绕有public问题的。我不确定你可以在 Java 中的单行上放多少,但如果你有一个任意字符串,然后是 a public,然后是单行上的另一个字符串,那么你的正则表达式将匹配,public因为它可以找到"之前和在它之后。即使这是不可能的,如果您在同一个输入中有两个块注释,那么public这两个块注释之间的任何一个都会导致匹配。所以你需要找到一种方法来断言你public真的在里面 "..."/*...*/不仅仅是这些文字可以在它左边的任何地方找到。

下一件事:匹配不能重叠。但是您的匹配包括从开头文字到结尾文字的所有内容。因此,如果您这样做"public public",只会导致一场比赛。捕获在这里无法为您提供帮助。通常避免这种情况的技巧是使用环视(不包括在匹配中)。但是(我们稍后会看到)lookbehind 并没有你想象的那么好,因为它不能是任意长度(只有在 .NET 中才有可能)。

现在是最糟糕的。如果您有"内部评论怎么办?这应该不算数吧?如果你有///**/在一个字符串中怎么办?这应该不算数吧?那么'inside "-strings 和"inside '-strings 呢?更糟糕的是,\"inside "-string 呢?因此,为了 100% 的稳健性,您还必须对周围的分隔符进行类似的检查。这通常是正则表达式达到其功能极限的地方,这就是为什么您需要一个适当的解析器来遍历输入字符串并构建整个代码树的原因。

但是假设您在字符串中没有注释文字,并且在注释中没有引号(或者只有匹配的引号,因为它们会构成一个字符串,而且我们也不希望public在字符串中)。所以我们基本上假设每个有问题的文字都正确匹配,并且它们从不嵌套。在这种情况下,您可以使用前瞻来检查您是在其中一个文字内部还是外部(实际上是多个前瞻)。我很快就会谈到这一点。

但是还剩下一件事。什么不起作用(?<!//).*public.*?为此,(?<!//)匹配任何单个位置就足够了。例如,如果您刚刚输入// public,引擎会在字符串开头(字符串开头的左侧)尝试否定的lookbehind,会发现 no //,然后使用.*to 消耗//和空间,然后 match public。你真正想要的是(?<!//.*)public. 这将从 的起始位置开始向后看,public并一直向左看当前行。但是......这是一个可变长度的lookbehind,仅受.NET支持。

但是让我们看看如何确保我们真的在字符串之外。我们可以使用前瞻来一直查看输入的末尾,并检查途中是否有偶数个引号。

public(?=[^"]*("[^"]*"[^"]*)*$)

现在,如果我们真的很努力,我们也可以在字符串中忽略转义的引号:

public(?=[^"]*("(?:[^"\\]|\\.)*"[^"]*)*$)

因此,一旦我们遇到 a"我们将接受非引号、非反斜杠字符或反斜杠字符以及它后面的任何内容(也允许转义反斜杠字符,因此"a string\\"我们不会将关闭"视为被转义)。我们可以将它与多行模式 ( re.M) 一起使用,以避免一直走到输入的末尾(因为行尾就足够了):

public(?=[^"\r\n]*("(?:[^"\r\n\\]|\\.)*"[^"\r\n]*)*$)

re.M对于以下所有模式都是隐含的)

这是它寻找单引号字符串的内容:

public(?=[^'\r\n]*('(?:[^'\r\n\\]|\\.)*'[^'\r\n]*)*$)

对于块注释,它更容易一些,因为我们只需要寻找/*或字符串的结尾(这次真的是整个字符串的结尾),而不会*/在途中遇到。这是通过对每个位置进行负前瞻来完成的,直到搜索结束:

public(?=(?:(?![*]/)[\s\S])*(?:/[*]|\Z))

但正如我所说,我们现在被单行注释难住了。但无论如何,我们可以将最后三个正则表达式合并为一个,因为前瞻实际上并没有提前正则表达式引擎在目标字符串上的位置:

public(?=[^"\r\n]*("(?:[^"\r\n\\]|\\.)*"[^"\r\n]*)*$)(?=[^'\r\n]*('(?:[^'\r\n\\]|\\.)*'[^'\r\n]*)*$)(?=(?:(?![*]/)[\s\S])*(?:/[*]|\Z))

现在那些单行注释呢?模拟可变长度lookbehinds的技巧通常是反转字符串和模式 - 这使得lookbehind成为一个lookahead:

cilbup(?!.*//)

当然,这意味着我们也必须反转所有其他模式。好消息是,如果我们不关心转义,它们看起来完全一样(因为引号和块注释都是对称的)。因此,您可以在反向输入上运行此模式:

cilbup(?=[^"\r\n]*("[^"\r\n]*"[^"\r\n]*)*$)(?=[^'\r\n]*('[^'\r\n]*'[^'\r\n]*)*$)(?=(?:(?![*]/)[\s\S])*(?:/[*]|\Z))(?!.*//)

然后,您可以使用inputLength -foundMatchPosition - foundMatchLength.

现在逃跑怎么办?这现在很烦人,因为如果引号后面跟着反斜杠,我们必须跳过引号。由于一些回溯问题,我们需要在五个地方解决这个问题。三次,在使用非引号字符时(因为我们"\现在也需要允许。两次,在使用引号字符时(使用负前瞻来确保它们后面没有反斜杠)。让我们看看双引号:

cilbup(?=(?:[^"\r\n]|"\\)*(?:"(?!\\)(?:[^"\r\n]|"\\)*"(?!\\)(?:[^"\r\n]|"\\)*)*$)

(它看起来很可怕,但如果你将它与忽略转义的模式进行比较,你会注意到一些差异。)

因此将其合并到上述模式中:

cilbup(?=(?:[^"\r\n]|"\\)*(?:"(?!\\)(?:[^"\r\n]|"\\)*"(?!\\)(?:[^"\r\n]|"\\)*)*$)(?=(?:[^'\r\n]|'\\)*(?:'(?!\\)(?:[^'\r\n]|'\\)*'(?!\\)(?:[^'\r\n]|'\\)*)*$)(?=(?:(?![*]/)[\s\S])*(?:/[*]|\Z))(?!.*//)

因此,这实际上可能适用于许多情况。但正如你所见,它很糟糕,几乎无法阅读,而且绝对无法维护。

有什么注意事项?字符串中没有注释文字,其他类型的字符串中没有字符串文字,注释中没有字符串文字。另外,我们有四个独立的前瞻,这可能需要一些时间(至少我认为我有一个无效的大部分回溯)。

无论如何,我相信这与正则表达式一样接近。

编辑:

我刚刚意识到我忘记了public不能成为更长文字一部分的条件。你包括了空格,但如果它是输入中的第一件事呢?最简单的方法是使用\b. 匹配单词字符和非单词字符之间的位置(不包括周围的字符)。但是,Java 标识符可能包含任何 Unicode 字母或数字,我不确定 Python\b是否支持 Unicode。此外,Java 标识符可能包含$. 无论如何,这会破坏它。环视救援!与其断言每一边都有一个空格字符,不如断言没有非空格字符。因为我们需要负面的环视,我们将获得不包括这些字符免费的优势:

(?<!\S)cilbup(?!\S)(?=(?:[^"\r\n]|"\\)*(?:"(?!\\)(?:[^"\r\n]|"\\)*"(?!\\)(?:[^"\r\n]|"\\)*)*$)(?=(?:[^'\r\n]|'\\)*(?:'(?!\\)(?:[^'\r\n]|'\\)*'(?!\\)(?:[^'\r\n]|'\\)*)*$)(?=(?:(?![*]/)[\s\S])*(?:/[*]|\Z))(?!.*//)

而且因为仅仅从向右滚动这段代码片段无法完全理解这个正则表达式有多么可笑,这里它处于自由空间模式 ( re.X) 并带有一些注释:

(?<!\S)      # make sure there is no trailing non-whitespace character
cilbup       # public
(?!\S)       # make sure there is no leading non-whitespace character
(?=          # lookahead (effectively lookbehind!) to ensure we are not inside a
             # string
  (?:[^"\r\n]|"\\)*
             # consume everything except for line breaks and quotes, unless the
             # quote is followed by a backslash (preceded in the actual input)
  (?:        # subpattern that matches two (unescaped) quotes
    "(?!\\)  # a quote that is not followed by a backslash
    (?:[^"\r\n]|"\\)*
             # we've seen that before
    "(?!\\)  # a quote that is not followed by a backslash
    (?:[^"\r\n]|"\\)*
             # we've seen that before
  )*         # end of subpattern - repeat 0 or more times (ensures even no. of ")
  $          # end of line (start of line in actual input)
)            # end of double-quote lookahead
(?=(?:[^'\r\n]|'\\)*(?:'(?!\\)(?:[^'\r\n]|'\\)*'(?!\\)(?:[^'\r\n]|'\\)*)*$)
             # the same horrible bastard again for single quotes
(?=          # lookahead (effectively lookbehind) for block comments
  (?:        # subgroup to consume anything except */
    (?![*]/) # make sure there is no */ coming up
    [\s\S]   # consume an arbitrary character
  )*         # repeat
  (?:/[*]|\Z)# require to find either /* or the end of the string
)            # end of lookahead for block comments
(?!.*//)     # make sure there is no // on this line
于 2012-12-13T09:16:47.127 回答
2

您是否考虑过使用该方法将所有注释以及单引号和双引号字符串文字替换为空字符串re sub()。然后只需对要查找的单词的结果文件进行简单的搜索/匹配/查找?

这至少会给你单词所在的行号。您也许可以使用该信息来编辑原始文件。

于 2012-12-11T09:10:27.543 回答
1

您可以使用在注释或双引号字符串之外pyparsing查找关键字:public

from pyparsing import Keyword, javaStyleComment, dblQuotedString

keyword = "public"
expr = Keyword(keyword).ignore(javaStyleComment | dblQuotedString)

例子

for [token], start, end in expr.scanString(r"""{keyword} should match
    /*
    {keyword} should not match "
    */
    // this {keyword} also shouldn't match
    "neither this \" {keyword}"
    but this {keyword} will
    re{keyword} is ignored
    '{keyword}' - also match (only double quoted strings are ignored)
    """.format(keyword=keyword)):
    assert token == keyword and len(keyword) == (end - start)
    print("Found at %d" % start)

输出

Found at 0
Found at 146
Found at 187

要忽略单引号字符串,您可以使用quotedString代替dblQuotedString.

要仅使用正则表达式,请参阅regex-negationSO 上的标记,例如,正则表达式以匹配不包含单词的字符串?或者使用更少的正则表达式功能 正则表达式:通过排除匹配,没有前瞻 - 有可能吗?. 简单的方法是使用正匹配并跳过匹配的注释、引用的字符串。结果是其余的比赛。

于 2012-12-11T18:56:28.283 回答
0

它找到了相反的结果,因为这正是你所要求的。:)

我不知道在一个正则表达式中将它们全部匹配的方法(尽管理论上应该是可能的,因为常规语言在补码和交叉点下是封闭的)。但是您绝对可以搜索所有公共实例,然后删除与您的“坏”正则表达式之一匹配的任何实例。例如,尝试set.differencematch.start和的match.end属性上使用re.finditer

于 2012-12-11T06:08:11.083 回答