Auriez vous une idée du pourquoi du comment ? Et si j'essaie de faire ça comme ça, c'est pour faire une fonction générique qui prend une structure parmi mon ensemble de structure et comme elle contiennent à peu prés toute les mêmes propriétés autant faire ça que de faire une fonction pour chaque structure
EDIT: Ah si autant pour moi! j'utilisais ma version de printf qui n'était pas complète sur les float
Non, pas la moindre idée. Mais tu ne montres pas ta fonction print_figure_structs().
Quant à la généricité, tu feras comme tu veux, mais c'est toi qui va te casser la tête à lire tes pointeurs. Je préfère de loin écrire plusieurs fonctions qui seront sans ambiguités.
Edit: écrire un programme, c'est aussi penser à la lisibilité du code, et à sa maintenance.
- Edité par edgarjacobs 6 mai 2023 à 1:22:01
On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent
Bon, je viens de réussir mais effectivement je viens de penser à un truc auquel j'aurais du y penser plus tôt.
Je peux avoir x nombres de structure et ça, ça fausse tout .. enfin peut-être en multipliant j'atteindrai le bon endroit pointé mais bon pour relire après
Et l'offset pour accéder au float n'est pas le bon. Il faut "sauter" 4 int pour y accéder
*((float *)(ptr + sizeof(int) * 4)) = -193.;
La taille d'un int est souvent la même que celle de float mais pas toujours. Mais tous les offsets supposent que les 3 char après alignement reviennent à la taille d'un int (offset du 1er int), ça n'est pas non plus toujours le cas.
Pour accéder de manière tarabiscotée à ces offsets, il existe la macro offsetof(type,membre).
PS : il me semblait que l'arithmétique était interdite sur les pointeurs génériques.
Test avec
void foo() {
int n;
void *p = &n;
p++;
}
$ gcc -std=c17 -Wall -Wextra -pedantic -c b.c
b.c: Dans la fonction « foo »:
b.c:5:3: attention: type d'argument erroné pour un incrément [-Wpointer-arith]
5 | p++;
| ^~
et qu'il faudrait plutôt ressortir les char* pour faire ce genre de sport.
PS 2: en effet.
1. En C il y a 3 espèces de types
les types d'objets
les types de fonctions
les types incomplets (pour lesquels on n"a pas d'information sur leur taille)
2. Le standard C définit void comme un type incomplet
3. L'addition n'est permise qu'entre deux types arithmétiques, ou un entier avec un pointeur vers un type d'objet. (donc pas void*)
gcc le permet néanmoins, mais c'est une extension.
Merci pour vos conseils, mais dans ce cas comment puis-je en faire une fonction générique plus propre que ça ?
Parce-que la problématique est que j'ai plusieurs structures qui contient les mêmes champs et j'aimerai qu'elle passe toute par une seule fonction plutôt que de créer X structures qui feront exactement la même chose avec une ou deux instructions de plus.
Je dois parser ce genre de fichier
A 8.2 255,255,255
C -50,0,20 8,4,3 70
L -40,0,30 0.7 255,255,255
pl 12,11,8 4.,1.0,6.0 255,18,225
sp 7,12,20 20 255,19,48
cy 50.0,0.0,20.6 0,0,1.0 14.2 21.42 10,0,255
Je stock déjà mon fichier ligne par ligne dans une liste chainée (remix @mbillaud #ptr_array)
PS : il me semblait que l'arithmétique était interdite sur les pointeurs génériques.
Test avec
void foo() {
int n;
void *p = &n;
p++;
}
$ gcc -std=c17 -Wall -Wextra -pedantic -c b.c
b.c: Dans la fonction « foo »:
b.c:5:3: attention: type d'argument erroné pour un incrément [-Wpointer-arith]
5 | p++;
| ^~
et qu'il faudrait plutôt ressortir les char* pour faire ce genre de sport.
PS 2: en effet.
1. En C il y a 3 espèces de types
les types d'objets
les types de fonctions
les types incomplets (pour lesquels on n"a pas d'information sur leur taille)
2. Le standard C définit void comme un type incomplet
3. L'addition n'est permise qu'entre deux types arithmétiques, ou un entier avec un pointeur vers un type d'objet. (donc pas void*)
gcc le permet néanmoins, mais c'est une extension.
Merci pour vos conseils, mais dans ce cas comment puis-je en faire une fonction générique plus propre que ça ?
Parce-que la problématique est que j'ai plusieurs structures qui contient les mêmes champs et j'aimerai qu'elle passe toute par une seule fonction plutôt que de créer X structures qui feront exactement la même chose avec une ou deux instructions de plus.
Je dois parser ce genre de fichier
A 8.2 255,255,255
C -50,0,20 8,4,3 70
L -40,0,30 0.7 255,255,255
pl 12,11,8 4.,1.0,6.0 255,18,225
sp 7,12,20 20 255,19,48
cy 50.0,0.0,20.6 0,0,1.0 14.2 21.42 10,0,255
Le problème, c'est qu'on (= moi, au moins) ne comprend pas ce que tu veux faire, concrètement.
Faisons simple, ta fonction (générique ou pas), à partir d'une des lignes de ce fichier, elle doit produire quoi ? Que ferait
A 8.2 255,255,255
pl 12,11,8 4.,1.0,6.0 255,18,225
Comment auriez-vous fait, vous ?
Si c'était moi...
Supposons que le but soit de ranger ces données dans un tableau de structures (probablement celles que tu as définies). (Ou une liste chaînée de structures, peu importe... sauf pour moi, je préfère les tableaux.)
Chaque ligne obéit à une syntaxe (je ne sais pas laquelle, j'imagine qu'en relisant tes structures on pourrait la deviner mais je n'ai pas le temps) : plusieurs "mots" séparés par des espaces, la syntaxe dépendant du premier "mot".
Si c'était moi, je lirais le fichier caractère par caractère et je mettrais le "mot courant" dans une chaîne de caractères de longueur fixe, par exemple 100 (à mon avis ça rentre).
Je lis les caractères du fichier un par un jusqu'à tomber sur un espace ou fin de ligne. Le premier mot courant est donc "A". Une seule lettre, c'est un nom de point. Je le stocke dans mon tableau. Je sais qu'après un nom de point, il y aura un coefficient suivi d'un triplet RVB.
Je continue à lire les caractères du fichier un par un jusqu'à tomber sur un espace ou fin de ligne. Le mot courant est cette fois "8.2". C'est le coefficient qui suit le nom de point, je le convertis en nombre à virgule avec 'sscanf', et je le stocke dans mon tableau.
Je continue à lire les caractères du fichier un par un jusqu'à tomber sur un espace ou fin de ligne. Le mot courant est "255,255,255". C'est un triplet RVB, je récupère les trois entiers avec 'sscanf' et je les stocke dans le tableau.
J'ai noté qu'on a fait un passage à la ligne, donc on a fini de lire le point.
Je continue à lire les caractères du fichier un par un jusqu'à tomber sur un espace ou fin de ligne. Le mot courant est cette fois "pl". Ah, cette fois la syntaxe attendue est : triplet d'entiers, triplet de nombres à virgule, triplets d'entiers.
Et ainsi de suite...
Algorithme :
Tant_que pas fin de fichier :
numpoint = 0
Lire(mot)
Exploiter(mot)
Fin Tant_que
---------------------------------------------------
Lire(mot) :
i = 0
c = Lire_dans_fichier
Tant_que c <> ' ' et c <> '\n' :
mot[i] = c
i++
Fin Tant-que
mot[i+1] = '\0'
---------------------------------------------------
Exploiter(mot) :
Si longueur(mot) == 1 :
Exploiter_point(mot)
Sinon si longueur(mot) == 2 et (autre critère...)
Exploiter_truc(mot)
Sinon si longueur(mot) == 2 et (encore un autre critère...)
Exploiter_machin(mot)
---------------------------------------------------
Exploiter_point(mot) :
// Syntaxe attendue : lettre, coeff, triplet RVB
Tableau_Point[numpoint].nom = mot // strcpy
Recup_coeff
Recup_RVB
numpoint++
---------------------------------------------------
Recup_coeff :
Lire(mot)
sscanf(mot,"%d", &n)
Tableau_Point[numpoint].coeff = n
---------------------------------------------------
Recup_RVB :
Lire(mot)
sscanf(mot,"%f,%f,%f", &r, &v, &b)
Tableau_Point[numpoint].R = r
Tableau_Point[numpoint].V = v
Tableau_Point[numpoint].B = b
Quelque chose comme ça (là j'ai fait vite, c'est juste pour décrire l'idée).
Je me rend compte que l'identifier ne sert à rien, mais à part ça, ça devrait être comme ça (lien du Sujet)
robun a écrit:
Si c'était moi...
Supposons que le but soit de ranger ces données dans un tableau de structures (probablement celles que tu as définies). (Ou une liste chaînée de structures, peu importe... sauf pour moi, je préfère les tableaux.)
Chaque ligne obéit à une syntaxe (je ne sais pas laquelle, j'imagine qu'en relisant tes structures on pourrait la deviner mais je n'ai pas le temps) : plusieurs "mots" séparés par des espaces, la syntaxe dépendant du premier "mot".
Si c'était moi, je lirais le fichier caractère par caractère et je mettrais le "mot courant" dans une chaîne de caractères de longueur fixe, par exemple 100 (à mon avis ça rentre).
Je lis les caractères du fichier un par un jusqu'à tomber sur un espace ou fin de ligne. Le premier mot courant est donc "A". Une seule lettre, c'est un nom de point. Je le stocke dans mon tableau. Je sais qu'après un nom de point, il y aura un coefficient suivi d'un triplet RVB.
Je continue à lire les caractères du fichier un par un jusqu'à tomber sur un espace ou fin de ligne. Le mot courant est cette fois "8.2". C'est le coefficient qui suit le nom de point, je le convertis en nombre à virgule avec 'sscanf', et je le stocke dans mon tableau.
Je continue à lire les caractères du fichier un par un jusqu'à tomber sur un espace ou fin de ligne. Le mot courant est "255,255,255". C'est un triplet RVB, je récupère les trois entiers avec 'sscanf' et je les stocke dans le tableau.
J'ai noté qu'on a fait un passage à la ligne, donc on a fini de lire le point.
Je continue à lire les caractères du fichier un par un jusqu'à tomber sur un espace ou fin de ligne. Le mot courant est cette fois "pl". Ah, cette fois la syntaxe attendue est : triplet d'entiers, triplet de nombres à virgule, triplets d'entiers.
Et ainsi de suite...
Algorithme :
Tant_que pas fin de fichier :
numpoint = 0
Lire(mot)
Exploiter(mot)
Fin Tant_que
---------------------------------------------------
Lire(mot) :
i = 0
c = Lire_dans_fichier
Tant_que c <> ' ' et c <> '\n' :
mot[i] = c
i++
Fin Tant-que
mot[i+1] = '\0'
---------------------------------------------------
Exploiter(mot) :
Si longueur(mot) == 1 :
Exploiter_point(mot)
Sinon si longueur(mot) == 2 et (autre critère...)
Exploiter_truc(mot)
Sinon si longueur(mot) == 2 et (encore un autre critère...)
Exploiter_machin(mot)
---------------------------------------------------
Exploiter_point(mot) :
// Syntaxe attendue : lettre, coeff, triplet RVB
Tableau_Point[numpoint].nom = mot // strcpy
Recup_coeff
Recup_RVB
numpoint++
---------------------------------------------------
Recup_coeff :
Lire(mot)
sscanf(mot,"%d", &n)
Tableau_Point[numpoint].coeff = n
---------------------------------------------------
Recup_RVB :
Lire(mot)
sscanf(mot,"%f,%f,%f", &r, &v, &b)
Tableau_Point[numpoint].R = r
Tableau_Point[numpoint].V = v
Tableau_Point[numpoint].B = b
Quelque chose comme ça (là j'ai fait vite, c'est juste pour décrire l'idée).
Oui c'est déjà un peu ce que je fait, ma fonction get_data sert à ça justement, skip les espaces et me renvoyer juste le bout de chaine correspondante, si j'ai
" 12,25,35 20 14"
Elle me renverra alors
"12,25,35\0 20 14"
Et une fois le contenu récupérer je n'aurais juste a skip le \0 pour passer a la prochaine "data"
Mais le problème est le même, c'est que avec tout ce que tu me dis je suis obligé de créer plusieurs fonctions pour chaque structs, après peut-être que c'est moi qui me prend la tête pour rien, et de le fait de créer une fonction pour une structure ne sera jamais mieux que d'en créer autant qu'il y en a.
S'il y a plusieurs syntaxes différentes pour des types de lignes différents, je serais tenté de choisir entre ces deux alternatives :
− Soit on crée en effet plusieurs fonctions pour lire les données en fonction de la syntaxe. Est-ce vraiment un inconvénient ? C'est plus simple, donc plus rapide.
− Soit on organise les données différemment. Il me semble que c'est parfois cette solution qu'il faut choisir, en tout cas il ne faut pas hésiter à passer du temps à bien définir l'organisation des données. Mais pour ce que tu fais, je ne sais pas si c'est possible.
Après, tout dépend de l'objectif. Si tu as un but pragmatique − ce truc doit marcher − autant aller au plus simple. Mais si tu fais ça pour progresser, je comprends ta démarche : tu vas peut-être en baver, mais aussi progresser.
Après, tout dépend de l'objectif. Si tu as un but pragmatique − ce truc doit marcher − autant aller au plus simple. Mais si tu fais ça pour progresser, je comprends ta démarche : tu vas peut-être en baver, mais aussi progresser.
C'est exactement ça, je ne veux pas aller au plus simple enfin c'est pas mon but de chercher le plus dur possible non plus mais comme tu l'a si bien dit, bien organiser ses données, au début de ma formation c'est ce que je faisais j'allais au plus simple et j'me disais que de toute façon ce n'est qu'un projet d'école mais en ce moment à chaque nouveau projet je peux rester très longtemps avant de démarrer juste parce-que je me prend la tête à tout organiser correctement. du moins à essayer
En tout cas je prend note, merci pour t'es conseils et je vais réfléchir à nouveau si je peux essayer de voir la conception complètement différemment de ce que j'ai pu faire jusqu'à maintenant. je trouvais le fait de jouait avec les adresses c'était cool et c'est la première fois que je fait ça, mais c'est vrai que pour une personne tiers lisant le code, il va s'en mordre les doigts et me maudire
Merci je reviendrai vers vous si j'ai réussi à faire quelquechose de ma matière grise!
La fonction "generique" qui remplit des structures différentes selon le contenu de la chaîne à analyser , ça ressemble au pattern "factory" (*)
Une possibilité serait qu'elle retourne une structure contenant
Une indication de la nature de l'objet (une énumération, typiquement)
Une union des différentes structures possibles (ou un pointeur générique void* si les structures sont allouées dynamiquement)
Ça mériterait un exemple pour illustrer le concept, mais ça sera pas ce soir.
Pour faire plus riche, on pourrait même avoir une factory générique, auprès de qui on inscrit (dynamiquement) les fonctions de parsing. On lui dit par exemple, si le premier mot de la ligne est "sp", faut appeler "parse_sphere" qui fera le boulot. Mais bon, c'est peut être un "overkill".
(*) ah ben oui, les patterns de conception, c'est pas que pour les langages OO.
La fonction "generique" qui remplit des structures différentes selon le contenu de la chaîne à analyser , ça ressemble au pattern "factory" (*)
Une possibilité serait qu'elle retourne une structure contenant
Une indication de la nature de l'objet (une énumération, typiquement)
Une union des différentes structures possibles (ou un pointeur générique void* si les structures sont allouées dynamiquement)
Ça mériterait un exemple pour illustrer le concept, mais ça sera pas ce soir.
(*) ah ben oui, les patterns de conception, c'est pas que pour les langages OO.
J'avoue que pour l'instant c'est assez flou, j'allais aussi demander un exemple minimaliste mais apparramment ce sera pas pour ce soir, alors j'attendrais votre exemple avec impatience!
> Ça mériterait un exemple pour illustrer le concept, mais ça sera pas ce soir.
J'ai encore craqué.
En simplifiant, juste pour montrer le concept. Il s'agit d'analyser une ligne contenant un mot (tag) et la description d'un structure dont le type exact (structure contenant un int ou un double) dépend du tag
Oui très intéréssant ! ca va être compliqué d'adapter ça à mon cas mais je vais quand même voir ce que je peux faire! merci du coup de main et de l'exemple!!
Le truc ci-dessus, c'était pour le problème de "polymorphisme", où une fonction doit fournir des résultats de type différents.
Pour ça on décide que la fonction retourne une structure avec indication de la nature de ce qui a été trouvé, et une union de tous les types concernés.
Bon maintenant, le parsing. Le truc, c'est de s'occuper des détails le plus tard possible.
Une approche objet : on définit un objet "parser" dont le rôle est de gober successivement les éléments de la chaîne de caractères, et de fournir les valeurs correspondantes en les plaçant à un emplacement indiqué.
Par exemple :
void parser_get_int(t_parser *parser,
int *where);
ça va aller regarder si le prochain élément de la chaîne est bien un int correct, et l'envoyer dans *where.
Au passage, ça va faire "avancer" le scanner, qui garde un pointeur sur le prochain caractère à traiter.
Donc on va se définir des get_int, des get_float, get_identifier, et à partir de ça on peut construire des trucs de plus haut niveau
avec get_color qui fait 3 appels à get_int, en vérifiant qu'il y a bien les virgules entre (code à la fin).
Le test :
test_ambiant_light("A 8.2 255,255,255");
permet de vérifier que ça a l'air de marcher
cc -std=c17 -Wall -Wextra -pedantic -Werror -Wno-unused -D_XOPEN_SOURCE=700 -g prog.c -o prog
./prog
# Analyse d'une chaine
## test_ambiant_light("A 8.2 255,255,255")
ID A
ratio 8.200000
color (255, 255, 255)
Hourra !
x x x
Le code
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <ctype.h>
#include <string.h>
/*
Problème : on veut remplir une structure
de type "t_ambiant_light" à partir des informations
fournies par une chaine de caractères comme
A 8.2 255,255,255
soit
- un identifiant A
- un ratio 8.2
- une couleur décrite par 3 entiers séparés par des
virgules.
*/
// voila les types
typedef struct s_color {
int r;
int g;
int b;
} t_color;
typedef struct s_ambient_light {
char identifier[3];
t_color color;
float ratio;
} t_ambiant_light;
/*
pour ça on va employer un objet "parser"
qui extrait les informations de la ligne
*/
typedef struct s_parser {
const char *next; // prochain caractère à utiliser
const char *error; // NULL si pas d'erreur
} t_parser;
void parser_start(t_parser *parser, const char string[])
{
parser->error = NULL;
parser->next = string;
}
char parser_next_char(const t_parser *parser)
{
return *(parser->next);
}
void parser_advance(t_parser *parser)
{
(parser->next) ++;
}
bool parser_end_reached(t_parser* parser)
{
return parser_next_char(parser) == '\0';
}
void parser_notify_error(t_parser *parser, char message[])
{
parser->error = message;
}
void parser_ignore_spaces(t_parser *parser)
{
while (isspace(parser_next_char(parser))) {
parser_advance(parser);
}
}
void parser_get_identifier(t_parser *parser,
char *where, int max_length)
{
parser_ignore_spaces(parser);
if (parser_end_reached(parser)) {
parser_notify_error(parser,
"end of string when identifier expected");
return;
}
char c;
int length = 0;
while (isalnum(c = parser_next_char(parser))) {
length ++;
if (length > max_length) {
parser_notify_error(parser, "identifier too long");
return;
}
*where++ = c;
parser_advance(parser);
}
*where = '\0';
if (length == 0) {
parser_notify_error(parser, "empty identifier");
return;
}
}
bool is_valid_float_char(char c)
{
return isdigit(c) ||
strchr("+-.Ee", c);
}
void parser_get_float(t_parser *parser,
float *where)
{
parser_ignore_spaces(parser);
if (parser_end_reached(parser)) {
parser_notify_error(parser,
"end of string when float expected");
return;
}
int buffer_size = 10;
char buffer[10];
int length = 0;
char c;
while (is_valid_float_char(c = parser_next_char(parser))) {
buffer[length++] = c;
if (length >= buffer_size) {
parser_notify_error(parser, "float too long");
return;
}
parser_advance(parser);
}
*where = '\0';
if (length == 0) {
parser_notify_error(parser, "float missing");
return;
}
if (sscanf(buffer, "%f", where) != 1) {
parser_notify_error(parser, "incorrect float number");
return;
}
}
// ne marche que pour les entiers non signés
void parser_get_int(t_parser *parser,
int *where)
{
parser_ignore_spaces(parser);
if (parser_end_reached(parser)) {
parser_notify_error(parser,
"end of string when int expected");
return;
}
int buffer_size = 10;
char buffer[10];
int length = 0;
char c;
while (isdigit(c = parser_next_char(parser))) {
buffer[length++] = c;
if (length >= buffer_size) {
parser_notify_error(parser, "int too long");
return;
}
parser_advance(parser);
}
if (length == 0) {
parser_notify_error(parser, "int missing");
return;
}
buffer[length] = '\0';
if (sscanf(buffer, "%d", where) != 1) {
printf("DEBUG '%s'\n", buffer);
parser_notify_error(parser, "incorrect int number");
return;
}
}
void parser_get_color(t_parser *parser, t_color *where)
{
parser_get_int(parser, & where->r);
if (parser_next_char(parser) == ',') {
parser_advance(parser);
} else {
parser_notify_error(parser, "missing comma between R and G in color");
return;
}
parser_get_int(parser, & where->g);
if (parser_next_char(parser) == ',') {
parser_advance(parser);
} else {
parser_notify_error(parser, "missing comma between G and B in color");
return;
}
parser_get_int(parser, & where->b);
}
void parser_get_ambiant_light(t_parser *parser,
t_ambiant_light *where)
{
parser_get_identifier(parser, where->identifier, 2);
parser_get_float(parser, & where->ratio);
parser_get_color(parser, & where->color);
}
void test_ambiant_light(const char string[])
{
printf("## test_ambiant_light(\"%s\")\n", string);
t_parser parser;
parser_start(&parser, string);
t_ambiant_light ambiant_light;
parser_get_ambiant_light(&parser, &ambiant_light);
if (parser.error != NULL) {
printf("Error whilst parsing: %s\n", parser.error);
return;
}
printf("ID\t%s\n"
"ratio\t%f\n"
"color\t(%d, %d, %d)\n",
ambiant_light.identifier,
ambiant_light.ratio,
ambiant_light.color.r,
ambiant_light.color.g,
ambiant_light.color.b);
}
int main()
{
printf("# Analyse d'une chaine\n");
test_ambiant_light("A 8.2 255,255,255");
return EXIT_SUCCESS;
}
pour gagner en abstraction (= éviter de m'embrouiller dans des détails techniques, ce qui est la difficulté). En mettant des "inline", ça garantirait que c'est sans impact sur les performances.
les trois fonctions pour le parsing des objets de base (int, float, id) se ressemblaient : utilisation d'une fonction paramétrée par un prédicat (fonction qui dit si les caractères sont admis, un format de conversion du résultat, une table de messages,...). Exemple
// Parser: type de base int (entier non signé en fait)
const char *messages_for_int[] = {
[TOKEN_IS_MISSING] = "end of string when int expected",
[TOKEN_IS_TOO_BIG] = "int number has too much digits",
[TOKEN_EXPECTED] = "an int number was expected",
[TOKEN_CONVERSION_FAILED] = "int conversion failed"
};
// ne marche que pour les entiers non signés
bool is_valid_int_char(char c)
{
return isdigit(c);
}
void parser_get_int(t_parser *parser,
int *where)
{
char buffer[20];
parser_get_token(parser, &is_valid_int_char,
sizeof(buffer), buffer,
"%d", where, messages_for_int);
}
inlining pour les petites fonctions (faut juste mettre "static inline" devant la déclaration)
les fonctions de parsing s'arrêtent à la première erreur
plus de tests
Voila ce que ça affiche
billaud@sims:~/Essais/parser2$ make
cc -std=c17 -Wall -Wextra -pedantic -Werror -Wno-unused -D_XOPEN_SOURCE=700 -g prog.c -o prog
billaud@sims:~/Essais/parser2$ make run
./prog
# Analyse d'une chaine correcte
## test_ambiant_light("A 8.2 255,255,255")
ID A
ratio 8.200000
color (255, 255, 255)
# Quelques tests d'erreur
## test_ambiant_light(" ")
Error whilst parsing: end of string when id expected
## test_ambiant_light("? 8.2 255,255,255")
Error whilst parsing: an identifier was expected
## test_ambiant_light("12 8.2 255,255,255")
ID 12
ratio 8.200000
color (255, 255, 255)
## test_ambiant_light("xxx 8.2 255,255,255")
Error whilst parsing: id has more than 2 chars
## test_ambiant_light("A x.2 255,255,255")
Error whilst parsing: a float number was expected
## test_ambiant_light("A 8.2 xxx,255,255")
Error whilst parsing: an int number was expected
## test_ambiant_light("A 8.2 25x,255,255")
Error whilst parsing: missing comma between R and G in color
## test_ambiant_light("A 8.2 253+255,255")
Error whilst parsing: missing comma between R and G in color
## test_ambiant_light("A x.2 255,255")
Error whilst parsing: a float number was expected
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <ctype.h>
#include <string.h>
/*
Problème : on veut remplir une structure
de type "t_ambiant_light" à partir des informations
fournies par une chaine de caractères comme
A 8.2 255,255,255
soit
- un identifiant A
- un ratio 8.2
- une couleur décrite par 3 entiers séparés par des
virgules.
*/
// voila les types
typedef struct s_color {
int r;
int g;
int b;
} t_color;
typedef struct s_ambient_light {
char identifier[3];
t_color color;
float ratio;
} t_ambiant_light;
/*
pour ça on va employer un objet "parser"
qui extrait les informations de la ligne
*/
typedef struct s_parser {
const char *next; // prochain caractère à utiliser
const char *error; // NULL si pas d'erreur
} t_parser;
static inline
void parser_start(t_parser *parser, const char string[])
{
parser->error = NULL;
parser->next = string;
}
static inline
char parser_next_char(const t_parser *parser)
{
return *(parser->next);
}
static inline
void parser_advance(t_parser *parser)
{
(parser->next) ++;
}
static inline
bool parser_end_reached(t_parser* parser)
{
return parser_next_char(parser) == '\0';
}
static inline
void parser_notify_error(t_parser *parser, const char message[])
{
parser->error = message;
}
static inline
bool parser_has_error(t_parser *parser)
{
return parser->error != NULL;
}
static inline
const char * parser_get_error_message(t_parser *parser)
{
return parser->error;
}
static inline
void parser_ignore_spaces(t_parser *parser)
{
while (isspace(parser_next_char(parser))) {
parser_advance(parser);
}
}
enum error_message_number {
TOKEN_IS_MISSING,
TOKEN_IS_TOO_BIG,
TOKEN_EXPECTED,
TOKEN_CONVERSION_FAILED
};
/*
const char *generic_messages[] = {
[TOKEN_IS_MISSING] = "end of string reached when token expected",
[TOKEN_IS_TOO_BIG] = "token is too big",
[TOKEN_EXPECTED] = "token is empty",
[TOKEN_CONVERSION_FAILED] = "token conversion failed"
};
*/
void parser_get_token(t_parser *parser,
bool(*is_char_ok)(char),
int buffer_size,
char buffer[buffer_size],
const char conversion_format[],
void *where,
const char *messages[])
{
parser_ignore_spaces(parser);
if (parser_end_reached(parser)) {
parser_notify_error(parser,
messages[TOKEN_IS_MISSING]);
return;
}
char c;
int length = 0;
while (is_char_ok(c = parser_next_char(parser))) {
buffer[length ++] = c;
if (length >= buffer_size) {
parser_notify_error(parser, messages[TOKEN_IS_TOO_BIG]);
return;
}
parser_advance(parser);
}
buffer[length] = '\0';
if (length == 0) {
parser_notify_error(parser, messages[TOKEN_EXPECTED]);
return;
}
if (sscanf(buffer, conversion_format, where) != 1) {
parser_notify_error(parser, messages[TOKEN_CONVERSION_FAILED]);
return;
}
}
// Parser : type de base "identificateur"
const char *messages_for_id[] = {
[TOKEN_IS_MISSING] = "end of string when id expected",
[TOKEN_IS_TOO_BIG] = "id has more than 2 chars",
[TOKEN_EXPECTED] = "an identifier was expected",
[TOKEN_CONVERSION_FAILED] = "**programmer error (parsing id)**"
};
bool is_valid_identifier_char(char c)
{
return isalnum(c);
}
void parser_get_identifier(t_parser *parser,
char *where)
{
char buffer[3];
parser_get_token(parser, &is_valid_identifier_char,
sizeof(buffer), buffer,
"%s", where, messages_for_id);
}
// Parser : type de base "float"
const char *messages_for_float[] = {
[TOKEN_IS_MISSING] = "end of string when float expected",
[TOKEN_IS_TOO_BIG] = "float number has too much digits",
[TOKEN_EXPECTED] = "a float number was expected",
[TOKEN_CONVERSION_FAILED] = "float conversion failed"
};
bool is_valid_float_char(char c)
{
return isdigit(c) ||
strchr("+-.Ee", c);
}
void parser_get_float(t_parser *parser,
float *where)
{
char buffer[20];
parser_get_token(parser, &is_valid_float_char,
sizeof(buffer), buffer,
"%f", where, messages_for_float);
}
// Parser: type de base int (entier non signé en fait)
const char *messages_for_int[] = {
[TOKEN_IS_MISSING] = "end of string when int expected",
[TOKEN_IS_TOO_BIG] = "int number has too much digits",
[TOKEN_EXPECTED] = "an int number was expected",
[TOKEN_CONVERSION_FAILED] = "int conversion failed"
};
// ne marche que pour les entiers non signés
bool is_valid_int_char(char c)
{
return isdigit(c);
}
void parser_get_int(t_parser *parser,
int *where)
{
char buffer[20];
parser_get_token(parser, &is_valid_int_char,
sizeof(buffer), buffer,
"%d", where, messages_for_int);
}
// parser: type composé "color"
void parser_get_color(t_parser *parser, t_color *where)
{
parser_get_int(parser, & where->r);
if (parser_has_error(parser)) return;
if (parser_next_char(parser) == ',') {
parser_advance(parser);
} else {
parser_notify_error(parser, "missing comma between R and G in color");
return;
}
parser_get_int(parser, & where->g);
if (parser_has_error(parser)) return;
if (parser_next_char(parser) == ',') {
parser_advance(parser);
} else {
parser_notify_error(parser, "missing comma between G and B in color");
return;
}
parser_get_int(parser, & where->b);
}
// parser: type composé "ambiant light"
void parser_get_ambiant_light(t_parser *parser,
t_ambiant_light *where)
{
parser_get_identifier(parser, where->identifier);
if (parser_has_error(parser)) return;
parser_get_float(parser, & where->ratio);
if (parser_has_error(parser)) return;
parser_get_color(parser, & where->color);
}
// -----------------------------------------------
// Les tests
//
void test_ambiant_light(const char string[])
{
printf("## test_ambiant_light(\"%s\")\n", string);
t_parser parser;
parser_start(&parser, string);
t_ambiant_light ambiant_light;
parser_get_ambiant_light(&parser, &ambiant_light);
if (parser_has_error(&parser)) {
printf("Error whilst parsing: %s\n\n",
parser_get_error_message(&parser));
return;
}
printf("ID\t%s\n"
"ratio\t%f\n"
"color\t(%d, %d, %d)\n\n",
ambiant_light.identifier,
ambiant_light.ratio,
ambiant_light.color.r,
ambiant_light.color.g,
ambiant_light.color.b);
}
int main()
{
printf("# Analyse d'une chaine correcte\n");
test_ambiant_light("A 8.2 255,255,255");
printf("# Quelques tests d'erreur\n");
test_ambiant_light(" ");
test_ambiant_light("? 8.2 255,255,255");
test_ambiant_light("12 8.2 255,255,255");
test_ambiant_light("xxx 8.2 255,255,255");
test_ambiant_light("A x.2 255,255,255");
test_ambiant_light("A 8.2 xxx,255,255");
test_ambiant_light("A 8.2 25x,255,255");
test_ambiant_light("A 8.2 253+255,255");
test_ambiant_light("A x.2 255,255");
return EXIT_SUCCESS;
}
Merci beaucoup!!! Je vais regarder tout ça plus en détail demain mais je me devais de répondre!
Sérieux c'est un vrai boulot que tu (je me permet de te tutoyer) as fourni, aussi il y a une chose que je comprend pas depuis ptr_array, tu fais des sortes de getters en C mais je comprend pas l'avantage hormis le faite que ce soit + lisible/compréhensible ?
J'ai eu le temps de survoler et c'est vrai que c'est un boulot monstre! Aussi, on est d'accord que messages_for_int est une variable globale ?
Je sens que je vais bien me régaler demain, sérieusement merci encore!
L'avantage c'est justement que c'est plus compréhensible. On n'a pas une infinité de neurones, alors on se simplifie le boulot pour arriver à le faire avec ceux qu'on a.
Et l'abstraction. La plupart du code ne s'occupe pas de faire avancer des pointeurs sur une chaîne. Et on pourrait facilement remplacer par un truc qui prend les caracteres dans un FILE*, au lieu d'une chaîne.
Aussi, on est d'accord que messages_for_int est une variable globale ?
Oui, mais qui n'est connue que du code qui se trouve après la ligne 202. Par exemple, la fonction parser_get_identifier() ne la connait pas. C'est ce qu'on appelle la portée des variables.
- Edité par edgarjacobs 13 mai 2023 à 23:12:04
On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent
On pourrait la déclarer directement dans la fonction messages_for_int ça serait aussi bien. Ça limiterait la portée de la déclaration au strict nécessaire:
Un point délicat : il faut y mettre le qualificateur static. Sinon, si on ne met pas static,
c'est censé être un tableau alloué automatiquement (sur la pile) quand on rentre dans la fonction
et qui disparaît quand on en sort
Or, on affecte l'adresse de ses éléments pour la notification, dans le champ messages de l'objet parser, qui s'en sert une fois ressorti de la fonction qui a "créé" le tableau de messages.
Trouble ensues, and shit happens.
Ce qui fait la distinction entre
Portée des déclarations
Durée de vie des objets déclarés
(Ici "objet" au sens C : l'espace mémoire qui contient une donnée représentant des informations)
Ps j'ai lu plus haut getter/setter. Hum. Pas vraiment. L'intention est de matérialiser par des appels de fonction les services que peut assurer l'objet (au sens approche orientée-objet). Afin d'encapsuler les détails de représentation matérielle pour ne pas avoir à "en connaître" plus loin . C'est pas d'aller chercher/changer la valeur d'un champ.
Les getters setters, c'est souvent dans la prog orientée objets mal enseignée (*) aux débutants, où
On part d'une structure avec des champs
On les planque avec private pour faire joli
On se demande comment on va y accéder quand même.
Bref, on part de la représentation interne, plutôt que des services qui sont la raison d'exister de l'objet.
(*) si vous me branchez là-dessus, on n'est pas sortis...
PS2: en parlant de qualificateurs, il en manque pour les tables de messages.
Ce sont des tables contenant des pointeurs vers des chaînes constantes. Mais on n'a pas le droit de modifier les tables en cours d'exécution, donc la déclaration devrait être
Avec un const en plus. Tableau de pointeurs non modifiables vers des octets non modifiables.
Comme ça devient lourdingue, c'est raisonnable et avantageux de déclarer un type
#include <stdio.h>
typedef const char * const table_messages[];
table_messages french = {"oui", "non"};
void foo(table_messages m) {
printf("ça marche = %s\n", m[0]);
}
int main()
{
foo(french);
return EXIT_SUCCESS;
}
- Edité par michelbillaud 14 mai 2023 à 13:03:47
Affecter une valeur négative à un float
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent
On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent
En recherche d'emploi.
Recueil de code C et C++ http://fvirtman.free.fr/recueil/index.html
On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent