Interfacer Lex et Yacc

1. Coopération entre Lex et Yacc

Les unités lexicales (tokens) transmises par Lex à Yacc sont des identificateurs définies dans le fichier-yacc où ils constituent les terminaux de sa grammaire (ces terminaux sont conventionnellement notés avec une minuscule ou un identifiant commençant par une minuscule) ; Lex utilisera ces tokens en conséquence dans le fichier-lex. Dans le fichier-lex, le code associé à chaque expression se terminera par  return nomtoken, la valeur nomtoken étant ainsi transmise à Yacc quand le token correspondant est reconnu par Lex.

Exemple 1 : définition de tokens
fichier-lex
%%
[0-9]+ {return entier;}  
"+"    {return plus;}
"−"    {return moins;}
fichier-yacc
%token entier
%token plus
%token moins
%%
Operation : Operation plus entier
          | Operation moins entier
          | entier
          ;

La variable globale yylval commune à Lex et Yacc peut être utilisée pour transmettre de Lex à Yacc la valeur associée à un token. Cette valeur sera généralement calculée à partir de yytext qui correspond à la suite de caractères extraits par Lex du flux d'entrée. Par défaut, yylval est définie comme un entier par Yacc, il doit alors être déclaré comme variable externe dans Lex.

Exemple 2 : transmission de valeurs
fichier-lex
%{
extern int yylval;
%}
%%
[0-9]+ {yylval=atoi(yytext); return entier;}  
"+"    {return plus;}
"−"    {return moins;}
fichier-yacc
...
...
%%
Operation : Operation plus entier  {$$=$1+$3;}
          | Operation moins entier {$$=$1−$3;}
          | entier                 {$$=yylval;}
          ;
...

yylval peut être redéfini comme une union dans la deuxième partie du fichier-yac avec la déclaration  %union. Cela permet de transmettre de Lex vers Yacc des valeurs dont le type varie selon le motif reconnu. Il faudra alors typer les terminaux et/ou non-terminaux de la grammaire Yacc en conséquence en utilisant  %type.

Exemple 3 : typage des valeurs transmises
fichier-lex
%%
...
[0-9]+    {yylval.nombre=atoi(yytext); return entier;} 
[a-zA-Z]+ {strcpy(yylval.variable,yytext); return identificateur;}  
...
fichier-yacc
...
%union {int nombre; char* variable;}
%token entier identificateur
%type <nombre> Operation
...
%%
Operation : Operation plus entier {$$=$1+$3;}
          | Operation moins entier {$$=$1−$3;}
          | entier           {$$=yylval.nombre;}
          | identificateur   {$$=valeur(yyval.variable);}
          ;
...

La définition  %union  sera traduite par Yacc en :
  typedef union{int nombre; char* variable;} YYSTYPE
et la variable yylval sera automatiquement déclarée de type YYSTYPE.

2. Compilation coordonnée de lex.yy. et yy.tab.c

Dans le programme exécutable issu de la compilation de lex.yy. et yy.tab.c, la fonction main doit être la fonction attendue : incluse dans le fichier Yacc, LA fonction main doit appeler la fonction d'analyse syntaxique yyparse qui elle-même doit appeler la fonction d'analyse lexicale yylex. De plus, la fonction main utilisée doit être prioritairement celle du fichier-yacc ou sinon celle fournit par l'option −ly.

Les dépendances entre les codes sources produits par Lex et par Yacc peuvent être résumées comme suit :
– yylex() doit être défini avant yyparse(),
– yyparse() doit être défini avant main(),
– la bibliothèque de Yacc (-ly) doit être incluse avant celle de Lex (-ll),
– les tokens définis dans le fichier-yacc doivent être utilisables dans le fichier-lex.

Pour interfacer Lex et Yacc, la méthode de référence préconise d'inclure y.tab.h dans la première partie du fichier-lex.

Méthode de référence pour interfacer Lex et Yacc.
– Construire le fichier-lex et le fichier-yacc en vérifiant leur bonne coordination (voir précédemment)
– Lancer Yacc avec l'option −d pour créer  y.tab.h :     yacc −d fichier.yacc
– Ajouter l'inclusion de la définition des tokens dans la première partie du fichier-lex :     #define y.tab.h
– Lancer Lex :     lex fichier.lex
– Vérifier la présence des trois fichiers  lex.yy.c, yy.tab.h, yy.tab.c
– Compiler dans le bon ordre :     gcc lex.yy.c y.tab.c -ly -ll

Ainsi la fonction main utilisée sera celle écrite par l'utilisateur dans le fichier-yacc ou à défaut celle écrite par l'utilisateur dans le fichier-lex ou sinon la fonction main() par défaut de Yacc. Avec cette méthode, le fichier-lex ne contient pas de main().

Remarque : d'autres méthodes sont possibles mais elles ne sont pas recommandées.
Deuxième méthode : inclure lex.yy.c dans la dernière partie du fichier-yacc.
– Construire les fichier-lex et fichier-yacc comme précédemment.
– Ajouter au début de la dernière partie du fichier-yacc la directive : #include lex.yy.c
– Le cas échéant, rajouter en fin de fichier-yacc les fonctions yyerror() et main().
– Lancer Yacc sur le fichier-yacc et Lex sur le fichier-lex.
– Compiler : gcc y.tab.c -ly -ll
Troisième méthode (la pire) : copier directement y.tab.c dans la dernière partie du fichier-lex, avant de compiler comme précédemment.