Exemple pratique

Parfois, quand on met à jour du code, on peut avoir besoin de faire des rechercher/remplacer massifs. J'ai eu à transformer un code C JNI de la forme (*env)->maFonction(env, ...) en code C++ correspondant (toujours pour JNI) : env->maFonction(...). Intuitivement, on se dit qu'un bête rechercher/remplacer devrait pouvoir faire l'affaire : un premier pour remplacer (*env) par env, et un deuxième pour remplacer (env,  par (. Certes ^^ Sauf que ça fait faire en plusieurs fois, et qu'en pratique on peut faire mieux :p Surtout que là on pourrait finalement avoir des faux positifs si on utilise env comme argument ailleurs !

Rechercher

La première étape va être de trouver dans tous le fichier les parties à remplacer. J'ai utilisé Eclipse car on développe dessus, mais tout éditeur de code est capable de gérer des regexps (en général c'est une simple option à cocher dans la boite de dialogue de recherche). Le principe est simple : au lieu de chercher rigoureusement un texte dans le fichier, on va chercher un motif, voire même une succession de motifs. Il faut donc déterminer quels sont les motifs qui représentent notre recherche. 

Assez intuitivement, on voit qu'on peut découper la recherche en 3 motifs : 

  • (*env)
  • ->maFonction
  • (env,

La particularité de ces motifs est que autant les premiers et derniers sont immuables dans notre recherche, autant celui du milieu peut varier... La chaine qu'on va rechercher sera donc en fait la regexp composée par (*env), suivi de lettres non identifiées, suivi de (env,.

C'est là qu'on voit apparaitre la force des regexps : ils permettent de chercher un élément qu'on ne connait pas. L'opérateur (ou le symbole, je ne sais pas comment on dit... motif ou expression ?) pour représenter cela est '.' : le point permet de dire "n'importe quel symbole, mais un seul". Donc si je cherche "t.t.", ça va matcher avec "tata", "toto", mais aussi "titi", "tati", etc... C'est pratique ça !

Sauf que là, on ne veut pas matcher 1 caractère, mais une suite dont on ne connait pas le nombre... FACILE ! Avec les regexp, on a 2 opérateurs très utiles : * et + :

  • * permet de dire "je veux 0 ou plus occurences de ce qui précède". A savoir que "A*" va matcher la chaine vide, mais aussi "A", "AA", "AAA", ... 
  • + permet de dire la même chose, mais avec "1 occurence ou plus".

Dans notre exemple on pourrait donc utiliser l'un ou l'autre, mais par habitude je vais utiliser *. En le combinant avec l'opérateur , on peut donc chercher l'expression .* , à savoir "n'importe quel caractère, et ce n'importe quel nombre de fois". Donc en théorie en cherchant (*env).*(env,  , ça devrait tout reconnaitre, quel que soit le nom de la fonction... 

Oui mais non ^^ Là dans la partie gauche de l'expression on a utilisé * pour dire qu'on voulait matcher * !! Evil :p  En fait pour les caractères qui ont une signification particulière dans une regexp, on met le caractère d'échappement \ devant pour dire "ce n'est pas l'opérateur spécial que je veux, mais le caractère normal". Et en fait comme les parenthèses ont aussi une signification particulière (dans la plupart des éditeurs), il faut aussi mettre le caractère d'échappement devant...

On obtient donc la regexp suivante : \(\*env\).*\(env,  . Je vous accodre que c'est beaucoup moins beau et lisible que ce qu'on aurait voulu avoir, mais... ça fonctionne !

Remplacer

Donc là on a une belle expression qui permet de trouver tous les appels que l'on veut modifier, mais on doit encore s'occuper du remplacement... Pas de panique, la plupart des éditeurs qui permettent la recherche de regexp permettent aussi le rechercher/remplacer de celles-ci (et c'est là que ça devient très puissant). En fait on peut mettre dans une expression régulière des parenthèses (d'où le caractère d'échappement devant), qui permette de dire "ce que tu trouves ici, tu te le mets de côté pour que je puisse m'en servir plus tard". Chacune de ces occurences est alors numértées. Donc si je fais comme regexp "\(\*env\)(.*)\(env,", ça va mémoriser le nom de la fonction (avec la flèche devant) !! Mais alors comment on l'utilise par la suite ? C'est simple, les occurences sont numérotés, donc il suffit de saisir le numéro de l'occurence que l'on souhaite écrire (précédé du caractère d'échappement, bien entendu !) :

  • A chercher : \(\*env\)(.*)\(env,
  • A remplacer par : env\1(

ET CA MARCHE !

Ajustements

Bon, c'est un peu mystique au début, et en vrai ça foire sur certains cas ^^ Exemple : (*env)->maFonction(env). Là ça ne va pas trouver la virgule, donc ça ne matche pas, et donc ça ne remplace par... Que néni, il existe une solution ! On va dire que la virgule est optionnelle : si elle y est, on la prend, sinon, on ne la prend pas. Pour cela on va utiliser un autre opérateur : ? (c'est en gros l'inverse du + : on prend 0 ou 1 fois seulement...).

On cherchera donc \(\*env\)(.*)\(env,?, et là pour le coup on peut matcher (et remplacer) les 2 chaines ci-dessous :

(*env)->maFonction(env, ...)

(*env)->maFonction(env)

par

env->maFonction( ...)

env->maFonction()

Et on a gagné :p Alors oui, ça peut paraître long et compliqué au premier abord vu comme ça, mais quand on a l'habitude c'est très pratique et rapide à utiliser :). 

Si ça vous intéresser d'en savoir plus (il existe plein d'autres opérateurs très utiles), je ferai un deuxième billet pour exposer d'autres exemples d'utilisation avec ces opérateurs. Et si vous avez un doute et que vous voulez voir comment faire pour faire matcher avec un type de texte précis, n'hésitez pas à me demander ;)