Dans mon application Python, je dois écrire une expression régulière qui correspond à une boucle C++ for
ou while
terminée par un point-virgule (;
). Par exemple, cela devrait correspondre à ceci:
for (int i = 0; i < 10; i++);
... mais pas ceci:
for (int i = 0; i < 10; i++)
Cela a l'air trivial au premier abord, jusqu'à ce que vous réalisiez que le texte entre les parenthèses ouvrante et fermante peut contenir d'autres parenthèses, par exemple:
for (int i = funcA(); i < funcB(); i++);
J'utilise le module python.re. Pour le moment, mon expression habituelle est la suivante (j'ai laissé mes commentaires pour que vous puissiez mieux comprendre):
# 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*
Cela fonctionne parfaitement pour tous les cas ci-dessus, mais il se rompt dès que vous essayez de faire en sorte que la troisième partie de la boucle for contienne une fonction, comme ceci:
for (int i = 0; i < 10; doSomethingTo(i));
Je pense que ça casse car dès que vous mettez du texte entre les parenthèses ouvrante et fermante, le groupe "équilibré" correspond à celui qui contenait du texte, et donc la partie (?P=balanced)
ne fonctionne plus car elle ne correspond pas (en raison de la fait que le texte à l'intérieur de la parenthèse est différent).
Dans mon code Python, j'utilise les indicateurs VERBOSE et MULTILINE et crée l'expression régulière de la manière suivante:
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)
Quelqu'un peut-il suggérer une amélioration à cette expression régulière? Cela devient trop compliqué pour que je puisse avoir ma tête.
Vous pouvez écrire une petite routine très simple qui le fait, sans utiliser d'expression régulière:
pos
de manière à ce qu'il pointe juste avant le crochet d’ouverture après votre for
ou while
. openBr
à 0
.pos
, en lisant les caractères aux positions respectives, et incrémentez openBr
lorsque vous voyez un crochet d’ouverture, et décrémentez-le lorsque vous voyez un crochet de fermeture. Cela l'incrémentera une fois au début, pour le premier crochet d'ouverture dans "for (
", incrémentera et décrémentera un peu plus pour certains crochets entre les deux, et le remettra à 0
lorsque votre crochet for
sera fermé. openBr
est à nouveau 0
.La position d'arrêt est votre parenthèse fermante de for(...)
. Maintenant, vous pouvez vérifier s'il y a un point-virgule ou non.
C'est le genre de chose que vous ne devriez vraiment pas faire avec une expression régulière. Il suffit d'analyser la chaîne, un caractère à la fois, en gardant une trace des parenthèses ouvrantes/fermantes.
Si c'est tout ce que vous cherchez, vous n'avez certainement pas besoin d'un lexer/analyseur syntaxique C++ complet. Si vous voulez vous entraîner, vous pouvez écrire un petit analyseur récursif-décent, mais même un peu trop pour faire correspondre les parenthèses.
C'est un excellent exemple d'utilisation du mauvais outil pour le travail. Les expressions régulières ne gèrent pas très bien les sous-correspondances imbriquées de manière arbitraire. Ce que vous devriez faire à la place est d'utiliser un vrai lexer et un analyseur syntaxique (une grammaire pour C++ devrait être facile à trouver) et de rechercher des corps de boucles vides inattendus.
Je ne ferais même pas attention au contenu des parens.
Faites simplement correspondre une ligne commençant par for
et se terminant par un point-virgule:
^\t*for.+;$
Sauf si vous avez des instructions for
réparties sur plusieurs lignes, cela fonctionnera-t-il correctement?
Essayez cette expression rationnelle
^\s*(for|while)\s*
\(
(?P<balanced>
[^()]*
|
(?P=balanced)
\)
\s*;\s
J'ai enlevé le \( \)
enveloppant autour de (?P=balanced)
et déplacé le *
derrière la séquence any non paren. J'ai eu ce travail avec boost xpressive, et revérifié ce site ( Xpressive ) pour rafraîchir ma mémoire.
Greg a absolument raison. Ce type d'analyse ne peut pas être effectué avec des expressions régulières. Je suppose qu’il est possible de construire une monstruosité horrible qui fonctionnerait dans de nombreux cas, mais vous ne rencontrerez alors que quelque chose qui fonctionne.
Vous devez vraiment utiliser des techniques d'analyse plus traditionnelles. Par exemple, il est assez simple d’écrire un analyseur décent récursif pour faire ce dont vous avez besoin.
Je ne sais pas si regex gérerait quelque chose comme ça très bien. Essayez quelque chose comme ça
line = line.Trim();
if(line.StartsWith("for") && line.EndsWith(";")){
//your code here
}
Une autre pensée qui ignore les parenthèses et traite la for
comme une construction contenant trois valeurs délimitées par des points-virgules:
for\s*\([^;]+;[^;]+;[^;]+\)\s*;
Cette option fonctionne même lorsqu'elle est divisée en plusieurs lignes (une fois que MULTILINE est activée), mais suppose que for ( ... ; ... ; ... )
est la seule construction valide. Ne fonctionnerait donc pas avec une construction for ( x in y )
ou avec d'autres écarts.
Suppose également qu'il n'y a pas de fonction contenant des points-virgules en tant qu'arguments, tels que:
for ( var i = 0; i < ListLen('a;b;c',';') ; i++ );
Que ce soit un cas probable ou non dépend de ce que vous faites réellement.
Comme Frank l'a suggéré, c'est mieux sans regex. Voici (un vilain) one-liner:
match_string = orig_string[orig_string.index("("):len(orig_string)-orig_string[::-1].index(")")]
Correspondant à la ligne de troll est mentionné dans son commentaire:
orig_string = "for (int i = 0; i < 10; doSomethingTo(\"(\"));"
match_string = orig_string[orig_string.index("("):len(orig_string)-orig_string[::-1].index(")")]
renvoie (int i = 0; i < 10; doSomethingTo("("))
Cela fonctionne en parcourant la chaîne en avant jusqu'à la première paren ouverte, puis en arrière jusqu'à la première paren de fermeture. Il utilise ensuite ces deux index pour trancher la chaîne.