Re-littéraliser la valeur d'une constante numérique (outillage de code)

8 réponses
Avatar
Patrick 'Zener' Brunet
Bonjour.

Je suis à la recherche d'une idée, relevant de l'outillage de code C
(pour des traces/logs/etc. fortement explicites).

Je suis un adepte du code propre et non-cabalistique. Donc je déteste
les constantes numériques qui traînent, particulièrement si elles
peuvent intervenir à plusieurs endroits.
Je préfère de loin des symboles aux noms bien explicites.

Pour les entiers, les constantes d'enums sont une évidence àmha.

Mais dans un log ou autre trace, on récupère dans une variable la valeur
numérique d'une telle constante. C'est dommage.

Comment la remplacer par un texte nommant le symbole ? On fait
l'hypothèse que le jeu de symboles candidats est identifié par le
contexte de la trace.

Il semble évident qu'il faut constituer un tableau de paires
(valeur_numérique, chaîne_constante). Mais il doit rester synchronisé
avec la définition des constantes.

Je cherche une astuce d'écriture, pouvant utiliser des macros, voire un
préprocesseur (genre M4) pour générer ça à partir d'une déclaration
"presque" normale des constantes.
En un mot il faut que ça reste pratique et lisible.

Une idée ? Merci.

Cordialement.
--
* Patrick BRUNET www.ipzb.fr www.ipzb-pro.com
* E-mail: lien sur http://zener131.eu/ContactMe

8 réponses

Avatar
Marc Boyer
Le 26-04-2012, Patrick 'Zener' Brunet a écrit :
Je cherche une astuce d'écriture, pouvant utiliser des macros, voire un
préprocesseur (genre M4) pour générer ça à partir d'une déclaration
"presque" normale des constantes.



Genre #define et enum ?

En un mot il faut que ça reste pratique et lisible.

Une idée ? Merci.



Un script sed/awk/perl/python ?

Marc Boyer
--
À mesure que les inégalités regressent, les attentes se renforcent.
François Dubet
Avatar
Pascal J. Bourguignon
Patrick 'Zener' Brunet writes:

Bonjour.

Je suis à la recherche d'une idée, relevant de l'outillage de code C
(pour des traces/logs/etc. fortement explicites).

Je suis un adepte du code propre et non-cabalistique. Donc je déteste
les constantes numériques qui traînent, particulièrement si elles
peuvent intervenir à plusieurs endroits.
Je préfère de loin des symboles aux noms bien explicites.

Pour les entiers, les constantes d'enums sont une évidence àmha.

Mais dans un log ou autre trace, on récupère dans une variable la
valeur numérique d'une telle constante. C'est dommage.

Comment la remplacer par un texte nommant le symbole ? On fait
l'hypothèse que le jeu de symboles candidats est identifié par le
contexte de la trace.

Il semble évident qu'il faut constituer un tableau de paires
(valeur_numérique, chaîne_constante). Mais il doit rester synchronisé
avec la définition des constantes.

Je cherche une astuce d'écriture, pouvant utiliser des macros, voire
un préprocesseur (genre M4) pour générer ça à partir d'une déclaration
"presque" normale des constantes.
En un mot il faut que ça reste pratique et lisible.

Une idée ? Merci.



#define level(x) x,#x

enum{
debug=1,
info=2,
notice=3,
warn=4,
error=5,
};

void write_log(int level,const char* label,const char* message);

{
write_log(level(debug),"Hello");
}


--
__Pascal Bourguignon__ http://www.informatimago.com/
A bad day in () is better than a good day in {}.
Avatar
Antoine Leca
Patrick 'Zener' Brunet écrivit :
Pour les entiers, les constantes d'enums sont une évidence àmha.

Mais dans un log ou autre trace, on récupère dans une variable la valeur
numérique d'une telle constante. C'est dommage.

Comment la remplacer par un texte nommant le symbole ?



En C j'ai souvent vu cela implémenté via le préprocesseur, au travers de
l'inclusion multiple (souvent qualifiée d'abusive) de la liste des
valeurs possibles stockées dans un fichier séparé.
/* Symboles.inc */
SYMBOLE(debug,1,continue)
SYMBOLE(info,2,continue)
SYMBOLE(averto,3,continue)
SYMBOLE(erreur,4,break)
SYMBOLE(fatal,5,exit(125))

Et utilisé genre

#define SYMBOLE(label,valeur,x) label, /* ou label=valeur, */
enum {
#include "Symboles.inc"
} raison_t;
#undef SYMBOLE

