philpep's blog - tag wm philpep's blog - tag wm rss http://blog.philpep.org fr Tue, 20 Apr 2010 18:38:22 GMT Pblog Le parseur de wmfs http://blog.philpep.org/post/Le-parseur-de-wmfs http://blog.philpep.org/post/Le-parseur-de-wmfs Tue, 20 Apr 2010 18:38:22 GMT <p><strong>EDIT</strong>: 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.</p> <p>En même temps que j'ai laché <a href="http://dwm.suckless.org">dwm</a> pour revenir au bon vieux (pas si vieux que ça) <a href="http://www.wmfs.info">wmfs</a>, j'ai totalement recodé le parseur du fichier de configuration.</p> <p>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.</p> <p>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 <a href="http://fr.wikipedia.org/wiki/Langage_formel">langages formels</a>.</p> <p><a href="http://fr.wikipedia.org/wiki/Lex_et_yacc">Yacc et lex</a> sont souvent utilisés pour ce genre de choses. Mon code est juste une implémentation C d'un <a href="http://fr.wikipedia.org/wiki/Machine_%C3%A0_%C3%A9tats_finis">automate fini déterministe</a>, il ne doit pas être loin du fonctionnement de yacc <strong>pour une grammaire et une syntaxe particulière</strong> (évidement yacc&amp;lex sont beaucoup plus évolués que mon petit bout de code).</p> <p>Donc le travail de lex c'est de lire le fichier caractère par caractère et de renvoyer des <a href="http://fr.wikipedia.org/wiki/Analyse_lexicale#Token">token</a>, c'est à dire un identifiant syntaxique.</p> <p>Le langage de configuration est comme ça :</p> <div class="codehilite"><pre>[section] option = valeur truc = { &quot;liste&quot;, 1, False } [sous-section] [sous-sous-section] machin = True [/sous-sous-section] [/sous-section] [/section] </pre></div> <p>Je fais une première transformation purement syntaxique :</p> <div class="codehilite"><pre><span class="k">enum</span> <span class="n">conf_type</span> <span class="p">{</span> <span class="n">SEC_START</span><span class="p">,</span> <span class="n">SEC_END</span><span class="p">,</span> <span class="n">WORD</span><span class="p">,</span> <span class="n">EQUAL</span><span class="p">,</span> <span class="n">LIST_START</span><span class="p">,</span> <span class="n">LIST_END</span><span class="p">,</span> <span class="n">NONE</span> <span class="p">};</span> <span class="cm">/* qui correspond à [ [/ un_mot = { } autre */</span> </pre></div> <p>Et je range ça dans deux listes chainées, une qui contient des conf_type et une autre qui contient les mots (des char *) :</p> <div class="codehilite"><pre>Liste des TOKEN Liste des mots (WORD) SEC_START WORD &quot;section&quot; WORD &quot;option&quot; EQUAL WORD &quot;valeur&quot; WORD &quot;truc&quot; EQUAL LIST_START WORD &quot;liste&quot; WORD &quot;1&quot; WORD &quot;False&quot; SEC_START WORD &quot;sous-section&quot; SEC_START WORD &quot;sous-sous-section&quot; WORD &quot;machin&quot; EQUAL WORD &quot;True&quot; SEC_END WORD &quot;sous-sous-section&quot; SEC_END WORD &quot;sous-section&quot; SEC_END WORD &quot;section&quot; </pre></div> <p>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.</p> <div class="codehilite"><pre>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 */ ; </pre></div> <p>Le parseur va ainsi remplir une structure générique capable de contenir toutes les configurations qui respectent la grammaire et la syntaxe.</p> <p>Pour wmfs en gros c'est ça :</p> <div class="codehilite"><pre><span class="k">struct</span> <span class="n">conf_opt</span> <span class="p">{</span> <span class="cm">/* options */</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</span><span class="p">;</span> <span class="kt">char</span> <span class="o">*</span><span class="n">val</span><span class="p">[</span><span class="mi">10</span><span class="p">];</span> <span class="cm">/* au plus 10 éléments dans une liste */</span> <span class="n">SLIST_ENTRY</span><span class="p">(</span><span class="n">conf_opt</span><span class="p">)</span> <span class="n">entry</span><span class="p">;</span> <span class="p">}</span> <span class="k">struct</span> <span class="n">conf_sec</span> <span class="p">{</span> <span class="cm">/* section */</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</span><span class="p">;</span> <span class="n">SLIST_HEAD</span><span class="p">(,</span> <span class="n">conf_opt</span><span class="p">)</span> <span class="n">optlist</span><span class="p">;</span> <span class="cm">/* liste chaînée des options de la section */</span> <span class="n">TAILQ_HEAD</span><span class="p">(,</span> <span class="n">conf_sec</span><span class="p">)</span> <span class="n">sub</span><span class="p">;</span> <span class="cm">/* liste chaînée des sous sections */</span> <span class="n">TAILQ_ENTRY</span><span class="p">(</span><span class="n">conf_sec</span><span class="p">)</span> <span class="n">entry</span><span class="p">;</span> <span class="p">}</span> </pre></div> <p>J'utilise abusivement des macros de <a href="http://www.freebsd.org/cgi/man.cgi?query=queue">&lt;sys/queue.h&gt;</a>.</p> <p>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.</p> <p>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.</p> <p>EDIT : j'ai failli oublier de donner le lien vers <a href="http://git.wmfs.info/wmfs.git/tree/src/parse">le code</a></p> Dwm sur FreeBSD http://blog.philpep.org/post/Dwm-sur-FreeBSD http://blog.philpep.org/post/Dwm-sur-FreeBSD Tue, 08 Dec 2009 17:35:58 GMT <p>Pour installer <a href="http://blog.philpep.org/post/Dwm">dwm</a> sur freebsd il y a deux méthodes. Soit par les ports soit en récupérant la dernière sur le dépôt mercurial. Le problème avec la version mercurial c'est qu'il faut modifier un peu le <code>config.mk</code> pour que ça compile proprement puis faut se tenir au courant des mises à jours etc. Et dans mon imaginaire la version des dépôt compilait avec un <code>config.h</code> fixé, mais en fait c'est faux le Makefile laisse le choix o/</p> <div class="codehilite"><pre>.if defined(DWM_CONF) @${ECHO_MSG} &quot;creating config.h from ${DWM_CONF}&quot; @${CP} ${DWM_CONF} ${WRKSRC}/config.h .endif </pre></div> <p>Donc hop, j'ai juste à lui donner <a href="http://git.philpep.org/config.git/tree/dwm/config.h">mon config.h</a> dans <code>/etc/make.conf</code></p> <div class="codehilite"><pre>echo &quot;DWM_CONF=/home/phil/config/dwm/config.h&quot; &gt;&gt; /etc/make.conf make -C /usr/ports/x11-wm/dwm install clean </pre></div> <p>Et roulez jeunesse. </p> Dwm http://blog.philpep.org/post/Dwm http://blog.philpep.org/post/Dwm Mon, 16 Nov 2009 05:07:06 GMT <p>Après avoir passé beaucoup de temps sur <a href="http://www.wmfs.info">wmfs</a>, je cherchais un wm encore plus léger et surtout plus rapide, avec une conf toute simple. Rien que sur wmfs, qui est loin d'être le plus lourd des wm, je n'utilise que 10% des fonctionnalités, la conf est blindée et le démarage reste assez lent.</p> <p>Au hasard du web je suis tombé sur <a href="http://dwm.suckless.org">dwm</a>, un wm développé par la communauté <a href="http://suckless.org">suckless</a> dont voici un extrait du <a href="http://suckless.org/manifest/">manifeste</a> :</p> <div class="codehilite"><pre>Ingenious ideas are simple. Ingenious software is simple. Simplicity is the heart of the Unix philosophy. The more code lines you have removed, the more progress you have made. As the number of lines of code in your software shrinks, the more skilled you have become and the less your software sucks. </pre></div> <p>À partir de là tout est dit. Dans dwm pas de fonctions à tout faire, mais du code efficace, sans bugs et utilisable. Le dernier point dépend bien entendu que de vous. Personnellement toutes les fonctions que j'attends d'un wm y sont.</p> <p>Dans dwm pas de fichier de configuration, il y a dans le répertoire des sources un fichier config.h dans lequel vous devez mettre votre configuration. Il faut recompiler à chaque modification de la configuration, comme ça vous avez votre propre dwm et c'est assez sympa comme principe je trouve.</p> <p>De plus vous pouvez créer vos propre fonctions assez facilement, le code est simple, lisible et surtout très court (~2000 lignes). Pour vous donner un exemple il n'y a pas de fonction qui permette d'atteindre le tag précédent ou suivant. Vous pouvez vous inspirer de <a href="http://git.philpep.org/config.git/tree/dwm/config.h">mon config.h</a> (c'est un vieux patch que j'ai trouvé sur la mailing list que j'ai du un peu modifier pour que ça marche avec dwm 5.8). Beaucoup d'autres patchs sont disponibles sur le site de dwm.</p> <p>Je crois que j'ai trouvé mon wm, mais bon je le dis à chaque fois que je trouve un wm :-)</p>