• Aucun résultat trouvé

3 Programmation avec les expressions r´ eguli` eres

La syntaxe concr`ete est celle introduite dans les sections pr´ec´edentes. L’arbre de syntaxe abstraite est donn´e `a la figure 4.

Fig. 4 – Syntaxe abstraite de /\*([^*]|\*+[^*/])*\*+/

·

/ ·

\* ·

*

|

[^*] ·

+

\*

[^*/]

·

+

\*

/

3 Programmation avec les expressions r´ eguli` eres

Dans cette section nous nous livrons `a une description rapide de quelques outils qui emploient les expressions r´eguli`eres de fa¸con cruciale. Le sujet est extrˆemement vaste, car le formalisme des expressions r´eguli`eres est suffisamment expressif pour permettre de nombreuses recherches dans les fichiers textes, voire de nombreuses transformations de ces fichiers. Le livre de Jeffrey E. F. Friedl [3] traite des expressions r´eguli`ere du point de vue des outils.

3.1 Dans l’interpr`ete de commandes Unix

Leshell c’est `a dire l’interpr`ete de commandes Unix reconnaˆıt quelques expressions r´eguli`eres qu’il applique aux noms des fichiers du r´epertoire courant. La syntaxe concr`ete des motifs est franchement particuli`ere. Notons simplement que? repr´esente un caract`ere quelconque (not´e pr´ec´edemment .), tandis que *repr´esente un mot quelconque (not´e pr´ec´edemment .*), et que l’alternative se note `a peu pr`es{p1,p2} (pourp1|p2). . .

Ainsi, on pourra effacer tous les fichiers objets Java par la commande :

% /bin/rm *.class

Dans le mˆeme ordre d’id´ee, on peut compter toutes les lignes des fichiers source du r´epertoire courant :

% wc -l *.java

La commande wc1 (pour word count) compte les lignes, mots et caract`eres dans les fichiers donn´es en argument. L’option (( -l )) restreint l’affichage au seul compte des lignes. Enfin, on peut faire la liste de tous les fichiers du r´epertoire courant dont le nom comprend de un `a trois caract`eres :

% ls {?,??,???}

3.2 Recherche de lignes dans un fichier, la commande egrep

