Analyseurs Lexicaux et Syntaxiques


    L'objectif de ces séances de travaux pratiques de compilation est de se familiariser aux commandesflexetbison,les deux outils de compilation par défaut sur les systèmesunix depuis plusieurs décennies ( déjà !). Le premier outil flex(versiongnude la commande lex) construit unanalyseur lexical à partir d?un ensemble de règles/actions décrites par des expressions régulières. Le second outilbisonest un compilateur de compilateur, version gnu de la célèbre commande yaccacronyme de « yet another compiler of compilers ». Il construit un compilateur d?un langage décrit par un ensemble de règles et actions d?une grammaire LARL sous une forme proche de la forme BNF de Backus-Naur.
 
 

[ manuel flex ] [ manuel bison] [ lex/yacc ] [ lex/flex ] [ yacc/bison ]



Utilisation conjointe de flex et bison


La source yacc utilise l'analyseur lexical yylex()qui peut etre construit avec flex. L'option -d de la commenadebison  génére  un fichier entete qui doit etre inclus dans la source de votre programme flex pour pouvoir utiliser les symboles definis par bison. Ainsi, pour compiler l'analyseur syntaxique lysa decrit par  miny.y utilisant la definition d'un analyseur lexical mini.lex on procede :
 
 

lysa : mini.lex  mini.y
        bison  -d -omini.c mini.y
        flex   mini.lex
        gcc -Wall -c lex.yy.c
        gcc -Wall -c mini.c
        gcc -Wall -o lysa  lex.yy.o mini.o -ll

[ 1 ] Compilez votre analyseurmini.y avec l'option -d  de bison. Jetez un coup d'oeil au fichier entete qui a été créé.

[ 2 ] Ecrire l'analyseur lexical de votre calculette  à  mémoires avec flex.

[ 3 ] Compilez. Verifiez le bon fonctionnement de votre calculette à  mémoires.
 
 

Gestion des symboles


A ce stade,  votre calculette gère essentiellement deux terminaux : MEM et NB, tous deux de type entier ( int). Pour inclure des symboles plus complexes, on introduit un terminal nouveau symbole terminal ID.

[ 4 ] Modifiez votre langage pour éviter la confusion entre la case mémoire "a" et l'identificateur "a". On pourra par exemple utiliser la chaine "$A" pour désigner la case mémoire "A", dans ce cas, la ligne du genre :
 

{MEM} yylval = 'A' - yytext[0]  ; return MEM;


devient
 

{MEM} yylval = 'A' - yytext[1]  ; return MEM;


[ 5 ] Modifiez votre analyseur lexical, pour insérer les identificateurs rencontrés dans une table de symboles, au moyen de la règle :

 
{ID} if ( ! inserer( yytext ) ) printf("\ninsertion...");
        else printf("\ndeja vu...");


On suppose que inserer(char * k) recherche et renvoie/crée un pointeur une entrée dans la table des symboles. Pour une gestion rudimentaire, on peut utiliser :

 
typedef struct liste{
    char * cle;
    int    data;
    struct liste * svt;
  } noeud, *liste;

liste ts = NULL;

liste inserer( char * k )
  {
    liste aux;
    aux = ts;
    while ( aux ) {
      if ( strcmp( aux->cle, k ) == 0 )
        return aux;
      aux = aux -> svt;
    }
    printf("\nInsertion...");
    aux = ( liste ) malloc( sizeof(noeud) );
    aux->cle = (char *) malloc( strlen(k) + 1 );
    strcpy( aux->cle, k );
    aux->data = 0;
    aux->svt  = ts;
    ts = aux;

    return ts;

  }


[ 6 ] Pour gérer les valeurs des symboles, il faut manipuler plusieurs types au niveau du portyylval.
Les attributs de MEM et NBsont entiers et l'attribut de ID est de type liste.

La déclaration d'union bison
 

%union { int val; liste ptr;}


definit le type adéquate dans YYSTYPE.

 
{MEM} yylval.val = 'A' - yytext[1]  ; return MEM;
{NB}  yylval.val = atoi( yytext )   ; return NB;
{ID}  yylval.ptr = inserer( yytext ); return ID;


Compilez votre analyseur mini.y avec l'option -d debison. Jetez un coup d'oeil au fichier entete créé.

[ 7 ] La gestion des champs au niveau des non-terminaux peut se faire explicitement sous la forme $<champ><numero>par exemple :
 

$<val>4, $<ptr>->data$

etc...


Mais aprés, une déclaration :
 

%type <champ> EXP


les actions  sémantiques réfèrent par défaut au champ correspondant lors de l'utilisation  de l'attribut du symbole  EXP.

[ 7 ] Redéfinissez les attributs dans les actions  sémantiques de votre programme. Compilez.

[ 8 ] Ajoutez l'affectation des variables.

Constructions diverses


[ 9 ] Modifiez vos analyseurs pour gérer l'appel de fonctions prédéfinies, comme par exemple
le calcul du pgcd de deux entiers par la fonction :
 

int pgcd( int a, int b ) {
    if ( b ) return pgcd(b, a % b);
    return a;
}


[ A ] Modifiez votre langage pour gérer les tableaux d'entiers.
 
 

[ manuel flex ] [ manuel bison] [ lex/yacc ] [ lex/flex ] [ yacc/bison ]



 
 
 
 


Philippe Langevin, Janvier 2002.