2009-02-07 12 views
33

W mojej aplikacji Pythona, muszę napisać wyrażenie regularne, które pasuje do C++ for lub while pętlę, która została rozwiązana ze średnikiem (;). Na przykład, należy dopasować to:Wyrażenie regularne do wykrywania średnik zakończony C++ dla & przy jednoczesnym pętle

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

... ale nie w ten sposób:

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

To wygląda na pierwszy rzut oka banalne, dopóki nie uświadomimy sobie, że tekst pomiędzy otwarciem i zamknięciem nawiasu mogą zawierać inny nawias, na przykład:

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

Używam modułu python.re. Teraz moje wyrażenie regularne wygląda następująco (Zostawiłam moje komentarze w tak można go zrozumieć łatwiej):

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

Działa to doskonale dla wszystkich powyższych przypadkach, ale zrywa jak najszybciej spróbować zrobić trzecia część pętli for zawierać funkcję tak:

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

myślę, że łamie bo tak szybko, jak można umieścić tekst pomiędzy otwierającym i zamykającym nawiasie, grupa „zrównoważony” pasuje który zawierał tekst, a zatem część (?P=balanced) nie działa już więcej, ponieważ nie będzie pasować (ze względu na fakt, że tekst wewnątrz nawiasów jest inny).

W moim kodu Pythona Używam gadatliwym i multilinii flagi i tworzenia wyrażenia regularnego tak:

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) 

Może ktoś sugerują poprawę do tego wyrażenia regularnego? To zbyt skomplikowane, żebym się obrócił.

Odpowiedz

97

można napisać trochę, bardzo prostą procedurę, która robi to bez użycia wyrażenia regularnego:

  • Ustaw licznik pozycji pos tak, aby punkty tuż przed otwarciem wspornik po twoim for lub while.
  • Ustawienie licznika otwartych nawiasów openBr na 0.
  • Teraz zwiększaj liczbę pos, odczytywanie znaków w odpowiednich pozycjach i zwiększaj openBr, gdy zobaczysz nawias otwierający i zmniejsz go, gdy zobaczysz nawias zamykający. To zwiększy ją raz na początku dla pierwszego nawiasu otwierającego w "for (", zwiększając i zmniejszając nieco więcej w przypadku niektórych nawiasów pośrednich i ustawiając go ponownie na 0 po zamknięciu wspornika for.
  • Zatrzymaj się ponownie, gdy openBr ponownie uzyskasz 0.

Pozycja zatrzymania to konsola zamykająca for(...). Teraz możesz sprawdzić, czy istnieje znak średnika, czy nie.

+0

Dzięki - myślę, że wyrazy regularne naprawdę są niewłaściwym narzędziem do pracy! – Thomi

+10

Należy również wziąć pod uwagę komentarze i łańcuchy, które rzucają ten algorytm. –

+2

Możesz wcześniej usunąć komentarze i ciągi za pomocą wyrażenia regularnego. :) Lub wprowadzić więcej zmiennych, takich jak openBr, które wskazują, czy jesteś wewnątrz komentarza (i jakiego typu komentarz, więc wiesz, co zamyka go) lub ciąg. – Frank

20

Jest to coś, czego nie powinno się robić zwykłym wyrażeniem. Po prostu przeanalizuj ciąg znaków po jednym znaku naraz, śledząc nawiasy otwierające/zamykające.

Jeśli to wszystko, czego szukasz, zdecydowanie nie potrzebujesz pełnego gramatyki lexer/parsera języka C++. Jeśli chcesz ćwiczyć, możesz napisać trochę parser rekursywny, ale nawet to trochę za dopasowywanie nawiasów.

+0

Właściwie, z doładowaniem: Xpressive i prawdopodobnie Python, możesz mieć regexp, które wykonują zrównoważone dopasowanie paren. –

8

Jest to doskonały przykład użycia niewłaściwego narzędzia do pracy. Wyrażenia regularne nie radzą sobie z bardzo dobrze zagnieżdżonymi podzakresami. Zamiast tego należy użyć prawdziwego lexera i parsera (gramatyka dla C++ powinna być łatwa do znalezienia) i szukać nieoczekiwanie pustych ciałek pętli.

