"""Code to help instantiate templated classes, methods and functions.""" # pylint: disable=too-many-arguments, too-many-instance-attributes, no-self-use, no-else-return, too-many-arguments, unused-format-string-argument, unused-variable import itertools from copy import deepcopy from typing import List import gtwrap.interface_parser as parser def instantiate_type(ctype: parser.Type, template_typenames: List[str], instantiations: List[parser.Typename], cpp_typename: parser.Typename, instantiated_class=None): """ Instantiate template typename for @p ctype. Args: instiated_class (InstantiatedClass): @return If ctype's name is in the @p template_typenames, return the corresponding type to replace in @p instantiations. If ctype name is `This`, return the new typename @p `cpp_typename`. Otherwise, return the original ctype. """ # make a deep copy so that there is no overwriting of original template params ctype = deepcopy(ctype) # Check if the return type has template parameters if len(ctype.typename.instantiations) > 0: for idx, instantiation in enumerate(ctype.typename.instantiations): if instantiation.name in template_typenames: template_idx = template_typenames.index(instantiation.name) ctype.typename.instantiations[idx] = instantiations[ template_idx] return ctype str_arg_typename = str(ctype.typename) if str_arg_typename in template_typenames: idx = template_typenames.index(str_arg_typename) return parser.Type( typename=instantiations[idx], is_const=ctype.is_const, is_shared_ptr=ctype.is_shared_ptr, is_ptr=ctype.is_ptr, is_ref=ctype.is_ref, is_basic=ctype.is_basic, ) elif str_arg_typename == 'This': if instantiated_class: name = instantiated_class.original.name namespaces_name = instantiated_class.namespaces() namespaces_name.append(name) # print("INST: {}, {}, CPP: {}, CLS: {}".format( # ctype, instantiations, cpp_typename, instantiated_class.instantiations # ), file=sys.stderr) cpp_typename = parser.Typename( namespaces_name, instantiations=instantiated_class.instantiations) return parser.Type( typename=cpp_typename, is_const=ctype.is_const, is_shared_ptr=ctype.is_shared_ptr, is_ptr=ctype.is_ptr, is_ref=ctype.is_ref, is_basic=ctype.is_basic, ) else: return ctype def instantiate_args_list(args_list, template_typenames, instantiations, cpp_typename): """ Instantiate template typenames in an argument list. Type with name `This` will be replaced by @p `cpp_typename`. @param[in] args_list A list of `parser.Argument` to instantiate. @param[in] template_typenames List of template typenames to instantiate, e.g. ['T', 'U', 'V']. @param[in] instantiations List of specific types to instantiate, each associated with each template typename. Each type is a parser.Typename, including its name and full namespaces. @param[in] cpp_typename Full-namespace cpp class name of this instantiation to replace for arguments of type named `This`. @return A new list of parser.Argument which types are replaced with their instantiations. """ instantiated_args = [] for arg in args_list: new_type = instantiate_type(arg.ctype, template_typenames, instantiations, cpp_typename) default = [arg.default] if isinstance(arg, parser.Argument) else '' instantiated_args.append(parser.Argument(name=arg.name, ctype=new_type, default=default)) return instantiated_args def instantiate_return_type(return_type, template_typenames, instantiations, cpp_typename, instantiated_class=None): """Instantiate the return type.""" new_type1 = instantiate_type(return_type.type1, template_typenames, instantiations, cpp_typename, instantiated_class=instantiated_class) if return_type.type2: new_type2 = instantiate_type(return_type.type2, template_typenames, instantiations, cpp_typename, instantiated_class=instantiated_class) else: new_type2 = '' return parser.ReturnType(new_type1, new_type2) def instantiate_name(original_name, instantiations): """ Concatenate instantiated types with an @p original name to form a new instantiated name. TODO(duy): To avoid conflicts, we should include the instantiation's namespaces, but I find that too verbose. """ inst_name = '' instantiated_names = [] for inst in instantiations: # Ensure the first character of the type is capitalized name = inst.instantiated_name() # Using `capitalize` on the complete name causes other caps to be lower case instantiated_names.append(name.replace(name[0], name[0].capitalize())) return "{}{}".format(original_name, "".join(instantiated_names)) class InstantiatedGlobalFunction(parser.GlobalFunction): """ Instantiate global functions. E.g. template T add(const T& x, const T& y); """ def __init__(self, original, instantiations=(), new_name=''): self.original = original self.instantiations = instantiations self.template = '' self.parent = original.parent if not original.template: self.name = original.name self.return_type = original.return_type self.args = original.args else: self.name = instantiate_name( original.name, instantiations) if not new_name else new_name self.return_type = instantiate_return_type( original.return_type, self.original.template.typenames, self.instantiations, # Keyword type name `This` should already be replaced in the # previous class template instantiation round. cpp_typename='', ) instantiated_args = instantiate_args_list( original.args.args_list, self.original.template.typenames, self.instantiations, # Keyword type name `This` should already be replaced in the # previous class template instantiation round. cpp_typename='', ) self.args = parser.ArgumentList(instantiated_args) super().__init__(self.name, self.return_type, self.args, self.template, parent=self.parent) def to_cpp(self): """Generate the C++ code for wrapping.""" if self.original.template: instantiated_names = [ inst.instantiated_name() for inst in self.instantiations ] ret = "{}<{}>".format(self.original.name, ",".join(instantiated_names)) else: ret = self.original.name return ret def __repr__(self): return "Instantiated {}".format( super(InstantiatedGlobalFunction, self).__repr__()) class InstantiatedMethod(parser.Method): """ We can only instantiate template methods with a single template parameter. """ def __init__(self, original, instantiations: List[parser.Typename] = ''): self.original = original self.instantiations = instantiations self.template = '' self.is_const = original.is_const self.parent = original.parent # Check for typenames if templated. # This way, we can gracefully handle bot templated and non-templated methois. typenames = self.original.template.typenames if self.original.template else [] self.name = instantiate_name(original.name, self.instantiations) self.return_type = instantiate_return_type( original.return_type, typenames, self.instantiations, # Keyword type name `This` should already be replaced in the # previous class template instantiation round. cpp_typename='', ) instantiated_args = instantiate_args_list( original.args.args_list, typenames, self.instantiations, # Keyword type name `This` should already be replaced in the # previous class template instantiation round. cpp_typename='', ) self.args = parser.ArgumentList(instantiated_args) super().__init__(self.template, self.name, self.return_type, self.args, self.is_const, parent=self.parent) def to_cpp(self): """Generate the C++ code for wrapping.""" if self.original.template: # to_cpp will handle all the namespacing and templating instantiation_list = [x.to_cpp() for x in self.instantiations] # now can simply combine the instantiations, separated by commas ret = "{}<{}>".format(self.original.name, ",".join(instantiation_list)) else: ret = self.original.name return ret def __repr__(self): return "Instantiated {}".format( super(InstantiatedMethod, self).__repr__()) class InstantiatedClass(parser.Class): """ Instantiate the class defined in the interface file. """ def __init__(self, original: parser.Class, instantiations=(), new_name=''): """ Template Instantiations: [T1, U1] """ self.original = original self.instantiations = instantiations self.template = '' self.is_virtual = original.is_virtual self.parent_class = original.parent_class self.parent = original.parent # If the class is templated, check if the number of provided instantiations # match the number of templates, else it's only a partial instantiation which is bad. if original.template: assert len(original.template.typenames) == len( instantiations), "Typenames and instantiations mismatch!" # Get the instantiated name of the class. E.g. FuncDouble self.name = instantiate_name( original.name, instantiations) if not new_name else new_name # Check for typenames if templated. # By passing in typenames, we can gracefully handle both templated and non-templated classes # This will allow the `This` keyword to be used in both templated and non-templated classes. typenames = self.original.template.typenames if self.original.template else [] # Instantiate the constructors, static methods, properties, respectively. self.ctors = self.instantiate_ctors(typenames) self.static_methods = self.instantiate_static_methods(typenames) self.properties = self.instantiate_properties(typenames) # Instantiate all operator overloads self.operators = self.instantiate_operators(typenames) # Set enums self.enums = original.enums # Instantiate all instance methods instantiated_methods = \ self.instantiate_class_templates_in_methods(typenames) # Second instantiation round to instantiate templated methods. # This is done in case both the class and the method are templated. self.methods = [] for method in instantiated_methods: if not method.template: self.methods.append(InstantiatedMethod(method, '')) else: instantiations = [] # Get all combinations of template parameters for instantiations in itertools.product( *method.template.instantiations): self.methods.append( InstantiatedMethod(method, instantiations)) super().__init__( self.template, self.is_virtual, self.name, [self.parent_class], self.ctors, self.methods, self.static_methods, self.properties, self.operators, self.enums, parent=self.parent, ) def __repr__(self): return "{virtual} class {name} [{cpp_class}]: {parent_class}\n"\ "{ctors}\n{static_methods}\n{methods}".format( virtual="virtual" if self.is_virtual else '', name=self.name, cpp_class=self.cpp_class(), parent_class=self.parent, ctors="\n".join([repr(ctor) for ctor in self.ctors]), methods="\n".join([repr(m) for m in self.methods]), static_methods="\n".join([repr(m) for m in self.static_methods]), operators="\n".join([repr(op) for op in self.operators]) ) def instantiate_ctors(self, typenames): """ Instantiate the class constructors. Args: typenames: List of template types to instantiate. Return: List of constructors instantiated with provided template args. """ instantiated_ctors = [] for ctor in self.original.ctors: instantiated_args = instantiate_args_list( ctor.args.args_list, typenames, self.instantiations, self.cpp_typename(), ) instantiated_ctors.append( parser.Constructor( name=self.name, args=parser.ArgumentList(instantiated_args), parent=self, )) return instantiated_ctors def instantiate_static_methods(self, typenames): """ Instantiate static methods in the class. Args: typenames: List of template types to instantiate. Return: List of static methods instantiated with provided template args. """ instantiated_static_methods = [] for static_method in self.original.static_methods: instantiated_args = instantiate_args_list( static_method.args.args_list, typenames, self.instantiations, self.cpp_typename()) instantiated_static_methods.append( parser.StaticMethod( name=static_method.name, return_type=instantiate_return_type( static_method.return_type, typenames, self.instantiations, self.cpp_typename(), instantiated_class=self), args=parser.ArgumentList(instantiated_args), parent=self, )) return instantiated_static_methods def instantiate_class_templates_in_methods(self, typenames): """ This function only instantiates the class-level templates in the methods. Template methods are instantiated in InstantiatedMethod in the second round. E.g. ``` template class Greeter{ void sayHello(T& name); }; Args: typenames: List of template types to instantiate. Return: List of methods instantiated with provided template args on the class. """ class_instantiated_methods = [] for method in self.original.methods: instantiated_args = instantiate_args_list( method.args.args_list, typenames, self.instantiations, self.cpp_typename(), ) class_instantiated_methods.append( parser.Method( template=method.template, name=method.name, return_type=instantiate_return_type( method.return_type, typenames, self.instantiations, self.cpp_typename(), ), args=parser.ArgumentList(instantiated_args), is_const=method.is_const, parent=self, )) return class_instantiated_methods def instantiate_operators(self, typenames): """ Instantiate the class-level template in the operator overload. Args: typenames: List of template types to instantiate. Return: List of methods instantiated with provided template args on the class. """ instantiated_operators = [] for operator in self.original.operators: instantiated_args = instantiate_args_list( operator.args.args_list, typenames, self.instantiations, self.cpp_typename(), ) instantiated_operators.append( parser.Operator( name=operator.name, operator=operator.operator, return_type=instantiate_return_type( operator.return_type, typenames, self.instantiations, self.cpp_typename(), ), args=parser.ArgumentList(instantiated_args), is_const=operator.is_const, parent=self, )) return instantiated_operators def instantiate_properties(self, typenames): """ Instantiate the class properties. Args: typenames: List of template types to instantiate. Return: List of properties instantiated with provided template args. """ instantiated_properties = instantiate_args_list( self.original.properties, typenames, self.instantiations, self.cpp_typename(), ) return instantiated_properties def cpp_class(self): """Generate the C++ code for wrapping.""" return self.cpp_typename().to_cpp() def cpp_typename(self): """ Return a parser.Typename including namespaces and cpp name of this class. """ if self.original.template: name = "{}<{}>".format( self.original.name, ", ".join([inst.to_cpp() for inst in self.instantiations])) else: name = self.original.name namespaces_name = self.namespaces() namespaces_name.append(name) return parser.Typename(namespaces_name) def instantiate_namespace_inplace(namespace): """ Instantiate the classes and other elements in the `namespace` content and assign it back to the namespace content attribute. @param[in/out] namespace The namespace whose content will be replaced with the instantiated content. """ instantiated_content = [] typedef_content = [] for element in namespace.content: if isinstance(element, parser.Class): original_class = element if not original_class.template: instantiated_content.append( InstantiatedClass(original_class, [])) else: # This case is for when the templates have enumerated instantiations. # Use itertools to get all possible combinations of instantiations # Works even if one template does not have an instantiation list for instantiations in itertools.product( *original_class.template.instantiations): instantiated_content.append( InstantiatedClass(original_class, list(instantiations))) elif isinstance(element, parser.GlobalFunction): original_func = element if not original_func.template: instantiated_content.append( InstantiatedGlobalFunction(original_func, [])) else: # Use itertools to get all possible combinations of instantiations # Works even if one template does not have an instantiation list for instantiations in itertools.product( *original_func.template.instantiations): instantiated_content.append( InstantiatedGlobalFunction(original_func, list(instantiations))) elif isinstance(element, parser.TypedefTemplateInstantiation): # This is for the case where `typedef` statements are used # to specify the template parameters. typedef_inst = element top_level = namespace.top_level() original_element = top_level.find_class_or_function( typedef_inst.typename) # Check if element is a typedef'd class or function. if isinstance(original_element, parser.Class): typedef_content.append( InstantiatedClass(original_element, typedef_inst.typename.instantiations, typedef_inst.new_name)) elif isinstance(original_element, parser.GlobalFunction): typedef_content.append( InstantiatedGlobalFunction( original_element, typedef_inst.typename.instantiations, typedef_inst.new_name)) elif isinstance(element, parser.Namespace): instantiate_namespace_inplace(element) instantiated_content.append(element) else: instantiated_content.append(element) instantiated_content.extend(typedef_content) namespace.content = instantiated_content