Bonjour,
J'ai sans doute besoin d'implanter manuellement l'équivalent du polymorphisme par sous-typage. Ci-dessous j'explique un peu mon appli pour que vous puissiez vous en faire une idée et peut-être avoir un point de vue différent ; puis je montre ma solution actuelle avec polymorphisme, au cas où vous verriez une façon plus simple de faire.
Ca va être un peu long, désolé, mais autant vous fournir toutes les billes d'un coup putôt qu'au compte-gouttes.
Le projet est une bibliothèque de parsing du style PEG. Voici un exemple de motif et de textes-sources qui y correspondent:
La bibliothèque définit une variété de *types* de motifs, comme les litéraux (DEUXPOINTS), les chaînes (nom), les composites (contact) et bien d'autres. On voit que certains motifs complexes incluent dans leur définition un ou plusieurs sous-motifs.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13 définition du motif: nom <-- lettre+ DEUXPOINTS <-- ":" téléphone <-- chiffre{10} favori <-- "*" opt_favori <-- favori? contact <-- nom DEUXPOINTS téléphone opt_favori sources corrects: "Marco:9876543210" "Maria:0123456789*"
Chaque type de motif est conceptuellement une struct d'un type donné avec les infos qui le définissent en fonction de son type (chaîne litérale, classe de caractère, sous-motif(s)...). Il y a au moins 2 méthodes pour chaque type : 'notation' construit un texte pour l'affichage, 'trial' tente de "matcher" (faire correspondre) le source avec le motif (à la position courante). Dans le cas d'un motif complexe, chacune de ces 2 routines doit (récursivement) faire appel aux routines correspondantes de ses sous-motifs : pour afficher contact, il faut afficher nom, DEUXPOINTS, téléphone, opt_favori ; de même pour matcher contact, il faut matcher chacun de ses sous-motifs.
J'espère que je suis clair jusque là.
Il me semble que pour faire cela, il n'y a pas moyen d'échapper à une sorte de sous-typage. Je suis obligé d'avoir une routine 'notation' et une routine 'trial' génériques, puisque je ne peux pas connaître à l'avance les types de sous-motifs. Ca veut dire qu'en fait il y a conceptuellement une sorte de super-type 'Pattern' (motif), avec une série de variantes. Pattern serait donc une union étiquetée (tag-ée), et ses 2 routines principales consisteraient chacune juste en un grand switch fonction du tag de type.
Est-ce que vous voyez des alternatives possibles ? Comment réalise-t-on ce genre de choses, typiquement, en C ? Est-ce que je passe à côté d'une façon plus simple de faire ça ?
Concrètement, j'ai plutôt scindé Pattern en deux, avec les champs génériques (juste le nom et le tag) d'une part, et de l'autre une union anonyme qui contient les données de définition du motif en fonction de son type. Ca donne:
Et chaque type de motif consiste ainsi en une mini-struct avec (essentiellement) les versions spécifiques des 2 "méthodes" principales, plus un "constructeur" ; par exemple pour le type Compose:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 typedef struct PatternData * Pattern ; /* pattern type tags */ typedef enum { LITERAL, CLASS, WORD, OPTION, CHOICE, REPETE0, REPETE1, REPETEN, COMPOSE, /*PREFIX, POSTFIX, DELIM, LIST, */ /* ***TODO*** */ } PatternType ; /* generic pattern type */ typedef struct PatternData { PatternType type ; // type tag Text name ; union { Literal literal ; Class class ; Word word ; Option option ; Choice choice ; Repete0 repete0 ; Repete1 repete1 ; RepeteN repeteN ; Compose compose ; } ; } PatternData ; Text pat_notation (Pattern pat) ; Node pat_trial (Pattern pat, Source src) ;
La réalisation est ensuite toute con. Pout noter ou matcher un motif d'un type donné, j'accède juste à la variante correspondante de l'union, par exemple pat.compose. Et la routine générique est bien juste un big switch:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8 typedef struct Compose { Pattern *subpats ; uint count ; // count of sub-patterns } Compose ; Pattern compose (Pattern *subpats, uint count) ; // "constructeur" Text compose_format (Pattern pat) ; Node compose_trial (Pattern pat, Source src) ;
Pour chaque type de motif, je définis donc un type de struct correspondant et les 2 méthodes spécifiques, ce qui est très bien. Mais en plus, je dois ajouter le type (1) dans la liste des tags (2) dans la liste des variantes de l'union (3) dans les switchs des méthodes génériques. Et tout ça ça me gonfle un peu : c'est stupide, c'est redondant, il n'y a pas de code dedans, bref c'est du boulot pour une machine.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14 Node compose_trial (Pattern pat, Source src) { Pattern * subpats = pat->compose.subpats ; uint count = pat->compose.count ; // ... et puis matcher (les sous-motifs) ... } Node pat_trial (Pattern pat, Source src) { switch (pat->type) { case LITERAL : return literal_trial (pat, src) ; break ; // ... tout plein d'autres types ... case COMPOSE : return compose_trial (pat, src) ; break ; default : assert(0) ; } }
Y a-t-il mieux à faire ? Comment vous y prendriez-vous pour implanter une sorte de polymorphisme par sous-typage ?
Tou conseil bienvenu,
merci,
Denis
Partager