diff --git a/pyste/src/ClassExporter.py b/pyste/src/ClassExporter.py index d72ff9db..64bf3834 100644 --- a/pyste/src/ClassExporter.py +++ b/pyste/src/ClassExporter.py @@ -34,12 +34,9 @@ class ClassExporter(Exporter): # declarations: outside the BOOST_PYTHON_MODULE macro self.sections['declaration'] = [] self.sections['include'] = [] - # a list of Method instances - self.methods = [] # a list of Constructor instances self.constructors = [] - # a dict of methodname => _WrapperVirtualMethod instances - self.virtual_wrappers = {} + self.wrapper_generator = None # a list of code units, generated by nested declarations self.nested_codeunits = [] @@ -83,13 +80,12 @@ class ClassExporter(Exporter): def Export(self, codeunit, exported_names): - self.GetMethods() self.ExportBasics() self.ExportBases(exported_names) self.ExportConstructors() self.ExportVariables() self.ExportMethods() - self.GenerateVirtualWrapper() + self.ExportVirtualMethods() self.ExportOperators() self.ExportNestedClasses(exported_names) self.ExportNestedEnums() @@ -151,7 +147,7 @@ class ClassExporter(Exporter): def Add(self, section, item): 'Add the item into the corresponding section' - self.sections[section].append(item.strip()) + self.sections[section].append(item) def ExportBasics(self): @@ -203,8 +199,14 @@ class ClassExporter(Exporter): # declare no_init self.Add('constructor', py_ns + 'no_init') else: - # write one of the constructors to the class_ constructor - self.Add('constructor', init_code(constructors.pop(0))) + # write the constructor with less parameters to the constructor section + smaller = None + for cons in constructors: + if smaller is None or len(cons.parameters) < len(smaller.parameters): + smaller = cons + assert smaller is not None + self.Add('constructor', init_code(smaller)) + constructors.remove(smaller) # write the rest to the inside section, using def() for cons in constructors: code = '.def(%s)' % init_code(cons) @@ -234,18 +236,6 @@ class ClassExporter(Exporter): self.Add('inside', code) - def GetMethods(self): - 'fill self.methods with a list of Method instances' - # get a list of all methods - def IsValid(m): - 'Returns true if the given method is exportable by this routine' - ignore = (Constructor, ClassOperator, Destructor) - return isinstance(m, Method) and not isinstance(m, ignore) - - self.methods = [x for x in self.public_members if IsValid(x)] - - - printed_policy_warnings = {} def CheckPolicy(self, m): @@ -265,25 +255,22 @@ class ClassExporter(Exporter): def ExportMethods(self): - 'Export all the methods of the class' + 'Export all the non-virtual methods of this class' def OverloadName(m): - 'Returns the name of the overloads struct for the given method' - + 'Returns the name of the overloads struct for the given method' return _ID(m.FullName()) + ('_overloads_%i_%i' % (m.minArgs, m.maxArgs)) declared = {} def DeclareOverloads(m): 'Declares the macro for the generation of the overloads' - if m.virtual: - func = self.virtual_wrappers[m.PointerDeclaration()].DefaultName() - else: + if not m.virtual: func = m.name - code = 'BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(%s, %s, %i, %i)\n' - code = code % (OverloadName(m), func, m.minArgs, m.maxArgs) - if code not in declared: - declared[code] = True - self.Add('declaration', code) + code = 'BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(%s, %s, %i, %i)\n' + code = code % (OverloadName(m), func, m.minArgs, m.maxArgs) + if code not in declared: + declared[code] = True + self.Add('declaration', code) def Pointer(m): @@ -292,9 +279,6 @@ class ClassExporter(Exporter): wrapper = self.info[method.name].wrapper if wrapper: return '&' + wrapper.FullName() - # if this method is virtual, return the pointers to the class and its wrapper - if m.virtual: - return self.virtual_wrappers[m.PointerDeclaration()].Pointer() # return normal pointers to the methods of the class is_unique = self.class_.IsUnique(m.name) if is_unique: @@ -302,19 +286,19 @@ class ClassExporter(Exporter): else: return method.PointerDeclaration() - - for method in self.methods: + def IsExportable(m): + 'Returns true if the given method is exportable by this routine' + ignore = (Constructor, ClassOperator, Destructor) + return isinstance(m, Method) and not isinstance(m, ignore) and not m.virtual + + methods = [x for x in self.public_members if IsExportable(x)] + + for method in methods: if self.info[method.name].exclude: continue # skip this method name = self.info[method.name].rename or method.name - # check if this method needs to be wrapped as a virtual method - if method.virtual: - wrapper = _WrapperVirtualMethod(self.class_, method, name) - self.virtual_wrappers[method.PointerDeclaration()] = wrapper - # abstract methods don't need to be exported - if method.abstract: - continue # skip .def declaration for abstract methods + # warn the user if this method needs a policy and doesn't have one self.CheckPolicy(method) @@ -328,15 +312,9 @@ class ClassExporter(Exporter): # add the overloads for this method overload_name = OverloadName(method) DeclareOverloads(method) - if not method.virtual: - overload = ', %s%s()' % (namespaces.pyste, overload_name) - else: - pyste_ns = namespaces.pyste - pointer = self.virtual_wrappers[method.PointerDeclaration()].DefaultPointer() - defcode = '.def("%s", %s, %s%s())' % \ - (name, pointer, pyste_ns, overload_name) - self.Add('inside', defcode) - # build the string to export the method + overload = ', %s%s()' % (namespaces.pyste, overload_name) + + # build the .def string to export the method pointer = Pointer(method) code = '.def("%s", %s' % (name, pointer) code += policy @@ -353,38 +331,21 @@ class ClassExporter(Exporter): self.Add('declaration', wrapper.code) - def GenerateVirtualWrapper(self): - 'Generate the wrapper to dispatch virtual methods' - # check if this class needs a wrapper first - for m in self.methods: - if m.virtual: + def ExportVirtualMethods(self): + # check if this class has any virtual methods + has_virtual_methods = False + for member in self.class_.members: + if type(member) == Method and member.virtual: + has_virtual_methods = True break - else: - return - # add the wrapper name to the template section - wrapper_name = _WrapperName(self.class_) - self.Add('template', namespaces.pyste + wrapper_name) - indent = self.INDENT - method_codes = [x.Code(indent) for x in self.virtual_wrappers.values()] - body = '\n'.join(method_codes) - # generate the class code - class_name = self.class_.FullName() - code = 'struct %s: %s\n' % (wrapper_name, class_name) - code += '{\n' - # generate constructors - for cons in self.constructors: - params, param_names, param_types = _ParamsInfo(cons) - if params: - params = ', ' + params - cons_code = indent + '%s(PyObject* self_%s):\n' % (wrapper_name, params) - cons_code += indent*2 + '%s(%s), self(self_) {}\n\n' % \ - (class_name, ', '.join(param_names)) - code += cons_code - code += body + '\n' - code += indent + 'PyObject* self;\n' - code += '};\n' - self.Add('declaration', code) + if has_virtual_methods: + generator = _VirtualWrapperGenerator(self.class_, self.info) + self.Add('template', generator.FullName()) + for definition in generator.GenerateDefinitions(): + self.Add('inside', definition) + self.Add('declaration', generator.GenerateVirtualWrapper(self.INDENT)) + # operators natively supported by boost BOOST_SUPPORTED_OPERATORS = '+ - * / % ^ & ! ~ | < > == != <= >= << >> && || += -='\ @@ -585,104 +546,181 @@ def _ID(name): # Virtual Wrapper utils #============================================================================== -def _WrapperName(class_): - return _ID(class_.FullName()) + '_Wrapper' - - -def _ParamsInfo(m): - param_names = ['p%i' % i for i in range(len(m.parameters))] - param_types = [x.FullName() for x in m.parameters] +def _ParamsInfo(m, count=None): + if count is None: + count = len(m.parameters) + param_names = ['p%i' % i for i in range(count)] + param_types = [x.FullName() for x in m.parameters[:count]] params = ['%s %s' % (t, n) for t, n in zip(param_types, param_names)] - for i, p in enumerate(m.parameters): - if p.default is not None: - #params[i] += '=%s' % p.default - params[i] += '=%s' % (p.name + '()') + #for i, p in enumerate(m.parameters[:count]): + # if p.default is not None: + # #params[i] += '=%s' % p.default + # params[i] += '=%s' % (p.name + '()') params = ', '.join(params) return params, param_names, param_types -class _WrapperVirtualMethod(object): - 'Holds information about a virtual method that will be wrapped' +class _VirtualWrapperGenerator(object): + 'Generates code to export the virtual methods of the given class' - def __init__(self, class_, method, rename): - self.method = method - if rename is None: - rename = method.name - self.rename = rename + def __init__(self, class_, info): self.class_ = class_ + self.info = info + self.wrapper_name = _ID(class_.FullName()) + '_Wrapper' - def DefaultName(self): - return 'default_' + self.method.name - - - def DefaultPointer(self): - ns = namespaces.pyste - wrapper_name = _WrapperName(self.class_) - default_name = self.DefaultName() - fullname = '%s%s::%s' % (ns, wrapper_name, default_name) - if self.class_.IsUnique(self.method.name): - return '&%s' % fullname + def DefaultImplementationNames(self, method): + '''Returns a list of default implementations for this method, one for each + number of default arguments. Always returns at least one name, and return from + the one with most arguments to the one with the least. + ''' + base_name = 'default_' + method.name + minArgs = method.minArgs + maxArgs = method.maxArgs + if minArgs == maxArgs: + return [base_name] else: - # the method is not unique, so we must specify the entire signature with it - param_list = [x.FullName() for x in self.method.parameters] - params = ', '.join(param_list) - result = self.method.result.FullName() - signature = '%s (%s%s::*)(%s)' % (result, ns, wrapper_name, params) - return '(%s)%s' % (signature, fullname) + return [base_name + ('_%i' % i) for i in range(minArgs, maxArgs+1)] + + + def Declaration(self, method, indent): + '''Returns a string with the declarations of the virtual wrapper and + its default implementations. This string must be put inside the Wrapper + body. + ''' + pyste = namespaces.pyste + python = namespaces.python + rename = self.info[method.name].rename or method.name + result = method.result.FullName() + return_str = 'return ' + if result == 'void': + return_str = '' + params, param_names, param_types = _ParamsInfo(method) + constantness = '' + if method.const: + constantness = ' const' + + # call_method callback + decl = indent + '%s %s(%s)%s {\n' % (result, method.name, params, constantness) + param_names_str = ', '.join(param_names) + if param_names_str: + param_names_str = ', ' + param_names_str + decl += indent*2 + '%s%scall_method<%s>(self, "%s"%s);\n' %\ + (return_str, python, result, rename, param_names_str) + decl += indent + '}\n' + + # default implementations (with overloading) + if not method.abstract: + minArgs = method.minArgs + maxArgs = method.maxArgs + impl_names = self.DefaultImplementationNames(method) + for impl_name, argNum in zip(impl_names, range(minArgs, maxArgs+1)): + params, param_names, param_types = _ParamsInfo(method, argNum) + decl += '\n' + decl += indent + '%s %s(%s)%s {\n' % (result, impl_name, params, constantness) + decl += indent*2 + '%s%s::%s(%s);\n' % \ + (return_str, self.class_.FullName(), method.name, ', '.join(param_names)) + decl += indent + '}\n' + return decl - def Pointer(self): - '''Returns the "pointer" declaration for this method, ie, the contents - of the .def after the method name (.def("name", ))''' - ns = namespaces.pyste - default_name = self.DefaultName() - name = self.method.name + def MethodDefinition(self, method): + '''Returns a list of lines, which should be put inside the class_ + statement to export this method.''' + # dont define abstract methods + if method.abstract: + return [] + pyste = namespaces.pyste + rename = self.info[method.name].rename or method.name + default_names = self.DefaultImplementationNames(method) class_name = self.class_.FullName() - wrapper = ns + _WrapperName(self.class_) - if self.class_.IsUnique(self.method.name): - return '&%s::%s, &%s::%s' % (class_name, name, wrapper, default_name) + wrapper_name = pyste + self.wrapper_name + result = method.result.FullName() + is_method_unique = self.class_.IsUnique(method.name) + constantness = '' + if method.const: + constantness = ' const' + + # create a list of default-impl pointers + minArgs = method.minArgs + maxArgs = method.maxArgs + if is_method_unique: + default_pointers = ['&%s::%s' % (wrapper_name, x) for x in default_names] else: - # the method is not unique, so we must specify the entire signature with it - param_list = [x.FullName() for x in self.method.parameters] - params = ', '.join(param_list) - result = self.method.result.FullName() - default_sig = '%s (%s::*)(%s)' % (result, wrapper, params) - normal_sig = '%s (%s::*)(%s)' % (result, class_name, params) - return '(%s)%s::%s, (%s)%s::%s' % \ - (normal_sig, class_name, name, default_sig, wrapper, default_name) + default_pointers = [] + for impl_name, argNum in zip(default_names, range(minArgs, maxArgs+1)): + param_list = [x.FullName() for x in method.parameters[:argNum]] + params = ', '.join(param_list) + signature = '%s (%s::*)(%s)%s' % (result, wrapper_name, params, constantness) + default_pointer = '(%s)%s::%s' % (signature, wrapper_name, impl_name) + default_pointers.append(default_pointer) + + # get the pointer of the method + if is_method_unique: + pointer = '&' + method.FullName() + else: + pointer = method.PointerDeclaration() + # generate the defs + definitions = [] + # basic def + definitions.append('.def("%s", %s, %s)' % (rename, pointer, default_pointers[-1])) + for default_pointer in default_pointers[:-1]: + definitions.append('.def("%s", %s)' % (rename, default_pointer)) + return definitions - def Code(self, indent): - params, param_names, param_types = _ParamsInfo(self.method) - result = self.method.result.FullName() - return_ = 'return ' - if result == 'void': - return_ = '' - param_names = ', '.join(param_names) - class_name = self.class_.FullName() - method_name = self.method.name - default_name = self.DefaultName() - # constantness - const = '' - if self.method.const: - const = 'const ' - code = '' - # create default_method if this method has a default implementation - if not self.method.abstract: - default_sig = '%s %s(%s) %s' % (result, default_name, params, const) - body = '{ %s%s::%s(%s); } ' % \ - (return_, class_name, method_name, param_names) - code += indent + default_sig + body + '\n' - # create normal method - normal_sig = '%s %s(%s) %s' % (result, method_name, params, const) - if param_names: - param_names = ', ' + param_names - body = '{ %s%scall_method< %s >(self, "%s"%s); }' % \ - (return_, namespaces.python, result, self.rename, param_names) - code += indent + normal_sig + body + '\n' - - return code - - + def FullName(self): + return namespaces.pyste + self.wrapper_name + + + def VirtualMethods(self): + return [m for m in self.class_.members if type(m) == Method and m.virtual] + + + def Constructors(self): + return [m for m in self.class_.members if isinstance(m, Constructor)] + + + def GenerateDefinitions(self): + defs = [] + for method in self.VirtualMethods(): + if not self.info[method.name].exclude: + defs.extend(self.MethodDefinition(method)) + return defs + + + def GenerateVirtualWrapper(self, indent): + 'Return the wrapper for this class' + + # generate the class code + class_name = self.class_.FullName() + code = 'struct %s: %s\n' % (self.wrapper_name, class_name) + code += '{\n' + # generate constructors (with the overloads for each one) + for cons in self.Constructors(): + minArgs = cons.minArgs + maxArgs = cons.maxArgs + # from the min number of arguments to the max number, generate + # all version of the given constructor + cons_code = '' + for argNum in range(minArgs, maxArgs+1): + params, param_names, param_types = _ParamsInfo(cons, argNum) + if params: + params = ', ' + params + cons_code += indent + '%s(PyObject* self_%s):\n' % \ + (self.wrapper_name, params) + cons_code += indent*2 + '%s(%s), self(self_) {}\n\n' % \ + (class_name, ', '.join(param_names)) + code += cons_code + # generate the body + body = [] + for method in self.VirtualMethods(): + if not self.info[method.name].exclude: + body.append(self.Declaration(method, indent)) + body = '\n'.join(body) + code += body + '\n' + # add the self member + code += indent + 'PyObject* self;\n' + code += '};\n' + return code diff --git a/pyste/src/FunctionExporter.py b/pyste/src/FunctionExporter.py index 2638a776..60735ca0 100644 --- a/pyste/src/FunctionExporter.py +++ b/pyste/src/FunctionExporter.py @@ -25,7 +25,11 @@ class FunctionExporter(Exporter): def CheckPolicy(self, func): 'Warns the user if this function needs a policy' + def IsString(type): + return type.const and type.name == 'char' and isinstance(type, PointerType) needs_policy = isinstance(func.result, (ReferenceType, PointerType)) + if IsString(func.result): + needs_policy = False if needs_policy and self.info.policy is None: print '---> Error: Function "%s" needs a policy.' % func.FullName() print