it-swarm.com.de

Regulärer Ausdruck zum Ermitteln von Semikolons, die mit C++ für und while-Schleifen beendet wurden

In meiner Python-Anwendung muss ich einen regulären Ausdruck schreiben, der einer for- oder while-Schleife entspricht, die mit einem Semikolon (;) abgeschlossen wurde. Zum Beispiel sollte es dazu passen:

for (int i = 0; i < 10; i++);

... aber nicht das:

for (int i = 0; i < 10; i++)

Das sieht auf den ersten Blick trivial aus, bis Sie feststellen, dass der Text zwischen der öffnenden und schließenden Klammer andere Klammern enthalten kann, zum Beispiel:

for (int i = funcA(); i < funcB(); i++);

Ich verwende das python.re-Modul. Im Moment sieht mein regulärer Ausdruck so aus (ich habe meine Kommentare hinterlassen, damit Sie ihn leichter verstehen können):

# 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*

Dies funktioniert perfekt für alle oben genannten Fälle, bricht jedoch ab, sobald Sie versuchen, den dritten Teil der for-Schleife eine Funktion enthalten zu lassen.

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

Ich denke, es bricht, weil, sobald Sie etwas Text zwischen die öffnende und schließende Klammer setzen, die "ausgeglichene" Gruppe mit dem enthaltenen Text übereinstimmt und somit der (?P=balanced)-Teil nicht mehr funktioniert, da er nicht passt (aufgrund der Tatsache, dass der Text in der Klammer anders ist).

In meinem Python-Code verwende ich die VERBOSE- und MULTILINE-Flags und erstelle den regulären Ausdruck wie folgt:

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)

Kann jemand eine Verbesserung dieses regulären Ausdrucks vorschlagen? Es wird zu kompliziert für mich, um meinen Kopf herumzubringen.

34
Thomi

Sie könnten eine kleine, sehr einfache Routine schreiben, die es tut, ohne einen regulären Ausdruck zu verwenden:

  • Stellen Sie einen Positionszähler pos so ein, dass er direkt vor der öffnenden Klammer nach Ihrer for oder while zeigt. 
  • Setzen Sie einen Zähler für offene Klammern openBr auf 0.
  • Erhöhen Sie nun weiterhin pos, lesen Sie die Zeichen an den entsprechenden Positionen und erhöhen Sie openBr, wenn Sie eine öffnende Klammer sehen, und verringern Sie sie, wenn Sie eine schließende Klammer sehen. Dadurch wird es am Anfang einmal für die erste öffnende Klammer in "for (" inkrementiert. Inkrementieren und dekrementieren Sie einige Klammern dazwischen und setzen Sie es wieder auf 0, wenn Ihre for-Klammer geschlossen wird. 
  • Stoppen Sie also, wenn openBr wieder 0 ist.

Die Halteposition ist Ihre schließende Klammer von for(...). Jetzt können Sie überprüfen, ob ein Semikolon folgt oder nicht.

108
Frank

Dies ist etwas, was Sie eigentlich nicht mit einem regulären Ausdruck tun sollten. Analysieren Sie die Zeichenfolge nur einzeln, und achten Sie auf das Öffnen und Schließen von Klammern.

Wenn dies alles ist, wonach Sie suchen, brauchen Sie definitiv keinen ausgewachsenen C++ - Grammatik-Lexer/Parser. Wenn Sie etwas üben möchten, können Sie einen rekursiv-anständigen Parser schreiben, aber selbst für ein paar passende Klammern ist das ein bisschen viel.

20
Jesse Beder

Dies ist ein hervorragendes Beispiel für die Verwendung des falschen Tools für den Job. Reguläre Ausdrücke behandeln willkürlich verschachtelte Unterkämpfe nicht sehr gut. Stattdessen sollten Sie einen echten Lexer und Parser verwenden (eine Grammatik für C++ sollte leicht zu finden sein) und nach unerwartet leeren Schleifenkörpern suchen.

8
Greg Hewgill

Ich würde nicht einmal auf den Inhalt der Eltern achten.

Passen Sie einfach jede Zeile an, die mit for beginnt und mit Semikolon endet:

^\t*for.+;$

Wenn Sie for-Anweisungen nicht über mehrere Zeilen aufgeteilt haben, funktioniert das gut.

