Lex et yacc : lire un fichier de configuration

Creative Commons License
Cette création est mise à disposition sous un contrat Creative Commons.

L'article original se trouve sur http://ymettier.free.fr/articles_lmag/.

Article publié dans le numéro 29 (juin 2001) de GNU/Linux France Magazine


Table des matières

1. Introduction
1.1. Accès au fichier par l'utilisateur
1.2. Ecriture du fichier par l'application
1.3. Lecture du fichier par l'application
2. Le format du fichier de configuration
3. Lex et yacc, rappels
4. Définition des mots : read_config_lex.l
4.1. Format du fichier
4.2. Le fichier read_config_lex.l
4.3. Les explications
5. Définition de la syntaxe des lignes : read_config_yy.y
5.1. Format du fichier
5.2. Le fichier read_config_yy.y
5.3. Les explications
6. Compilation
7. Le programme de démonstration
7.1. main.c
7.2. Génération de l'exécutable demo
7.3. Le fichier de configuration
7.4. Le Makefile
8. ./configure; make; make install
8.1. Changements dans configure.in
8.2. Changements dans Makefile.am
8.3. configure.in
8.4. Makefile.am
8.5. src/Makefile.am
9. Conclusion
10. Références :

1. Introduction

Toute application propre dispose d'un fichier de configuration au moins. Elle doit pouvoir le lire, l'écrire, et proposer un moyen facile à l'utilisateur d'y accéder. Pour ces trois objectifs, une étude doit être faite afin de déterminer quelle solution utiliser. Voici les résultats pour une application ni trop simple ni trop compliquée.

1.1. Accès au fichier par l'utilisateur

Si le fichier est en mode texte, l'utilisateur pourra utiliser son éditeur de texte favori pour l'éditer. Sinon, il faudra une application spécifique pour y accéder. C'est souvent le cas de l'application que l'on programme, qui donne un moyen d'accéder au fichier de configuration (menu préférences) afin de rendre les options modifiables d'une manière plutôt conviviale. Mais si le fichier est corrompu et empêche le lancement de l'application, et que par dessus le marché c'était le seul moyen pour l'utilisateur d'atteindre le fichier de configuration, on se trouve dans une situation où il faut tout réinstaller.

Un format de fichier de configuration intermédiaire est le XML qui est en mode texte, et qui dispose d'outils pour le lire (cf LMAG 27 et son dossier spécial XML.)

1.2. Ecriture du fichier par l'application

Si l'application n'est pas distribuée, il est très facile pour le programmeur d'écraser le fichier de configuration existant et de le remplacer par un nouveau que l'application écrira avec de simples printf(). Cette partie de l'application est relativement rébarbative à coder, mais une fois que cela est fait, la convivialité est au rendez-vous aussi bien pour le programmeur que pour l'utilisateur.

1.3. Lecture du fichier par l'application

Au vu de ce qui précède, nous avons déjà plus ou moins choisi un format de fichier texte. Il reste maintenant le format de ce fichier qui va conditionner son mode de lecture. Au premier abord, on a tendance à utiliser les fonctions C de base afin de lire le fichier. L'inconvénient majeur est qu'il est rébarbatif de coder des fonctions de vérification de la validité du format du fichier. Et s'il est corrompu, l'appli ne plantera pas forcément avant la fin de la lecture, mais peut-être après, pendant que des données sont traitées. On va donc éliminer le code basic et se servir d'outils qui font le travail de vérification.

Deux systèmes seront donc via lex & yacc avec un format de fichier quelconque, et via des fonctions xml (sax, dom...) avec un format de fichier xml.

Choisissons arbitrairement le format quelconque afin d'utiliser lex & yacc (lisez LMAG27 si vous préférez XML). Un avantage supplémentaire de l'utilisation de lex & yacc est que le format de fichier n'est pas figé. C'est à nous de l'inventer et donc de le rendre le plus convivial possible.

2. Le format du fichier de configuration