#define SYMBOLE(label,valeur,x) #label, /* ou [valeur]=#label */
static char raisons[] {
#include "Symboles.inc"
};

Évidemment, on peut faire des choses plus compliquées, comme avoir des
listes de chaînes plutôt qu'un tableau, voire même des méthodes comme
esquissé ci-dessus (mais ce n'est pas forcément une bonne voie...)


Antoine
Avatar
Patrick 'Zener' Brunet
Bonjour.

Pascal J. Bourguignon a écrit :
Patrick 'Zener' Brunet writes:

Bonjour.

Je suis à la recherche d'une idée, relevant de l'outillage de code C
(pour des traces/logs/etc. fortement explicites).

Je suis un adepte du code propre et non-cabalistique. Donc je déteste
les constantes numériques qui traînent, particulièrement si elles
peuvent intervenir à plusieurs endroits.
Je préfère de loin des symboles aux noms bien explicites.

Pour les entiers, les constantes d'enums sont une évidence àmha.

Mais dans un log ou autre trace, on récupère dans une variable la
valeur numérique d'une telle constante. C'est dommage.

Comment la remplacer par un texte nommant le symbole ? On fait
l'hypothèse que le jeu de symboles candidats est identifié par le
contexte de la trace.

Il semble évident qu'il faut constituer un tableau de paires
(valeur_numérique, chaîne_constante). Mais il doit rester synchronisé
avec la définition des constantes.

Je cherche une astuce d'écriture, pouvant utiliser des macros, voire
un préprocesseur (genre M4) pour générer ça à partir d'une déclaration
"presque" normale des constantes.
En un mot il faut que ça reste pratique et lisible.

Une idée ? Merci.





#define level(x) x,#x



Oui, dans le principe, ça fait partie des ingrédients envisagés...
Mais attention:


enum{
debug=1,
info=2,
notice=3,
warn=4,
error=5,
};

void write_log(int level,const char* label,const char* message);

{
write_log(level(debug),"Hello");
}





Ça déjà ça ne devrait pas compiler, je préfère soigner le nom de la
macro pour éviter les collisions. Disons par exemple:

#define LOG_ENCODE_VALUE( x) x, #x

Et là on voit bien le souci: dans le monde réel, le but est de
relitteraliser des valeurs d'enums du process, donc on aura:

enum VariousProcessOps
{
VpoInit,
VpoLoad,
VpoCompute,
VpoDisplay,
VpoTerminate
};

void some_process_part( VariousProcessOps step, ------ )
{
...
if( cata)
write_log( LOG_ENCODE_VALUE( step), "Problem here");
}

...
some_process_part( VpoInit, -----);
...
some_process_part( VpoLoad, -----);
...
etc.

Le but c'est de trouver un moyen de récupérer "VpoInit", "VpoLoad" etc.,
à partir des valeurs 0 à 4 (typées) qui arrivent dans step, et non pas
pas "step" dans tous les cas.

Donc il faudra un tableau de correspondance.

Je ne vois pas de technique à base de macro qui permette d'écrire l'enum
et de générer ledit tableau de manière syntaxiquement correcte.

C'est assez velu comme objectif, n'est-ce pas ?

Merci.
Cordialement.
--
* Patrick BRUNET www.ipzb.fr www.ipzb-pro.com
* E-mail: lien sur http://zener131.eu/ContactMe

*
* Anglais
* Français

* Anglais
* Français

<javascript:void(0);>
Avatar
Patrick 'Zener' Brunet
Bonjour.

Antoine Leca a écrit :
Patrick 'Zener' Brunet écrivit :
Pour les entiers, les constantes d'enums sont une évidence àmha.

Mais dans un log ou autre trace, on récupère dans une variable la valeur
numérique d'une telle constante. C'est dommage.

Comment la remplacer par un texte nommant le symbole ?



En C j'ai souvent vu cela implémenté via le préprocesseur, au travers de
l'inclusion multiple (souvent qualifiée d'abusive) de la liste des
valeurs possibles stockées dans un fichier séparé.
/* Symboles.inc */
SYMBOLE(debug,1,continue)
SYMBOLE(info,2,continue)
SYMBOLE(averto,3,continue)
SYMBOLE(erreur,4,break)
SYMBOLE(fatal,5,exit(125))

Et utilisé genre

#define SYMBOLE(label,valeur,x) label, /* ou label=valeur, */
enum {
#include "Symboles.inc"
} raison_t;
#undef SYMBOLE

#define SYMBOLE(label,valeur,x) #label, /* ou [valeur]=#label */
static char raisons[] {
#include "Symboles.inc"
};

