Le parseur de wmfs

EDIT: ce billet se voulait une vulgarisation, mais il faut quand même savoir manipuler les notions d’automates, de grammaire etc. Si ça donne envie d’en savoir plus, tant mieux.

En même temps que j’ai laché dwm pour revenir au bon vieux (pas si vieux que ça) wmfs, j’ai totalement recodé le parseur du fichier de configuration.

Un fichier de configuration se doit d’être lisible, et sa projection en mémoire doit être facile à manipuler, malheureusement utiliser le même langage (comme le fait dwm avec son fichier de configuration directement en C) rend la configuration moins convi pour peu que l’on ne soit pas expert. Le parseur sert à ça, traduire un langage simple en langage compréhensible par la machine.

L’idée c’est d’expliquer un peu comment le parseur de wmfs fonctionne parce que ça touche une partie de l’informatique théorique que j’aime bien : l’étude des langages formels.

Yacc et lex sont souvent utilisés pour ce genre de choses. Mon code est juste une implémentation C d’un automate fini déterministe, il ne doit pas être loin du fonctionnement de yacc pour une grammaire et une syntaxe particulière (évidement yacc&lex sont beaucoup plus évolués que mon petit bout de code).

Donc le travail de lex c’est de lire le fichier caractère par caractère et de renvoyer des token, c’est à dire un identifiant syntaxique.

Le langage de configuration est comme ça :

[section]
option = valeur
truc = { "liste", 1, False }
[sous-section]
[sous-sous-section]
machin = True
[/sous-sous-section]
[/sous-section]
[/section]

Je fais une première transformation purement syntaxique :

enum conf_type { SEC_START, SEC_END, WORD, EQUAL, LIST_START, LIST_END, NONE };
/* qui correspond à  [        [/    un_mot  =        {         }       autre */

Et je range ça dans deux listes chainées, une qui contient des conf_type et une autre qui contient les mots (des char *) :

Liste des TOKEN				Liste des mots (WORD)
SEC_START
WORD						"section"
WORD						"option"
EQUAL
WORD						"valeur"
WORD						"truc"
EQUAL
LIST_START
WORD						"liste"
WORD						"1"
WORD						"False"
SEC_START
WORD						"sous-section"
SEC_START
WORD						"sous-sous-section"
WORD						"machin"
EQUAL
WORD						"True"
SEC_END
WORD						"sous-sous-section"
SEC_END
WORD						"sous-section"
SEC_END
WORD						"section"

Une fois ces deux piles construites, il suffit de dépiler en suivant la grammaire (c’est ce que fait yacc). C’est là qu’on parle d’états finis. Désolé ça aurait été plus compréhensible avec un schéma, mais je sais pas en faire donc j’ai écrit ça avec la syntaxe qu’on utilise pour décrire des grammaires.

start: /* rien */
| SEC_START WORD sections SEC_END WORD start /* une suite de sections */
;
sections: /* rien */
| SEC_START WORD sections SEC_END WORD /* des sous sections */
| WORD EQUAL option sections /* des options */
;
option: WORD /* un mot tout simple (une valeur quoi) */
| LIST_START list LIST_END /* une liste de valeurs */
;
list: /* rien */
| WORD list /* une liste de mots */
;

Le parseur va ainsi remplir une structure générique capable de contenir toutes les configurations qui respectent la grammaire et la syntaxe.

Pour wmfs en gros c’est ça :

struct conf_opt { /* options */
char *name;
char *val[10]; /* au plus 10 éléments dans une liste */
SLIST_ENTRY(conf_opt) entry;
}
struct conf_sec { /* section */
char *name;
SLIST_HEAD(, conf_opt) optlist; /* liste chaînée des options de la section */
TAILQ_HEAD(, conf_sec) sub; /* liste chaînée des sous sections */
TAILQ_ENTRY(conf_sec) entry;
}

J’utilise abusivement des macros de <sys/queue.h>.

Ensuite il suffit de coder une API qui va récupérer des options/sections précises sur la structure, mais c’est pas le plus dur à faire.

Voilà un peu l’idée, l’intérêt de cette méthode c’est qu’elle est certaine (pas ou peu de bugs incompréhensibles), rapide : le fichier de configuration n’est traversé qu’une seule fois, pas besoin de strlen(), strchr(), strcmp(), strstr() qui prennent des plombes.

EDIT : j’ai failli oublier de donner le lien vers le code