Регулярное выражение для обнаружения C++ в конце и в конце цикла C++
В моем приложении Python мне нужно написать регулярное выражение, соответствующее C++ for
или же while
цикл, который был завершен точкой с запятой (;
). Например, оно должно соответствовать этому:
for (int i = 0; i < 10; i++);
... но не это:
for (int i = 0; i < 10; i++)
На первый взгляд это выглядит тривиально, пока вы не поймете, что текст между открывающей и закрывающей скобками может содержать другие скобки, например:
for (int i = funcA(); i < funcB(); i++);
Я использую модуль python.re. Прямо сейчас мое регулярное выражение выглядит следующим образом (я оставил свои комментарии, чтобы вам было легче это понять):
# match any line that begins with a "for" or "while" statement:
^\s*(for|while)\s*
\( # match the initial opening parenthesis
# Now make a named group 'balanced' which matches a balanced substring.
(?P<balanced>
# A balanced substring is either something that is not a parenthesis:
[^()]
| # …or a parenthesised string:
\( # A parenthesised string begins with an opening parenthesis
(?P=balanced)* # …followed by a sequence of balanced substrings
\) # …and ends with a closing parenthesis
)* # Look for a sequence of balanced substrings
\) # Finally, the outer closing parenthesis.
# must end with a semi-colon to match:
\s*;\s*
Это прекрасно работает для всех вышеперечисленных случаев, но прерывается, как только вы пытаетесь сделать третью часть цикла for содержащей функцию, например:
for (int i = 0; i < 10; doSomethingTo(i));
Я думаю, что это ломается, потому что как только вы помещаете некоторый текст между открывающей и закрывающей скобками, "сбалансированная" группа совпадает с текстом, и, таким образом, (?P=balanced)
part больше не работает, так как не будет совпадать (из-за того, что текст внутри скобок отличается).
В своем коде Python я использую флаги VERBOSE и MULTILINE и создаю регулярное выражение следующим образом:
REGEX_STR = r"""# match any line that begins with a "for" or "while" statement:
^\s*(for|while)\s*
\( # match the initial opening parenthesis
# Now make a named group 'balanced' which matches
# a balanced substring.
(?P<balanced>
# A balanced substring is either something that is not a parenthesis:
[^()]
| # …or a parenthesised string:
\( # A parenthesised string begins with an opening parenthesis
(?P=balanced)* # …followed by a sequence of balanced substrings
\) # …and ends with a closing parenthesis
)* # Look for a sequence of balanced substrings
\) # Finally, the outer closing parenthesis.
# must end with a semi-colon to match:
\s*;\s*"""
REGEX_OBJ = re.compile(REGEX_STR, re.MULTILINE| re.VERBOSE)
Кто-нибудь может предложить улучшение этого регулярного выражения? Это становится слишком сложным для меня, чтобы разобраться.
9 ответов
Вы можете написать небольшую, очень простую процедуру, которая делает это, без использования регулярного выражения:
- Установить счетчик положения
pos
так что это указывает непосредственно перед открывающей скобкой после вашегоfor
или жеwhile
, - Установить счетчик открытых скобок
openBr
в0
, - Теперь продолжайте увеличивать
pos
, чтение символов в соответствующих позициях и увеличениеopenBr
когда вы видите открывающую скобку, и уменьшите ее, когда вы видите закрывающую скобку. Это будет увеличивать его один раз в начале, для первой открывающей скобки в "for (
msgstr ", увеличьте и уменьшите еще несколько скобок между ними и установите его обратно0
Когда вашfor
скобка закрывается. - Итак, остановитесь, когда
openBr
является0
снова.
Положение остановки - Ваша заключительная скобка for(...)
, Теперь вы можете проверить, идет ли точка с запятой или нет.
Это то, что вы не должны делать с регулярным выражением. Просто анализируйте строку по одному символу за раз, отслеживая открывающие / закрывающие скобки.
Если это все, что вам нужно, вам определенно не нужен полноценный лексер / парсер грамматики C++. Если вы хотите попрактиковаться, вы можете написать небольшой рекурсивно-приличный синтаксический анализатор, но даже это немного для совпадения скобок.
Это отличный пример использования неправильного инструмента для работы. Регулярные выражения не очень хорошо обрабатывают произвольно вложенные под-совпадения. Вместо этого вы должны использовать реальный лексер и парсер (грамматику для C++ легко найти) и искать неожиданно пустые тела цикла.
Попробуйте это регулярное выражение
^\s*(for|while)\s*
\(
(?P<balanced>
[^()]*
|
(?P=balanced)
\)
\s*;\s
Я снял упаковку \( \)
вокруг (?P=balanced)
и переместил *
за какой-то непаренной последовательностью. У меня была эта работа с boost xpressive, и я перепроверил этот сайт ( Xpressive), чтобы освежить мою память.
Немного опоздал на вечеринку, но я думаю, что регулярные выражения не являются подходящим инструментом для работы.
Проблема в том, что вы столкнетесь с крайними случаями, которые добавят постороннюю сложность к регулярному выражению. est упомянул пример строки:
for (int i = 0; i < 10; doSomethingTo("("));
Этот строковый литерал содержит (несбалансированную!) Скобку, которая нарушает логику. По-видимому, вы должны игнорировать содержимое строковых литералов. Для этого необходимо учитывать двойные кавычки. Но сами строковые литералы могут содержать двойные кавычки. Например, попробуйте это:
for (int i = 0; i < 10; doSomethingTo("\"(\\"));
Если вы решите это с помощью регулярных выражений, это еще больше усложнит ваш шаблон.
Я думаю, что вам лучше разбирать язык. Например, вы можете использовать инструмент распознавания языков, такой как ANTLR. ANTLR - это инструмент генератора парсеров, который также может генерировать парсер в Python. Вы должны предоставить грамматику, определяющую целевой язык, в вашем случае C++. Уже существует множество грамматик для многих языков, так что вы можете просто взять грамматику C++.
Затем вы можете легко пройтись по дереву парсера, ища пустые операторы как while
или же for
тело петли.
Я бы даже не обратил внимания на содержание паренов.
Просто сопоставьте любую строку, которая начинается с for
и заканчивается точкой с запятой:
^\t*for.+;$
Если у вас нет for
операторы разделены на несколько строк, которые будут работать нормально?
Я не знаю, что регулярное выражение очень хорошо справится с чем-то подобным. Попробуйте что-то вроде этого
line = line.Trim();
if(line.StartsWith("for") && line.EndsWith(";")){
//your code here
}
Как предположил Фрэнк, это лучше без регулярных выражений. Вот (уродливый) один вкладыш:
match_string = orig_string[orig_string.index("("):len(orig_string)-orig_string[::-1].index(")")]
Соответствие линии тролля, упомянутой в его комментарии:
orig_string = "for (int i = 0; i < 10; doSomethingTo(\"(\"));"
match_string = orig_string[orig_string.index("("):len(orig_string)-orig_string[::-1].index(")")]
возвращается (int i = 0; i < 10; doSomethingTo("("))
Это работает, проходя через струну вперед до тех пор, пока она не достигнет первого открытого парена, а затем назад, пока не достигнет первого закрывающего парена. Затем он использует эти два индекса, чтобы разрезать строку.
Еще одна мысль, которая игнорирует скобки и рассматривает for
как конструкция, содержащая три значения, разделенные точкой с запятой:
for\s*\([^;]+;[^;]+;[^;]+\)\s*;
Эта опция работает даже при разделении на несколько строк (после включения MULTILINE), но предполагает, что for ( ... ; ... ; ... )
является единственной допустимой конструкцией, поэтому не будет работать с for ( x in y )
построить или другие отклонения.
Также предполагается, что нет функций, содержащих точки с запятой в качестве аргументов, таких как:
for ( var i = 0; i < ListLen('a;b;c',';') ; i++ );
Является ли это вероятным случаем, зависит от того, для чего вы на самом деле делаете это.
Грег абсолютно прав. Этот вид анализа не может быть сделан с регулярными выражениями. Я предполагаю, что возможно создать какое-то чудовищное чудовище, которое будет работать во многих случаях, но тогда вы просто столкнетесь с чем-то, что работает.
Вам действительно нужно использовать более традиционные методы разбора. Например, довольно просто написать рекурсивный приличный парсер, который сделает то, что вам нужно.