2
Peter Boughton

Versuchen Sie diese Regexp

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

Ich entfernte die Umhüllung \( \) um (?P=balanced) und verschob den * hinter die any not paren-Sequenz. Ich hatte diese Arbeit mit boost xpressive und überprüfte diese Website ( Xpressive ), um mein Gedächtnis aufzufrischen.

2
Bill Perkins

Greg ist absolut richtig. Diese Art der Analyse kann nicht mit regulären Ausdrücken durchgeführt werden. Ich nehme an, es ist möglich, eine schreckliche Monstrosität aufzubauen, die in vielen Fällen funktionieren würde, aber dann stoßen Sie nur auf etwas, das funktioniert.

Sie müssen wirklich traditionelle Parsing-Techniken verwenden. Beispielsweise ist es ziemlich einfach, einen rekursiven anständigen Parser zu schreiben, um das zu tun, was Sie brauchen. 

1
Foredecker

Ich weiß nicht, dass Regex mit so etwas sehr gut umgehen würde. Versuchen Sie so etwas

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

Ein weiterer Gedanke, der Klammern ignoriert und die for als Konstrukt behandelt, das drei durch Semikolons getrennte Werte enthält:

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

Diese Option funktioniert auch, wenn sie über mehrere Zeilen aufgeteilt wird (sobald MULTILINE aktiviert wurde). Es wird jedoch davon ausgegangen, dass for ( ... ; ... ; ... ) das einzig gültige Konstrukt ist. Es funktioniert also nicht mit einem for ( x in y )-Konstrukt oder anderen Abweichungen.

Es wird auch davon ausgegangen, dass es keine Funktionen gibt, die Semikolons als Argumente enthalten, z.

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

Ob dies wahrscheinlich ist, hängt davon ab, wofür Sie dies tatsächlich tun.

1
Peter Boughton

Ein bisschen zu spät zur Party, aber ich denke reguläre Ausdrücke sind nicht das richtige Werkzeug für den Job .

Das Problem ist, dass Sie auf Edge-Fälle stoßen, die dem regulären Ausdruck eine übermäßige Komplexität verleihen würden. @ est erwähnt eine Beispielzeile :

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

Dieses String-Literal enthält eine (unsymmetrische!) Klammer, die die Logik verletzt. Anscheinend müssen Sie den Inhalt von String-Literalen ignorieren. Dazu müssen Sie die doppelten Anführungszeichen berücksichtigen. Zeichenkettenliterale selbst können jedoch doppelte Anführungszeichen enthalten. Versuchen Sie zum Beispiel Folgendes:

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

Wenn Sie dies mit regulären Ausdrücken ansprechen, wird Ihr Muster dadurch noch komplexer.

Ich denke, Sie sind besser dran, die Sprache zu analysieren. Sie können beispielsweise ein Spracherkennungs-Tool wie ANTLR verwenden. ANTLR ist ein Parser-Generator-Tool, mit dem auch ein Parser in Python generiert werden kann. Sie müssen eine Grammatik angeben, die die Zielsprache definiert, in Ihrem Fall C++. Es gibt bereits zahlreiche Grammatiken für viele Sprachen, Sie können also einfach die C++ - Grammatik .

Dann können Sie den Parserbaum leicht durchgehen und nach leeren Anweisungen als while oder for Schleifenkörper suchen.

0
MC Emperor

Wie Frank vorschlug, ist dies am besten ohne Regex. Hier ist ein hässlicher Einzeiler:

match_string = orig_string[orig_string.index("("):len(orig_string)-orig_string[::-1].index(")")]

Passend zur Trolllinie est, die in seinem Kommentar erwähnt wurde:

orig_string = "for (int i = 0; i < 10; doSomethingTo(\"(\"));"
match_string = orig_string[orig_string.index("("):len(orig_string)-orig_string[::-1].index(")")]

gibt (int i = 0; i < 10; doSomethingTo("(")) zurück

Dies funktioniert, indem Sie die Saite vorwärts durchlaufen, bis sie den ersten geöffneten Paren erreicht, und dann rückwärts, bis sie den ersten schließenden Paren erreicht. Diese zwei Indizes werden dann zum Schneiden der Zeichenfolge verwendet.

0
bendl