diff options
Diffstat (limited to 'libs/python/pyste/src')
21 files changed, 3985 insertions, 0 deletions
diff --git a/libs/python/pyste/src/Pyste/ClassExporter.py b/libs/python/pyste/src/Pyste/ClassExporter.py new file mode 100644 index 000000000..decaf628e --- /dev/null +++ b/libs/python/pyste/src/Pyste/ClassExporter.py @@ -0,0 +1,918 @@ +# Copyright Bruno da Silva de Oliveira 2003. Use, modification and +# distribution is subject to the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +import exporters +from Exporter import Exporter +from declarations import * +from settings import * +from policies import * +from SingleCodeUnit import SingleCodeUnit +from EnumExporter import EnumExporter +from utils import makeid, enumerate +import copy +import exporterutils +import re + +#============================================================================== +# ClassExporter +#============================================================================== +class ClassExporter(Exporter): + 'Generates boost.python code to export a class declaration' + + def __init__(self, info, parser_tail=None): + Exporter.__init__(self, info, parser_tail) + # sections of code + self.sections = {} + # template: each item in the list is an item into the class_<...> + # section. + self.sections['template'] = [] + # constructor: each item in the list is a parameter to the class_ + # constructor, like class_<C>(...) + self.sections['constructor'] = [] + # inside: everything within the class_<> statement + self.sections['inside'] = [] + # scope: items outside the class statement but within its scope. + # scope* s = new scope(class<>()); + # ... + # delete s; + self.sections['scope'] = [] + # declarations: outside the BOOST_PYTHON_MODULE macro + self.sections['declaration'] = [] + self.sections['declaration-outside'] = [] + self.sections['include'] = [] + # a list of Constructor instances + self.constructors = [] + # a list of code units, generated by nested declarations + self.nested_codeunits = [] + + + def ScopeName(self): + return makeid(self.class_.FullName()) + '_scope' + + + def Name(self): + return self.info.name + + + def SetDeclarations(self, declarations): + Exporter.SetDeclarations(self, declarations) + if self.declarations: + decl = self.GetDeclaration(self.info.name) + if isinstance(decl, Typedef): + self.class_ = self.GetDeclaration(decl.type.name) + if not self.info.rename: + self.info.rename = decl.name + else: + self.class_ = decl + self.class_ = copy.deepcopy(self.class_) + else: + self.class_ = None + + + def ClassBases(self): + all_bases = [] + for level in self.class_.hierarchy: + for base in level: + all_bases.append(base) + return [self.GetDeclaration(x.name) for x in all_bases] + + + def Order(self): + '''Return the TOTAL number of bases that this class has, including the + bases' bases. Do this because base classes must be instantialized + before the derived classes in the module definition. + ''' + num_bases = len(self.ClassBases()) + return num_bases, self.class_.FullName() + + + def Export(self, codeunit, exported_names): + self.InheritMethods(exported_names) + self.MakeNonVirtual() + if not self.info.exclude: + self.ExportBasics() + self.ExportBases(exported_names) + self.ExportConstructors() + self.ExportVariables() + self.ExportVirtualMethods(codeunit) + self.ExportMethods() + self.ExportOperators() + self.ExportNestedClasses(exported_names) + self.ExportNestedEnums(exported_names) + self.ExportSmartPointer() + self.ExportOpaquePointerPolicies() + self.ExportAddedCode() + self.Write(codeunit) + exported_names[self.Name()] = 1 + + + def InheritMethods(self, exported_names): + '''Go up in the class hierarchy looking for classes that were not + exported yet, and then add their public members to this classes + members, as if they were members of this class. This allows the user to + just export one type and automatically get all the members from the + base classes. + ''' + valid_members = (Method, ClassVariable, NestedClass, ClassEnumeration) + fullnames = [x.FullName() for x in self.class_] + pointers = [x.PointerDeclaration(True) for x in self.class_ if isinstance(x, Method)] + fullnames = dict([(x, None) for x in fullnames]) + pointers = dict([(x, None) for x in pointers]) + for level in self.class_.hierarchy: + level_exported = False + for base in level: + base = self.GetDeclaration(base.name) + if base.FullName() not in exported_names: + for member in base: + if type(member) in valid_members: + member_copy = copy.deepcopy(member) + member_copy.class_ = self.class_.FullName() + if isinstance(member_copy, Method): + pointer = member_copy.PointerDeclaration(True) + if pointer not in pointers: + self.class_.AddMember(member) + pointers[pointer] = None + elif member_copy.FullName() not in fullnames: + self.class_.AddMember(member) + else: + level_exported = True + if level_exported: + break + def IsValid(member): + return isinstance(member, valid_members) and member.visibility == Scope.public + self.public_members = [x for x in self.class_ if IsValid(x)] + + + def Write(self, codeunit): + indent = self.INDENT + boost_ns = namespaces.python + pyste_ns = namespaces.pyste + code = '' + # begin a scope for this class if needed + nested_codeunits = self.nested_codeunits + needs_scope = self.sections['scope'] or nested_codeunits + if needs_scope: + scope_name = self.ScopeName() + code += indent + boost_ns + 'scope* %s = new %sscope(\n' %\ + (scope_name, boost_ns) + # export the template section + template_params = ', '.join(self.sections['template']) + code += indent + boost_ns + 'class_< %s >' % template_params + # export the constructor section + constructor_params = ', '.join(self.sections['constructor']) + code += '(%s)\n' % constructor_params + # export the inside section + in_indent = indent*2 + for line in self.sections['inside']: + code += in_indent + line + '\n' + # write the scope section and end it + if not needs_scope: + code += indent + ';\n' + else: + code += indent + ');\n' + for line in self.sections['scope']: + code += indent + line + '\n' + # write the contents of the nested classes + for nested_unit in nested_codeunits: + code += '\n' + nested_unit.Section('module') + # close the scope + code += indent + 'delete %s;\n' % scope_name + + # write the code to the module section in the codeunit + codeunit.Write('module', code + '\n') + + # write the declarations to the codeunit + declarations = '\n'.join(self.sections['declaration']) + for nested_unit in nested_codeunits: + declarations += nested_unit.Section('declaration') + if declarations: + codeunit.Write('declaration', declarations + '\n') + declarations_outside = '\n'.join(self.sections['declaration-outside']) + if declarations_outside: + codeunit.Write('declaration-outside', declarations_outside + '\n') + + # write the includes to the codeunit + includes = '\n'.join(self.sections['include']) + for nested_unit in nested_codeunits: + includes += nested_unit.Section('include') + if includes: + codeunit.Write('include', includes) + + + def Add(self, section, item): + 'Add the item into the corresponding section' + self.sections[section].append(item) + + + def ExportBasics(self): + '''Export the name of the class and its class_ statement.''' + class_name = self.class_.FullName() + self.Add('template', class_name) + name = self.info.rename or self.class_.name + self.Add('constructor', '"%s"' % name) + + + def ExportBases(self, exported_names): + 'Expose the bases of the class into the template section' + hierarchy = self.class_.hierarchy + exported = [] + for level in hierarchy: + for base in level: + if base.visibility == Scope.public and base.name in exported_names: + exported.append(base.name) + if exported: + break + if exported: + code = namespaces.python + 'bases< %s > ' % (', '.join(exported)) + self.Add('template', code) + + + def ExportConstructors(self): + '''Exports all the public contructors of the class, plus indicates if the + class is noncopyable. + ''' + py_ns = namespaces.python + indent = self.INDENT + + def init_code(cons): + 'return the init<>() code for the given contructor' + param_list = [p.FullName() for p in cons.parameters] + min_params_list = param_list[:cons.minArgs] + max_params_list = param_list[cons.minArgs:] + min_params = ', '.join(min_params_list) + max_params = ', '.join(max_params_list) + init = py_ns + 'init< ' + init += min_params + if max_params: + if min_params: + init += ', ' + init += py_ns + ('optional< %s >' % max_params) + init += ' >()' + return init + + constructors = [x for x in self.public_members if isinstance(x, Constructor)] + # don't export copy constructors if the class is abstract + # we could remove all constructors, but this will have the effect of + # inserting no_init in the declaration, which would not allow + # even subclasses to be instantiated. + self.constructors = constructors[:] + if self.class_.abstract: + for cons in constructors: + if cons.IsCopy(): + constructors.remove(cons) + break + + if not constructors: + # declare no_init + self.Add('constructor', py_ns + 'no_init') + else: + # 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) + self.Add('inside', code) + + # check if the class is copyable + if not self.class_.HasCopyConstructor() or self.class_.abstract: + self.Add('template', namespaces.boost + 'noncopyable') + + + def ExportVariables(self): + 'Export the variables of the class, both static and simple variables' + vars = [x for x in self.public_members if isinstance(x, Variable)] + for var in vars: + if self.info[var.name].exclude: + continue + name = self.info[var.name].rename or var.name + fullname = var.FullName() + if var.type.const: + def_ = '.def_readonly' + else: + def_ = '.def_readwrite' + code = '%s("%s", &%s)' % (def_, name, fullname) + self.Add('inside', code) + + + def OverloadName(self, method): + 'Returns the name of the overloads struct for the given method' + name = makeid(method.FullName()) + overloads = '_overloads_%i_%i' % (method.minArgs, method.maxArgs) + return name + overloads + + + def GetAddedMethods(self): + added_methods = self.info.__added__ + result = [] + if added_methods: + for name, rename in added_methods: + decl = self.GetDeclaration(name) + self.info[name].rename = rename + result.append(decl) + return result + + + def ExportMethods(self): + '''Export all the non-virtual methods of this class, plus any function + that is to be exported as a method''' + + declared = {} + def DeclareOverloads(m): + 'Declares the macro for the generation of the overloads' + if (isinstance(m, Method) and m.static) or type(m) == Function: + func = m.FullName() + macro = 'BOOST_PYTHON_FUNCTION_OVERLOADS' + else: + func = m.name + macro = 'BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS' + code = '%s(%s, %s, %i, %i)\n' % (macro, self.OverloadName(m), func, m.minArgs, m.maxArgs) + if code not in declared: + declared[code] = True + self.Add('declaration', code) + + + def Pointer(m): + 'returns the correct pointer declaration for the method m' + # check if this method has a wrapper set for him + wrapper = self.info[m.name].wrapper + if wrapper: + return '&' + wrapper.FullName() + else: + return m.PointerDeclaration() + + def IsExportable(m): + 'Returns true if the given method is exportable by this routine' + ignore = (Constructor, ClassOperator, Destructor) + return isinstance(m, Function) and not isinstance(m, ignore) and not m.virtual + + methods = [x for x in self.public_members if IsExportable(x)] + methods.extend(self.GetAddedMethods()) + + staticmethods = {} + + for method in methods: + method_info = self.info[method.name] + + # skip this method if it was excluded by the user + if method_info.exclude: + continue + + # rename the method if the user requested + name = method_info.rename or method.name + + # warn the user if this method needs a policy and doesn't have one + method_info.policy = exporterutils.HandlePolicy(method, method_info.policy) + + # check for policies + policy = method_info.policy or '' + if policy: + policy = ', %s%s()' % (namespaces.python, policy.Code()) + # check for overloads + overload = '' + if method.minArgs != method.maxArgs and not method_info.wrapper: + # add the overloads for this method + DeclareOverloads(method) + overload_name = self.OverloadName(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 + code += overload + code += ')' + self.Add('inside', code) + # static method + if isinstance(method, Method) and method.static: + staticmethods[name] = 1 + # add wrapper code if this method has one + wrapper = method_info.wrapper + if wrapper and wrapper.code: + self.Add('declaration', wrapper.code) + + # export staticmethod statements + for name in staticmethods: + code = '.staticmethod("%s")' % name + self.Add('inside', code) + + + + def MakeNonVirtual(self): + '''Make all methods that the user indicated to no_override no more virtual, delegating their + export to the ExportMethods routine''' + for member in self.class_: + if type(member) == Method and member.virtual: + member.virtual = not self.info[member.name].no_override + + + def ExportVirtualMethods(self, codeunit): + # check if this class has any virtual methods + has_virtual_methods = False + for member in self.class_: + if type(member) == Method and member.virtual: + has_virtual_methods = True + break + + holder = self.info.holder + if has_virtual_methods: + generator = _VirtualWrapperGenerator(self.class_, self.ClassBases(), self.info, codeunit) + if holder: + self.Add('template', holder(generator.FullName())) + else: + self.Add('template', generator.FullName()) + for definition in generator.GenerateDefinitions(): + self.Add('inside', definition) + self.Add('declaration', generator.GenerateVirtualWrapper(self.INDENT)) + else: + if holder: + self.Add('template', holder(self.class_.FullName())) + + # operators natively supported by boost + BOOST_SUPPORTED_OPERATORS = '+ - * / % ^ & ! ~ | < > == != <= >= << >> && || += -= '\ + '*= /= %= ^= &= |= <<= >>='.split() + # create a map for faster lookup + BOOST_SUPPORTED_OPERATORS = dict(zip(BOOST_SUPPORTED_OPERATORS, range(len(BOOST_SUPPORTED_OPERATORS)))) + + # a dict of operators that are not directly supported by boost, but can be exposed + # simply as a function with a special name + BOOST_RENAME_OPERATORS = { + '()' : '__call__', + } + + # converters which have a special name in python + # it's a map of a regular expression of the converter's result to the + # appropriate python name + SPECIAL_CONVERTERS = { + re.compile(r'(const)?\s*double$') : '__float__', + re.compile(r'(const)?\s*float$') : '__float__', + re.compile(r'(const)?\s*int$') : '__int__', + re.compile(r'(const)?\s*long$') : '__long__', + re.compile(r'(const)?\s*char\s*\*?$') : '__str__', + re.compile(r'(const)?.*::basic_string<.*>\s*(\*|\&)?$') : '__str__', + } + + + def ExportOperators(self): + 'Export all member operators and free operators related to this class' + + def GetFreeOperators(): + 'Get all the free (global) operators related to this class' + operators = [] + for decl in self.declarations: + if isinstance(decl, Operator): + # check if one of the params is this class + for param in decl.parameters: + if param.name == self.class_.FullName(): + operators.append(decl) + break + return operators + + def GetOperand(param): + 'Returns the operand of this parameter (either "self", or "other<type>")' + if param.name == self.class_.FullName(): + return namespaces.python + 'self' + else: + return namespaces.python + ('other< %s >()' % param.name) + + + def HandleSpecialOperator(operator): + # gatter information about the operator and its parameters + result_name = operator.result.name + param1_name = '' + if operator.parameters: + param1_name = operator.parameters[0].name + + # check for str + ostream = 'basic_ostream' + is_str = result_name.find(ostream) != -1 and param1_name.find(ostream) != -1 + if is_str: + namespace = namespaces.python + 'self_ns::' + self_ = namespaces.python + 'self' + return '.def(%sstr(%s))' % (namespace, self_) + + # is not a special operator + return None + + + + frees = GetFreeOperators() + members = [x for x in self.public_members if type(x) == ClassOperator] + all_operators = frees + members + operators = [x for x in all_operators if not self.info['operator'][x.name].exclude] + + for operator in operators: + # gatter information about the operator, for use later + wrapper = self.info['operator'][operator.name].wrapper + if wrapper: + pointer = '&' + wrapper.FullName() + if wrapper.code: + self.Add('declaration-outside', wrapper.code) + else: + pointer = operator.PointerDeclaration() + rename = self.info['operator'][operator.name].rename + + # check if this operator will be exported as a method + export_as_method = wrapper or rename or operator.name in self.BOOST_RENAME_OPERATORS + + # check if this operator has a special representation in boost + special_code = HandleSpecialOperator(operator) + has_special_representation = special_code is not None + + if export_as_method: + # export this operator as a normal method, renaming or using the given wrapper + if not rename: + if wrapper: + rename = wrapper.name + else: + rename = self.BOOST_RENAME_OPERATORS[operator.name] + policy = '' + policy_obj = self.info['operator'][operator.name].policy + if policy_obj: + policy = ', %s()' % policy_obj.Code() + self.Add('inside', '.def("%s", %s%s)' % (rename, pointer, policy)) + + elif has_special_representation: + self.Add('inside', special_code) + + elif operator.name in self.BOOST_SUPPORTED_OPERATORS: + # export this operator using boost's facilities + op = operator + is_unary = isinstance(op, Operator) and len(op.parameters) == 1 or\ + isinstance(op, ClassOperator) and len(op.parameters) == 0 + if is_unary: + self.Add('inside', '.def( %s%sself )' % \ + (operator.name, namespaces.python)) + else: + # binary operator + if len(operator.parameters) == 2: + left_operand = GetOperand(operator.parameters[0]) + right_operand = GetOperand(operator.parameters[1]) + else: + left_operand = namespaces.python + 'self' + right_operand = GetOperand(operator.parameters[0]) + self.Add('inside', '.def( %s %s %s )' % \ + (left_operand, operator.name, right_operand)) + + # export the converters. + # export them as simple functions with a pre-determined name + + converters = [x for x in self.public_members if type(x) == ConverterOperator] + + def ConverterMethodName(converter): + result_fullname = converter.result.FullName() + result_name = converter.result.name + for regex, method_name in self.SPECIAL_CONVERTERS.items(): + if regex.match(result_fullname): + return method_name + else: + # extract the last name from the full name + result_name = makeid(result_name) + return 'to_' + result_name + + for converter in converters: + info = self.info['operator'][converter.result.FullName()] + # check if this operator should be excluded + if info.exclude: + continue + + special_code = HandleSpecialOperator(converter) + if info.rename or not special_code: + # export as method + name = info.rename or ConverterMethodName(converter) + pointer = converter.PointerDeclaration() + policy_code = '' + if info.policy: + policy_code = ', %s()' % info.policy.Code() + self.Add('inside', '.def("%s", %s%s)' % (name, pointer, policy_code)) + + elif special_code: + self.Add('inside', special_code) + + + + def ExportNestedClasses(self, exported_names): + nested_classes = [x for x in self.public_members if isinstance(x, NestedClass)] + for nested_class in nested_classes: + nested_info = self.info[nested_class.name] + nested_info.include = self.info.include + nested_info.name = nested_class.FullName() + exporter = self.__class__(nested_info) + exporter.SetDeclarations(self.declarations) + codeunit = SingleCodeUnit(None, None) + exporter.Export(codeunit, exported_names) + self.nested_codeunits.append(codeunit) + + + def ExportNestedEnums(self, exported_names): + nested_enums = [x for x in self.public_members if isinstance(x, ClassEnumeration)] + for enum in nested_enums: + enum_info = self.info[enum.name] + enum_info.include = self.info.include + enum_info.name = enum.FullName() + exporter = EnumExporter(enum_info) + exporter.SetDeclarations(self.declarations) + codeunit = SingleCodeUnit(None, None) + exporter.Export(codeunit, exported_names) + self.nested_codeunits.append(codeunit) + + + def ExportSmartPointer(self): + smart_ptr = self.info.smart_ptr + if smart_ptr: + class_name = self.class_.FullName() + smart_ptr = smart_ptr % class_name + self.Add('scope', '%sregister_ptr_to_python< %s >();' % (namespaces.python, smart_ptr)) + + + def ExportOpaquePointerPolicies(self): + # check all methods for 'return_opaque_pointer' policies + methods = [x for x in self.public_members if isinstance(x, Method)] + for method in methods: + return_opaque_policy = return_value_policy(return_opaque_pointer) + if self.info[method.name].policy == return_opaque_policy: + macro = exporterutils.EspecializeTypeID(method.result.name) + if macro: + self.Add('declaration-outside', macro) + + def ExportAddedCode(self): + if self.info.__code__: + for code in self.info.__code__: + self.Add('inside', code) + + +#============================================================================== +# Virtual Wrapper utils +#============================================================================== + +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[: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 _VirtualWrapperGenerator(object): + 'Generates code to export the virtual methods of the given class' + + def __init__(self, class_, bases, info, codeunit): + self.class_ = copy.deepcopy(class_) + self.bases = bases[:] + self.info = info + self.wrapper_name = makeid(class_.FullName()) + '_Wrapper' + self.virtual_methods = None + self._method_count = {} + self.codeunit = codeunit + self.GenerateVirtualMethods() + + + SELF = 'py_self' + + + 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: + 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%s {\n' % (result, method.name, params, constantness, method.Exceptions()) + param_names_str = ', '.join(param_names) + if param_names_str: + param_names_str = ', ' + param_names_str + + self_str = self.SELF + + decl += indent*2 + '%(return_str)s%(python)scall_method< %(result)s >' \ + '(%(self_str)s, "%(rename)s"%(param_names_str)s);\n' % locals() + decl += indent + '}\n' + + # default implementations (with overloading) + def DefaultImpl(method, param_names): + 'Return the body of a default implementation wrapper' + indent2 = indent * 2 + wrapper = self.info[method.name].wrapper + if not wrapper: + # return the default implementation of the class + return indent2 + '%s%s(%s);\n' % \ + (return_str, method.FullName(), ', '.join(param_names)) + else: + if wrapper.code: + self.codeunit.Write('declaration-outside', wrapper.code) + # return a call for the wrapper + params = ', '.join(['this'] + param_names) + return indent2 + '%s%s(%s);\n' % (return_str, wrapper.FullName(), params) + + if not method.abstract and method.visibility != Scope.private: + 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 += DefaultImpl(method, param_names) + decl += indent + '}\n' + return decl + + + 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 + pyste = namespaces.pyste + rename = self.info[method.name].rename or method.name + default_names = self.DefaultImplementationNames(method) + class_name = self.class_.FullName() + wrapper_name = pyste + self.wrapper_name + result = method.result.FullName() + is_method_unique = method.is_unique + constantness = '' + if method.const: + constantness = ' const' + + # create a list of default-impl pointers + minArgs = method.minArgs + maxArgs = method.maxArgs + if method.abstract: + default_pointers = [] + elif is_method_unique: + default_pointers = ['&%s::%s' % (wrapper_name, x) for x in default_names] + else: + 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 + pointer = method.PointerDeclaration() + if method.abstract: + pointer = namespaces.python + ('pure_virtual(%s)' % pointer) + + # warn the user if this method needs a policy and doesn't have one + method_info = self.info[method.name] + method_info.policy = exporterutils.HandlePolicy(method, method_info.policy) + + # Add policy to overloaded methods also + policy = method_info.policy or '' + if policy: + policy = ', %s%s()' % (namespaces.python, policy.Code()) + + # generate the defs + definitions = [] + # basic def + if default_pointers: + definitions.append('.def("%s", %s, %s%s)' % (rename, pointer, default_pointers[-1], policy)) + for default_pointer in default_pointers[:-1]: + definitions.append('.def("%s", %s%s)' % (rename, default_pointer, policy)) + else: + definitions.append('.def("%s", %s%s)' % (rename, pointer, policy)) + return definitions + + + def FullName(self): + return namespaces.pyste + self.wrapper_name + + + def GenerateVirtualMethods(self): + '''To correctly export all virtual methods, we must also make wrappers + for the virtual methods of the bases of this class, as if the methods + were from this class itself. + This method creates the instance variable self.virtual_methods. + ''' + def IsVirtual(m): + if type(m) is Method: + pure_virtual = m.abstract and m.virtual + virtual = m.virtual and m.visibility != Scope.private + return virtual or pure_virtual + else: + return False + + # extract the virtual methods, avoiding duplications. The duplication + # must take in account the full signature without the class name, so + # that inherited members are correctly excluded if the subclass overrides + # them. + def MethodSig(method): + if method.const: + const = ' const' + else: + const = '' + if method.result: + result = method.result.FullName() + else: + result = '' + params = ', '.join([x.FullName() for x in method.parameters]) + return '%s %s(%s)%s%s' % ( + result, method.name, params, const, method.Exceptions()) + + already_added = {} + self.virtual_methods = [] + for member in self.class_: + if IsVirtual(member): + already_added[MethodSig(member)] = None + self.virtual_methods.append(member) + + for base in self.bases: + base_methods = [copy.deepcopy(x) for x in base if IsVirtual(x)] + for base_method in base_methods: + self.class_.AddMember(base_method) + + all_methods = [x for x in self.class_ if IsVirtual(x)] + + for member in all_methods: + sig = MethodSig(member) + if IsVirtual(member) and not sig in already_added: + self.virtual_methods.append(member) + already_added[sig] = 0 + + + def Constructors(self): + return self.class_.Constructors(publics_only=True) + + + def GenerateDefinitions(self): + defs = [] + for method in self.virtual_methods: + exclude = self.info[method.name].exclude + # generate definitions only for public methods and non-abstract methods + if method.visibility == Scope.public and not 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(): # only public 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* %s_%s):\n' % \ + (self.wrapper_name, self.SELF, params) + cons_code += indent*2 + '%s(%s), %s(%s_) {}\n\n' % \ + (class_name, ', '.join(param_names), self.SELF, self.SELF) + code += cons_code + # generate the body + body = [] + for method in self.virtual_methods: + 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* %s;\n' % self.SELF + code += '};\n' + return code diff --git a/libs/python/pyste/src/Pyste/CodeExporter.py b/libs/python/pyste/src/Pyste/CodeExporter.py new file mode 100644 index 000000000..382fffbd5 --- /dev/null +++ b/libs/python/pyste/src/Pyste/CodeExporter.py @@ -0,0 +1,26 @@ +# Copyright Bruno da Silva de Oliveira 2003. Use, modification and +# distribution is subject to the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +from Exporter import Exporter + +#============================================================================== +# CodeExporter +#============================================================================== +class CodeExporter(Exporter): + + def __init__(self, info): + Exporter.__init__(self, info) + + + def Name(self): + return self.info.code + + + def Export(self, codeunit, exported_names): + codeunit.Write(self.info.section, self.info.code) + + + def WriteInclude(self, codeunit): + pass diff --git a/libs/python/pyste/src/Pyste/CppParser.py b/libs/python/pyste/src/Pyste/CppParser.py new file mode 100644 index 000000000..be68a448a --- /dev/null +++ b/libs/python/pyste/src/Pyste/CppParser.py @@ -0,0 +1,247 @@ +# Copyright Bruno da Silva de Oliveira 2003. Use, modification and +# distribution is subject to the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +from GCCXMLParser import ParseDeclarations +import tempfile +import shutil +import os +import sys +import os.path +import settings +import shutil +import shelve +from cPickle import dump, load + +#============================================================================== +# exceptions +#============================================================================== +class CppParserError(Exception): pass + +#============================================================================== +# CppParser +#============================================================================== +class CppParser: + 'Parses a header file and returns a list of declarations' + + def __init__(self, includes=None, defines=None, cache_dir=None, version=None, gccxml_path = 'gccxml'): + 'includes and defines ar the directives given to gcc' + if includes is None: + includes = [] + if defines is None: + defines = [] + self.includes = includes + self.gccxml_path = gccxml_path + self.defines = defines + self.version = version + #if cache_dir is None: + # cache_dir = tempfile.mktemp() + # self.delete_cache = True + #else: + # self.delete_cache = False + self.delete_cache = False + self.cache_dir = cache_dir + self.cache_files = [] + self.mem_cache = {} + # create the cache dir + if cache_dir: + try: + os.makedirs(cache_dir) + except OSError: pass + + + def __del__(self): + self.Close() + + + def _IncludeParams(self, filename): + includes = self.includes[:] + filedir = os.path.dirname(filename) + if not filedir: + filedir = '.' + includes.insert(0, filedir) + includes = ['-I "%s"' % self.Unixfy(x) for x in includes] + return ' '.join(includes) + + + def _DefineParams(self): + defines = ['-D "%s"' % x for x in self.defines] + return ' '.join(defines) + + + def FindHeader(self, header): + if os.path.isfile(header): + return header + for path in self.includes: + filename = os.path.join(path, header) + if os.path.isfile(filename): + return filename + else: + name = os.path.basename(header) + raise RuntimeError, 'Header file "%s" not found!' % name + + + def AppendTail(self, filename, tail): + '''Creates a temporary file, appends the text tail to it, and returns + the filename of the file. + ''' + if hasattr(tempfile, 'mkstemp'): + f_no, temp = tempfile.mkstemp('.h') + f = file(temp, 'a') + os.close(f_no) + else: + temp = tempfile.mktemp('.h') + f = file(temp, 'a') + f.write('#include "%s"\n\n' % os.path.abspath(filename)) + f.write(tail) + f.write('\n') + f.close() + return temp + + + def Unixfy(self, path): + return path.replace('\\', '/') + + + def ParseWithGCCXML(self, header, tail): + '''Parses the given header using gccxml and GCCXMLParser. + ''' + header = self.FindHeader(header) + if tail: + filename = self.AppendTail(header, tail) + else: + filename = header + xmlfile = tempfile.mktemp('.xml') + try: + # get the params + includes = self._IncludeParams(filename) + defines = self._DefineParams() + # call gccxml + cmd = '%s %s %s "%s" -fxml=%s' + filename = self.Unixfy(filename) + xmlfile = self.Unixfy(xmlfile) + status = os.system(cmd % (self.gccxml_path, includes, defines, filename, xmlfile)) + if status != 0 or not os.path.isfile(xmlfile): + raise CppParserError, 'Error executing gccxml' + # parse the resulting xml + declarations = ParseDeclarations(xmlfile) + # make the declarations' location to point to the original file + if tail: + for decl in declarations: + decl_filename = os.path.normpath(os.path.normcase(decl.location[0])) + filename = os.path.normpath(os.path.normcase(filename)) + if decl_filename == filename: + decl.location = header, decl.location[1] + # return the declarations + return declarations + finally: + if settings.DEBUG and os.path.isfile(xmlfile): + debugname = os.path.basename(header) + debugname = os.path.splitext(debugname)[0] + '.xml' + print 'DEBUG:', debugname + shutil.copy(xmlfile, debugname) + # delete the temporary files + try: + os.remove(xmlfile) + if tail: + os.remove(filename) + except OSError: pass + + + def Parse(self, header, interface, tail=None): + '''Parses the given filename related to the given interface and returns + the (declarations, headerfile). The header returned is normally the + same as the given to this method (except that it is the full path), + except if tail is not None: in this case, the header is copied to a temp + filename and the tail code is appended to it before being passed on to + gccxml. This temp filename is then returned. + ''' + if tail is None: + tail = '' + tail = tail.strip() + declarations = self.GetCache(header, interface, tail) + if declarations is None: + declarations = self.ParseWithGCCXML(header, tail) + self.CreateCache(header, interface, tail, declarations) + header_fullpath = os.path.abspath(self.FindHeader(header)) + return declarations, header_fullpath + + + def CacheFileName(self, interface): + interface_name = os.path.basename(interface) + cache_file = os.path.splitext(interface_name)[0] + '.pystec' + cache_file = os.path.join(self.cache_dir, cache_file) + return cache_file + + + def GetCache(self, header, interface, tail): + key = (header, interface, tail) + # try memory cache first + if key in self.mem_cache: + return self.mem_cache[key] + + # get the cache from the disk + if self.cache_dir is None: + return None + header = self.FindHeader(header) + cache_file = self.CacheFileName(interface) + if os.path.isfile(cache_file): + f = file(cache_file, 'rb') + try: + version = load(f) + if version != self.version: + return None + cache = load(f) + if cache.has_key(key): + self.cache_files.append(cache_file) + return cache[key] + else: + return None + finally: + f.close() + else: + return None + + + def CreateCache(self, header, interface, tail, declarations): + key = (header, interface, tail) + + # our memory cache only holds one item + self.mem_cache.clear() + self.mem_cache[key] = declarations + + # save the cache in the disk + if self.cache_dir is None: + return + header = self.FindHeader(header) + cache_file = self.CacheFileName(interface) + if os.path.isfile(cache_file): + f = file(cache_file, 'rb') + try: + version = load(f) + cache = load(f) + finally: + f.close() + else: + cache = {} + cache[key] = declarations + self.cache_files.append(cache_file) + f = file(cache_file, 'wb') + try: + dump(self.version, f, 1) + dump(cache, f, 1) + finally: + f.close() + return cache_file + + + def Close(self): + if self.delete_cache and self.cache_files: + for filename in self.cache_files: + try: + os.remove(filename) + except OSError: + pass + self.cache_files = [] + shutil.rmtree(self.cache_dir) diff --git a/libs/python/pyste/src/Pyste/EnumExporter.py b/libs/python/pyste/src/Pyste/EnumExporter.py new file mode 100644 index 000000000..0107fbee3 --- /dev/null +++ b/libs/python/pyste/src/Pyste/EnumExporter.py @@ -0,0 +1,58 @@ +# Copyright Bruno da Silva de Oliveira 2003. Use, modification and +# distribution is subject to the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +from Exporter import Exporter +from settings import * +import utils + +#============================================================================== +# EnumExporter +#============================================================================== +class EnumExporter(Exporter): + 'Exports enumerators' + + def __init__(self, info): + Exporter.__init__(self, info) + + + def SetDeclarations(self, declarations): + Exporter.SetDeclarations(self, declarations) + if self.declarations: + self.enum = self.GetDeclaration(self.info.name) + else: + self.enum = None + + def Export(self, codeunit, exported_names): + if self.info.exclude: + return + indent = self.INDENT + in_indent = self.INDENT*2 + rename = self.info.rename or self.enum.name + full_name = self.enum.FullName() + unnamed_enum = False + if rename.startswith('$_') or rename.startswith('._'): + unnamed_enum = True + code = '' + if not unnamed_enum: + code += indent + namespaces.python + code += 'enum_< %s >("%s")\n' % (full_name, rename) + for name in self.enum.values: + rename = self.info[name].rename or name + value_fullname = self.enum.ValueFullName(name) + if not unnamed_enum: + code += in_indent + '.value("%s", %s)\n' % (rename, value_fullname) + else: + code += indent + namespaces.python + code += 'scope().attr("%s") = (int)%s;\n' % (rename, value_fullname ) + if self.info.export_values and not unnamed_enum: + code += in_indent + '.export_values()\n' + if not unnamed_enum: + code += indent + ';\n' + code += '\n' + codeunit.Write('module', code) + exported_names[self.enum.FullName()] = 1 + + def Name(self): + return self.info.name diff --git a/libs/python/pyste/src/Pyste/Exporter.py b/libs/python/pyste/src/Pyste/Exporter.py new file mode 100644 index 000000000..d87b37c58 --- /dev/null +++ b/libs/python/pyste/src/Pyste/Exporter.py @@ -0,0 +1,94 @@ +# Copyright Bruno da Silva de Oliveira 2003. Use, modification and +# distribution is subject to the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +import os.path + +#============================================================================== +# Exporter +#============================================================================== +class Exporter(object): + 'Base class for objects capable to generate boost.python code.' + + INDENT = ' ' * 4 + + def __init__(self, info, parser_tail=None): + self.info = info + self.parser_tail = parser_tail + self.interface_file = None + self.declarations = [] + + + def Name(self): + raise NotImplementedError(self.__class__.__name__) + + + def Tail(self): + return self.parser_tail + + + def Parse(self, parser): + self.parser = parser + header = self.info.include + tail = self.parser_tail + declarations, parser_header = parser.parse(header, tail) + self.parser_header = parser_header + self.SetDeclarations(declarations) + + + def SetParsedHeader(self, parsed_header): + self.parser_header = parsed_header + + + def SetDeclarations(self, declarations): + self.declarations = declarations + + + def GenerateCode(self, codeunit, exported_names): + self.WriteInclude(codeunit) + self.Export(codeunit, exported_names) + + + def WriteInclude(self, codeunit): + codeunit.Write('include', '#include <%s>\n' % self.info.include) + + + def Export(self, codeunit, exported_names): + 'subclasses must override this to do the real work' + pass + + + def GetDeclarations(self, fullname): + decls = [] + for decl in self.declarations: + if decl.FullName() == fullname: + decls.append(decl) + if not decls: + raise RuntimeError, 'no %s declaration found!' % fullname + return decls + + + def GetDeclaration(self, fullname): + decls = self.GetDeclarations(fullname) + #assert len(decls) == 1 + return decls[0] + + + def Order(self): + '''Returns a string that uniquely identifies this instance. All + exporters will be sorted by Order before being exported. + ''' + return 0, self.info.name + + + def Header(self): + return self.info.include + + + def __eq__(self, other): + return type(self) is type(other) and self.Name() == other.Name() \ + and self.interface_file == other.interface_file + + def __ne__(self, other): + return not self == other diff --git a/libs/python/pyste/src/Pyste/FunctionExporter.py b/libs/python/pyste/src/Pyste/FunctionExporter.py new file mode 100644 index 000000000..5765f65e9 --- /dev/null +++ b/libs/python/pyste/src/Pyste/FunctionExporter.py @@ -0,0 +1,92 @@ +# Copyright Bruno da Silva de Oliveira 2003. Use, modification and +# distribution is subject to the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +from Exporter import Exporter +from policies import * +from declarations import * +from settings import * +import utils +import exporterutils + + +#============================================================================== +# FunctionExporter +#============================================================================== +class FunctionExporter(Exporter): + 'Generates boost.python code to export the given function.' + + def __init__(self, info, tail=None): + Exporter.__init__(self, info, tail) + + + def Export(self, codeunit, exported_names): + if not self.info.exclude: + decls = self.GetDeclarations(self.info.name) + for decl in decls: + self.info.policy = exporterutils.HandlePolicy(decl, self.info.policy) + self.ExportDeclaration(decl, len(decls) == 1, codeunit) + self.ExportOpaquePointer(decl, codeunit) + self.GenerateOverloads(decls, codeunit) + exported_names[self.Name()] = 1 + + + def ExportDeclaration(self, decl, unique, codeunit): + name = self.info.rename or decl.name + defs = namespaces.python + 'def("%s", ' % name + wrapper = self.info.wrapper + if wrapper: + pointer = '&' + wrapper.FullName() + else: + pointer = decl.PointerDeclaration() + defs += pointer + defs += self.PolicyCode() + overload = self.OverloadName(decl) + if overload: + defs += ', %s()' % (namespaces.pyste + overload) + defs += ');' + codeunit.Write('module', self.INDENT + defs + '\n') + # add the code of the wrapper + if wrapper and wrapper.code: + codeunit.Write('declaration', wrapper.code + '\n') + + + def OverloadName(self, decl): + if decl.minArgs != decl.maxArgs: + return '%s_overloads_%i_%i' % \ + (decl.name, decl.minArgs, decl.maxArgs) + else: + return '' + + + def GenerateOverloads(self, declarations, codeunit): + codes = {} + for decl in declarations: + overload = self.OverloadName(decl) + if overload and overload not in codes: + code = 'BOOST_PYTHON_FUNCTION_OVERLOADS(%s, %s, %i, %i)' %\ + (overload, decl.FullName(), decl.minArgs, decl.maxArgs) + codeunit.Write('declaration', code + '\n') + codes[overload] = None + + + def PolicyCode(self): + policy = self.info.policy + if policy is not None: + assert isinstance(policy, Policy) + return ', %s()' % policy.Code() + else: + return '' + + + def ExportOpaquePointer(self, function, codeunit): + if self.info.policy == return_value_policy(return_opaque_pointer): + typename = function.result.name + macro = exporterutils.EspecializeTypeID(typename) + if macro: + codeunit.Write('declaration-outside', macro) + + + def Name(self): + return self.info.name diff --git a/libs/python/pyste/src/Pyste/GCCXMLParser.py b/libs/python/pyste/src/Pyste/GCCXMLParser.py new file mode 100644 index 000000000..4a1017204 --- /dev/null +++ b/libs/python/pyste/src/Pyste/GCCXMLParser.py @@ -0,0 +1,478 @@ +# Copyright Bruno da Silva de Oliveira 2003. Use, modification and +# distribution is subject to the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +from declarations import * +try: + # try to use internal elementtree + from xml.etree.cElementTree import ElementTree +except ImportError: + # try to use cElementTree if avaiable + try: + from cElementTree import ElementTree + except ImportError: + # fall back to the normal elementtree + from elementtree.ElementTree import ElementTree +from xml.parsers.expat import ExpatError +from copy import deepcopy +from utils import enumerate + + +#============================================================================== +# Exceptions +#============================================================================== +class InvalidXMLError(Exception): pass + +class ParserError(Exception): pass + +class InvalidContextError(ParserError): pass + + +#============================================================================== +# GCCXMLParser +#============================================================================== +class GCCXMLParser(object): + 'Parse a GCC_XML file and extract the top-level declarations.' + + interested_tags = {'Class':0, 'Function':0, 'Variable':0, 'Enumeration':0} + + def Parse(self, filename): + self.elements = self.GetElementsFromXML(filename) + # high level declarations + self.declarations = [] + self._names = {} + # parse the elements + for id in self.elements: + element, decl = self.elements[id] + if decl is None: + try: + self.ParseElement(id, element) + except InvalidContextError: + pass # ignore those nodes with invalid context + # (workaround gccxml bug) + + + def Declarations(self): + return self.declarations + + + def AddDecl(self, decl): + if decl.FullName() in self._names: + decl.is_unique= False + for d in self.declarations: + if d.FullName() == decl.FullName(): + d.is_unique = False + self._names[decl.FullName()] = 0 + self.declarations.append(decl) + + + def ParseElement(self, id, element): + method = 'Parse' + element.tag + if hasattr(self, method): + func = getattr(self, method) + func(id, element) + else: + self.ParseUnknown(id, element) + + + def GetElementsFromXML(self,filename): + 'Extracts a dictionary of elements from the gcc_xml file.' + + tree = ElementTree() + try: + tree.parse(filename) + except ExpatError: + raise InvalidXMLError, 'Not a XML file: %s' % filename + + root = tree.getroot() + if root.tag != 'GCC_XML': + raise InvalidXMLError, 'Not a valid GCC_XML file' + + # build a dictionary of id -> element, None + elementlist = root.getchildren() + elements = {} + for element in elementlist: + id = element.get('id') + if id: + elements[id] = element, None + return elements + + + def GetDecl(self, id): + if id not in self.elements: + if id == '_0': + raise InvalidContextError, 'Invalid context found in the xml file.' + else: + msg = 'ID not found in elements: %s' % id + raise ParserError, msg + + elem, decl = self.elements[id] + if decl is None: + self.ParseElement(id, elem) + elem, decl = self.elements[id] + if decl is None: + raise ParserError, 'Could not parse element: %s' % elem.tag + return decl + + + def GetType(self, id): + def Check(id, feature): + pos = id.find(feature) + if pos != -1: + id = id[:pos] + id[pos+1:] + return True, id + else: + return False, id + const, id = Check(id, 'c') + volatile, id = Check(id, 'v') + restricted, id = Check(id, 'r') + decl = self.GetDecl(id) + if isinstance(decl, Type): + res = deepcopy(decl) + if const: + res.const = const + if volatile: + res.volatile = volatile + if restricted: + res.restricted = restricted + else: + res = Type(decl.FullName(), const) + res.volatile = volatile + res.restricted = restricted + return res + + + def GetLocation(self, location): + file, line = location.split(':') + file = self.GetDecl(file) + return file, int(line) + + + def Update(self, id, decl): + element, _ = self.elements[id] + self.elements[id] = element, decl + + + def ParseUnknown(self, id, element): + name = '__Unknown_Element_%s' % id + decl = Unknown(name) + self.Update(id, decl) + + + def ParseNamespace(self, id, element): + namespace = element.get('name') + context = element.get('context') + if context: + outer = self.GetDecl(context) + if not outer.endswith('::'): + outer += '::' + namespace = outer + namespace + if namespace.startswith('::'): + namespace = namespace[2:] + self.Update(id, namespace) + + + def ParseFile(self, id, element): + filename = element.get('name') + self.Update(id, filename) + + + def ParseVariable(self, id, element): + # in gcc_xml, a static Field is declared as a Variable, so we check + # this and call the Field parser. + context = self.GetDecl(element.get('context')) + if isinstance(context, Class): + self.ParseField(id, element) + elem, decl = self.elements[id] + decl.static = True + else: + namespace = context + name = element.get('name') + type_ = self.GetType(element.get('type')) + location = self.GetLocation(element.get('location')) + variable = Variable(type_, name, namespace) + variable.location = location + self.AddDecl(variable) + self.Update(id, variable) + + + def GetArguments(self, element): + args = [] + for child in element: + if child.tag == 'Argument': + type = self.GetType(child.get('type')) + type.default = child.get('default') + args.append(type) + return args + + + def GetExceptions(self, exception_list): + if exception_list is None: + return None + + exceptions = [] + for t in exception_list.split(): + exceptions.append(self.GetType(t)) + + return exceptions + + + def ParseFunction(self, id, element, functionType=Function): + '''functionType is used because a Operator is identical to a normal + function, only the type of the function changes.''' + name = element.get('name') + returns = self.GetType(element.get('returns')) + namespace = self.GetDecl(element.get('context')) + location = self.GetLocation(element.get('location')) + params = self.GetArguments(element) + incomplete = bool(int(element.get('incomplete', 0))) + throws = self.GetExceptions(element.get('throw', None)) + function = functionType(name, namespace, returns, params, throws) + function.location = location + self.AddDecl(function) + self.Update(id, function) + + + def ParseOperatorFunction(self, id, element): + self.ParseFunction(id, element, Operator) + + + def GetHierarchy(self, bases): + '''Parses the string "bases" from the xml into a list of tuples of Base + instances. The first tuple is the most direct inheritance, and then it + goes up in the hierarchy. + ''' + + if bases is None: + return [] + base_names = bases.split() + this_level = [] + next_levels = [] + for base in base_names: + # get the visibility + split = base.split(':') + if len(split) == 2: + visib = split[0] + base = split[1] + else: + visib = Scope.public + decl = self.GetDecl(base) + if not isinstance(decl, Class): + # on windows, there are some classes which "bases" points to an + # "Unimplemented" tag, but we are not interested in this classes + # anyway + continue + base = Base(decl.FullName(), visib) + this_level.append(base) + # normalize with the other levels + for index, level in enumerate(decl.hierarchy): + if index < len(next_levels): + next_levels[index] = next_levels[index] + level + else: + next_levels.append(level) + hierarchy = [] + if this_level: + hierarchy.append(tuple(this_level)) + if next_levels: + hierarchy.extend(next_levels) + return hierarchy + + + def GetMembers(self, member_list): + # members must be a string with the ids of the members + if member_list is None: + return [] + members = [] + for member in member_list.split(): + decl = self.GetDecl(member) + if type(decl) in Class.ValidMemberTypes(): + members.append(decl) + return members + + + def ParseClass(self, id, element): + name = element.get('name') + abstract = bool(int(element.get('abstract', '0'))) + location = self.GetLocation(element.get('location')) + context = self.GetDecl(element.get('context')) + incomplete = bool(int(element.get('incomplete', 0))) + if isinstance(context, str): + class_ = Class(name, context, [], abstract) + else: + # a nested class + visib = element.get('access', Scope.public) + class_ = NestedClass( + name, context.FullName(), visib, [], abstract) + class_.incomplete = incomplete + # we have to add the declaration of the class before trying + # to parse its members and bases, to avoid recursion. + self.AddDecl(class_) + class_.location = location + self.Update(id, class_) + # now we can get the members and the bases + class_.hierarchy = self.GetHierarchy(element.get('bases')) + if class_.hierarchy: + class_.bases = class_.hierarchy[0] + members = self.GetMembers(element.get('members')) + for member in members: + class_.AddMember(member) + + + def ParseStruct(self, id, element): + self.ParseClass(id, element) + + + FUNDAMENTAL_RENAME = { + 'long long int' : 'boost::int64_t', + 'long long unsigned int' : 'boost::uint64_t', + } + + def ParseFundamentalType(self, id, element): + name = element.get('name') + name = self.FUNDAMENTAL_RENAME.get(name, name) + type_ = FundamentalType(name) + self.Update(id, type_) + + + def ParseArrayType(self, id, element): + type = self.GetType(element.get('type')) + min = element.get('min') + max = element.get('max') + array = ArrayType(type.name, type.const, min, max) + self.Update(id, array) + + + def ParseReferenceType(self, id, element): + type = self.GetType(element.get('type')) + expand = not isinstance(type, FunctionType) + ref = ReferenceType(type.name, type.const, None, expand, type.suffix) + self.Update(id, ref) + + + def ParsePointerType(self, id, element): + type = self.GetType(element.get('type')) + expand = not isinstance(type, FunctionType) + ref = PointerType(type.name, type.const, None, expand, type.suffix) + self.Update(id, ref) + + + def ParseFunctionType(self, id, element): + result = self.GetType(element.get('returns')) + args = self.GetArguments(element) + func = FunctionType(result, args) + self.Update(id, func) + + + def ParseMethodType(self, id, element): + class_ = self.GetDecl(element.get('basetype')).FullName() + result = self.GetType(element.get('returns')) + args = self.GetArguments(element) + method = MethodType(result, args, class_) + self.Update(id, method) + + + def ParseField(self, id, element): + name = element.get('name') + visib = element.get('access', Scope.public) + classname = self.GetDecl(element.get('context')).FullName() + type_ = self.GetType(element.get('type')) + static = bool(int(element.get('extern', '0'))) + location = self.GetLocation(element.get('location')) + var = ClassVariable(type_, name, classname, visib, static) + var.location = location + self.Update(id, var) + + + def ParseMethod(self, id, element, methodType=Method): + name = element.get('name') + result = self.GetType(element.get('returns')) + classname = self.GetDecl(element.get('context')).FullName() + visib = element.get('access', Scope.public) + static = bool(int(element.get('static', '0'))) + virtual = bool(int(element.get('virtual', '0'))) + abstract = bool(int(element.get('pure_virtual', '0'))) + const = bool(int(element.get('const', '0'))) + location = self.GetLocation(element.get('location')) + throws = self.GetExceptions(element.get('throw', None)) + params = self.GetArguments(element) + method = methodType( + name, classname, result, params, visib, virtual, abstract, static, const, throws) + method.location = location + self.Update(id, method) + + + def ParseOperatorMethod(self, id, element): + self.ParseMethod(id, element, ClassOperator) + + + def ParseConstructor(self, id, element): + name = element.get('name') + visib = element.get('access', Scope.public) + classname = self.GetDecl(element.get('context')).FullName() + location = self.GetLocation(element.get('location')) + params = self.GetArguments(element) + artificial = element.get('artificial', False) + ctor = Constructor(name, classname, params, visib) + ctor.location = location + self.Update(id, ctor) + + + def ParseDestructor(self, id, element): + name = element.get('name') + visib = element.get('access', Scope.public) + classname = self.GetDecl(element.get('context')).FullName() + virtual = bool(int(element.get('virtual', '0'))) + location = self.GetLocation(element.get('location')) + des = Destructor(name, classname, visib, virtual) + des.location = location + self.Update(id, des) + + + def ParseConverter(self, id, element): + self.ParseMethod(id, element, ConverterOperator) + + + def ParseTypedef(self, id, element): + name = element.get('name') + type = self.GetType(element.get('type')) + context = self.GetDecl(element.get('context')) + if isinstance(context, Class): + context = context.FullName() + typedef = Typedef(type, name, context) + self.Update(id, typedef) + self.AddDecl(typedef) + + + def ParseEnumeration(self, id, element): + name = element.get('name') + location = self.GetLocation(element.get('location')) + context = self.GetDecl(element.get('context')) + incomplete = bool(int(element.get('incomplete', 0))) + if isinstance(context, str): + enum = Enumeration(name, context) + else: + visib = element.get('access', Scope.public) + enum = ClassEnumeration(name, context.FullName(), visib) + self.AddDecl(enum) + enum.location = location + for child in element: + if child.tag == 'EnumValue': + name = child.get('name') + value = int(child.get('init')) + enum.values[name] = value + enum.incomplete = incomplete + self.Update(id, enum) + + + +def ParseDeclarations(filename): + 'Returns a list of the top declarations found in the gcc_xml file.' + + parser = GCCXMLParser() + parser.Parse(filename) + return parser.Declarations() + + +if __name__ == '__main__': + ParseDeclarations(r'D:\Programming\Libraries\boost-cvs\boost\libs\python\pyste\example\test.xml') diff --git a/libs/python/pyste/src/Pyste/HeaderExporter.py b/libs/python/pyste/src/Pyste/HeaderExporter.py new file mode 100644 index 000000000..47651ba70 --- /dev/null +++ b/libs/python/pyste/src/Pyste/HeaderExporter.py @@ -0,0 +1,81 @@ +# Copyright Bruno da Silva de Oliveira 2003. Use, modification and +# distribution is subject to the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +from Exporter import Exporter +from ClassExporter import ClassExporter +from FunctionExporter import FunctionExporter +from EnumExporter import EnumExporter +from VarExporter import VarExporter +from infos import * +from declarations import * +import os.path +import exporters +import MultipleCodeUnit + +#============================================================================== +# HeaderExporter +#============================================================================== +class HeaderExporter(Exporter): + 'Exports all declarations found in the given header' + + def __init__(self, info, parser_tail=None): + Exporter.__init__(self, info, parser_tail) + + + def WriteInclude(self, codeunit): + pass + + + def IsInternalName(self, name): + '''Returns true if the given name looks like a internal compiler + structure''' + return name.startswith('_') + + + def Export(self, codeunit, exported_names): + header = os.path.normpath(self.parser_header) + for decl in self.declarations: + # check if this declaration is in the header + location = os.path.abspath(decl.location[0]) + if location == header and not self.IsInternalName(decl.name): + # ok, check the type of the declaration and export it accordingly + self.HandleDeclaration(decl, codeunit, exported_names) + + + def HandleDeclaration(self, decl, codeunit, exported_names): + '''Dispatch the declaration to the appropriate method, that must create + a suitable info object for a Exporter, create a Exporter, set its + declarations and append it to the list of exporters. + ''' + dispatch_table = { + Class : ClassExporter, + Enumeration : EnumExporter, + Function : FunctionExporter, + Variable : VarExporter, + } + + exporter_class = dispatch_table.get(type(decl)) + if exporter_class is not None: + self.HandleExporter(decl, exporter_class, codeunit, exported_names) + + + def HandleExporter(self, decl, exporter_type, codeunit, exported_names): + # only export complete declarations + if not decl.incomplete: + info = self.info[decl.name] + info.name = decl.FullName() + info.include = self.info.include + exporter = exporter_type(info) + exporter.SetDeclarations(self.declarations) + exporter.SetParsedHeader(self.parser_header) + if isinstance(codeunit, MultipleCodeUnit.MultipleCodeUnit): + codeunit.SetCurrent(self.interface_file, exporter.Name()) + else: + codeunit.SetCurrent(exporter.Name()) + exporter.GenerateCode(codeunit, exported_names) + + + def Name(self): + return self.info.include diff --git a/libs/python/pyste/src/Pyste/MultipleCodeUnit.py b/libs/python/pyste/src/Pyste/MultipleCodeUnit.py new file mode 100644 index 000000000..65faad45d --- /dev/null +++ b/libs/python/pyste/src/Pyste/MultipleCodeUnit.py @@ -0,0 +1,135 @@ +# Copyright Bruno da Silva de Oliveira 2003. Use, modification and +# distribution is subject to the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +from SingleCodeUnit import SingleCodeUnit +import os +import utils +from SmartFile import SmartFile + + +#============================================================================== +# MultipleCodeUnit +#============================================================================== +class MultipleCodeUnit(object): + ''' + Represents a bunch of cpp files, where each cpp file represents a header + to be exported by pyste. Another cpp, named <module>.cpp is created too. + ''' + + def __init__(self, modulename, outdir): + self.modulename = modulename + self.outdir = outdir + self.codeunits = {} # maps from a (filename, function) to a SingleCodeUnit + self.functions = [] + self._current = None + self.all = SingleCodeUnit(None, None) + + + def _FunctionName(self, interface_file): + name = os.path.splitext(interface_file)[0] + return 'Export_%s' % utils.makeid(name) + + + def _FileName(self, interface_file): + filename = os.path.basename(interface_file) + filename = '_%s.cpp' % os.path.splitext(filename)[0] + return os.path.join(self.outdir, filename) + + + def SetCurrent(self, interface_file, export_name): + 'Changes the current code unit' + if export_name is None: + self._current = None + elif export_name is '__all__': + self._current = self.all + else: + filename = self._FileName(interface_file) + function = self._FunctionName(interface_file) + try: + codeunit = self.codeunits[filename] + except KeyError: + codeunit = SingleCodeUnit(None, filename) + codeunit.module_definition = 'void %s()' % function + self.codeunits[filename] = codeunit + if function not in self.functions: + self.functions.append(function) + self._current = codeunit + + + def Current(self): + return self._current + + current = property(Current, SetCurrent) + + + def Write(self, section, code): + if self._current is not None: + self.current.Write(section, code) + + + def Section(self, section): + if self._current is not None: + return self.current.Section(section) + + + def _CreateOutputDir(self): + try: + os.mkdir(self.outdir) + except OSError: pass # already created + + + def Save(self): + # create the directory where all the files will go + self._CreateOutputDir(); + # order all code units by filename, and merge them all + codeunits = {} # filename => list of codeunits + + # While ordering all code units by file name, the first code + # unit in the list of code units is used as the main unit + # which dumps all the include, declaration and + # declaration-outside sections at the top of the file. + for filename, codeunit in self.codeunits.items(): + if filename not in codeunits: + # this codeunit is the main codeunit. + codeunits[filename] = [codeunit] + codeunit.Merge(self.all) + else: + main_unit = codeunits[filename][0] + for section in ('include', 'declaration', 'declaration-outside'): + main_unit.code[section] = main_unit.code[section] + codeunit.code[section] + codeunit.code[section] = '' + codeunits[filename].append(codeunit) + + # Now write all the codeunits appending them correctly. + for file_units in codeunits.values(): + append = False + for codeunit in file_units: + codeunit.Save(append) + if not append: + append = True + + + def GenerateMain(self, interfaces): + # generate the main cpp + filename = os.path.join(self.outdir, '_main.cpp') + fout = SmartFile(filename, 'w') + fout.write(utils.left_equals('Include')) + fout.write('#include <boost/python/module.hpp>\n\n') + fout.write(utils.left_equals('Exports')) + functions = [self._FunctionName(x) for x in interfaces] + for function in functions: + fout.write('void %s();\n' % function) + fout.write('\n') + fout.write(utils.left_equals('Module')) + fout.write('BOOST_PYTHON_MODULE(%s)\n' % self.modulename) + fout.write('{\n') + indent = ' ' * 4 + for function in functions: + fout.write(indent) + fout.write('%s();\n' % function) + fout.write('}\n') + + + diff --git a/libs/python/pyste/src/Pyste/SingleCodeUnit.py b/libs/python/pyste/src/Pyste/SingleCodeUnit.py new file mode 100644 index 000000000..2e59dbb80 --- /dev/null +++ b/libs/python/pyste/src/Pyste/SingleCodeUnit.py @@ -0,0 +1,121 @@ +# Copyright Bruno da Silva de Oliveira 2003. Use, modification and +# distribution is subject to the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +from settings import namespaces +import settings +from utils import remove_duplicated_lines, left_equals +from SmartFile import SmartFile + + +#============================================================================== +# SingleCodeUnit +#============================================================================== +class SingleCodeUnit: + ''' + Represents a cpp file, where other objects can write in one of the + predefined sections. + The avaiable sections are: + pchinclude - The pre-compiled header area + include - The include area of the cpp file + declaration - The part before the module definition + module - Inside the BOOST_PYTHON_MODULE macro + ''' + + def __init__(self, modulename, filename): + self.modulename = modulename + self.filename = filename + # define the avaiable sections + self.code = {} + # include section + self.code['pchinclude'] = '' + # include section + self.code['include'] = '' + # declaration section (inside namespace) + self.code['declaration'] = '' + # declaration (outside namespace) + self.code['declaration-outside'] = '' + # inside BOOST_PYTHON_MACRO + self.code['module'] = '' + # create the default module definition + self.module_definition = 'BOOST_PYTHON_MODULE(%s)' % modulename + + + def Write(self, section, code): + 'write the given code in the section of the code unit' + if section not in self.code: + raise RuntimeError, 'Invalid CodeUnit section: %s' % section + self.code[section] += code + + + def Merge(self, other): + for section in ('include', 'declaration', 'declaration-outside', 'module'): + self.code[section] = self.code[section] + other.code[section] + + + def Section(self, section): + return self.code[section] + + + def SetCurrent(self, *args): + pass + + + def Current(self): + pass + + + def Save(self, append=False): + 'Writes this code unit to the filename' + space = '\n\n' + if not append: + flag = 'w' + else: + flag = 'a' + fout = SmartFile(self.filename, flag) + fout.write('\n') + # includes + # boost.python header + if self.code['pchinclude']: + fout.write(left_equals('PCH')) + fout.write(self.code['pchinclude']+'\n') + fout.write('#ifdef _MSC_VER\n') + fout.write('#pragma hdrstop\n') + fout.write('#endif\n') + else: + fout.write(left_equals('Boost Includes')) + fout.write('#include <boost/python.hpp>\n') + # include numerical boost for int64 definitions + fout.write('#include <boost/cstdint.hpp>\n') + fout.write('\n') + # other includes + if self.code['include']: + fout.write(left_equals('Includes')) + includes = remove_duplicated_lines(self.code['include']) + fout.write(includes) + fout.write(space) + # using + if settings.USING_BOOST_NS and not append: + fout.write(left_equals('Using')) + fout.write('using namespace boost::python;\n\n') + # declarations + declaration = self.code['declaration'] + declaration_outside = self.code['declaration-outside'] + if declaration_outside or declaration: + fout.write(left_equals('Declarations')) + if declaration_outside: + fout.write(declaration_outside + '\n\n') + if declaration: + pyste_namespace = namespaces.pyste[:-2] + fout.write('namespace %s {\n\n' % pyste_namespace) + fout.write(declaration) + fout.write('\n}// namespace %s\n' % pyste_namespace) + fout.write(space) + # module + fout.write(left_equals('Module')) + fout.write(self.module_definition + '\n') + fout.write('{\n') + fout.write(self.code['module']) + fout.write('}\n\n') + fout.close() diff --git a/libs/python/pyste/src/Pyste/SmartFile.py b/libs/python/pyste/src/Pyste/SmartFile.py new file mode 100644 index 000000000..039579e3b --- /dev/null +++ b/libs/python/pyste/src/Pyste/SmartFile.py @@ -0,0 +1,60 @@ +# Copyright Bruno da Silva de Oliveira 2003. Use, modification and +# distribution is subject to the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +import os +import md5 + +#============================================================================== +# SmartFile +#============================================================================== +class SmartFile(object): + ''' + A file-like object used for writing files. The given file will only be + actually written to disk if there's not a file with the same name, or if + the existing file is *different* from the file to be written. + ''' + + def __init__(self, filename, mode='w'): + self.filename = filename + self.mode = mode + self._contents = [] + self._closed = False + + + def __del__(self): + if not self._closed: + self.close() + + + def write(self, string): + self._contents.append(string) + + + def _dowrite(self, contents): + f = file(self.filename, self.mode) + f.write(contents) + f.close() + + + def _GetMD5(self, string): + return md5.new(string).digest() + + + def close(self): + # if the filename doesn't exist, write the file right away + this_contents = ''.join(self._contents) + if not os.path.isfile(self.filename): + self._dowrite(this_contents) + else: + # read the contents of the file already in disk + f = file(self.filename) + other_contents = f.read() + f.close() + # test the md5 for both files + this_md5 = self._GetMD5(this_contents) + other_md5 = self._GetMD5(other_contents) + if this_md5 != other_md5: + self._dowrite(this_contents) + self._closed = True diff --git a/libs/python/pyste/src/Pyste/VarExporter.py b/libs/python/pyste/src/Pyste/VarExporter.py new file mode 100644 index 000000000..d3571e751 --- /dev/null +++ b/libs/python/pyste/src/Pyste/VarExporter.py @@ -0,0 +1,40 @@ +# Copyright Bruno da Silva de Oliveira 2003. Use, modification and +# distribution is subject to the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +from Exporter import Exporter +from settings import * +import utils + +#============================================================================== +# VarExporter +#============================================================================== +class VarExporter(Exporter): + '''Exports a global variable. + ''' + + def __init__(self, info): + Exporter.__init__(self, info) + + + def Export(self, codeunit, exported_names): + if self.info.exclude: return + decl = self.GetDeclaration(self.info.name) + if not decl.type.const: + msg = '---> Warning: The global variable "%s" is non-const:\n' \ + ' changes in Python will not reflect in C++.' + print msg % self.info.name + print + rename = self.info.rename or self.info.name + code = self.INDENT + namespaces.python + code += 'scope().attr("%s") = %s;\n' % (rename, self.info.name) + codeunit.Write('module', code) + + + def Order(self): + return 0, self.info.name + + + def Name(self): + return self.info.name diff --git a/libs/python/pyste/src/Pyste/__init__.py b/libs/python/pyste/src/Pyste/__init__.py new file mode 100644 index 000000000..02eec64b7 --- /dev/null +++ b/libs/python/pyste/src/Pyste/__init__.py @@ -0,0 +1,6 @@ +# Copyright Bruno da Silva de Oliveira 2003. Use, modification and +# distribution is subject to the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + + diff --git a/libs/python/pyste/src/Pyste/declarations.py b/libs/python/pyste/src/Pyste/declarations.py new file mode 100644 index 000000000..6eff97dc5 --- /dev/null +++ b/libs/python/pyste/src/Pyste/declarations.py @@ -0,0 +1,653 @@ +# Copyright Bruno da Silva de Oliveira 2003. Use, modification and +# distribution is subject to the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +''' +Defines classes that represent declarations found in C++ header files. + +''' + +# version indicates the version of the declarations. Whenever a declaration +# changes, this variable should be updated, so that the caches can be rebuilt +# automatically +version = '1.0' + +#============================================================================== +# Declaration +#============================================================================== +class Declaration(object): + '''Base class for all declarations. + @ivar name: The name of the declaration. + @ivar namespace: The namespace of the declaration. + ''' + + def __init__(self, name, namespace): + ''' + @type name: string + @param name: The name of this declaration + @type namespace: string + @param namespace: the full namespace where this declaration resides. + ''' + self.name = name + self.namespace = namespace + self.location = '', -1 # (filename, line) + self.incomplete = False + self.is_unique = True + + + def FullName(self): + ''' + Returns the full qualified name: "boost::inner::Test" + @rtype: string + @return: The full name of the declaration. + ''' + namespace = self.namespace or '' + if namespace and not namespace.endswith('::'): + namespace += '::' + return namespace + self.name + + + def __repr__(self): + return '<Declaration %s at %s>' % (self.FullName(), id(self)) + + + def __str__(self): + return 'Declaration of %s' % self.FullName() + + +#============================================================================== +# Class +#============================================================================== +class Class(Declaration): + ''' + Represents a C++ class or struct. Iteration through it yields its members. + + @type abstract: bool + @ivar abstract: if the class has any abstract methods. + + @type bases: tuple + @ivar bases: tuple with L{Base} instances, representing the most direct + inheritance. + + @type hierarchy: list + @ivar hierarchy: a list of tuples of L{Base} instances, representing + the entire hierarchy tree of this object. The first tuple is the parent + classes, and the other ones go up in the hierarchy. + ''' + + def __init__(self, name, namespace, members, abstract): + Declaration.__init__(self, name, namespace) + self.__members = members + self.__member_names = {} + self.abstract = abstract + self.bases = () + self.hierarchy = () + self.operator = {} + + + def __iter__(self): + '''iterates through the class' members. + ''' + return iter(self.__members) + + + def Constructors(self, publics_only=True): + '''Returns a list of the constructors for this class. + @rtype: list + ''' + constructors = [] + for member in self: + if isinstance(member, Constructor): + if publics_only and member.visibility != Scope.public: + continue + constructors.append(member) + return constructors + + + def HasCopyConstructor(self): + '''Returns true if this class has a public copy constructor. + @rtype: bool + ''' + for cons in self.Constructors(): + if cons.IsCopy(): + return True + return False + + + def HasDefaultConstructor(self): + '''Returns true if this class has a public default constructor. + @rtype: bool + ''' + for cons in self.Constructors(): + if cons.IsDefault(): + return True + return False + + + def AddMember(self, member): + if member.name in self.__member_names: + member.is_unique = False + for m in self: + if m.name == member.name: + m.is_unique = False + else: + member.is_unique = True + self.__member_names[member.name] = 1 + self.__members.append(member) + if isinstance(member, ClassOperator): + self.operator[member.name] = member + + + def ValidMemberTypes(): + return (NestedClass, Method, Constructor, Destructor, ClassVariable, + ClassOperator, ConverterOperator, ClassEnumeration) + ValidMemberTypes = staticmethod(ValidMemberTypes) + + +#============================================================================== +# NestedClass +#============================================================================== +class NestedClass(Class): + '''The declaration of a class/struct inside another class/struct. + + @type class: string + @ivar class: fullname of the class where this class is contained. + + @type visibility: L{Scope} + @ivar visibility: the visibility of this class. + ''' + + def __init__(self, name, class_, visib, members, abstract): + Class.__init__(self, name, None, members, abstract) + self.class_ = class_ + self.visibility = visib + + + def FullName(self): + '''The full name of this class, like ns::outer::inner. + @rtype: string + ''' + return '%s::%s' % (self.class_, self.name) + + +#============================================================================== +# Scope +#============================================================================== +class Scope: + '''Used to represent the visibility of various members inside a class. + @cvar public: public visibility + @cvar private: private visibility + @cvar protected: protected visibility + ''' + public = 'public' + private = 'private' + protected = 'protected' + + +#============================================================================== +# Base +#============================================================================== +class Base: + '''Represents a base class of another class. + @ivar _name: the full name of the base class. + @ivar _visibility: the visibility of the derivation. + ''' + + def __init__(self, name, visibility=Scope.public): + self.name = name + self.visibility = visibility + + +#============================================================================== +# Function +#============================================================================== +class Function(Declaration): + '''The declaration of a function. + @ivar _result: instance of L{Type} or None. + @ivar _parameters: list of L{Type} instances. + @ivar _throws: exception specifiers or None + ''' + + def __init__(self, name, namespace, result, params, throws=None): + Declaration.__init__(self, name, namespace) + # the result type: instance of Type, or None (constructors) + self.result = result + # the parameters: instances of Type + self.parameters = params + # the exception specification + self.throws = throws + + + def Exceptions(self): + if self.throws is None: + return "" + else: + return " throw(%s)" % ', '.join ([x.FullName() for x in self.throws]) + + + def PointerDeclaration(self, force=False): + '''Returns a declaration of a pointer to this function. + @param force: If True, returns a complete pointer declaration regardless + if this function is unique or not. + ''' + if self.is_unique and not force: + return '&%s' % self.FullName() + else: + result = self.result.FullName() + params = ', '.join([x.FullName() for x in self.parameters]) + return '(%s (*)(%s)%s)&%s' % (result, params, self.Exceptions(), self.FullName()) + + + def MinArgs(self): + min = 0 + for arg in self.parameters: + if arg.default is None: + min += 1 + return min + + minArgs = property(MinArgs) + + + def MaxArgs(self): + return len(self.parameters) + + maxArgs = property(MaxArgs) + + + +#============================================================================== +# Operator +#============================================================================== +class Operator(Function): + '''The declaration of a custom operator. Its name is the same as the + operator name in C++, ie, the name of the declaration "operator+(..)" is + "+". + ''' + + def FullName(self): + namespace = self.namespace or '' + if not namespace.endswith('::'): + namespace += '::' + return namespace + 'operator' + self.name + + +#============================================================================== +# Method +#============================================================================== +class Method(Function): + '''The declaration of a method. + + @ivar _visibility: the visibility of this method. + @ivar _virtual: if this method is declared as virtual. + @ivar _abstract: if this method is virtual but has no default implementation. + @ivar _static: if this method is static. + @ivar _class: the full name of the class where this method was declared. + @ivar _const: if this method is declared as const. + @ivar _throws: list of exception specificiers or None + ''' + + def __init__(self, name, class_, result, params, visib, virtual, abstract, static, const, throws=None): + Function.__init__(self, name, None, result, params, throws) + self.visibility = visib + self.virtual = virtual + self.abstract = abstract + self.static = static + self.class_ = class_ + self.const = const + + + def FullName(self): + return self.class_ + '::' + self.name + + + def PointerDeclaration(self, force=False): + '''Returns a declaration of a pointer to this member function. + @param force: If True, returns a complete pointer declaration regardless + if this function is unique or not. + ''' + if self.static: + # static methods are like normal functions + return Function.PointerDeclaration(self, force) + if self.is_unique and not force: + return '&%s' % self.FullName() + else: + result = self.result.FullName() + params = ', '.join([x.FullName() for x in self.parameters]) + const = '' + if self.const: + const = 'const' + return '(%s (%s::*)(%s) %s%s)&%s' %\ + (result, self.class_, params, const, self.Exceptions(), self.FullName()) + + +#============================================================================== +# Constructor +#============================================================================== +class Constructor(Method): + '''A class' constructor. + ''' + + def __init__(self, name, class_, params, visib): + Method.__init__(self, name, class_, None, params, visib, False, False, False, False) + + + def IsDefault(self): + '''Returns True if this constructor is a default constructor. + ''' + return len(self.parameters) == 0 and self.visibility == Scope.public + + + def IsCopy(self): + '''Returns True if this constructor is a copy constructor. + ''' + if len(self.parameters) != 1: + return False + param = self.parameters[0] + class_as_param = self.parameters[0].name == self.class_ + param_reference = isinstance(param, ReferenceType) + is_public = self.visibility == Scope.public + return param_reference and class_as_param and param.const and is_public + + + def PointerDeclaration(self, force=False): + return '' + + +#============================================================================== +# Destructor +#============================================================================== +class Destructor(Method): + 'The destructor of a class.' + + def __init__(self, name, class_, visib, virtual): + Method.__init__(self, name, class_, None, [], visib, virtual, False, False, False) + + def FullName(self): + return self.class_ + '::~' + self.name + + + def PointerDeclaration(self, force=False): + return '' + + + +#============================================================================== +# ClassOperator +#============================================================================== +class ClassOperator(Method): + 'A custom operator in a class.' + + def FullName(self): + return self.class_ + '::operator ' + self.name + + + +#============================================================================== +# ConverterOperator +#============================================================================== +class ConverterOperator(ClassOperator): + 'An operator in the form "operator OtherClass()".' + + def FullName(self): + return self.class_ + '::operator ' + self.result.FullName() + + + +#============================================================================== +# Type +#============================================================================== +class Type(Declaration): + '''Represents the type of a variable or parameter. + @ivar _const: if the type is constant. + @ivar _default: if this type has a default value associated with it. + @ivar _volatile: if this type was declared with the keyword volatile. + @ivar _restricted: if this type was declared with the keyword restricted. + @ivar _suffix: Suffix to get the full type name. '*' for pointers, for + example. + ''' + + def __init__(self, name, const=False, default=None, suffix=''): + Declaration.__init__(self, name, None) + # whatever the type is constant or not + self.const = const + # used when the Type is a function argument + self.default = default + self.volatile = False + self.restricted = False + self.suffix = suffix + + def __repr__(self): + if self.const: + const = 'const ' + else: + const = '' + return '<Type ' + const + self.name + '>' + + + def FullName(self): + if self.const: + const = 'const ' + else: + const = '' + return const + self.name + self.suffix + + +#============================================================================== +# ArrayType +#============================================================================== +class ArrayType(Type): + '''Represents an array. + @ivar min: the lower bound of the array, usually 0. Can be None. + @ivar max: the upper bound of the array. Can be None. + ''' + + def __init__(self, name, const, min, max): + 'min and max can be None.' + Type.__init__(self, name, const) + self.min = min + self.max = max + + + +#============================================================================== +# ReferenceType +#============================================================================== +class ReferenceType(Type): + '''A reference type.''' + + def __init__(self, name, const=False, default=None, expandRef=True, suffix=''): + Type.__init__(self, name, const, default) + if expandRef: + self.suffix = suffix + '&' + + +#============================================================================== +# PointerType +#============================================================================== +class PointerType(Type): + 'A pointer type.' + + def __init__(self, name, const=False, default=None, expandPointer=False, suffix=''): + Type.__init__(self, name, const, default) + if expandPointer: + self.suffix = suffix + '*' + + +#============================================================================== +# FundamentalType +#============================================================================== +class FundamentalType(Type): + 'One of the fundamental types, like int, void, etc.' + + def __init__(self, name, const=False, default=None): + Type.__init__(self, name, const, default) + + + +#============================================================================== +# FunctionType +#============================================================================== +class FunctionType(Type): + '''A pointer to a function. + @ivar _result: the return value + @ivar _parameters: a list of Types, indicating the parameters of the function. + @ivar _name: the name of the function. + ''' + + def __init__(self, result, parameters): + Type.__init__(self, '', False) + self.result = result + self.parameters = parameters + self.name = self.FullName() + + + def FullName(self): + full = '%s (*)' % self.result.FullName() + params = [x.FullName() for x in self.parameters] + full += '(%s)' % ', '.join(params) + return full + + +#============================================================================== +# MethodType +#============================================================================== +class MethodType(FunctionType): + '''A pointer to a member function of a class. + @ivar _class: The fullname of the class that the method belongs to. + ''' + + def __init__(self, result, parameters, class_): + self.class_ = class_ + FunctionType.__init__(self, result, parameters) + + + def FullName(self): + full = '%s (%s::*)' % (self.result.FullName(), self.class_) + params = [x.FullName() for x in self.parameters] + full += '(%s)' % ', '.join(params) + return full + + +#============================================================================== +# Variable +#============================================================================== +class Variable(Declaration): + '''Represents a global variable. + + @type _type: L{Type} + @ivar _type: The type of the variable. + ''' + + def __init__(self, type, name, namespace): + Declaration.__init__(self, name, namespace) + self.type = type + + +#============================================================================== +# ClassVariable +#============================================================================== +class ClassVariable(Variable): + '''Represents a class variable. + + @type _visibility: L{Scope} + @ivar _visibility: The visibility of this variable within the class. + + @type _static: bool + @ivar _static: Indicates if the variable is static. + + @ivar _class: Full name of the class that this variable belongs to. + ''' + + def __init__(self, type, name, class_, visib, static): + Variable.__init__(self, type, name, None) + self.visibility = visib + self.static = static + self.class_ = class_ + + + def FullName(self): + return self.class_ + '::' + self.name + + +#============================================================================== +# Enumeration +#============================================================================== +class Enumeration(Declaration): + '''Represents an enum. + + @type _values: dict of str => int + @ivar _values: holds the values for this enum. + ''' + + def __init__(self, name, namespace): + Declaration.__init__(self, name, namespace) + self.values = {} # dict of str => int + + + def ValueFullName(self, name): + '''Returns the full name for a value in the enum. + ''' + assert name in self.values + namespace = self.namespace + if namespace: + namespace += '::' + return namespace + name + + +#============================================================================== +# ClassEnumeration +#============================================================================== +class ClassEnumeration(Enumeration): + '''Represents an enum inside a class. + + @ivar _class: The full name of the class where this enum belongs. + @ivar _visibility: The visibility of this enum inside his class. + ''' + + def __init__(self, name, class_, visib): + Enumeration.__init__(self, name, None) + self.class_ = class_ + self.visibility = visib + + + def FullName(self): + return '%s::%s' % (self.class_, self.name) + + + def ValueFullName(self, name): + assert name in self.values + return '%s::%s' % (self.class_, name) + + +#============================================================================== +# Typedef +#============================================================================== +class Typedef(Declaration): + '''A Typedef declaration. + + @type _type: L{Type} + @ivar _type: The type of the typedef. + + @type _visibility: L{Scope} + @ivar _visibility: The visibility of this typedef. + ''' + + def __init__(self, type, name, namespace): + Declaration.__init__(self, name, namespace) + self.type = type + self.visibility = Scope.public + + + + + +#============================================================================== +# Unknown +#============================================================================== +class Unknown(Declaration): + '''A declaration that Pyste does not know how to handle. + ''' + + def __init__(self, name): + Declaration.__init__(self, name, None) diff --git a/libs/python/pyste/src/Pyste/exporters.py b/libs/python/pyste/src/Pyste/exporters.py new file mode 100644 index 000000000..f573d01be --- /dev/null +++ b/libs/python/pyste/src/Pyste/exporters.py @@ -0,0 +1,12 @@ +# Copyright Bruno da Silva de Oliveira 2003. Use, modification and +# distribution is subject to the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + + +# a list of Exporter instances +exporters = [] + +current_interface = None # the current interface file being processed +importing = False # whetever we are now importing a pyste file. + # exporters created here shouldn't export themselves diff --git a/libs/python/pyste/src/Pyste/exporterutils.py b/libs/python/pyste/src/Pyste/exporterutils.py new file mode 100644 index 000000000..363700d2b --- /dev/null +++ b/libs/python/pyste/src/Pyste/exporterutils.py @@ -0,0 +1,87 @@ +# Copyright Bruno da Silva de Oliveira 2003. Use, modification and +# distribution is subject to the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +''' +Various helpers for interface files. +''' + +from settings import * +from policies import * +from declarations import * + +#============================================================================== +# FunctionWrapper +#============================================================================== +class FunctionWrapper(object): + '''Holds information about a wrapper for a function or a method. It is + divided in 2 parts: the name of the Wrapper, and its code. The code is + placed in the declaration section of the module, while the name is used to + def' the function or method (with the pyste namespace prepend to it). If + code is None, the name is left unchanged. + ''' + + def __init__(self, name, code=None): + self.name = name + self.code = code + + def FullName(self): + if self.code: + return namespaces.pyste + self.name + else: + return self.name + + +_printed_warnings = {} # used to avoid double-prints of warnings + +#============================================================================== +# HandlePolicy +#============================================================================== +def HandlePolicy(function, policy): + '''Show a warning to the user if the function needs a policy and doesn't + have one. Return a policy to the function, which is the given policy itself + if it is not None, or a default policy for this method. + ''' + + def IsString(type): + 'Return True if the Type instance can be considered a string' + return type.FullName() == 'const char*' + + def IsPyObject(type): + return type.FullName() == '_object *' # internal name of PyObject + + result = function.result + # if the function returns const char*, a policy is not needed + if IsString(result) or IsPyObject(result): + return policy + # if returns a const T&, set the default policy + if policy is None and result.const and isinstance(result, ReferenceType): + policy = return_value_policy(copy_const_reference) + # basic test if the result type demands a policy + needs_policy = isinstance(result, (ReferenceType, PointerType)) + # show a warning to the user, if needed + if needs_policy and policy is None: + global _printed_warnings + warning = '---> Error: %s returns a pointer or a reference, ' \ + 'but no policy was specified.' % function.FullName() + if warning not in _printed_warnings: + print warning + print + # avoid double prints of the same warning + _printed_warnings[warning] = 1 + return policy + + +#============================================================================== +# EspecializeTypeID +#============================================================================== +_exported_type_ids = {} +def EspecializeTypeID(typename): + global _exported_type_ids + macro = 'BOOST_PYTHON_OPAQUE_SPECIALIZED_TYPE_ID(%s)\n' % typename + if macro not in _exported_type_ids: + _exported_type_ids[macro] = 1 + return macro + else: + return None diff --git a/libs/python/pyste/src/Pyste/infos.py b/libs/python/pyste/src/Pyste/infos.py new file mode 100644 index 000000000..2a4f01eaf --- /dev/null +++ b/libs/python/pyste/src/Pyste/infos.py @@ -0,0 +1,259 @@ +# Copyright Bruno da Silva de Oliveira 2003. Use, modification and +# distribution is subject to the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +import os.path +import copy +import exporters +from ClassExporter import ClassExporter +from FunctionExporter import FunctionExporter +from EnumExporter import EnumExporter +from HeaderExporter import HeaderExporter +from VarExporter import VarExporter +from CodeExporter import CodeExporter +from exporterutils import FunctionWrapper +from utils import makeid +import warnings + +#============================================================================== +# DeclarationInfo +#============================================================================== +class DeclarationInfo: + + def __init__(self, otherInfo=None): + self.__infos = {} + self.__attributes = {} + if otherInfo is not None: + self.__infos = copy.deepcopy(otherInfo.__infos) + self.__attributes = copy.deepcopy(otherInfo.__attributes) + + + def __getitem__(self, name): + 'Used to access sub-infos' + if name.startswith('__'): + raise AttributeError + default = DeclarationInfo() + default._Attribute('name', name) + return self.__infos.setdefault(name, default) + + + def __getattr__(self, name): + return self[name] + + + def _Attribute(self, name, value=None): + if value is None: + # get value + return self.__attributes.get(name) + else: + # set value + self.__attributes[name] = value + + + def AddExporter(self, exporter): + # this was causing a much serious bug, as reported by Niall Douglas: + # another solution must be found! + #if not exporters.importing: + if exporter not in exporters.exporters: + exporters.exporters.append(exporter) + exporter.interface_file = exporters.current_interface + + +#============================================================================== +# FunctionInfo +#============================================================================== +class FunctionInfo(DeclarationInfo): + + def __init__(self, name, include, tail=None, otherOption=None, + exporter_class = FunctionExporter): + DeclarationInfo.__init__(self, otherOption) + self._Attribute('name', name) + self._Attribute('include', include) + self._Attribute('exclude', False) + # create a FunctionExporter + exporter = exporter_class(InfoWrapper(self), tail) + self.AddExporter(exporter) + + +#============================================================================== +# ClassInfo +#============================================================================== +class ClassInfo(DeclarationInfo): + + def __init__(self, name, include, tail=None, otherInfo=None, + exporter_class = ClassExporter): + DeclarationInfo.__init__(self, otherInfo) + self._Attribute('name', name) + self._Attribute('include', include) + self._Attribute('exclude', False) + # create a ClassExporter + exporter = exporter_class(InfoWrapper(self), tail) + self.AddExporter(exporter) + + +#============================================================================== +# templates +#============================================================================== +def GenerateName(name, type_list): + name = name.replace('::', '_') + names = [name] + type_list + return makeid('_'.join(names)) + + +class ClassTemplateInfo(DeclarationInfo): + + def __init__(self, name, include, + exporter_class = ClassExporter): + DeclarationInfo.__init__(self) + self._Attribute('name', name) + self._Attribute('include', include) + self._exporter_class = exporter_class + + + def Instantiate(self, type_list, rename=None): + if not rename: + rename = GenerateName(self._Attribute('name'), type_list) + # generate code to instantiate the template + types = ', '.join(type_list) + tail = 'typedef %s< %s > %s;\n' % (self._Attribute('name'), types, rename) + tail += 'void __instantiate_%s()\n' % rename + tail += '{ sizeof(%s); }\n\n' % rename + # create a ClassInfo + class_ = ClassInfo(rename, self._Attribute('include'), tail, self, + exporter_class = self._exporter_class) + return class_ + + + def __call__(self, types, rename=None): + if isinstance(types, str): + types = types.split() + return self.Instantiate(types, rename) + +#============================================================================== +# EnumInfo +#============================================================================== +class EnumInfo(DeclarationInfo): + + def __init__(self, name, include, exporter_class = EnumExporter): + DeclarationInfo.__init__(self) + self._Attribute('name', name) + self._Attribute('include', include) + self._Attribute('exclude', False) + self._Attribute('export_values', False) + exporter = exporter_class(InfoWrapper(self)) + self.AddExporter(exporter) + + +#============================================================================== +# HeaderInfo +#============================================================================== +class HeaderInfo(DeclarationInfo): + + def __init__(self, include, exporter_class = HeaderExporter): + warnings.warn('AllFromHeader is not working in all cases in the current version.') + DeclarationInfo.__init__(self) + self._Attribute('include', include) + exporter = exporter_class(InfoWrapper(self)) + self.AddExporter(exporter) + + +#============================================================================== +# VarInfo +#============================================================================== +class VarInfo(DeclarationInfo): + + def __init__(self, name, include, exporter_class = VarExporter): + DeclarationInfo.__init__(self) + self._Attribute('name', name) + self._Attribute('include', include) + exporter = exporter_class(InfoWrapper(self)) + self.AddExporter(exporter) + + +#============================================================================== +# CodeInfo +#============================================================================== +class CodeInfo(DeclarationInfo): + + def __init__(self, code, section, exporter_class = CodeExporter): + DeclarationInfo.__init__(self) + self._Attribute('code', code) + self._Attribute('section', section) + exporter = exporter_class(InfoWrapper(self)) + self.AddExporter(exporter) + + +#============================================================================== +# InfoWrapper +#============================================================================== +class InfoWrapper: + 'Provides a nicer interface for a info' + + def __init__(self, info): + self.__dict__['_info'] = info # so __setattr__ is not called + + def __getitem__(self, name): + return InfoWrapper(self._info[name]) + + def __getattr__(self, name): + return self._info._Attribute(name) + + def __setattr__(self, name, value): + self._info._Attribute(name, value) + + +#============================================================================== +# Functions +#============================================================================== +def exclude(info): + info._Attribute('exclude', True) + +def set_policy(info, policy): + info._Attribute('policy', policy) + +def rename(info, name): + info._Attribute('rename', name) + +def set_wrapper(info, wrapper): + if isinstance(wrapper, str): + wrapper = FunctionWrapper(wrapper) + info._Attribute('wrapper', wrapper) + +def instantiate(template, types, rename=None): + if isinstance(types, str): + types = types.split() + return template.Instantiate(types, rename) + +def use_shared_ptr(info): + info._Attribute('smart_ptr', 'boost::shared_ptr< %s >') + +def use_auto_ptr(info): + info._Attribute('smart_ptr', 'std::auto_ptr< %s >') + +def holder(info, function): + msg = "Expected a callable that accepts one string argument." + assert callable(function), msg + info._Attribute('holder', function) + +def add_method(info, name, rename=None): + added = info._Attribute('__added__') + if added is None: + info._Attribute('__added__', [(name, rename)]) + else: + added.append((name, rename)) + + +def class_code(info, code): + added = info._Attribute('__code__') + if added is None: + info._Attribute('__code__', [code]) + else: + added.append(code) + +def final(info): + info._Attribute('no_override', True) + + +def export_values(info): + info._Attribute('export_values', True) diff --git a/libs/python/pyste/src/Pyste/policies.py b/libs/python/pyste/src/Pyste/policies.py new file mode 100644 index 000000000..57ebd0dea --- /dev/null +++ b/libs/python/pyste/src/Pyste/policies.py @@ -0,0 +1,95 @@ +# Copyright Bruno da Silva de Oliveira 2003. Use, modification and +# distribution is subject to the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + + + +class Policy(object): + 'Represents one of the call policies of boost.python.' + + def __init__(self): + if type(self) is Policy: + raise RuntimeError, "Can't create an instance of the class Policy" + + + def Code(self): + 'Returns the string corresponding to a instancialization of the policy.' + pass + + + def _next(self): + if self.next is not None: + return ', %s >' % self.next.Code() + else: + return ' >' + + + def __eq__(self, other): + try: + return self.Code() == other.Code() + except AttributeError: + return False + + + +class return_internal_reference(Policy): + 'Ties the return value to one of the parameters.' + + def __init__(self, param=1, next=None): + ''' + param is the position of the parameter, or None for "self". + next indicates the next policy, or None. + ''' + self.param = param + self.next=next + + + def Code(self): + c = 'return_internal_reference< %i' % self.param + c += self._next() + return c + + + +class with_custodian_and_ward(Policy): + 'Ties lifetime of two arguments of a function.' + + def __init__(self, custodian, ward, next=None): + self.custodian = custodian + self.ward = ward + self.next = next + + def Code(self): + c = 'with_custodian_and_ward< %i, %i' % (self.custodian, self.ward) + c += self._next() + return c + + + +class return_value_policy(Policy): + 'Policy to convert return values.' + + def __init__(self, which, next=None): + self.which = which + self.next = next + + + def Code(self): + c = 'return_value_policy< %s' % self.which + c += self._next() + return c + +class return_self(Policy): + + def Code(self): + return 'return_self<>' + + +# values for return_value_policy +reference_existing_object = 'reference_existing_object' +copy_const_reference = 'copy_const_reference' +copy_non_const_reference = 'copy_non_const_reference' +manage_new_object = 'manage_new_object' +return_opaque_pointer = 'return_opaque_pointer' +return_by_value = 'return_by_value' diff --git a/libs/python/pyste/src/Pyste/pyste.py b/libs/python/pyste/src/Pyste/pyste.py new file mode 100644 index 000000000..cedffff55 --- /dev/null +++ b/libs/python/pyste/src/Pyste/pyste.py @@ -0,0 +1,424 @@ +# Copyright Bruno da Silva de Oliveira 2003. Use, modification and +# distribution is subject to the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +""" +Pyste version %s + +Usage: + pyste [options] interface-files + +where options are: + --module=<name> The name of the module that will be generated; + defaults to the first interface filename, without + the extension. + -I <path> Add an include path + -D <symbol> Define symbol + --multiple Create various cpps, instead of only one + (useful during development) + --out=<name> Specify output filename (default: <module>.cpp) + in --multiple mode, this will be a directory + --no-using Do not declare "using namespace boost"; + use explicit declarations instead + --pyste-ns=<name> Set the namespace where new types will be declared; + default is the empty namespace + --debug Writes the xml for each file parsed in the current + directory + --cache-dir=<dir> Directory for cache files (speeds up future runs) + --only-create-cache Recreates all caches (doesn't generate code). + --generate-main Generates the _main.cpp file (in multiple mode) + --file-list A file with one pyste file per line. Use as a + substitute for passing the files in the command + line. + --gccxml-path=<path> Path to gccxml executable (default: gccxml) + --no-default-include Do not use INCLUDE environment variable for include + files to pass along gccxml. + -h, --help Print this help and exit + -v, --version Print version information +""" + +import sys +import os +import getopt +import exporters +import SingleCodeUnit +import MultipleCodeUnit +import infos +import exporterutils +import settings +import gc +import sys +from policies import * +from CppParser import CppParser, CppParserError +import time +import declarations + +__version__ = '0.9.30' + +def RecursiveIncludes(include): + 'Return a list containg the include dir and all its subdirectories' + dirs = [include] + def visit(arg, dir, names): + # ignore CVS dirs + if os.path.split(dir)[1] != 'CVS': + dirs.append(dir) + os.path.walk(include, visit, None) + return dirs + + +def GetDefaultIncludes(): + if 'INCLUDE' in os.environ: + include = os.environ['INCLUDE'] + return include.split(os.pathsep) + else: + return [] + + +def ProcessIncludes(includes): + if sys.platform == 'win32': + index = 0 + for include in includes: + includes[index] = include.replace('\\', '/') + index += 1 + + +def ReadFileList(filename): + f = file(filename) + files = [] + try: + for line in f: + line = line.strip() + if line: + files.append(line) + finally: + f.close() + return files + + +def ParseArguments(): + + def Usage(): + print __doc__ % __version__ + sys.exit(1) + + try: + options, files = getopt.getopt( + sys.argv[1:], + 'R:I:D:vh', + ['module=', 'multiple', 'out=', 'no-using', 'pyste-ns=', 'debug', 'cache-dir=', + 'only-create-cache', 'version', 'generate-main', 'file-list=', 'help', + 'gccxml-path=', 'no-default-include']) + except getopt.GetoptError, e: + print + print 'ERROR:', e + Usage() + + default_includes = GetDefaultIncludes() + includes = [] + defines = [] + module = None + out = None + multiple = False + cache_dir = None + create_cache = False + generate_main = False + gccxml_path = 'gccxml' + + for opt, value in options: + if opt == '-I': + includes.append(value) + elif opt == '-D': + defines.append(value) + elif opt == '-R': + includes.extend(RecursiveIncludes(value)) + elif opt == '--module': + module = value + elif opt == '--out': + out = value + elif opt == '--no-using': + settings.namespaces.python = 'boost::python::' + settings.USING_BOOST_NS = False + elif opt == '--pyste-ns': + settings.namespaces.pyste = value + '::' + elif opt == '--debug': + settings.DEBUG = True + elif opt == '--multiple': + multiple = True + elif opt == '--cache-dir': + cache_dir = value + elif opt == '--only-create-cache': + create_cache = True + elif opt == '--file-list': + files += ReadFileList(value) + elif opt in ['-h', '--help']: + Usage() + elif opt in ['-v', '--version']: + print 'Pyste version %s' % __version__ + sys.exit(2) + elif opt == '--generate-main': + generate_main = True + elif opt == '--gccxml-path': + gccxml_path = value + elif opt == '--no-default-include': + default_includes = [] + else: + print 'Unknown option:', opt + Usage() + + includes[0:0] = default_includes + if not files: + Usage() + if not module: + module = os.path.splitext(os.path.basename(files[0]))[0] + if not out: + out = module + if not multiple: + out += '.cpp' + for file in files: + d = os.path.dirname(os.path.abspath(file)) + if d not in sys.path: + sys.path.append(d) + + if create_cache and not cache_dir: + print 'Error: Use --cache-dir to indicate where to create the cache files!' + Usage() + sys.exit(3) + + if generate_main and not multiple: + print 'Error: --generate-main only valid in multiple mode.' + Usage() + sys.exit(3) + + ProcessIncludes(includes) + return includes, defines, module, out, files, multiple, cache_dir, create_cache, \ + generate_main, gccxml_path + + +def PCHInclude(*headers): + code = '\n'.join(['#include <%s>' % x for x in headers]) + infos.CodeInfo(code, 'pchinclude') + + +def CreateContext(): + 'create the context where a interface file will be executed' + context = {} + context['Import'] = Import + # infos + context['Function'] = infos.FunctionInfo + context['Class'] = infos.ClassInfo + context['Include'] = lambda header: infos.CodeInfo('#include <%s>\n' % header, 'include') + context['PCHInclude'] = PCHInclude + context['Template'] = infos.ClassTemplateInfo + context['Enum'] = infos.EnumInfo + context['AllFromHeader'] = infos.HeaderInfo + context['Var'] = infos.VarInfo + # functions + context['rename'] = infos.rename + context['set_policy'] = infos.set_policy + context['exclude'] = infos.exclude + context['set_wrapper'] = infos.set_wrapper + context['use_shared_ptr'] = infos.use_shared_ptr + context['use_auto_ptr'] = infos.use_auto_ptr + context['holder'] = infos.holder + context['add_method'] = infos.add_method + context['final'] = infos.final + context['export_values'] = infos.export_values + # policies + context['return_internal_reference'] = return_internal_reference + context['with_custodian_and_ward'] = with_custodian_and_ward + context['return_value_policy'] = return_value_policy + context['reference_existing_object'] = reference_existing_object + context['copy_const_reference'] = copy_const_reference + context['copy_non_const_reference'] = copy_non_const_reference + context['return_opaque_pointer'] = return_opaque_pointer + context['manage_new_object'] = manage_new_object + context['return_by_value'] = return_by_value + context['return_self'] = return_self + # utils + context['Wrapper'] = exporterutils.FunctionWrapper + context['declaration_code'] = lambda code: infos.CodeInfo(code, 'declaration-outside') + context['module_code'] = lambda code: infos.CodeInfo(code, 'module') + context['class_code'] = infos.class_code + return context + + +def Begin(): + # parse arguments + includes, defines, module, out, interfaces, multiple, cache_dir, create_cache, generate_main, gccxml_path = ParseArguments() + # run pyste scripts + for interface in interfaces: + ExecuteInterface(interface) + # create the parser + parser = CppParser(includes, defines, cache_dir, declarations.version, gccxml_path) + try: + if not create_cache: + if not generate_main: + return GenerateCode(parser, module, out, interfaces, multiple) + else: + return GenerateMain(module, out, OrderInterfaces(interfaces)) + else: + return CreateCaches(parser) + finally: + parser.Close() + + +def CreateCaches(parser): + # There is one cache file per interface so we organize the headers + # by interfaces. For each interface collect the tails from the + # exporters sharing the same header. + tails = JoinTails(exporters.exporters) + + # now for each interface file take each header, and using the tail + # get the declarations and cache them. + for interface, header in tails: + tail = tails[(interface, header)] + declarations = parser.ParseWithGCCXML(header, tail) + cachefile = parser.CreateCache(header, interface, tail, declarations) + print 'Cached', cachefile + + return 0 + + +_imported_count = {} # interface => count + +def ExecuteInterface(interface): + old_interface = exporters.current_interface + if not os.path.exists(interface): + if old_interface and os.path.exists(old_interface): + d = os.path.dirname(old_interface) + interface = os.path.join(d, interface) + if not os.path.exists(interface): + raise IOError, "Cannot find interface file %s."%interface + + _imported_count[interface] = _imported_count.get(interface, 0) + 1 + exporters.current_interface = interface + context = CreateContext() + context['INTERFACE_FILE'] = os.path.abspath(interface) + execfile(interface, context) + exporters.current_interface = old_interface + + +def Import(interface): + exporters.importing = True + ExecuteInterface(interface) + exporters.importing = False + + +def JoinTails(exports): + '''Returns a dict of {(interface, header): tail}, where tail is the + joining of all tails of all exports for the header. + ''' + tails = {} + for export in exports: + interface = export.interface_file + header = export.Header() + tail = export.Tail() or '' + if (interface, header) in tails: + all_tails = tails[(interface,header)] + all_tails += '\n' + tail + tails[(interface, header)] = all_tails + else: + tails[(interface, header)] = tail + + return tails + + + +def OrderInterfaces(interfaces): + interfaces_order = [(_imported_count[x], x) for x in interfaces] + interfaces_order.sort() + interfaces_order.reverse() + return [x for _, x in interfaces_order] + + + +def GenerateMain(module, out, interfaces): + codeunit = MultipleCodeUnit.MultipleCodeUnit(module, out) + codeunit.GenerateMain(interfaces) + return 0 + + +def GenerateCode(parser, module, out, interfaces, multiple): + # prepare to generate the wrapper code + if multiple: + codeunit = MultipleCodeUnit.MultipleCodeUnit(module, out) + else: + codeunit = SingleCodeUnit.SingleCodeUnit(module, out) + # stop referencing the exporters here + exports = exporters.exporters + exporters.exporters = None + exported_names = dict([(x.Name(), None) for x in exports]) + + # order the exports + order = {} + for export in exports: + if export.interface_file in order: + order[export.interface_file].append(export) + else: + order[export.interface_file] = [export] + exports = [] + interfaces_order = OrderInterfaces(interfaces) + for interface in interfaces_order: + exports.extend(order[interface]) + del order + del interfaces_order + + # now generate the code in the correct order + #print exported_names + tails = JoinTails(exports) + for i in xrange(len(exports)): + export = exports[i] + interface = export.interface_file + header = export.Header() + if header: + tail = tails[(interface, header)] + declarations, parsed_header = parser.Parse(header, interface, tail) + else: + declarations = [] + parsed_header = None + ExpandTypedefs(declarations, exported_names) + export.SetDeclarations(declarations) + export.SetParsedHeader(parsed_header) + if multiple: + codeunit.SetCurrent(export.interface_file, export.Name()) + export.GenerateCode(codeunit, exported_names) + # force collect of cyclic references + exports[i] = None + del declarations + del export + gc.collect() + # finally save the code unit + codeunit.Save() + if not multiple: + print 'Module %s generated' % module + return 0 + + +def ExpandTypedefs(decls, exported_names): + '''Check if the names in exported_names are a typedef, and add the real class + name in the dict. + ''' + for name in exported_names.keys(): + for decl in decls: + if isinstance(decl, declarations.Typedef): + exported_names[decl.type.FullName()] = None + +def UsePsyco(): + 'Tries to use psyco if possible' + try: + import psyco + psyco.profile() + except: pass + + +def main(): + start = time.clock() + UsePsyco() + status = Begin() + print '%0.2f seconds' % (time.clock()-start) + sys.exit(status) + + +if __name__ == '__main__': + main() diff --git a/libs/python/pyste/src/Pyste/settings.py b/libs/python/pyste/src/Pyste/settings.py new file mode 100644 index 000000000..ba613b234 --- /dev/null +++ b/libs/python/pyste/src/Pyste/settings.py @@ -0,0 +1,21 @@ +# Copyright Bruno da Silva de Oliveira 2003. Use, modification and +# distribution is subject to the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + + +#============================================================================== +# Global information +#============================================================================== + +DEBUG = False +USING_BOOST_NS = True + +class namespaces: + boost = 'boost::' + pyste = '' + python = '' # default is to not use boost::python namespace explicitly, so + # use the "using namespace" statement instead + +import sys +msvc = sys.platform == 'win32' diff --git a/libs/python/pyste/src/Pyste/utils.py b/libs/python/pyste/src/Pyste/utils.py new file mode 100644 index 000000000..a8843e3f6 --- /dev/null +++ b/libs/python/pyste/src/Pyste/utils.py @@ -0,0 +1,78 @@ +# Copyright Bruno da Silva de Oliveira 2003. Use, modification and +# distribution is subject to the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +from __future__ import generators +import string +import sys + +#============================================================================== +# enumerate +#============================================================================== +def enumerate(seq): + i = 0 + for x in seq: + yield i, x + i += 1 + + +#============================================================================== +# makeid +#============================================================================== +_valid_chars = string.ascii_letters + string.digits + '_' +_valid_chars = dict(zip(_valid_chars, _valid_chars)) + +def makeid(name): + 'Returns the name as a valid identifier' + if type(name) != str: + print type(name), name + newname = [] + for char in name: + if char not in _valid_chars: + char = '_' + newname.append(char) + newname = ''.join(newname) + # avoid duplications of '_' chars + names = [x for x in newname.split('_') if x] + return '_'.join(names) + + +#============================================================================== +# remove_duplicated_lines +#============================================================================== +def remove_duplicated_lines(text): + includes = text.splitlines() + d = dict([(include, 0) for include in includes]) + includes = d.keys() + includes.sort() + return '\n'.join(includes) + + +#============================================================================== +# left_equals +#============================================================================== +def left_equals(s): + s = '// %s ' % s + return s + ('='*(80-len(s))) + '\n' + + +#============================================================================== +# post_mortem +#============================================================================== +def post_mortem(): + + def info(type, value, tb): + if hasattr(sys, 'ps1') or not sys.stderr.isatty(): + # we are in interactive mode or we don't have a tty-like + # device, so we call the default hook + sys.__excepthook__(type, value, tb) + else: + import traceback, pdb + # we are NOT in interactive mode, print the exception... + traceback.print_exception(type, value, tb) + print + # ...then start the debugger in post-mortem mode. + pdb.pm() + + sys.excepthook = info |