Регулярное выражение для обнаружения замкнутых циклов С++ для циклов while и while

В моем приложении Python мне нужно написать регулярное выражение, которое соответствует циклу С++ 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)

Может ли кто-нибудь предложить улучшение этого регулярного выражения? Мне становится слишком сложно, чтобы я опустил голову.

Ответ 1

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

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

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

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

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

^\t*for.+;$

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

Ответ 5

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

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

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

Ответ 6

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

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

Ответ 7

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

line = line.Trim();
if(line.StartsWith("for") && line.EndsWith(";")){
    //your code here
}

Ответ 8

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

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

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

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

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

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

Ответ 9

Не решение Python (возможно, вы могли бы написать обертку...)

Сначала загрузите lexertl (http:/www.benhanson.net/lexertl.html), затем:

#include <algorithm>
#include "lexertl/generator.hpp"
#include <iostream>
#include "lexertl/lookup.hpp"

int main()
{
    lexertl::rules rules_;
    lexertl::state_machine sm_;

    rules_.add_state("FW");
    rules_.add_state("SEMI");
    rules_.add_state("NESTED");

    rules_.add("*", "[/][/].*|[/][*](.|\n)*?[*][/]|[\"](.|\\\")*[\"]",
        rules_.skip(), ".");
    rules_.add("INITIAL", "for\\s*\\([^;]*;[^;]*;|while\\s*\\(",
        rules_.skip(), "FW");
    rules_.add("FW", "\\)", rules_.skip(), "SEMI");
    rules_.add("FW,NESTED", "\\(", ">NESTED");
    rules_.add("NESTED", "\\)", rules_.skip(), "<");
    rules_.add("SEMI", "\\s*;", 1, "INITIAL");
    rules_.add("SEMI", ".|\n", rules_.skip(), "INITIAL");
    lexertl::generator::build (rules_, sm_);

    lexertl::memory_file buff_("main.cpp");
    const char *start_ = buff_.data ();
    const char *end_ = start_ + buff_.size ();
    lexertl::crmatch results_(start_, end_);

    do
    {
        lexertl::lookup(sm_, results_);

        if (results_.id == 1)
        {
            std::cout << "found on line " <<
                std::count(start_, results_.end, '\n') + 1 << '\n';
        }
    } while (results_.id != sm_.eoi());

    return 0;
}

Ответ 10

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

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

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

Ответ 11

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

Проблема в том, что вы столкнетесь с крайними случаями, которые добавят постороннюю сложность к регулярному выражению. @est упомянул пример строки:

for (int i = 0; i < 10; doSomethingTo("("));

Этот строковый литерал содержит (несбалансированную!) Скобку, которая нарушает логику. По-видимому, вы должны игнорировать содержимое строковых литералов. Для этого необходимо учитывать двойные кавычки. Но сами строковые литералы могут содержать двойные кавычки. Например, попробуйте это:

for (int i = 0; i < 10; doSomethingTo("\"(\\"));

Если вы решите это с помощью регулярных выражений, это еще больше усложнит ваш шаблон.

Я думаю, что вам лучше разбирать язык. Например, вы можете использовать инструмент распознавания языков, такой как ANTLR. ANTLR - это инструмент генератора парсеров, который также может генерировать парсер в Python. Вы должны предоставить грамматику, определяющую целевой язык, в вашем случае C++. Уже существует множество грамматик для многих языков, так что вы можете просто взять грамматику C++.

Тогда вы можете легко ходить по дереву синтаксического анализа, поиск пустых заявлений, как в while или for тела цикла.