Les plus connus sont les formats à la windows : clé=valeur; et à la apache : clé valeur(s). Choisissons le format à la apache qui est plus logique dans le cas de plusieurs valeurs :

UnEntier 1
# un commentaire
IntStr 2 bonjour
Boolean True

Ces trois lignes constitueront notre fichier de configuration d'exemple, et nous saurons en tirer le contenu à la fin de l'article.

La syntaxe de ce fichier de configuration est facile : tout ligne commence par un mot-clé connu suivi des valeurs associées à ce mot-clé. Ou alors la ligne commence par un # pour signaler un commentaire.

3. Lex et yacc, rappels

Lex et yacc sont deux outils d'analyse de syntaxe (lex) et de grammaire (yacc). En d'autres termes, yacc va trouver des mots, demander à lex de les lui traduire, puis interpréter leur sens au vu de leur organisation.

En d'autres termes encore, yacc va lire la ligne IntStr 2 bonjour. Puis il va demander à lex ce que IntStr veut dire, de même pour 2 et pour bonjour. Lex va lui répondre que IntStr veut dire INTSTR, que 2 est un entier et que bonjour est une chaîne de caractères. Et yacc sait ce que INTSTR veut dire : une ligne commençant par INTSTR doit se continuer par un entier et par une chaîne de caractères. Yacc sait donc qu'il doit attendre un entier puis une chaîne.

Le travail du programmeur est de définir ce qu'est IntStr, un entier et une chaîne quelconque. Puis il doit définir l'ordre de ces mots pour qu'ils aient une signification. Et enfin, quand il a programmé lex et yacc pour reconnaître le vocabulaire et la grammaire, il ne reste plus qu'à récupérer ce que lex et yacc ont reconnu afin de s'en servir par la suite dans son programme.

Pour bien enfoncer le clou, le helloworld de lex et yacc. La première application que l'on réalise avec lex et yacc est souvent une calculatrice 4 opérations. Ce helloworld mathématique se retrouve dans tous les cours sur lex et yacc en tant qu'illustration. Je me servirai de l'exemple juste pour signaler que lex sert à distinguer les nombres des signes et à rejeter tout autre symbole tel que les lettres. Yacc utilise ce que lex a reconnu afin d'avoir toujours nombre suivi de signe suivi de nombre, et de rejeter toutes les autres combinaisons tel que nombre puis signe, ou signe puis signe puis nombre etc. Enfin à l'intérieur du code qu'on fournit à yacc, on récupère les différents cas et pour chaque cas on effectue l'opération qu'il faut.

Pour ceux qui n'auraient pas fait de lex & yacc et qui s'y intéressent, c'est un petit exercice qui vaut le détour. L'astuce, pour se simplifier le travail, est qu'il faut mettre la fonction main() à la place de la fonction init_config() qui se trouve dans l'exemple plus bas. Et pas besoin de fichier main.c. Mais chut, je n'en dis pas plus...

En ce qui concerne lex et yacc, on en parle beaucoup, mais sur un système GNU, ce ne sont pas eux que l'on utilise, mais leur implémentation GNU : flex et bison -y.

4. Définition des mots : read_config_lex.l

4.1. Format du fichier

Le format du fichier pour flex est celui-ci :

définitions de mots-clés
%%
définitions des mots
%%
code C additionnel

4.2. Le fichier read_config_lex.l

%option nounput
%{
#include "read_config_yy.h"
%}

separator	[\t ]+