+1

+1, Ściśle mówiąc, wyrażenia regex w ogóle nie obsługują wyrażeń zagnieżdżonych. Wyrażenia regularne, które obsługują wyrażenia zagnieżdżone, przechodzą do gramatyk bezkontekstowych. – JaredPar

+0

Zgadzam się z używaniem flex/yacc lub podobnego. Ale czy gramatyka C++ jest naprawdę łatwa do znalezienia? Czy ktoś ma link? Pamiętam, że ludzie z CDT/Eclipse mieli problemy z analizowaniem danych wejściowych C++. – Frank

+0

Być może nie; C++ jest oczywiście notorycznie trudne do parsowania. Ponieważ pierwotne pytanie nie wymaga pełnej analizy semantycznej źródła wejściowego, prostszy, niepełny analizator składni mógłby prawdopodobnie wykonać tę pracę równie dobrze. –

2

Nie zwracałbym nawet uwagi na zawartość parens.

Wystarczy dopasować dowolną linię, która zaczyna się i kończy z for średnikiem:

^\t*for.+;$ 

chyba że masz for oświadczenia podzielone na kilka linii, które będą pracować w porządku?

+0

To prawdopodobnie nie wystarcza, ponieważ ludzie dzielą się na instrukcje() na wiele linii. – Frank

+0

dehmann ma rację - chodzi o to, że wzorzec dopasowuje przykłady z rzeczywistej podstawy kodu, więc musi być w stanie obsłużyć wszystkie poprawne konstrukcje pętli, w tym te wieloliniowe. – Thomi

1

Greg ma całkowitą rację. Tego rodzaju analizowanie nie może odbywać się za pomocą wyrażeń regularnych. Przypuszczam, że możliwe jest zbudowanie straszliwej monstrualności, która zadziałałaby w wielu przypadkach, ale wtedy po prostu natkniesz się na coś, co robi.

Naprawdę trzeba użyć bardziej tradycyjnych technik analizowania. Na przykład, całkiem proste jest napisanie rekurencyjnego przyzwoitego parsera, aby zrobić to, czego potrzebujesz.

1

Nie wiem, że regex za bardzo poradziłby sobie z czymś takim. Spróbuj coś takiego

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

+1. Oczywiście mówimy tutaj o Pythonie, więc składnia jest trywialnie inna. Ale jeśli właściwie nie analizujesz C, nie ma powodu, aby szukać czegoś innego niż ")" na końcu linii "dla". – bobince

1

Inna myśl, że ignoruje nawiasy i traktuje for jako konstrukt gospodarstwa trzy średnik rozdzielany wartości:

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

Opcja ta działa nawet wtedy, gdy podzielone na wiele linii (raz włączony MULTILINE), ale zakłada, że ​​for (... ; ... ; ...) jest jedyny prawidłowy konstrukt, więc nie działałby z konstrukcją for (x in y) lub innymi odchyleniami.

zakłada również, że nie istnieją żadne funkcje zawierające średników jako argumenty, takie jak:

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

czy jest to prawdopodobne przypadku zależy od tego, czego rzeczywiście robi to dla.

2

Spróbuj regexp

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

I usunięte zawijania \(\) około (?P=balanced) i przeniesiony * się za każdym nie nawias sekwencji. Miałem tę pracę z boostiem xpressive i ponownie sprawdziłem tę stronę (Xpressive), aby odświeżyć moją pamięć.

0

Jak zasugerował Frank, najlepiej bez regex.Oto (brzydki) jedno-liner:

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

Dopasowanie troll linię est mowa w jego komentarz:

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

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

ten działa poprzez uruchomienie przez ciąg do przodu, dopóki nie osiągnie pierwsze otwarte okienko, a następnie do tyłu, aż osiągnie pierwsze okienko zamykające. Następnie używa tych dwóch indeksów, aby podzielić ciąg znaków.

Powiązane problemy