La commande egrep motif fichier affiche toutes les lignes de fichier dont motif filtre un sous-mot (et non pas la ligne enti`ere). La syntaxe des motifs est relativement conforme `a ce que nous avons d´ej`a d´ecrit. Supposons, comme c’est souvent le cas, que le fichier/usr/share/dict/

frenchde votre machine Unix est un dictionnaire fran¸cais, donn´e sous forme d’un fichier texte,

`

a raison d’un mot par ligne. On peut alors trouver les mots qui contiennent au moins six fois la lettre ((i))de cette mani`ere.

% egrep ’i.*i.*i.*i.*i.*i’ /usr/share/dict/french indivisibilit´e

On notera que l’argument motif est donn´e entre simples quotes (( ’ )), ceci afin d’´eviter que le shell n’interpr`ete les ´etoiles comme faisant partie d’un motif `a appliquer aux noms de fichier. Ce n’est en fait pas toujours n´ecessaire, mais c’est toujours prudent.

3.3 En Java

Le support pour les expressions r´eguli`eres est assur´e par les classes Pattern et Matcher du package java.util.regexp, Les objets de la classe Pattern impl´ementent les motifs. et sont construits par la m´ethode statique Pattern.compile qui prend une chaˆıne repr´esentant un motif en argument et renvoie un Pattern. On pourrait penser que la m´ethode compile interpr`ete la chaˆıne donn´ee en argument et produit un arbre de syntaxe abstraite. En fait, par souci d’efficacit´e, elle proc`ede `a bien plus de travail, jusqu’`a produire unautomate (voir le chapitre suivant). Pour le moment, il suffit de consid´erer qu’unPatternest la forme Java d’un motif.

Pour confronter un Patternp`a un mot mil faut fabriquer cette fois unMatcher en invo-quant la m´ethodematcher(String text)dep, l’argumenttext´etant le motm. En simplifiant, leMatcherobtenu est donc la combinaison d’un motifpet d’un motm, il offre (entre autres !)

1On obtient le d´etail du fonctionnement d’une commande Unixcmd parman cmd.

les m´ethodes matches() etfind() qui renvoient des bool´eens. La premi`ere m´ethode matches teste si le motifpfiltre le motm, tandis que la secondefindteste si le motifpfiltre un sous-mot du mot m. Ainsi, par exemple, un moyen assez compliqu´e de savoir si un mot mot contient le sous-mot sous au moins deux fois est d’´ecrire :

static boolean estSousMotDeuxFois(String sous, String mot) {

return Pattern.compile(sous + ".*" + sous).matcher.().find(mot) ; }

Nous en savons maintenant assez pour pouvoir ´ecrire la commande egrep en Java, la classe Grep dont le code est donn´e `a la figure 5. Dans ce code, la m´ethode main se com-porte principalement ainsi : elle ouvre le fichier dont le nom est donn´e comme second argument de la ligne de commande, par new FileReader(arg[1]); puis enveloppe le fichier comme le BufferedReader(voir B.6.2.2)in, ceci afin de pouvoir le lire ligne `a ligne ; enfin le code appelle la m´ethode grep. Cette derni`ere, apr`es construction du Pattern pat, l’applique `a toutes les lignes du fichier. En cas de succ`es de l’appel `a find, la ligne est affich´ee sur la sortie standard.

Le comportement global est donc bien celui de la commandeegrep.

L’architecture du package java.util.regex peut paraˆıtre bien compliqu´ee, et c’est tout `a vrai. Mais. . .

ˆ L’existence de la classePatternse justifie d’abord par le souci d’abstraction : les auteurs ne souhaitent pas exposer comment sont impl´ement´es les motifs, afin de pouvoir changer cette impl´ementation dans les versions futures de Java. Il y a ´egalement un souci d’effica-cit´e, la transformation des chaˆınes vers les motifs est coˆuteuse et on souhaite la rentabiliser.

Par exemple dans notre Grep (figure 5), nous appelons Pattern.compile une seule fois et pratiquons de nombreux filtrages.

ˆ L’existence de la classeMatcher s’explique autrement : les Matcher poss`edent un ´etat interne que les m´ethodes de filtrage modifient. Ceci permet d’abord d’obtenir des informa-tions suppl´ementaires. Par exemple, sifind r´eussit, alors on obtient le sous-mot filtr´e en appelant la m´ethodegroup(). Par ailleurs, l’appel suivant `afindrecherchera un sous-mot filtr´e, non plus `a partir du d´ebut, mais au del`a du dernier sous-mot identifi´e par find.

Tout cela permet par exemple d’extraire tous les entiers pr´esents dans une chaˆıne.

static void allInts(String text) {

Matcher m = Pattern.compile("[0-9]+").matcher(text) ; while (m.find()) {

System.out.println(m.group()) ; }

}

On notera qu’il n’est pas imm´ediat que le code ci-dessus affiche bien tous les entiers de la chaˆıne text. En effet si text est par exemple"12" les deux affichages 12, ou encore 1 puis2sont corrects : il n’affichent que des sous-mots filtr´es par le motif "[0-9]+".

Plus g´en´eralement, sp´ecifier compl`etement ce que fait le couple de m´ethode find/group est un peu d´elicat. La solution `a mon avis la plus satisfaisante est sp´ecifier que le sous-mot filtr´e est d’abord le plus `a gauche et ensuite le le plus long. La sp´ecification de Java n’est pas aussi g´en´erale : au lieu de dire globalement ce qui doit ˆetre filtr´e, elle d´ecrit l’effet de chaque op´erateur des expressions r´eguli`ere individuellement. Ici elle dit que l’op´erateur ((+))estavide (greedy), c’est `a dire qu’il filtre le plus de caract`eres possibles. Dans le cas de l’expression r´eguli`ere simple "[0-9]+", cela revient en effet `a filtrer les sous-mots les plus longs.

Nous ne d´ecrirons pas plus en d´etail les expressions r´eguli`eres de Java. La documentation du langage offre de nombreuses pr´ecisions. En particulier, la documentation de la classe Pattern comprend la description compl`ete de la syntaxe des motifs qui est plus ´etendue que ce que nous

Fig. 5 – La commande egrepen Java, sourceGrep.java import java.io.* ; // Pour BufferedReader

import java.util.regex.* ; // Pour Pattern et Matcher class Grep {

// Affiche les lignes de in dont un sous-mot est filtr´e par le motif p static void grep(String p, BufferedReader in) throws IOException {

Pattern pat = Pattern.compile(p) ; String line = in.readLine() ; while (line != null) {

Matcher m = pat.matcher(line) ; i f (m.find()) {

System.out.println(line) ; }

line = in.readLine() ; }

}

public static void main(String [] arg) { i f (arg.length != 2) {

System.err.println("Usage: java Grep motif fichier") ; System.exit(2) ;

} try {

BufferedReader in = new BufferedReader (new FileReader (arg[1])) ; grep(arg[0], in) ;

in.close() ;

} catch (IOException e) {

System.err.println("Malaise: " + e.getMessage()) ; System.exit(2) ;

} } }

avons vu ici. Attention tout de mˆemes les descriptions de motifs sont donn´ees dans l’absolu, alors que les motifs sont souvent en pratique dans des chaˆınes. If faut donc en plus tenir compte des r`egles de citation dans les chaˆınes. Ainsi le motif \p{L}filtre n’importe quelle lettre (y compris les lettres accentu´ees) mais on le donne sous la forme de la chaˆıne"\\p{L}", car il faut citer un backslash avec un backslash !