Регулярное выражение для обнаружения завершенной точки с запятой C ++ для циклов & while - PullRequest
34 голосов
/ 07 февраля 2009

В моем приложении 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) больше не работает, так как не будет совпадение (из-за того, что текст внутри скобки отличается).

В своем коде 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 ]

108 голосов
/ 08 февраля 2009

Вы можете написать небольшую, очень простую процедуру, которая делает это, без использования регулярного выражения:

  • Установите счетчик положения pos так, чтобы он указывал непосредственно перед открывающей скобкой после ваших for или while.
  • Установить счетчик открытых скобок openBr на 0.
  • Теперь продолжайте увеличивать pos, читая символы в соответствующих позициях, и увеличивать openBr, когда вы видите открывающую скобку, и уменьшать ее, когда вы видите закрывающую скобку. Это будет увеличивать его один раз в начале, для первой открывающей скобки в «for (», увеличивать и уменьшать еще несколько для некоторых скобок между ними, и устанавливать обратно в 0, когда ваша скобка for закрывается.
  • Итак, остановитесь, когда openBr будет 0 снова.

Позиция остановки - ваша заключительная скобка for(...). Теперь вы можете проверить, идет ли точка с запятой или нет.

20 голосов
/ 07 февраля 2009

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

Если это все, что вам нужно, вам определенно не нужен полноценный лексер / парсер грамматики C ++. Если вы хотите попрактиковаться, вы можете написать небольшой рекурсивно-приличный синтаксический анализатор, но даже это немного для совпадения скобок.

8 голосов
/ 07 февраля 2009

Это отличный пример использования неправильного инструмента для работы. Регулярные выражения не очень хорошо обрабатывают произвольно вложенные вложенные совпадения. Вместо этого вы должны использовать настоящий лексер и парсер (грамматику для C ++ легко найти) и искать неожиданно пустые тела цикла.

2 голосов
/ 08 февраля 2009

Попробуйте это регулярное выражение

^\s*(for|while)\s*
\(
(?P<balanced>
[^()]*
|
(?P=balanced)
\)
\s*;\s

Я снял обтекание \( \) вокруг (?P=balanced) и переместил * в конец любой непаренной последовательности. У меня была эта работа с boost xpressive, и я перепроверил этот сайт ( Xpressive ), чтобы освежить мою память.

2 голосов
/ 07 февраля 2009

Я бы даже не обратил внимания на содержимое паренов.

Просто сопоставьте любую строку, которая начинается с for и заканчивается точкой с запятой:

^\t*for.+;$

Если у вас нет for операторов, разделенных на несколько строк, это будет работать нормально?

1 голос
/ 08 февраля 2009

Еще одна мысль, которая игнорирует скобки и обрабатывает for как конструкцию, содержащую три значения, разделенных точкой с запятой:

for\s*\([^;]+;[^;]+;[^;]+\)\s*;

Эта опция работает даже при разделении на несколько строк (после включения MULTILINE), но предполагает, что for ( ... ; ... ; ... ) является единственной допустимой конструкцией, поэтому не будет работать с конструкцией for ( x in y ) или другими отклонениями.

Также предполагается, что нет функций, содержащих точки с запятой в качестве аргументов, таких как:

for ( var i = 0; i < ListLen('a;b;c',';') ; i++ );

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

1 голос
/ 08 февраля 2009

Я не знаю, что регулярное выражение справится с чем-то подобным очень хорошо. Попробуйте что-то вроде этого

line = line.Trim();
if(line.StartsWith("for") && line.EndsWith(";")){
    //your code here
}
1 голос
/ 07 февраля 2009

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

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

0 голосов
/ 24 мая 2017

Как предположил Фрэнк, это лучше без регулярных выражений. Вот (уродливый) один вкладыш:

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("("))

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...