Suivez-nous sur X
|
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
ALL
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z
|
|
0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z
|
|
A propos d'Obligement
|
|
David Brunet
|
|
|
|
En pratique : Utilisation de Yacc (analyse lexicale et routines utilisateur)
(Article écrit par Pascal Amiable et extrait d'Amiga News Tech - avril 1992)
|
|
Fin de notre analyseur grammatical français
avec d'une part, l'analyse lexicale réalisée grâce à Flex
et d'autre part, le développement de routines "utilisateur" propres.
Nous allons définir les trois parties de l'analyseur lexical dans l'ordre d'apparition des
sections du programme Lex à créer, mais avant tout, un rapide rappel de la syntaxe utilisée
par Lex sera sans doute le bienvenu. Chaque expression de Lex est une chaîne de caractères,
constituée d'un ensemble de caractères, de motifs ou de sous-expressions. Par exemple,
[A-Za-z]* signifie toute chaîne de caractères contenant une ou plusieurs expressions des
lettres A à Z et a à z. La liste ci-dessous reprend les expressions de base les plus courantes
avec leur signification.
- x : le caractère "x".
- "x" "x" : même si "x" est un caractère spécial.
- \x "x" : même si "x" est un caractère spécial.
- [s] : n'importe quel caractère de la chaîne s.
- [x-y] : n'importe quel caractère compris entre x et y.
- [^s] : tout caractère n'appartenant pas à la chaîne s.
- ^x : un x en début de ligne.
- x$ : un x en fin de ligne.
- x* : 0 ou plusieurs occurrences de x.
- x+ : 1 ou plusieurs occurrences de x.
- x|y : un x ou un y.
- (x) : un x.
- x/y : un x suivi d'un y.
- {s} : une expression définie par s dans la zone déclaration.
- x{m,n}m : à travers n occurrences de x.
La section Définitions
La seule chose que nous allons définir dans cette section est le terminateur de mot. Un
mot peut être terminé par une virgule, un point, un espace ou le caractère retour chariot ('\n').
La section Règles
La section Règles tient compte des spécifications établies le
mois dernier (contenant la liste
des mots à reconnaître) ainsi que d'éventuelles dérivées (Vert, Verte), des majuscules et
minuscules (Disquette, disquette)... A chaque expression est associée une action, écrite
sous la forme d'un programme C indu entre deux accolades. Les expressions définies
représentent les mots à reconnaître, suivis par "\fin_mot" qui correspond au terminateur
défini dans la section Définitions. On considère que les noms communs peuvent commencer par
une minuscule ou une majuscule, les noms propres quant à eux commençant obligatoirement
par une majuscule.
Pour un mot reconnu, Lex envoie le symbole associé. Ce symbole est passé au programme
Yacc par l'intermédiaire des actions définies à côté de l'expression reconnue. Par
exemple pour un nom, la fonction exécutée est nom(). Ces fonctions sont décrites dans la section Utilisateur.
La section Utilisateur
Dans cette section se trouve, sous forme de source C, la description des différentes actions définies
dans la partie Règles. Ces fonctions ont pour but de retourner le symbole associé (NOM, VERBE, ADJECTIF,
NOMBRE...) à la partie Yacc de notre analyseur de texte. Elles positionnent également certains
attributs, comme un index dans un dictionnaire (ind_nom par exemple) et le type du mot (comme pluriel
ou singulier). Les nombres sont quant à eux indexés sur leurs valeurs avec le cas particulier des
articles "le" et "la", qui reçoivent arbitrairement l'index "0". Gràce à cette intégration d'index, la
modification du programme (en vue de l'ajout d'un dictionnaire par exemple) sera grandement facilitée.
Mais cette étude sort du cadre de cet article.
Voici donc le programme Lex complet :
fin_mot [,. \n]
%%
Stéphane/{fin_mot} {return(nom(1,1));}
Christine/{fin_mot} {return(nom(2,1));}
Anne/{fin_mot} {return(nom(3,1));}
[Dd]isquette/{fin_mot} {return(nom(4,1));}
[Dd]isquettes/{fin_mot} {return(nom(4,2));}
prend/{fin_mot} {return(verbe(1,2));}
prennent/{fin_mot} {return(verbe(1,1));}
range/{fin_mot} {return(verbe(2,2));}
range/{fin_mot} {return(verbe(2,1));}
donne/{fin_mot} {return(verbe(3,2));}
donnent/{fin_mot} {return(verbe(3,1));}
petit/{fin_mot} {return(adjectif(1,1));}
petite/{fin_mot} {return(adjectif(1,2));}
petits/{fin_mot} {return(adjectif(1,3));}
petites/{fin_mot} {return(adjectif(1,4));}
vert/{fin_mot} {return(adjectif(2,1));}
verte/{fin_mot} {return(adjectif(2,2));}
verts/{fin_mot} {return(adjectif(2,3));}
vertes/{fin_mot} {return(adjectif(2,4));}
rouge/{fin_mot} {return(adjectif(3,1));}
rouges/{fin_mot} {return(adjectif(3,2));}
[Ll][ea]/{fin_mot} {return(nombre(0));}
1/{fin_mot} {return(nombre(1));}
[Uu]n/{fin_mot} {return(nombre(1));}
2/{fin_mot} {return(nombre(2));}
[Dd]eux/{fin_mot} {return(nombre(2));}
3/{fin_mot} {return(nombre(3));}
[Tt]rois/{fin_mot} {return(nombre(3));}
4/{fin_mot} {return(nombre(4));}
[Qq]uatre/{fin_mot} {return(nombre(4));}
5/{fin_mot} {return(nombre(5));}
[Cc]inq/{fin_mot} {return(nombre(5));}
6/{fin_mot} {return(nombre(6));}
[Ss]ix/{fin_mot} {return(nombre(6));}
7/{fin_mot} {return(nombre(7));}
[Ss]ept/{fin_mot} {return(nombre(7));}
8/{fin_mot} {return(nombre(8));}
[Hh]uit/{fin_mot} {return(nombre(8));}
9/{fin_mot} {return(nombre(9));}
[Nn]euf/{fin_mot} {return(nombre(9));}
10/{fin_mot} {return(nombre(10));}
[Dd]ix/{fin_mot} {return(nombre(10));}
[ \.] {/* caractères non pris en compte*/}
\n {return(yytext[0]);}
[A-Za-z]+/{fin_mot} {printf("Je ne connais pas le mot : %s\n",yytext);return(INCONNU);}
[0-9A-Za-z \.\n] {printf("Il s'agit d'un caractère du contrôle : %s\n",yytext);return(CONTROLE);}
%%
nom(i,n)
int i,n;
{
ind_nom = i;
type = n;
printf(" nom : %s\n",yytext);
return(NOM);
}
verbe(i,n)
int i,n;
{
ind_verbe = i;
type = n;
printf(" verbe : %s\n",yytext);
return(VERBE);
}
adjectif(i,n)
int i,n;
{
ind_adjectif = i;
type = n;
printf(" adjectif : %s\n",yytext);
return(ADJECTIF);
}
nombre(i)
int i;
{
ind_nombre = i;
printf(" nombre : %s\n",yytext);
return(NOMBRE);
}
|
Connection Flex-Yacc
Le programme Lex ci-dessus a été sauvegardé dans le fichier lexicall. Il doit être transformé
en source C par la commande flex -8 -v lexical.1. Le résultat est contenu dans le fichier
lex.yy.c, qui doit être inclus dans le programme Yacc à l'aide de la directive #include "lex.yy.c
à la place de la définition de la fonction yylex() qui avait été laissée vide. On devra de
plus modifier légèrement le source Yacc pour tenir compte des deux nouveaux symboles, à savoir INCONNU
pour les mots non reconnus par l'analyseur, et CONTROLE, dans le cas de caractères non alphanumériques.
On remplacera donc la ligne %token VERBE NOM ADJECTIF NOMBRE par %token VERBE NOM ADJECTIF
NOMBRE INCONNU CONTROLE.
On ajoutera également en tête du programme Yacc, un fichier lexical.h contenant la déclaration
des variables globales utilisées dans la section Utilisateur. Ce fichier lexical.h est listé ci-dessous.
/*--------------------------------------------------------*/
/* Déclaration des variables globales de l'analyseur */
/*--------------------------------------------------------*/
int ind_nom, ind_verbe, ind_adjectif, ind_nombre;
int type;
|
Voici donc le programme Yacc complet et modifié pour prendre en compte le résultat des cogitations de FLex.
%{
#include "lexical.h"
%}
%token VERBE NOM ADJECTIF NOMBRE INCONNU CONTROLE
%%
Phrase : Sujet Predicat '\n' { printf(" Phrase déclarative \n");}
| Predicat '\n' { printf(" Phrase impérative \n");}
| error { printf("Erreur de Syntaxe \n");}
;
Predicat : VERBE Complement { printf(" Prédicat \n");}
;
Sujet : Objet { printf(" Sujet \n");}
;
Complement : Objet { printf(" Complément \n");}
;
Objet : NOM
|NOMBRE NOM
|ADJECTIF NOM
|NOMBRE ADJECTIF NOM
|NOMBRE NOM ADJECTIF
|NOMBRE ADJECTIF NOM ADJECTIF
;
%%
#include "lex.yy.c"
main()
{
while(1)
{
printf("? ");
yyparse();
}
}
yyerror()
{
}
|
Il ne reste dès lors plus qu'à passer exemple.y à la moulinette de Yacc avec la commande Yacc exemple.y
puis à compiler et relier le programme C généré par le -Lc y.tab.c. L'exécutable se nommera
tout naturellement y.tab et il peut être lancé directement. Voici un exemple des résultats obtenus.
Débogage
Il arrive parfois que l'on souhaite étudier ce que l'analyseur traite réellement. C'est tout à fait possible :
Yacc peut générer au sein du source C une partie d'affichage des itérations du programme en cours d'exécution.
Pour activer cette phase de débogage, deux choses sont à réaliser :
- Éditer le fichier y.tab.c et y insérer, tout en haut, la ligne #define YYDEBUG 1.
- Rechercher la déclaration de la variable yydebug, int yydebug; et la remplacer par
int yydebug = 1;.
Après compilation et édition des liens, le résultat d'exécution devient tout de suite plus transparent.
Voici à titre d'exemple ce qui est obtenu pour la phrase Stéphane prend trois petites disquettes
vierges, à comparer avec le résultat obtenu plus haut.
C'est aussi simple que cela ! Les fichiers sources lex.yy.c et y.tab.c ainsi que l'exécutable
y.tab se trouvent dans le répertoire ToolBox de la
disquette d'accompagnement de ce numéro.
|