Регулярное выражение для обнаружения 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++ );

Является ли это вероятным случаем, зависит от того, для чего вы на самом деле делаете это.

Грег абсолютно прав. Этот вид анализа не может быть сделан с регулярными выражениями. Я предполагаю, что возможно создать какое-то чудовищное чудовище, которое будет работать во многих случаях, но тогда вы просто столкнетесь с чем-то, что работает.

Вам действительно нужно использовать более традиционные методы разбора. Например, довольно просто написать рекурсивный приличный парсер, который сделает то, что вам нужно.

Другие вопросы по тегам