Évidemment, on peut faire des choses plus compliquées, comme avoir des
listes de chaînes plutôt qu'un tableau, voire même des méthodes comme
esquissé ci-dessus (mais ce n'est pas forcément une bonne voie...)




Ah oui, ça me plaît bien ça.

D'ailleurs j'utilise déjà une technique très proche pour appliquer des
#pragmas localement et de manière "sémantiquement portable".

Je préconise de mettre le #undef dans le fichier Symboles.inc lui-même:
- ça simplifie le code utilisateur,
- ça interdit d'oublier le #define approprié avant chaque #include
(d'ailleurs le fichier contient un #ifndef --> #error).

D'ailleurs ça ne m'ennuie pas de faire un petit fichier pour chaque enum
(ou groupe de) concerné, portant le même nom que le type...

Merci beaucoup Antoine, je vais travailler dans cette voie.

Cordialement.
--
* Patrick BRUNET www.ipzb.fr www.ipzb-pro.com
* E-mail: lien sur http://zener131.eu/ContactMe
Avatar
Antoine Leca
Patrick 'Zener' Brunet écrivit :
Antoine Leca a écrit :
Et utilisé genre

#define SYMBOLE(label,valeur,x) label, /* ou label=valeur, */
enum {
#include "Symboles.inc"
} raison_t;
#undef SYMBOLE

#define SYMBOLE(label,valeur,x) #label, /* ou [valeur]=#label */
static char raisons[] {
#include "Symboles.inc"
};



Je préconise de mettre le #undef dans le fichier Symboles.inc lui-même:
- ça simplifie le code utilisateur,



Certes.

- ça interdit d'oublier le #define approprié avant chaque #include



Pas vraiment ; si le #undef précédent est oublié, le second #define va
gueuler pour redéfinition indue, il ne va pas le laisser passer ;-)

(d'ailleurs le fichier contient un #ifndef --> #error).



Ah oui, ça c'est prudent, cela évite de chercher une erreur de syntaxe
incompréhensible...


Antoine
Avatar
Patrick 'Zener' Brunet
Bonsoir.

Antoine Leca a écrit :
Patrick 'Zener' Brunet écrivit :
Antoine Leca a écrit :




[...]
Je préconise de mettre le #undef dans le fichier Symboles.inc lui-même:
- ça simplifie le code utilisateur,



Certes.

- ça interdit d'oublier le #define approprié avant chaque #include



Pas vraiment ; si le #undef précédent est oublié, le second #define va
gueuler pour redéfinition indue, il ne va pas le laisser passer ;-)




Sauf si justement on oublie le deuxième #define et que par malheur la
réutilisation clandestine du premier se passe "bien" (dans le cas non
trivial où on n'outille pas qu'un seul enum).

(d'ailleurs le fichier contient un #ifndef --> #error).



Ah oui, ça c'est prudent, cela évite de chercher une erreur de syntaxe
incompréhensible...




Voilà. Chez moi, le code ne joue jamais aux devinettes.
En mode Adrian Monk + Hercule Poirot, et j'assume :o)

Et en C++, j'ai adoré les techniques de A. Alexandrescu, telles que
l'insertion de chaînes dans des expressions booléennes, pour faire
sortir des erreurs parlantes au compilateur :-)

Cordialement.
--
* Patrick BRUNET www.ipzb.fr www.ipzb-pro.com
* E-mail: lien sur http://zener131.eu/ContactMe
Avatar
Antoine Leca
Patrick 'Zener' Brunet écrivit :
Antoine Leca a écrit :
Patrick 'Zener' Brunet écrivit :
Antoine Leca a écrit :
Je préconise de mettre le #undef dans le fichier Symboles.inc lui-même:






[...]
- ça interdit d'oublier le #define approprié avant chaque #include



Pas vraiment ; si le #undef précédent est oublié, le second #define va
gueuler pour redéfinition indue, il ne va pas le laisser passer ;-)



Sauf si justement on oublie le deuxième #define et que par malheur la
réutilisation clandestine du premier se passe "bien"



Ce que je voulais dire, c'est que justement le cas où « cela se passe
bien » n'est pas un problème : les spécifications du préprocesseur C
assure que dans ce cas, la seconde définition est équivalente à la
première, sinon c'est une erreur qui sera reportée.

C'est un détail technique. Maintenant, sur les considérations de génie
logiciel, je suis d'accord qu'il faut suivre de bonnes pratiques et
qu'en l'occurence, le #undef à la fin en est une.


Antoine