| Ravi Welcome Documentation Documentation About Ravi Documentation Introduction Premiers pas avec Ravi ravitool Objets Scheme Le shell ravi Starting Ravi Le module trace Les ports d'E/S load, require, modules Système d'interruptions Scheme compiler C++ mode Generating C++ modules La déclaration struct Le type "C-object" More information Installation | Generating C++ modules./ug_make_module.scml
On trouve sur cette page:
./ug_make_module.scml
avant de lire ce chapitre, regardez l'exemple: LISEZMOI.C++ravi
./ug_make_module.scml
La génération d'interface prend comme données:
Plusieurs fichiers sont générés automatiquement, partant d'un fichier nom.ph:
La génération d'un module comporte plusieurs étapes:
Il est facile de comprendre que tout cela ne peut pas etre 100% automatique: par exemple, les librairies nécessaires a l'édition des liens ne peuvent pas etre inventés. En fait, chaque étape peut avoir besoin d'informations qui ne figurent pas dans le fichier .h initial - voilà pourquoi on introduit des déclarations. La génération est donc semi-automatique : L'essentiel du travail est réalisé par le générateur, mais le concepteur du module doit lui fournir quelques informations supplementaires pour le guider.
./ug_make_module.scml
Nom est le nom du module. On cherche le fichier nom.ph ou nom.h - oui, on peut générer l'interface à partir du seul fichier .h!
./ug_make_module.scml
Le fichier de prototype contient les éléments pour lesquels on veut générer une interface -fonctions, classes, typedef- au format C++ : int ma_fonction(int x, int y);
./ug_make_module.scml
Un fichier de prototype peut contenir des declarations de fonctions, de classes, de types, et des commentaires. // Declaration des types class Classe1; struct Classe2; typedef int montype; int fn1(); void fn2(int, int); Il y a des restrictions sur les commandes du macro-processeur. cf. ci-apres En tout état de cause, seulement certaines de ces commandes seront exécutées. En particulier, les fichiers #include ne sont pas lus: si un fichier #include définit des types - classes, structures, typdef, ..., ils doivent être introduits par des déclarations explicites, sinon l'analyse syntaxique aura des problèmes! je repete IMPORTANT La première phase de la génération effectue une analyse syntaxique des prototypes (plus exactement: de toutes les phase %%input), mais elle ne consulte pas les fichiers inclus avec #include. Or, l'analyseur a besoin de connaitre tous les noms de types.
./ug_make_module.scml
Si un fichier #include contient des déclarations de types qui sont utilisés par la suite, il faut les indiquer dans une déclaration pour le générateur, sous une des formes suivantes : * plusieurs déclarations dans la phase %%declare impliquent qu'il s'agit d'un type * dans une phase %%input, inclure class toto; etc. Ceci est encore plus vrai pour les noms de template! D'où la possibilité de déclarer
./ug_make_module.scml
Pour l'analyse syntaxique de C ou de C++, il est indispensable de connaitre les identificateurs de types. C'est pourquoi il faut mettre en debut de fichier la declaration de ces differents types :
Inutile de recopier la declaration complete de la classe si vous ne souhaitez pas générer une interface pour elle. Seule la declaration de l'existence du type est necessaire. NB: Ceci est le fait de la grammaire de C et de C++ en général et non pas de notre analyseur syntaxique. Tous les compilateurs necessitent la declaration des identificateurs de type avant qu'il y soit fait reference.
./ug_make_module.scml
Le fichier de phases dirige la génération dans ses grandes lignes, donnant des précisions pour chaque "phase" d'entrée et de sortie, car, bien entendu, les fichiers générés sont produits d'une façon bien déterminée. Le fichier phase commence toujours avec des phases d'entrée: %%declare pour des déclarations, %%input pour la lecture de données (c.à d. les prototypes/déclarations C-C++). Les phases d'entrée ne sont pas forcément uniques. Ensuite on trouve les phases de sortie, dans lesquelles les connaisseurs peuvent insérer des morceaux de codes, afin d'assurer la plus grande souplesse de l'ensemble. NB : Il faut utiliser le suffixe .ph pour le fichier de phases. Nom et type des fichiers de prototypes sont indifferents. La structure du fichier de phases est donc la suivante :
./ug_make_module.scml
Il y a, en général, une seule phase avec des déclarations; on peut cependant les placer partout. Voir aussi le paragraphe "Declaration ig dans les prototypes" Il y a une ou plusiers phases %%input, selon 2 formats différents. %%input "nomfichier" sert à indiquer le nom du fichier de prototypes à utiliser. nomfichier peut contenir des noms des variables Unix. %%input * , au lieu de faire appel à un fichier, introduit des données directement, terminées par un $$. C'est commode quand il y a juste quelques lignes. (C'est fréquemment utilisé en début de fichier, avec des déclarations). Exemple:
La phase %%scheme-inputInclusion de code Scheme dans le fichier .x.scm Aucune analyse n'est effectuée. Deux formats, comme pour les phases input :
./ug_make_module.scml
Lors de la génération du code source, le générateur procède par phases : sortie des includes, puis les variables locales, puis les fonctions d'interface, puis la fonction d'initialisation. Des commandes de la forme %%<phase> permettent d'insérer du code avant chacune des phases. C'est utile seulement pour des problèmes très particuliers, le "débutant" n'a pas a connaitre tout cela ... NB : Chacune de ces commandes est optionnelle. Mais si elles sont présentes, elles doivent apparaitre dans l'ordre des dexriptions ci-dessous phase include (%%include) Lors de cette phase, le générateur écrit les include standards de Ravi. Le code place avant cette phase sera placé en tête du fichier avant ces include. phase types (%%types) Lors de cette phase, le générateur déclare les variables locales au fichier. On peut placer avant ses variables locales et les includes qui doivent apparaitre apres les include standards. phase fntypes (%%types) C'est lors de cette phase que le générateur doit mettre en place les méthodes générées automatiquement pour delete et print. phase functions (%%functions) Cette phase correspond à l'écriture du code pour toutes les fonctions d'interface. Toutes les fonctions inline appelées par les fonctions d'interface devraient être placées avant cette phase. phase begin-init (%%begin-init) Cette phase correspond a l'ecriture de l'en-tete de la fonction d'initialisation. Le code place avant cette phase est insere avant cette fonction et apres les fonctions générees automatiquement. phase init (%%init) Du code place entre %%begin-init et %%init sera inseré au début de la fonction d'initialisation. Cette commande est reservee aux experts es modules. Il y a peu de raisons de l'utiliser. phase init (%%end-init) Le code place avant cette phase est insere a la fin de la fonction d'initialisation. La aussi, il vaut mieux attendre de bien connaitre le fonctionnement du générateur pour aller mettre des choses dedans.
./ug_make_module.scml
Les déclarations apparaissent dans une (ou plusieurs) phase %%declare sous forme d'une liste directement exploitée en Scheme. Les déclarations peuvent aussi être placées dans des commentaires à l'intérieur des fichiers prototypes (.h), voir le paragraphe "Declaration ig dans les prototypes". On n'a pas besoin de connaitre toutes les déclarations, mais on se rend rapidement compte de leur utilité. Les déclarations peuvent prendre les formes suivantes :
./ug_make_module.scml
Format: (classe id prop ...)
La déclaration "classe" regroupe toutes les informations concernant une classe identifiée par id; chacune de ces informations est une "propriété" qui est un couple attribut-valeur; le nom de l'attribut tout seul désigne la valeur par défaut (qui existe pour certains attributs seulement). Fini donc le jeu des #t ou #f pour dire oui ou non. id est: soit un symbole, soit une chaine définissant un type de classe avec la syntaxe C++. Vous notez que cela simplifie beaucoup de détails: plus besoin de savoir s'il faut mettre (* nom) ou nom ou (nom *) ... par contre, pour analyser correctement la chaine avec une classe définie par un template, il faut absolument la faire précéder par une declaration template, syntaxe oblige! Format de la déclaration template: (template name) (rien à signaler) Les propriétés sont:
./ug_make_module.scml
* Fonction d'impression: propriété print-function ou print-method On sait que chaque typec connu dans Ravi possède une fonction d'impression, que l'on peut préciser avec une déclaration. Pour ce qui est des classes, cette déclaration peut prendre une des 2 formes suivantes.
Voici l'exemple de la fonction générée pour la classe toto d'après la déclaration
./ug_make_module.scml
La propriété reference-count déclare que la classe est gérée avec des compteurs de référence, et qu'on utilise les methodes par défaut. La déclaration complète donne explicitement les noms des m'ethodes à appeler. On note un choix important: les 2 méthodes sont regroupées en une seule déclaration, pour des raisons de salubrité évidentes. A regarder de plus près, ceci est bien plus générale que les compteurs de reférence; la méthode reference est appelée lorsqu'on construit l'objet typec dans Ravi, la méthode unreference est appelée par le ramasse-miettes à la destruction du pointeur sur l'objet. Pour une classe avec reference-count, l'interface comporte toujours une fonction delete; en l'absence de déclaration, la fonction delete est générée. Remarque: la 2ieme forme reste a implementer, personne ne l'utilise pour l'instant.
./ug_make_module.scml
Chaque typec sous Ravi peut posséder une "fonction de destruction" appelée par le ramasse-miettes. Pour une classe, cette fonction peut être codée à la main, construite par l'interface, ou être absente.
RemarquesPour une classe avec reference-count, l'interface comporte toujours une fonction delete; en l'absence de déclaration, la fonction delete est générée. L'ancienne déclaration (delete #f) n'a plus de raison d'être: en absence de déclaration, il n'y a pas de fonction delete. Exemple d'une fonction delete générée automatiquement pour la classe toto avec compteur de ref'erence:
./ug_make_module.scml
La propriété (dynamic-type GetMethod SetMethod) signifie que toute méthode/fonction ayant un résultat de type classe (déclaré au niveau C++) aura pour résultat un objet typeC dont le type sera déterminé dynamiquement en appelant la méthode (virtuelle) GetMethod. La méthode statique SetMethod est appelée à l'initialisation du module. Le forme simple de la propriété dynamic-type introduit des noms par défaut: GetRaviType et class_name::SetRaviType.
./ug_make_module.scml
Des classes générées à partir d'un template peuvent faire partie de l'interface. Il faut explicitement demander leur génération à l'aide d'une déclaration: Les noms n1 ... se réfèrent à des typedef dans les déclarations de prototypes (fichier .h). La génération de la classe se fera à l'endroit où est placée le typedef. Exemple:Déclaration dans le .ph(generate-template-class afloat) Puis, dans le .h typedef array<float> afloat --> completer l'exemple!
./ug_make_module.scml
Les paramètres références d'une procédure peuvent correspondre à des résultats qu'il peut être utile de recupérer au niveau Ravi. Cela ne peut pas être déduit automatiquement par le système, il faut donc une déclaration; de plus, il faut un moyen des récupérer plusieurs résultats. La déclaration (result-mode #t) "positionne l'état" du générateur d'interface, en sorte que les paramètres référence des procédures sont considérés comme des résultats. La déclaration (result-mode #f) positionne l'état inverse. Le résultat au niveau Ravi est une liste comportant le résultat de la fonction, s'il y en a un, suivi des valeurs en sortie des paramètres passés en référence, et de type simples.
Exemple
Au niveau Ravi, la fonction f1 donne une liste de 2 float. La fonction toto::func1 donne une liste (toto entier). La fonction toto-GetCoord donne une liste avec un float. La fonction f2 une liste avec (entier booleen entier).
./ug_make_module.scml
Pour activer/désactiver le result-mode, on a besoin de mettre des déclarations un peu partout; d'où l'intérêt de la possibilité de les placer carrément dans les déclarations C++ (cad. dans le fichier .h). Cela a un autre intérêt - celui de pouvoir générer des interfaces directement à partir d'un fichier .h, sans l'intermédiaire d'un fichier "phases" .ph. Des déclarations Ravi peuvent donc être placées dans un commentaire C selon le format suivant:
Ce commentaire peut s'étendre sur plusieurs lignes. Il faudra voir à l'usage comment cette possibilité peut s'exploiter au mieux. --> suggestion de Bruno: utiliser le pragma ! Exemple
./ug_make_module.scml
... a completer: ce sont des "cas particuliers" ... (handle-with-new t1 ...) (line-no bool) (no-interface t1 ...) (instantiate t1 ...) (force-conversion t)
./ug_make_module.scml Quand on utilise des définitions de classes réparties dans plusieurs fichiers, par exemple une classe de base dans un fichier f1 et des classes dérivées dans un fichier f2, des déclarations concernant f1 doivent figurer dans f2.ph; il n'est pas évident de savoir quelles déclarations sont nécessaires, et cela peut provoquer des erreurs sournoises, notamment en cas de modifications de programmes. La solution: une unique déclaration (require module) suffit pour introduire toutes les définitions concernant un module. FonctionnementOn sait que la génération de modules produit un fichier nom_module.x.scm. Ce fichier contient désormais une déclaration avec toutes les informations utiles, à l'intention de la déclaration require. Conséquence :en conséquence, les déclarations nécessaires dans les fichiers .ph devraient devenir beaucoup plus limpides, car elles ne concernent rien que les classes (et autres déclarations C++) du fichier courant; toute information héritée d'ailleurs est repêchée ailleurs. Attention : il faut tout de même que l'ordre de génération des modules soit correct, c'est à régler au niveau du makefile.Quelles informations est-il nécessaire ou intéressant d'exporter? Voici les choix faits à présent : Sont exportées telles quelles un certain nombre de déclarations explicites:
Puis des informations collectées tout au long de l'analyse du module : et lors de la lecture des déclarations C++:
et les require rencontrés récursivement dans les modules indiqués.
./ug_make_module.scml
Un engrenage monstrueux dans lequel il était tout de même tentant de mettre un petit doigt ... voilà ce qui est actuellement possible. Les commandes du préprocesseur sont toutes reconnues et envoient vers des traitements spécifiques; un seul de ces traitements fait quelque chose - celui du #define. Plus précisement: une constante définie par un #define est exportée dans le programme Scheme de la même facon qu'une définition de constante en C++. Exemple:
Les constantes générées dans le .x.scm sont:
./ug_make_module.scml
De nombreux traitements sont désormais accessibles, par exemple il serait très facile de traiter les #if #ifdef et compagnie, et même d'avoir un mode de lecture des fichiers #include, de facon à en extraire les définitions utiles - cela modifierait peut-être une partie du require... Par contre, d'autres aspects du préprocesseurs restent exclus - tout ce qui touche au niveau caractère, et qui est généralement considéré magouilleux.
./ug_make_module.scml
Ces déclarations "de première génération" sont toujours acceptées. En principe, elles ont été remplacées par les déclarations décrites précédemment, mais il subsiste des cas où elles restent utiles - il faudra trier! <classe> est le nom d'un type C++ pour lequel on veut definir un type Scheme. Comme on utilisera généralement des pointeurs, la forme a utiliser est (* Toto). <print> : Definit la fonction d'impression. Cet argument peut etre :
Exemple
Nous verrons plus loin ou et comment ecrire les fonctions d'impression et de destruction. (syntax-type <Type1> <Type2> ...) Cette declaration sert a declarer l'existence d'un type a l'analyse syntaxique. Elle a exactement les memes effets qu'un typedef ou une declaration class ou struct dans le fichier de prototypes. (rename-type <Type1> <Type2>) (Donner a Type1 le nom Type2) Sert a renommer un type en un autre au niveau de Ravi. Cette declaration permet essentiellement d'eviter les conflits de noms. Si deux bibliotheques utilisent des types differents de meme nom, on peut le renommer dans l'une des deux interfaces. Exemple : (rename-type (BARAY *) (BARY *)) (libraries "lib1.a" "lib2.a") - Indique les bibliotheques utiles Cette declaration n'est utile que sur SunOS pour le chargeur dynamique. Elle precise la liste des bibliotheques a charger avec un module. L'ordre indique est l'ordre de chargement. (parent-type <Type> <Nom type2>) - Signale un heritage Cette commande permet d'indiquer qu'il faut prendre en compte l'heritage d'un type par rapport a un autre. Le deuxieme argument est le nom unique du type utilise par Ravi. Par la suite, une fonction qui attend un argument de type type2 acceptera egalement les arguments de type Type. Exemple :
(test-type <type> <nom-scheme> <nom-C>) - génération d'un predicat de test de type. Cette déclaration demande au générateur de créer une méthode de nom <nom-scheme> pour tester si une valeur est de type <type>. Pour éviter les conflits de noms, le concepteur donne également le nom de la fonction C qui sera généree automatiquement. (scheme-name <nom-scheme> <nom-c>) - Impose le nom scheme d'une fonction. Les noms de fonctions crees par le générateur ne sont pas toujours bien choisis. Cette declaration permet de choisir le nom scheme qui sera affecte a l'interface d'une fonction C. Exemple : (scheme-name segment-valide? IsSegmentValide) (scheme-function <name> <c-proc> <nbarg>) - Ajouter une fonction codee manuellement. Dans les cas ou la génération d'interface ne suffit pas, il peut etre necessaire de coder completement l'interface a la main. Cette declaration permet d'ajouter directement une fonction C++ dans Ravi en supposant qu'elle respecte les conventions. <name> est le nom scheme de la fonction. <c-proc> le nom de la fonction C. <nbarg> le nombre d'arguments scheme. Il ne devrait y avoir aucune raison d'utiliser cette declaration desormais !!!!
./ug_make_module.scml Etant donné qu'un fichier .ph contient des parties destinées à des processeurs différents, chacun avec sa syntaxe propre, il n'y a pas de format unique pour les commentaires; il faut donc faire attention, mais les possibilités sont raisonnables.
En dehors de ces phases, toutes les lignes du fichier .ph sont transmises telles quelles vers le .cc, y compris les lignes-commentaires. Remarque: une phase %%input peut contenir rien que des commentaires. C'est un moyen de placer un commentaire non transmis. ==================================================
./ug_make_module.scml
Vu la complexité de C++, le générateur d'interface n'a pas été construit en un jour; il est, au contraire, développé des façon progressive, ce qui explique les limitations qui subsistent. Ces limitations seront levées en fonctions des circonstances - notamment si quelqu'un en fait la demande! Initialiseurs ds constructeurUne vacherie syntaxique: le : xx(e1),yy(e2) dans les constructeurs; faudra régler ça un jour. Autre détails de syntaxe: throw, syntaxe complète du new, delete Les nouveaux cast PréprocesseurLes commandes du préprocesseur sont ignorées par défaut. Voir le paragraphe sur le pp. const ignoréDans l'interface, et dans l'analyse des surcharges, le const est systématiquement ignoré. Cela peut provoquer des erreurs (mais ne nous n'a jamais gênés). Noms de méthodes à éviterLorsqu'on définit des opérateurs, le générateur d'interface les traite de façon analogue aux méthodes; pour la lisibilité du code généré, les fonctions intermédiaires ont des noms assez simples, mais qu'on ne peut pas utiliser par ailleurs - on ne peut donc pas, par exemple, dans une classe définir un opérateur = et une méthode set. Ceci concerne les noms suivants: Ce limitation est facile à contourner, mais serait facile à éliminer aussi. Partie private non analyséeConséquence: si une classe doit être considérée abstraite en raison d'une déclaration dans la partie privée, le générateur produit quand même un constructeur. pour contourner; passer en public. ============================================================ |