%%
{separator}			;
^[#;].*$			;
^{separator}*[Ii]nt[Ss]tr	{ yylval.string = yytext; return INTSTR;}
^{separator}*[Bb]oolean		{ yylval.string = yytext; return UNBOOLEEN;}
^{separator}*[Uu]n[Ee]ntier	{ yylval.string = yytext; return UNENTIER;}
[Tt][rR][Uu][eE]                { yylval.bool = 1; return BOOLEAN; }
[Ff][Aa][Ll][Ss][eE]            { yylval.bool = 0; return BOOLEAN; }
[Yy][Ee][Ss]                    { yylval.bool = 1; return BOOLEAN; }
[Nn][Oo]                        { yylval.bool = 0; return BOOLEAN; }
[0-9]+				{ yylval.val = atoi(yytext); return INTEGER; }
\"[^"]*\"			{ yylval.string = yytext; return QSTRING;}
'[^']*'				{ yylval.string = yytext; return QSTRING;}
[^'" \t\n]+			{ yylval.string = yytext; return STRING;}
^{separator}*\n			;
{separator}*\n			{ return EOL; }
<<EOF>>				{ return 0 ; }
%%

4.3. Les explications

Un peu d'explications car j'ai mis non pas du superflus, mais des choses intéressantes.

Tout d'abord, la première ligne indique que le fichier généré par flex ne comportera pas la fonction unput. Elle peut être génante pour la suite, donc exit la fonction.

Ensuite, le fichier read_config_yy.h que nous n'écrirons pas est nécessaire ici. C'est bison qui le génèrera pour nous. Notons que tout code encapsulé dans %{ et %} est recopié tel quel par flex. Attention à l'ordre des deux caractères : c'est toujours le signe % qui précède l'accolade, qu'elle soit ouvrante ou fermante.

La définition de separator nous évite de retaper ce code par la suite. C'est une sorte d'alias pour la suite du fichier. Par contre, ce n'est pas là qu'on définit que separator est le séparateur. Ici, avant les %%, on ne définit separator qu'en tant qu'alias.

Puis, avant la reconnaissance des trois mots-clé, j'ai défini que separator est un séparateur. Eh oui, sinon, qui l'aurait dit a bison et à flex? Quand on définit le vocabulaire et la grammaire, on définit tout, y compris le caractère de séparation! Et du coup, dans la lignée des définitions évidentes mais qu'il faut quand même définir, on a sur ligne suivante ce qu'est un commentaire, à savoir une ligne commençant par # ou ;.

Viennent ensuite les trois mots-clé qui nous intéressent. Ainsi que quelques autres mots-clé qui nous serviront pour le booléen. Notons qu'ils sont sensibles à la casse.

Et enfin nous avons les définitions les plus générales avec les entiers INTEGER, les chaînes de caractères entre guillemets QSTRING (Q pour Quotes) puis sans guillemets STRING. Et c'est fini! J'ai choisi ces noms moi-même; je l'indique car vu leur nom, ce n'était peut-être pas évident pour tout le monde. Donc non, ces noms ne sont pas définis par défaut.

Nous compilerons ce fichier plus tard car nous avons besoin du fichier read_config_yy.h généré par bison.

5. Définition de la syntaxe des lignes : read_config_yy.y

5.1. Format du fichier

Voici le fichier read_config_yy.y qui définit le format du fichier de config. Il a le format suivant :

Définitions des mots-clefs
%%
Définition de la syntaxe
%%
Code C additionnel.

5.2. Le fichier read_config_yy.y

%{
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "my_config.h"

static int unentier;
static int intstr1;
static char *intstr2;
static int unbooleen;
%}

%union {
	int		val;
	char		*string;
	int		bool;
}

%token <string>		STRING
%token <string>		QSTRING
%token <bool>		BOOLEAN
%token <val>		INTEGER

%token		UNENTIER
%token		INTSTR
%token		UNBOOLEEN
%token		EOL

%%

config_file: 	lines
		;

lines:		one_line lines
		| one_line
		;

one_line:	line_UNENTIER EOL
		| line_INTSTR EOL
		| line_UNBOOLEEN EOL
		| EOL
		;

line_UNENTIER:	UNENTIER INTEGER { unentier = $2; }

line_INTSTR:	INTSTR INTEGER STRING
	{{
		intstr1 = $2;
		if(intstr2) free(intstr2);
		intstr2 = strdup($3);
	}}
	| INTSTR INTEGER QSTRING
	{{
		intstr1 = $2;
		if(intstr2) free(intstr2);
		intstr2 = strdup($3 + 1);
		intstr2[strlen(intstr2)-1] = 0;
	}}
	;

line_UNBOOLEEN:	UNBOOLEEN BOOLEAN { unbooleen = $2; }


%%
int
yyerror (char *s)
{
  printf ("Erreur de lecture du fichier de config : %s\n", s);
  exit (0);
  return (0);
}

MYCONFIG *
init_config (char *config_file_name)
{
  MYCONFIG *myconfig = NULL;
  FILE *rcfile;

  rcfile = fopen (config_file_name, "r");
  if (rcfile)
    {
      unentier = 0;
      intstr1 = 0;
      intstr2 = NULL;
      unbooleen = 0;

      /* lit le fichier de config */
      yyrestart (rcfile);
      yyparse ();
      fclose (rcfile);

      myconfig = (MYCONFIG*) malloc (sizeof (MYCONFIG));
      myconfig->unentier = unentier;
      myconfig->intstr1 = intstr1;
      myconfig->intstr2 = intstr2;
      myconfig->unbooleen = unbooleen;
    }
  else
    {
      printf ("Impossible d'ouvrir le fichier de config\n");
    }
  return (myconfig);
}

5.3. Les explications

Au début du fichier, on inclus des #include et des définitions de variables statiques. Ce sont ces variables qui vont contenir le fichier de configuration, lors de son traitement.

L'union, suivie des définitions des %token, servent à définir les types C des différents mots. Effectivement, pour l'instant, nous n'avions mis qu'une tonne de noms descriptifs dans le fichier read_config_lex.l, mais il n'y avait pas de C. Voila qui est fait!

Ensuite, entre les %%, se trouve la grammaire du parser.

  • La première règle sert à lire le fichier en entier. Elle a besoin de la règle lines qui suit.

  • Cette règle lines définit l'ensemble des lignes, à savoir

    • soit une ligne définie par one_line,

    • soit récursivement une ligne one_line et l'ensemble des lignes lines.

    C'est cette récursion qui permet de lire toutes les lignes.

  • Puis one_line définit chaque type de ligne possible :

    • line_UNENTIER

    • line_INTSTR

    • line_UNBOOLEEN

    • Notons que les commentaires étant exclus par le lexer qui ne renvoit rien dans ce cas, le parser n'a pas à traiter ce genre de lignes.

    La suite se comprend facilement : un ligne est constituée de mots-clé. Et logiquement, le premier mot-clé est celui qui définit les besoins pour la suite; le(s) suivant(s) défini(ssen)t les types des mots trouvés. Le premier mot, qui ne nous sert plus après, est contenu dans la variable $1, le suivant qui nous sert dans la variable $2 etc. Remarquez que pour le cas INTSTR INTEGER QSTRING, je prends la chaîne au second caractère et non pas au premier, et je supprime le dernier, sans aucune vérification que ces caractères existent. En d'autres termes, je ne prend pas le premier caractère qui est soit un guillemet soit une apostrophe (nous n'avons pas défini d'autre caractère dans read_config_lex.l pour les chaînes QSTRING). Effectivement, l'avantage du système est que je suis certain à cet endroit d'avoir une telle chaîne, sinon le parser ne serait pas entré dans ces lignes de code. Donc la vérification a déjà été faite; pas besoin de la refaire.

Enfin, après le dernier %% se trouve du code C additionnel. C'est là que j'ai mis une sorte d'interface pour cacher la lecture du fichier de configuration quand on voudra s'en servir. En effet, dans les autres fichiers source du programme, on appellera la fonction init_config qui renverra la configuration de manière transparente.

La fonction init_config est intéressante. Elle commence par ouvrir le fichier de configuration. Puis elle initialise les variables si cela a bien marché. C'est ici qu'on place les valeurs par défaut si les lignes correspondantes ne sont pas trouvées dans le fichier de config. Attention, le parser ne trouvera aucune faute de syntaxe s'il manque une ligne. Il n'en trouvera que s'il trouve une ligne qui ne respecte aucun format connu. Pour détecter si une ligne est manquante et agir en conséquence, il faut initialiser la variable associée à la ligne à une valeur qu'elle ne peut pas prendre en temps normal. Par exemple -4 pour le booléen ou NULL pour la chaîne de caractères. Et à la fin, il faut tester si cette variable est toujours dans un état impossible. Un autre moyen de faire est d'initialiser une variable supplémentaire pour chaque variable et de la mettre à zéro. Ensuite, il faut programmer le parser pour que quand il modifie une variable, il incrémente sa variable associée. A la fin, si une variable associée est à zéro, c'est que la variable n'a pas été fixée. Si la variable associée est à deux ou plus, c'est qu'il y a soit redondance dans les lignes du fichier de configuration et c'est la dernière qui a été prise en compte, soit c'est qu'il y a un bug dans le parser.

Ensuite, yyrestart() permet d'indiquer au parser qu'il ne doit pas lire sur l'entrée standard comme c'est le cas par défaut, (pour la calculatrice, ne pas mettre ce yyrestart() : le but est de lire sur l'entrée standard) mais dans le fichier décrit par rcfile. Puis yyparse fait tout le boulot. Et il ne reste qu'à fermer le fichier de configuration.

Si l'on est arrivé là, c'est que tout s'est bien passé et que les variables sont affectées aux bonnes valeurs (sauf s'il manquait des lignes dans le fichier de configuration. Mais ce point a été traité plus haut). Il ne reste plus qu'à les mettre dans une structure et à renvoyer cela au programmeur.

S'il y a eu un problème, c'est la fonction yyerror qui est appelée, et elle fait quitter le programme car j'ai mis un appel à exit() dedans. Il peut être sympathique de générer une belle fenêtre pour avertir l'utilisateur que son fichier de configuration est corrompu.

6. Compilation

  • Le fichier lexer est généré par flex read_config_lex.l. Il génère un fichier lex.yy.c qu'il est préférable de renommer en read_config_lex.c pour avoir un nom moins générique..

  • Le fichier parser est généré par bison -y -d read_config_yy.y. De même, il est préférable de renommer y.tab.c en read_config_yy.c et y.tab.h en read_config_yy.h. D'ailleurs, le fichier source généré par flex necéssite le fichier read_config_yy.h : c'est ce que l'on a mis explicitement au début de read_config_lex.l dans une ligne avec #include "read_config_lex.l".

Ensuite, il ne reste qu'à compiler les fichier C comme d'habitude :

gcc -c read_config_lex.c
gcc -c read_config_yy.c

Remarque : au début du fichier read_config_yy.y se trouve une ligne #include "my_config.h". Ce fichier my_config.h ne contient qu'une définition de la structure que renvoit la fonction init_config(). Ce fichier contient aussi le prototype de la fonction init_config(). C'est un fichier d'en-tête général qu'il convient d'avoir séparément car toute fonction qui aura besoin de la configuration du programme aura besoin de la définition de cette structure. Voici donc ce fichier :

typedef struct
{
  int unentier;
  int intstr1;
  char *intstr2;
  int unbooleen;
}
MYCONFIG;

MYCONFIG *init_config (char *config_file_name);

7. Le programme de démonstration

Nous avons beaucoup travaillé, mais il manque le principal; le programme de démonstration. Le voici; je ne le commente pas car il est simple à comprendre.

7.1. main.c

#include <stdio.h>
#include <stdlib.h>
#include "my_config.h"

int
main ()
{
  MYCONFIG *myconfig;

  myconfig = init_config ("configfile");
  if (myconfig)
    {
      printf ("'%d'\n'%d' '%s' \n'%s'\n\n",
	      myconfig->unentier,
	      myconfig->intstr1,
	      myconfig->intstr2, myconfig->unbooleen ? "TRUE" : "FALSE");
    }

  exit (1);
}

7.2. Génération de l'exécutable demo

Il ne reste qu'à compiler main.c et à le lier aux autres objets :

gcc -c main.c
gcc -o demo main.o read_config_lex.o read_config_yy.o -lfl

Une remarque importante : il faut lier le programme de démonstration à la librairie libfl, et pour une compilation sans erreurs, mettre ce -lfl après les objets.

7.3. Le fichier de configuration

Voici mon fichier de configuration, qui s'appelle configfile. Ce nom est codé en dur dans main.c. Ce n'est pas propre du tout, mais cela simplifie amplement le code. Dans un programme plus propre, il faudrait chercher ce fichier dans un sous-répertoire de l'utilisateur, et sinon dans un répertoire système contenant un fichier de configuration générique. Il faudrait aussi tester que le fichier est bien un fichier et non pas un répertoire. Et encore tester qu'il s'ouvre bien... Cela ne rentre pas dans le cadre de cet article.

Le fichier de configuration :

# un commentaire
Intstr 3 'bonjour le monde'
boolean yes
unEntier   13

Notons au passage que le nombre d'espaces n'a pas d'importance, que l'ordre des lignes n'a pas d'importance non plus, et que l'on peut mettre des commentaires où l'on veut.

De plus, ce fichier diffère au niveau contenu du fichier du début de l'article. Ce fichier qu'on avait plus haut doit également marcher!

7.4. Le Makefile

Pour vous éviter d'avoir trop à taper pour tout recompiler à la moindre erreur, voici mon fichier Makefile :

all: read_config_yy.h main.o read_config_lex.o read_config_yy.o
	gcc -o toto main.o read_config_yy.o read_config_lex.o -lfl
clean:
	rm -f *.o
	rm -f read_config_lex.c read_config_yy.c read_config_yy.h
	rm -f toto
	
read_config_lex.o:	read_config_lex.c
	gcc -c ${CFLAGS} read_config_lex.c
	
read_config_yy.o:	read_config_yy.c
	gcc -c ${CFLAGS} read_config_yy.c
	
read_config_lex.c: read_config_lex.l
	flex read_config_lex.l ; mv -f lex.yy.c read_config_lex.c
	
read_config_yy.c: read_config_yy.y
	bison -y -d read_config_yy.y ; mv -f y.tab.c read_config_yy.c; mv -f y.tab.h read_config_yy.h
	
read_config_yy.h: read_config_yy.y
	bison -y -d read_config_yy.y ; mv -f y.tab.c read_config_yy.c; mv -f y.tab.h read_config_yy.h

Rappel : en début de ligne de commande d'un Makefile on n'a jamais d'espaces mais des tabulations. Pour les gens qui ne me croiraient pas ou qui voudraient en savoir plus, l'éditeur favori des linuxiens propose un livre Makefile d'excellente qualité.

8. ./configure; make; make install

Ne passons pas ce point sous silence : il n'est pas si facile d'inclure des fichiers .l et .y dans les fichiers de configuration d'autoconf et automake.

8.1. Changements dans configure.in

Dans le fichier configure.in, il y a peu à faire : il suffit juste d'ajouter les lignes suivantes :

dnl Checks for programs.
dnl ====================
AM_PROG_LEX
AC_PROG_YACC

dnl Checks for libraries.
dnl =====================
AC_CHECK_LIB(fl, yywrap)

Ces lignes vérifient la présence des programmes flex et bison ou compatibles.

8.2. Changements dans Makefile.am

Par contre, dans le fichier Makefile.am qui se trouve dans le même répertoire que les fichiers sources, il y a plus de travail.

Premièrement, il faut ajouter au début une ligne :

YACC=@YACC@ -d

Il faut en effet ajouter l'option -d à yacc/bison pour qu'il génère le fichier d'en-têtes read_config_yy.h.

Puis on ajoute dans appli_SOURCES les fichier read_config_lex.l et read_config_yy.y. Il ne faut surtout pas ajouter les fichier C générés à partir de ces deux fichiers dans la liste. Attention : il est obligatoire de mettre le fichier read_config_yy.y AVANT le fichier read_config_lex.l dans cette liste car il doit être compilé avant : c'est à l'aide de ce fichier qu'on génère le fichier d'en-têtes read_config_yy.h dont le fichier read_config_lex.l a besoin. C'est plus loin qu'on ajoute les noms des fichiers C générés :

CLEANFILES = read_config_lex.c read_config_yy.c

En effet, ces fichiers ne font pas partie des sources car ils sont générés lors de la compilation. Par contre, si l'on fait un make dist, vu que ce sont des fichiers avec une extension .c, ils feront partie du package. Cela est préférable car une personne voulant recompiler l'appli n'aura pas besoin d'installer flex et bison ou compatibles pour la recompiler. Dans ce but, il est nécessaire d'ajouter le fichier read_config_yy.h à la liste des fichiers à conserver :

EXTRA_DIST = configfile read_config_yy.h

J'ai aussi mis le fichier configfile dans la liste pour qu'il ne soit pas perdu si l'on fait un make distclean!

Et voilà, il ne me reste plus qu'à vous livrer mon fichier configure.in qui m'a servi pour faire les tests :

8.3. configure.in

dnl Process this file with autoconf to produce a configure script.
AC_INIT(src/my_config.h)
AM_INIT_AUTOMAKE(demo_lexyacc, 0.1)

dnl Checks for programs.
AC_PROG_CC
AC_PROG_YACC
AM_PROG_LEX

dnl Checks for libraries.
AC_CHECK_LIB(fl, yywrap)

dnl Checks for header files.
AC_HEADER_STDC

dnl Checks for typedefs, structures, and compiler characteristics.

dnl Checks for library functions.
AC_CHECK_FUNCS(strdup)

AC_OUTPUT(Makefile src/Makefile)

8.4. Makefile.am

Le fichier Makefile.am à la racine est simplissime :

SUBDIRS = src

8.5. src/Makefile.am

Et le fichier Makefile.am dans le répertoire src/ n'est plus si compliqué maintenant qu'il a été expliqué :

YACC=@YACC@ -d
bin_PROGRAMS = demo_lexyacc
demo_lexyacc_SOURCES = main.c read_config_yy.y read_config_lex.l my_config.h
CLEANFILES = read_config_lex.c read_config_yy.c
EXTRA_DIST = configfile read_config_yy.h

9. Conclusion

Avant de finir cet article, j'aimerais signaler un projet de plus grande envergure que celui qui nous a servi d'exemple. C'est GTKtalog, qui lit son fichier de configuration de cette manière.

Les fichiers config_lex.l et config_parse.y sont forcément plus longs, mais n'en sont pas moins intéressants. De plus, on peut y trouver une astuce pour scanner une ou plusieurs chaînes de caractères, ce qui est intéressant lorsqu'on doit donner par exemple une liste de chemins. Cette astuce est la même pour les entiers, mais elle n'est utilisée que pour les chaînes dans GTKtalog. Etant le coordinateur de GTKtalog (qui ne s'en doutait pas à ce point?), je me suis donc très fortement inspiré de mes précédents développements. Cela vous permettra, si vous êtes intéressés, de décoder le lexer et le parser de GTKtalog plus facilement afin d'aller plus loin dans l'utilisation de lex et yacc dans le cadre de la lecture d'un fichier de configuration. Par contre, tout le code de cet article est indépendant et se trouve dans une mini application qui sera présente, je l'espère, sur le CD, sous le nom poëtique de demo_lexyacc :)

10. Références :

création est mise à disposition sous un contrat Creative Commons