#include #include #include #include #include /*----------------------------------------------------------------------------*/ /** * Une liste de type est définie comme une imbrication de * type_list. Par exemple : * * type_list< int, type_list >; * * Note : une liste de type doit toujours se terminer par no_type. */ template struct type_list { /** \brief Un élément de la liste de types. */ typedef HeadType head_type; /** \brief La suite de la liste. */ typedef TailType tail_type; }; // struct type_list /*----------------------------------------------------------------------------*/ /** \brief Ce type est utilisé pour marquer la fin d'une liste de types. */ struct no_type { }; /*----------------------------------------------------------------------------*/ /** * Une liste de type est définie comme une imbrication de * type_list. Par exemple : * * type_list< int, type_list >; * * Ce genre d'écriture est peu pratique. Par conséquent, on définit les classes * utilitaires ci-dessous qui définissent une liste de types à partir des types * passés en paramètres au patron de classe. */ template struct type_list_maker_1: public type_list { }; template struct type_list_maker_2: public type_list< T1, type_list_maker_1 > { }; template struct type_list_maker_3: public type_list< T1, type_list_maker_2 > { }; /*----------------------------------------------------------------------------*/ /** * Le code ci-dessous permet de créer des objets ayant des méthodes appelables * en mode texte. C'est à dire que des méthodes classiques peuvent être appelées * en donnant le nom de la méthode est ses paramètres sous forme de * texte. L'avantage immédiat est de pouvoir écrire des scripts appelant des * méthodes d'une classe sans avoir à redéfinir les méthodes comme en Lua. Le * coût se limite au final à l'utilisation de trois macros dans la classe et * d'une macro par méthode que l'on veut appelable en texte. * * Il y a deux limites dans le code ci-dessous : * - il n'y a pas de contexte lorsqu'on appelle une méthode (comme d'autres * objets dans la scène) * - il n'est pas possible d'utiliser des paramètres const, des pointeurs, des * références dans les méthodes appelables, ni même d'utiliser des méthodes * const. */ template class get_inner_type { public: typedef T type; }; template class get_inner_type: public get_inner_type { }; template class get_inner_type: public get_inner_type { }; /*----------------------------------------------------------------------------*/ /** * Prédéclaration du contexte d'exécution du script, passé jusqu'à convert * histoire de pouvoir convertir un paramètre en quelque chose de * l'environnement. */ class script_context; /*----------------------------------------------------------------------------*/ /** * Cette classe convertit une chaîne de caractères en une valeur d'un type * donné. Elle est séparée de arg_table::convert pour faciliter sa * spécialisation. */ template class string_to_type { public: static inline void convert ( const std::string& str, T& val, const script_context& context ) { std::istringstream iss(str); iss >> val; } }; // class string_to_type /*----------------------------------------------------------------------------*/ /** * arg_table est une classe utilitaire qui permet de récupérer le * type en position numéro N dans la TypeList (le premier est à la position * zéro), et qui définit une méthode de conversion depuis une chaine de * caractère vers ce type. * * Ceci est effectué à l'aide de méta-programmation, à base de patrons de * classe. */ /*----------------------------------------------------------------------------*/ /** Prédéclaration de la classe. */ template struct arg_table; /*----------------------------------------------------------------------------*/ /** Spécialisation pour le cas d'arrêt où il n'y a pas assez de types dans la liste (erreur). */ template struct arg_table { }; // struct arg_table /*----------------------------------------------------------------------------*/ /** Spécialisation pour le cas d'arrêt où le type voulu est trouvé. */ template struct arg_table<0, TypeList> { public: typedef typename TypeList::head_type type; typedef typename get_inner_type::type inner_type; public: /** la fonction qui convertit une chaîne de caractères dans le type voulu. */ static inline inner_type convert ( const std::string& s, const script_context& context ) { inner_type result; string_to_type::convert(s, result, context); return result; } }; // struct arg_table /*----------------------------------------------------------------------------*/ /** Si aucune des spécialisations ci-dessus ne correspond à l'instanciation voulue, on essaie ici de passer au paramètre suivant de la liste. */ template struct arg_table: public arg_table { }; // struct arg_table /*----------------------------------------------------------------------------*/ /** Prédéclaration de la classe de base des personnages pouvant recevoir des commandes. */ class base_exportable; // prédéclaration pour script_context class image; /*----------------------------------------------------------------------------*/ /** * Le contexte d'exécution du script, passé jusqu'à convert histoire de pouvoir * convertir un paramètre en quelque chose de l'environnement. * * Cette classe est à ajuster selon le programme. Elle doit au moins contenir ce * qui est nécéssaire à la conversion des paramètres par les spécialisations de * string_to_type. * */ class script_context { public: std::map images; }; // class script_context /*----------------------------------------------------------------------------*/ /** La classe de base pour les classes appelants les fonctions membre. Elle est utilisée pour stoker ces instances dans un seul conteneur. Pour définir une classe appelant une fonction membre, on préfèrera utiliser explicit_export_method. */ class export_method { public: virtual void execute ( base_exportable* self, const std::vector& args, const script_context& context ) const = 0; }; // class export_method /*----------------------------------------------------------------------------*/ /** La classe de base pour les classes appelants les fonctions membre. Elle convertit la classe passée à export_method::execute() vers une instance d'un type prédéfinit. */ template class explicit_export_method: public export_method { public: virtual void explicit_execute ( SelfClass& self, const std::vector& args, const script_context& context ) const = 0; private: void execute ( base_exportable* self, const std::vector& args, const script_context& context ) const { SelfClass* s = dynamic_cast(self); if ( s!=NULL ) explicit_execute(*s, args, context); } }; // explicit_export_method /*----------------------------------------------------------------------------*/ /** Cette classe définit le type d'une fonction membre non const, d'une classe donnée et avec aucun paramètre. */ template class make_member_fun_0 { public: typedef void (SelfClass::*mem_fun_type)(); }; // class make_member_fun_0 /*----------------------------------------------------------------------------*/ /** Cette classe définit un dérivé de explicit_export_method pour appeler une méthode donnée d'une classe donnée. Cette dernière est définit par SelfClasse. L'adresse de la méthode est Member et elle ne doit prendre aucun paramètre. */ template::mem_fun_type Member> class export_method_args_0 { public: typedef typename make_member_fun_0::mem_fun_type mem_fun_type; public: class caller_type: public explicit_export_method { private: void explicit_execute ( SelfClass& self, const std::vector& args, const script_context& context ) const { const mem_fun_type member(Member); (self.*member)(); } }; // class caller_type; public: /** Une seule instance de caller_type est suffisante dans la mesure où il n'y a pas de variables membres (s_caller ne sert qu'à faire la transition vers la méthode réelle). On pourra ainsi utiliser son adresse et éviter des allocations dynamiques. */ static const caller_type s_caller; }; // class export_method_args_0 /*----------------------------------------------------------------------------*/ /** Définition de export_method_args_0::s_caller pour les paramètres de patron de classes donnés. */ template::mem_fun_type Member> const typename export_method_args_0::caller_type export_method_args_0::s_caller; /*----------------------------------------------------------------------------*/ /** Cette classe définit le type d'une fonction membre non const, d'une classe donnée et avec un seul paramètre passé dans ArgsList. */ template class make_member_fun_1 { public: typedef void (SelfClass::*mem_fun_type) ( typename arg_table<0, ArgsList>::type ); }; // class make_member_fun_1 /*----------------------------------------------------------------------------*/ /** Cette classe définit un dérivé de explicit_export_method pour appeler une méthode donnée d'une classe donnée. Cette dernière est définit par SelfClasse. L'adresse de la méthode est Member et doit prendre un unique paramètre dont le type est donné dans ArgsList. */ template::mem_fun_type Member> class export_method_args_1 { public: typedef typename make_member_fun_1::mem_fun_type mem_fun_type; public: class caller_type: public explicit_export_method { private: void explicit_execute ( SelfClass& self, const std::vector& args, const script_context& context ) const { const mem_fun_type member(Member); (self.*member)( arg_table<0, ArgsList>::convert(args[0], context) ); } }; // class caller_type; public: /** Une seule instance de caller_type est suffisante dans la mesure où il n'y a pas de variables membres (s_caller ne sert qu'à faire la transition vers la méthode réelle). On pourra ainsi utiliser son adresse et éviter des allocations dynamiques. */ static const caller_type s_caller; }; // class export_method_args_1 /*----------------------------------------------------------------------------*/ /** Définition de export_method_args_1::s_caller pour les paramètres de patron de classes donnés. */ template::mem_fun_type Member> const typename export_method_args_1::caller_type export_method_args_1::s_caller; /*----------------------------------------------------------------------------*/ /** Cette classe définit le type d'une fonction membre non const, d'une classe donnée et avec deux paramètres passés dans ArgsList. */ template class make_member_fun_2 { public: typedef void (SelfClass::*mem_fun_type) ( typename arg_table<0, ArgsList>::type, typename arg_table<1, ArgsList>::type ); }; // class make_member_fun_2 /*----------------------------------------------------------------------------*/ /** Cette classe définit un dérivé de explicit_export_method pour appeler une méthode donnée d'une classe donnée. Cette dernière est définit par SelfClasse. L'adresse de la méthode est Member et doit prendre deux paramètres dont le type est donné dans ArgsList. */ template::mem_fun_type Member> class export_method_args_2 { public: typedef typename make_member_fun_2::mem_fun_type mem_fun_type; public: class caller_type: public explicit_export_method { private: void explicit_execute ( SelfClass& self, const std::vector& args, const script_context& context ) const { const mem_fun_type member(Member); (self.*member) ( arg_table<0, ArgsList>::convert(args[0], context), arg_table<1, ArgsList>::convert(args[1], context) ); } }; // class caller_type; public: /** Une seule instance de caller_type est suffisante dans la mesure où il n'y a pas de variables membres (s_caller ne sert qu'à faire la transition vers la méthode réelle). On pourra ainsi utiliser son adresse et éviter des allocations dynamiques. */ static const caller_type s_caller; }; // class export_method_args_2 /*----------------------------------------------------------------------------*/ /** Définition de export_method_args_2::s_caller pour les paramètres de patron de classes donnés. */ template::mem_fun_type Member> const typename export_method_args_2::caller_type export_method_args_2::s_caller; /*----------------------------------------------------------------------------*/ /** Cette classe définit le type d'une fonction membre non const, d'une classe donnée et avec trois paramètres passés dans ArgsList. */ template class make_member_fun_3 { public: typedef void (SelfClass::*mem_fun_type) ( typename arg_table<0, ArgsList>::type, typename arg_table<1, ArgsList>::type, typename arg_table<2, ArgsList>::type ); }; // class make_member_fun_3 /*----------------------------------------------------------------------------*/ /** Cette classe définit un dérivé de explicit_export_method pour appeler une méthode donnée d'une classe donnée. Cette dernière est définit par SelfClasse. L'adresse de la méthode est Member et doit prendre trois paramètres dont le type est donné dans ArgsList. */ template::mem_fun_type Member> class export_method_args_3 { public: typedef typename make_member_fun_3::mem_fun_type mem_fun_type; public: class caller_type: public explicit_export_method { private: void explicit_execute ( SelfClass& self, const std::vector& args, const script_context& context ) const { const mem_fun_type member(Member); (self.*member) ( arg_table<0, ArgsList>::convert(args[0], context), arg_table<1, ArgsList>::convert(args[1], context), arg_table<1, ArgsList>::convert(args[2], context) ); } }; // class caller_type; public: /** Une seule instance de caller_type est suffisante dans la mesure où il n'y a pas de variables membres (s_caller ne sert qu'à faire la transition vers la méthode réelle). On pourra ainsi utiliser son adresse et éviter des allocations dynamiques. */ static const caller_type s_caller; }; // class export_method_args_3 /*----------------------------------------------------------------------------*/ /** Définition de export_method_args_3::s_caller pour les paramètres de patron de classes donnés. */ template::mem_fun_type Member> const typename export_method_args_3::caller_type export_method_args_3::s_caller; /*----------------------------------------------------------------------------*/ /** On en a fini avec les classes utilitaires. Si on souhaite appeler des méthodes avec trois paramètres, il suffira de définir un nouveau couple de classes make_member_fun_3/export_method_args_3. De même pour plus de trois paramètres. */ /** On va définir ci-dessous la classe de base pour les objets pouvant recevoir des commandes. Ces classes contiennent une table statique des export_method à appeler pour un nom de méthode donnée. Ce sera définit avec les classes utilitaires ci-dessus d'une manière qui sera, au final, invisible pour le créateur de la classe. Ce dernier n'aura que quelques macros à appeler à la manière des événements de wxWidgets. */ /*----------------------------------------------------------------------------*/ /** Les données d'une méthode appelable. */ struct method_list_data { method_list_data( const char* n, const export_method* m ) : name(n), method(m) { } /** Le nom de la méthode. */ char const* name; /** L'exécuteur de la méthode. */ export_method const* method; }; // struct method_list_data /*----------------------------------------------------------------------------*/ /** La liste des méthode appelables. */ struct method_list { /** La liste de la classe mère. Cela implique qu'il n'y a pas d'héritage multiple pour l'instant. */ method_list const* parent; /** La table des exécuteurs. */ method_list_data const* data; }; // struct method_list /*----------------------------------------------------------------------------*/ /** Cette macro, appelée dans la définition d'une classe, déclare les variables membres statiques utilisées pour stoker les exécuteurs. */ #define DECLARE_METHOD_LIST \ protected: \ static const method_list s_method_list; \ \ private: \ virtual method_list get_method_list() const { return s_method_list; } \ \ private: \ static const method_list_data s_method_data[]; \ \ private: \ /*----------------------------------------------------------------------------*/ /** Cette macro est utilisée dans l'implémentation de la classe mère pour terminer la liste des exécuteurs de la hiérarchie. */ #define IMPLEMENT_ROOT_METHOD_LIST(self) \ const method_list self::s_method_list = {NULL, &s_method_data[0]}; \ const method_list_data self::s_method_data[] = \ { method_list_data(NULL, NULL) } /*----------------------------------------------------------------------------*/ /** Cette macro est utilisée dans l'implémentation d'une classe dérivant de base_exportable pour débuter la liste des exécuteurs. */ #define METHOD_LIST_BEGIN(parent, self) \ const method_list self::s_method_list = \ { &parent::s_method_list, &s_method_data[0] }; \ const method_list_data self::s_method_data[] = { /*----------------------------------------------------------------------------*/ /** Cette macro est utilisée dans l'implémentation d'une classe dérivant de base_exportable pour terminer la liste des exécuteurs. */ #define METHOD_LIST_END method_list_data(NULL, NULL) } /*----------------------------------------------------------------------------*/ /** Cette macro est utilisée dans l'implémentation d'une classe dérivant de base_exportable pour ajouter un exécuteur d'une méthode sans paramètres. */ #define CONNECT_METHOD_0( self_type, method_name ) \ method_list_data \ ( #method_name, \ &export_method_args_0::s_caller ), /*----------------------------------------------------------------------------*/ /** Cette macro est utilisée dans l'implémentation d'une classe dérivant de base_exportable pour ajouter un exécuteur d'une méthode ayant un unique paramètre, de type T1. */ #define CONNECT_METHOD_1( self_type, method_name, T1 ) \ method_list_data \ ( #method_name, \ &export_method_args_1 \ < \ self_type, \ type_list_maker_1, \ &self_type::method_name >::s_caller ), /*----------------------------------------------------------------------------*/ /** Cette macro est utilisée dans l'implémentation d'une classe dérivant de base_exportable pour ajouter un exécuteur d'une méthode ayant deux paramètres, de type T1 et T2. */ #define CONNECT_METHOD_2( self_type, method_name, T1, T2 ) \ method_list_data \ ( #method_name, \ &export_method_args_2 \ < \ self_type, \ type_list_maker_2, \ &self_type::method_name >::s_caller ), /*----------------------------------------------------------------------------*/ /** Cette macro est utilisée dans l'implémentation d'une classe dérivant de base_exportable pour ajouter un exécuteur d'une méthode ayant trois paramètres, de type T1, T2 et T3. */ #define CONNECT_METHOD_3( self_type, method_name, T1, T2, T3 ) \ method_list_data \ ( #method_name, \ &export_method_args_3 \ < \ self_type, \ type_list_maker_3, \ &self_type::method_name >::s_caller ), /*----------------------------------------------------------------------------*/ /** Cette classe est la base de toutes les classes pouvant exécuter des commandes. */ class base_exportable { DECLARE_METHOD_LIST public: /** Destructeur indispensable pour pouvoir utiliser le dynamic_cast dans explicit_export_method. */ virtual ~base_exportable() {} void execute( const std::string& n, const std::vector& args, const script_context& context ) { // on récupère la liste des exécuteurs dans la classe la plus basse dans la // hiérarchie. method_list m( get_method_list() ); bool stop(false); std::size_t i(0); while( !stop ) // there is at least the null-terminating entry in m.data if ( m.data[i].name == NULL ) { // On est au bout de la table des exécuteurs. On passe alors à la // table de la classe mère, si elle existe. if ( m.parent != NULL ) { m = *m.parent; i = 0; } else { std::cout << "Method '" << n << "' not found." << std::endl; stop = true; } } else if ( n == m.data[i].name ) { // On a trouvé la bonne méthode, on l'exécute. stop = true; m.data[i].method->execute(this, args, context); } else ++i; } }; // class base_exportable /*----------------------------------------------------------------------------*/ /** Initialisation des variables membres utilisées pour la liste des exécuteurs dans la classe base_exportable */ IMPLEMENT_ROOT_METHOD_LIST(base_exportable); /*----------------------------------------------------------------------------*/ /** À partir de là, on trouve le genre de code qui sera écrit lorsqu'on voudra créer une classe pouvant exécuter des commandes. */ // prédéclaration class image; /*----------------------------------------------------------------------------*/ /** Une classe bidon avec des méthodes appelables par commandes. */ class image: public base_exportable { DECLARE_METHOD_LIST private: void miroir() { std::cout << __FUNCTION__ << std::endl; } void tourner( double a, int cx, int cy ) { std::cout << __FUNCTION__ << ' ' << a << ' ' << cx << ' ' << cy << std::endl; } void copier( const image& img, int x, int y ) { std::cout << __FUNCTION__ << ' ' << &img << ' ' << x << ' ' << y << std::endl; } }; // class image /** Il faut connecter les méthodes pour qu'elles puisse être exécutées */ METHOD_LIST_BEGIN(base_exportable, image) CONNECT_METHOD_0( image, miroir ) CONNECT_METHOD_3( image, tourner, double, int, int ) CONNECT_METHOD_3( image, copier, const image&, int, int ) METHOD_LIST_END; /*----------------------------------------------------------------------------*/ /** * Cette classe convertit une chaîne de caractères en une valeur d'un type * donné. Cette spécialisation récupère le pointeur vers une instance * correspondant à un nom. */ template<> class string_to_type { private: typedef image value_type; public: static inline void convert ( const std::string& str, value_type& val, const script_context& context ) { val = *context.images.find(str)->second; } }; // class string_to_type /*----------------------------------------------------------------------------*/ class script_reader { public: void add_image( const std::string& name, image* a ) { m_context.images[name] = a; } void execute_command( const std::string& command ) { std::istringstream iss(command); std::string img; std::string method; std::vector args; iss >> img >> method; std::string a; while ( iss >> a ) args.push_back(a); m_context.images[img]->execute( method, args, m_context ); } private: script_context m_context; }; // script_reader /*----------------------------------------------------------------------------*/ int main() { image c1, c2; script_reader reader; reader.add_image("image_1", &c1); reader.add_image("image_2", &c2); reader.execute_command("image_1 miroir"); reader.execute_command("image_2 tourner 18.6 24 -16"); reader.execute_command("image_1 copier image_2 700 30"); reader.execute_command("image_1 miroir"); return 0; } // main()