J'ai écrit une bibliothèque pour faire correspondre les chaînes à un ensemble de modèles et je peux maintenant facilement intégrer des scanners lexicaux dans des programmes C.Style C: Macros ou préprocesseur?
Je sais qu'il existe de nombreux outils bien établis pour créer des scanners lexicaux (lex et re2c, pour ne nommer que les deux premiers qui viennent à l'esprit) cette question ne concerne pas les lexers, c'est la meilleure approche pour "étendre" C syntaxe. L'exemple lexer n'est qu'un cas concret de problème général.
Je vois deux solutions possibles:
- écrivent un préprocesseur qui convertit un fichier source avec le lexer intégré dans un fichier C ordinaire et, éventuellement, à un ensemble d'autres fichiers à utiliser dans la compilation.
- écrire un ensemble de macros C pour représenter les lexers sous une forme plus lisible.
J'ai déjà fait les deux, mais la question est: "lequel considérez-vous comme une meilleure pratique selon les critères suivants?"
- Lisibilité. La logique de lexer doit être claire et facile à comprendre.
- Maintenabilité. Trouver et réparer un bug ne devrait pas être un cauchemar!
- Interférence dans le processus de construction. Le préprocesseur nécessitera une étape supplémentaire dans le processus de construction, le préprocesseur devra être dans le chemin, etc.
En d'autres termes, si vous deviez maintenir ou écrire un logiciel utilisant l'un des les deux approches, laquelle vous décevra le moins?
À titre d'exemple, voici un lexer pour le problème suivant:
- Somme tous les numéros (peut être sous forme décimale, y compris exponentielle comme 1.3E-4.2)
- cordes Saut (double et simple cité)
- listes de saut (similaire à LISP listes: (3 4 (0 1)() 3))
- arrêt en rencontrant la fin de mot (cas est non pertinent) ou à la fin de la mémoire tampon
Dans les deux styles.
/**** SCANNER STYLE 1 (preprocessor) ****/
#include "pmx.h"
t = buffer
while (*t) {
switch pmx(t) { /* the preprocessor will handle this */
case "&q" : /* skip strings */
break;
case "&f<?=eE>&F" : /* sum numbers */
sum += atof(pmx(Start,0));
break;
case "&b()": /* skip lists */
break;
case "&iend" : /* stop processing */
t = "";
break;
case "<.>": /* skip a char and proceed */
break;
}
}
/**** SCANNER STYLE 2 (macros) ****/
#include "pmx.h"
/* There can be up to 128 tokens per scanner with id x80 to xFF */
#define TOK_STRING x81
#define TOK_NUMBER x82
#define TOK_LIST x83
#define TOK_END x84
#define TOK_CHAR x85
pmxScanner( /* pmxScanner() is a pretty complex macro */
buffer
,
pmxTokSet("&q" , TOK_STRING)
pmxTokSet("&f<?=eE>&F" , TOK_NUMBER)
pmxTokSet("&b()" , TOK_LIST)
pmxTokSet("&iend" , TOK_END)
pmxTokSet("<.>" , TOK_CHAR)
,
pmxTokCase(TOK_STRING) : /* skip strings */
continue;
pmxTokCase(TOK_NUMBER) : /* sum numbers */
sum += atof(pmxTokStart(0));
continue;
pmxTokCase(TOK_LIST): /* skip lists */
continue;
pmxTokCase(TOK_END) : /* stop processing */
break;
pmxTokCase(TOK_CHAR) : /* skip a char and proceed */
continue;
);
Si quelqu'un être intéressé par la mise en œuvre actuelle, le code est ici: http://sites.google.com/site/clibutl.
Je pourrais certainement utiliser un préprocesseur générique (comme gema, m4 ou même awk ou perl) pour créer le préprocesseur spécifique mais j'aurais d'autres dépendances à gérer (m4 pour Windows, par exemple). Ne serait-il pas préférable de fournir directement le préprocesseur dans le cadre du package lui-même? –
btw, je vous reçois comme un +1 pour la solution du préprocesseur :) –
Il n'est pas vraiment nécessaire que le générateur/préprocesseur de code choisi soit disponible sur toutes les plates-formes cibles. Il doit seulement être sur les plates-formes que vous voulez utiliser pour * développement *. Parce que les fichiers C générés seront portables (je suppose), vous pouvez les empaqueter et les expédier avec votre code source. Par exemple, il est de pratique courante pour les projets utilisant Flex ou Bison de distribuer les fichiers générés avec leurs archives afin que les utilisateurs qui veulent simplement compiler le paquet n'aient pas besoin d'installer ces outils. – 5gon12eder