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.
%{ #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); }
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.
© 2001 Yves Mettier