Извините, но я должен сообщить вам новость, что то, что вы пытаетесь сделать, невозможно. Причина в основном в том, что Java не является обычным языком. Как мы все уже знаем, большинство движков регулярных выражений предоставляют нерегулярные функции, но в Python, в частности, не хватает чего-то вроде рекурсии (PCRE) или групп балансировки (.NET), которые могли бы помочь. Но давайте рассмотрим это более подробно.
Прежде всего, почему ваши паттерны не так хороши, как вы думаете? (для задачи сопоставления public
внутри этих литералов; аналогичные проблемы будут применяться к обращению логики)
Как вы уже поняли, у вас будут проблемы с разрывами строк (в случае /*...*/
). Это можно решить, используя модификатор/опцию/флаг re.S
(который изменяет поведение .
) или используя [\s\S]
вместо .
(поскольку первый соответствует любому символу).
Но есть и другие проблемы. Вам нужно только найти окружающие вхождения строки или литералы комментариев. На самом деле вы не гарантируете, что они специально обернуты вокруг рассматриваемого public
. Я не уверен, сколько вы можете поместить в одну строку в Java, но если у вас есть произвольная строка, затем public
, а затем другая строка в одной строке, тогда ваше регулярное выражение будет соответствовать public
, потому что оно может найти "
до и после него. Даже если это невозможно, если у вас есть два блочных комментария на одном входе, то любой public
между этими двумя блочными комментариями вызовет совпадение. Таким образом, вам нужно найти способ утверждать только то, что ваш public
действительно внутри "..."
или /*...*/
, а не только то, что эти литералы можно найти где угодно слева или справа от него.
Следующее: совпадения не могут перекрываться. Но ваше совпадение включает в себя все, от начального до конечного литерала. Итак, если бы у вас было "public public"
, это привело бы только к одному совпадению. И захват здесь не поможет. Обычно хитрость, позволяющая избежать этого, заключается в использовании обходных путей (которые не включены в сопоставление). Но (как мы увидим позже) просмотр назад работает не так хорошо, как вы думаете, потому что он не может быть произвольной длины (это возможно только в .NET).
Теперь хуже всего. Что делать, если у вас есть "
внутри комментария? Это не должно считаться, верно? Что делать, если у вас есть //
или /*
или */
внутри строки? Это не должно считаться, верно? Как насчет '
внутри "
-строк и "
внутри '
-строк? Хуже того, как насчет \"
внутри "
-строки? Таким образом, для 100% надежности вам придется выполнить аналогичную проверку и для окружающих разделителей. И обычно именно здесь регулярные выражения достигают предела своих возможностей, и именно поэтому вам нужен правильный синтаксический анализатор, который проходит по входной строке и строит целое дерево вашего кода.
Но скажем, у вас никогда не будет литералов комментариев внутри строк и у вас никогда не будет кавычек внутри комментариев (или только совпадающих кавычек, потому что они составляют строку, а нам в любом случае не нужны public
внутри строк). Таким образом, мы в основном предполагаем, что каждый из рассматриваемых литералов правильно сопоставлен, и они никогда не вложены друг в друга. В этом случае вы можете использовать просмотр вперед, чтобы проверить, находитесь ли вы внутри или снаружи одного из литералов (фактически, несколько просмотров вперед). Я доберусь до этого в ближайшее время.
Но осталось еще одно. Что (?<!//).*public.*
не работает? Для этого достаточно, чтобы (?<!//)
совпадало в любой отдельной позиции. например если бы вы только что ввели // public
, движок попробовал бы отрицательный поиск назад прямо в начале строки (слева от начала строки), не нашел бы //
, затем использовал бы .*
для использования //
и пробела, а затем соответствует public
. На самом деле вам нужно (?<!//.*)public
. Это запустит просмотр назад с начальной позиции public
и просмотрит всю текущую строку влево. Но... это просмотр назад переменной длины, который поддерживается только .NET.
Но давайте посмотрим, как мы можем убедиться, что мы действительно находимся вне строки. Мы можем использовать просмотр вперед, чтобы просмотреть весь путь до конца ввода и проверить, есть ли на пути четное количество кавычек.
public(?=[^"]*("[^"]*"[^"]*)*$)
Теперь, если мы очень постараемся, мы также можем игнорировать экранированные кавычки внутри строки:
public(?=[^"]*("(?:[^"\\]|\\.)*"[^"]*)*$)
Поэтому, как только мы столкнемся с "
, мы примем либо символы без кавычек, символы без обратной косой черты, либо символ обратной косой черты и все, что следует за ним (это также позволяет экранировать символы обратной косой черты, так что в "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))
А как насчет однострочных комментариев? Трюк для эмуляции просмотра назад с переменной длиной обычно состоит в том, чтобы поменять местами строку и шаблон, что делает просмотр назад:
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
person
Martin Ender
schedule
13.12.2012
(?(id) yes|no)
в основном является регулярным выражением Python, если оператор. Если это может быть более простой способ проверить, каким было первое совпадение. Вы также можете сопоставить предыдущее совпадение, используя(?P=name)
или просто\number
, если вы не используете именованные группы. - person aquavitae   schedule 11.12.2012" /* \" " + public + " \" \*/ \\\" "
Является ли слово общедоступным внутри или снаружи? Отсутствие подстроки является безопасным признаком начала/конца комментария/строки, если вы не отслеживаете состояние с начала файла. (Я внутри строки/комментария или кода программы? Количество обратных слэшей перед кавычками четное или нечетное?) Если чудесным образом все реализовано регулярным выражением, то оно может рекурсивно повторять что-то сложное в каждой позиции и быть в тысячу раз медленнее, чем обычно. - person hynekcer   schedule 11.12.2012[m.span('keyword') for m in some_regex.finditer(text) if m.groupdict()['keyword'] is not None]
приемлемым? - person jfs   schedule 14.12.2012