2009-03-28 14 views
4

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:

  1. é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.
  2. é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.

Répondre

6

Le préprocesseur offrira une solution plus robuste et générique. D'un autre côté, les macros sont rapides à fouetter, fournissent une bonne preuve de concept et sont faciles à utiliser lorsque l'espace échantillon/mot clé est petit. L'extension/inclusion de nouvelles fonctionnalités peut devenir fastidieuse avec les macros après un point. Je dirais fouetter les macros pour commencer, puis les convertir en commandes préprocesseur. En outre, essayez d'utiliser un préprocesseur générique plutôt que d'écrire le vôtre, si possible.

[...] J'aurais d'autres dépendances à gérer (m4 pour Windows, par exemple).

Oui. Mais aussi toute solution que vous écrivez :) - et vous devez le maintenir. La plupart des programmes que vous avez nommés ont un port Windows disponible (par exemple, voir m4 for windows). Les avantages de l'utilisation d'une telle solution est que vous économisez un lot du temps. Bien sûr, l'inconvénient est que vous devez probablement aller vite avec le code source, si et quand le bug étrange se présente (bien que les gens qui les maintiennent sont très utiles et s'assureront certainement que vous avez toute l'aide).

Et encore une fois, oui, je préférerais une solution packagée pour rouler la mienne.

+0

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? –

+0

btw, je vous reçois comme un +1 pour la solution du préprocesseur :) –

+0

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

3

Le préprocesseur personnalisé est l'approche typique dans les générateurs d'analyseurs/interpréteurs, car les possibilités de macros sont très limitées et offrent des problèmes potentiels à l'étape d'expansion, ce qui rend le débogage un effort considérable.Je vous suggère d'utiliser un outil éprouvé comme les programmes classiques Yacc/Lex Unix, ou si vous voulez "étendre" C, utilisez C++ et Boost :: spirit, un générateur d'analyseurs qui utilise intensivement les templates.

+0

Merci Hernan. En supposant qu'un ensemble de macros est adéquat pour la tâche (comme dans l'exemple), je vais prendre votre point sur le débogage. Lexers où juste un exemple, et je dois rester dans le domaine C. –

+0

Je n'étais pas au courant que boost était disponible pour C. Et l'opérateur double ::. Je tuerais vraiment pour voir l'implémentation de boost en C;). –

+0

Non, non, Boost n'est disponible que pour C++! –