[pygccxml-development] Python Property Generation
Brought to you by:
mbaas,
roman_yakovenko
From: Lakin W. <lak...@gm...> - 2006-06-17 21:30:19
|
Hi all, In working on my python-ogre bindings using pyplusplus, I wanted to generate properties for the Ogre API. Roman had directed me towards the custom code creators included with pyplusplus: pygccxml/pyplusplus_dev/examples/custom_code_creator/generate_code.py The following code is heavily based on this example, however the Ogre API is not quite as rigorous as the cpp-code used in the examples, so it is also heavily modified: 1) Read Only properties are generated when there is no setter. 2) The method type is also included in the generated cpp code to distinguish between overloaded method names. 3) Extra checking is done to ensure that the methods found are indeed getters and setters. (Only one argument and void return type for setters. No arguments and non-void return type for getter. The getter return type and the setter argument type should match). 4) In Ogre, there are some cases where a base class initially defines the getter and setter, and the inheriting class overrides _only_ the getter(See the Ogre::Frustum, Ogre::Camera classes for an example). In this case, the above code was generating a read only property for the inheriting class. I've enhanced the code to look at base classes to complete the properties. IE, if there is no setter, an appropriate setter is searched for (recursively) along the bases, the same is done if there is no getter. 5) In Ogre, pointers and references returned are always internal references, and many of the properties return pointers and references. If the getters return a reference or a pointer the call_policies are set on the property. (this could be refactored to be more flexible). I made a best-effort to find the easiest way to do some of these things using the provided API, but I may have missed items. So I'm open to criticisms, or suggestions of how to improve the code by using pygccxml/pyplusplus API's. Lakin #------------------------------------------------------------------------------- class property_creator_t (code_creators.code_creator_t): '''A property creator which has support for generating readonly properties, if no set_function is provided. As well as adding a policy to reference_existing_object if the return type of the function is a reference or pointer.''' def __init__( self, get_function, set_function, parent=None ): #get/set functions are references to relevant declarations code_creators.code_creator_t.__init__ (self, parent) self.__get_function = get_function and get_function.declaration self.__set_function = set_function and set_function.declaration def _create_template (self, function): '''Use this function to create the reference to the function with the appropriate policy.''' if not function: return '' impl = '(%(type)s)( &%(function)s )' % dict ( function = declarations.full_name (function), type = function.function_type ().decl_string) if declarations.is_pointer (function.return_type) \ or declarations.is_reference (function.return_type): # Ogre's default is to return internal references impl = '''bp::make_function (%s, bp::return_value_policy <bp::reference_existing_object, bp::default_call_policies>())''' % impl return ', %s' % impl def _create_impl(self): template = '''add_property ("%(name)s" %(properties)s)''' properties_template = self._create_template (self.__get_function) properties_template += self._create_template (self.__set_function) property_name = self.__get_function.name[3:] property_name = property_name[0].lower() + property_name[1:] return template % dict (name=property_name, properties=properties_template) improper_getters = 0 improper_setters = 0 #------------------------------------------------------------------------------- def create_properties (mb): '''Ogre typically uses getX and setX for properties. However, we have to be careful because there are methods names setX and getX which are not true properties (in a pythonic sense), as sometimes a getX will take one or more arguments, or the setX will take more than one argument. TODO: there are probably exceptions to the naming scheme which should be found and documented.''' # First, some helper functions def is_relevant (code_creator): '''Ensures that we're looking at methods that start with get/set and are public member functions.''' if not isinstance (code_creator, code_creators.declaration_based_t): #We are looking for code creator, that has to export some declaration return False if not isinstance (code_creator.declaration, module_builder.member_function_t): #declaration should be member function return False decl = code_creator.declaration if decl.access_type != "public": # allow only public members. return False #member function name should start from "get" or "set" return decl.name.startswith ('get') or decl.name.startswith ('set') def is_property (getter, setter): '''Checks that we have at least a getter function and that the form of the functions is appropriate: getter has no arguments and returns the same type of object as the type of the only argument to the setter function.''' global improper_setters, improper_getters # Ignore these, as set-only properties don't make of sense. if not getter: return False get_decl = getter.declaration if len (get_decl.arguments) > 0 or get_decl.return_type == 'void': # Make sure it has a non-void return type. improper_getters += 1 return False # We're ok with read only properties if not setter: return True set_decl = setter.declaration if len(set_decl.arguments) != 1: # Getter has no args, and setter has one arg improper_setters += 1 improper_setters += 1 return False if get_decl.return_type != set_decl.arguments[0].type: # Make sure their types match up. improper_setters += 1 return False return True classes = filter (lambda creator: isinstance (creator, code_creators.class_t), code_creators.make_flatten(mb.code_creator)) code_creator_dict = {} for class_creator in classes: code_creator_dict[class_creator.declaration.name] = class_creator for class_creator in classes: print class_creator.declaration.name, "_"*80 accessor_list = filter (is_relevant, class_creator.creators) # get only the relevant code_creators accessors_db = {} # name : {'get':get creator, 'set':set creator] #Filling accessors_db for creator in accessor_list: type = creator.declaration.name[:3] property_name = creator.declaration.name[3:] print type, property_name if not accessors_db.has_key (property_name): accessors_db [property_name] = {'set':None,'get':None} if accessors_db[property_name][type]: # If we already have this type of creator we will prefer # ones without a const, or the first one if hasattr (accessors_db[property_name][type].declaration,'has_const') and \ accessors_db[property_name][type].declaration.has_const: print "SWITCHING:", creator.declaration.name, "instead of", accessors_db [property_name][type].declaration.name accessors_db [property_name][type] = creator else: accessors_db [property_name][type] = creator def find_accessor_in_base (code_creator, name, type): '''Searches in the base classes for the accessor with the right type and name.''' bases = code_creator.declaration.bases[:] # make a copy of direct bases while len(bases) > 0: base_class = bases.pop(0) print "Searching in:", base_class.related_class.name, "_"*80 if not code_creator_dict.has_key (base_class.related_class.name): continue accessor_list = filter (is_relevant, code_creator_dict[base_class.related_class.name].creators) for creator in accessor_list: if not hasattr (creator,"declaration"): continue foundType = creator.declaration.name[:3] property_name = creator.declaration.name[3:] if type == foundType and property_name == name: return creator # add indirect bases of this class. bases.extend (base_class.related_class.bases) return None for name,accessors in accessors_db.items(): # If we're missing an accessor, try to find it in the parent class accessors['get'] = accessors['get'] or find_accessor_in_base (class_creator,name,'get') accessors['set'] = accessors['set'] or find_accessor_in_base (class_creator,name,'set') if is_property (accessors['get'], accessors['set']): prop_creator = property_creator_t (accessors['get'], accessors['set']) class_creator.adopt_creator (prop_creator) print "-"*80 print "Improper Getters", improper_getters print "Improper Setters", improper_setters |