301 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
		
		
			
		
	
	
			301 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
|  | """Various helpers for instantiation.""" | ||
|  | 
 | ||
|  | import itertools | ||
|  | from copy import deepcopy | ||
|  | from typing import List, Sequence, Union | ||
|  | 
 | ||
|  | import gtwrap.interface_parser as parser | ||
|  | 
 | ||
|  | ClassMembers = Union[parser.Constructor, parser.Method, parser.StaticMethod, | ||
|  |                      parser.GlobalFunction, parser.Operator, parser.Variable, | ||
|  |                      parser.Enum] | ||
|  | InstantiatedMembers = Union['InstantiatedConstructor', 'InstantiatedMethod', | ||
|  |                             'InstantiatedStaticMethod', | ||
|  |                             'InstantiatedGlobalFunction'] | ||
|  | 
 | ||
|  | 
 | ||
|  | def is_scoped_template(template_typenames: Sequence[str], | ||
|  |                        str_arg_typename: str): | ||
|  |     """
 | ||
|  |     Check if the template given by `str_arg_typename` is a scoped template e.g. T::Value, | ||
|  |     and if so, return what template from `template_typenames` and | ||
|  |     the corresponding index matches the scoped template correctly. | ||
|  |     """
 | ||
|  |     for idx, template in enumerate(template_typenames): | ||
|  |         if "::" in str_arg_typename and \ | ||
|  |             template in str_arg_typename.split("::"): | ||
|  |             return template, idx | ||
|  |     return False, -1 | ||
|  | 
 | ||
|  | 
 | ||
|  | def instantiate_type( | ||
|  |         ctype: parser.Type, | ||
|  |         template_typenames: Sequence[str], | ||
|  |         instantiations: Sequence[parser.Typename], | ||
|  |         cpp_typename: parser.Typename, | ||
|  |         instantiated_class: 'InstantiatedClass' = None) -> parser.Type: | ||
|  |     """
 | ||
|  |     Instantiate template typename for `ctype`. | ||
|  | 
 | ||
|  |     Args: | ||
|  |         ctype: The original argument type. | ||
|  |         template_typenames: List of strings representing the templates. | ||
|  |         instantiations: List of the instantiations of the templates in `template_typenames`. | ||
|  |         cpp_typename: Full-namespace cpp class name of this instantiation | ||
|  |             to replace for arguments of type named `This`. | ||
|  |         instiated_class: The instantiated class which, if provided, | ||
|  |             will be used for instantiating `This`. | ||
|  | 
 | ||
|  |     Returns: | ||
|  |         If `ctype`'s name is in the `template_typenames`, return the | ||
|  |         corresponding type to replace in `instantiations`. | ||
|  |         If ctype name is `This`, return the new typename `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 as the typename's name | ||
|  |     if ctype.typename.instantiations: | ||
|  |         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].name =\ | ||
|  |                     instantiations[template_idx] | ||
|  | 
 | ||
|  | 
 | ||
|  |     str_arg_typename = str(ctype.typename) | ||
|  | 
 | ||
|  |     # Check if template is a scoped template e.g. T::Value where T is the template | ||
|  |     scoped_template, scoped_idx = is_scoped_template(template_typenames, | ||
|  |                                                      str_arg_typename) | ||
|  | 
 | ||
|  |     # Instantiate templates which have enumerated instantiations in the template. | ||
|  |     # E.g. `template<T={double}>`. | ||
|  | 
 | ||
|  |     # Instantiate scoped templates, e.g. T::Value. | ||
|  |     if scoped_template: | ||
|  |         # Create a copy of the instantiation so we can modify it. | ||
|  |         instantiation = deepcopy(instantiations[scoped_idx]) | ||
|  |         # Replace the part of the template with the instantiation | ||
|  |         instantiation.name = str_arg_typename.replace(scoped_template, | ||
|  |                                                       instantiation.name) | ||
|  |         return parser.Type( | ||
|  |             typename=instantiation, | ||
|  |             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, | ||
|  |         ) | ||
|  |     # Check for exact template match. | ||
|  |     elif 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, | ||
|  |         ) | ||
|  | 
 | ||
|  |     # If a method has the keyword `This`, we replace it with the (instantiated) class. | ||
|  |     elif str_arg_typename == 'This': | ||
|  |         # Check if the class is template instantiated | ||
|  |         # so we can replace it with the instantiated version. | ||
|  |         if instantiated_class: | ||
|  |             name = instantiated_class.original.name | ||
|  |             namespaces_name = instantiated_class.namespaces() | ||
|  |             namespaces_name.append(name) | ||
|  |             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, | ||
|  |         ) | ||
|  | 
 | ||
|  |     # Case when 'This' is present in the type namespace, e.g `This::Subclass`. | ||
|  |     elif 'This' in str_arg_typename: | ||
|  |         # Check if `This` is in the namespaces | ||
|  |         if 'This' in ctype.typename.namespaces: | ||
|  |             # Simply get the index of `This` in the namespace and | ||
|  |             # replace it with the instantiated name. | ||
|  |             namespace_idx = ctype.typename.namespaces.index('This') | ||
|  |             ctype.typename.namespaces[namespace_idx] = cpp_typename.name | ||
|  |         # Else check if it is in the template namespace, e.g vector<This::Value> | ||
|  |         else: | ||
|  |             for idx, instantiation in enumerate(ctype.typename.instantiations): | ||
|  |                 if 'This' in instantiation.namespaces: | ||
|  |                     ctype.typename.instantiations[idx].namespaces = \ | ||
|  |                         cpp_typename.namespaces + [cpp_typename.name] | ||
|  |         return ctype | ||
|  | 
 | ||
|  |     else: | ||
|  |         return ctype | ||
|  | 
 | ||
|  | 
 | ||
|  | def instantiate_args_list( | ||
|  |         args_list: Sequence[parser.Argument], | ||
|  |         template_typenames: Sequence[parser.template.Typename], | ||
|  |         instantiations: Sequence, cpp_typename: parser.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) | ||
|  |         instantiated_args.append( | ||
|  |             parser.Argument(name=arg.name, ctype=new_type, | ||
|  |                             default=arg.default)) | ||
|  |     return instantiated_args | ||
|  | 
 | ||
|  | 
 | ||
|  | def instantiate_return_type( | ||
|  |         return_type: parser.ReturnType, | ||
|  |         template_typenames: Sequence[parser.template.Typename], | ||
|  |         instantiations: Sequence[parser.Typename], | ||
|  |         cpp_typename: parser.Typename, | ||
|  |         instantiated_class: 'InstantiatedClass' = 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: str, | ||
|  |                      instantiations: Sequence[parser.Typename]): | ||
|  |     """
 | ||
|  |     Concatenate instantiated types with `original_name` to form a new | ||
|  |     instantiated name. | ||
|  | 
 | ||
|  |     NOTE: To avoid conflicts, we should include the instantiation's | ||
|  |     namespaces, but that is too verbose. | ||
|  |     """
 | ||
|  |     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 InstantiationHelper: | ||
|  |     """
 | ||
|  |     Helper class for instantiation templates. | ||
|  |     Requires that `instantiation_type` defines a class method called | ||
|  |     `construct` to generate the appropriate object type. | ||
|  | 
 | ||
|  |     Signature for `construct` should be | ||
|  |     ``` | ||
|  |         construct(method, | ||
|  |                   typenames, | ||
|  |                   class_instantiations, | ||
|  |                   method_instantiations, | ||
|  |                   instantiated_args, | ||
|  |                   parent=parent) | ||
|  |     ``` | ||
|  |     """
 | ||
|  |     def __init__(self, instantiation_type: InstantiatedMembers): | ||
|  |         self.instantiation_type = instantiation_type | ||
|  | 
 | ||
|  |     def instantiate(self, instantiated_methods: List[InstantiatedMembers], | ||
|  |                     method: ClassMembers, typenames: Sequence[str], | ||
|  |                     class_instantiations: Sequence[parser.Typename], | ||
|  |                     method_instantiations: Sequence[parser.Typename], | ||
|  |                     parent: 'InstantiatedClass'): | ||
|  |         """
 | ||
|  |         Instantiate both the class and method level templates. | ||
|  |         """
 | ||
|  |         instantiations = class_instantiations + method_instantiations | ||
|  | 
 | ||
|  |         instantiated_args = instantiate_args_list(method.args.list(), | ||
|  |                                                   typenames, instantiations, | ||
|  |                                                   parent.cpp_typename()) | ||
|  | 
 | ||
|  |         instantiated_methods.append( | ||
|  |             self.instantiation_type.construct(method, | ||
|  |                                               typenames, | ||
|  |                                               class_instantiations, | ||
|  |                                               method_instantiations, | ||
|  |                                               instantiated_args, | ||
|  |                                               parent=parent)) | ||
|  | 
 | ||
|  |         return instantiated_methods | ||
|  | 
 | ||
|  |     def multilevel_instantiation(self, methods_list: Sequence[ClassMembers], | ||
|  |                                  typenames: Sequence[str], | ||
|  |                                  parent: 'InstantiatedClass'): | ||
|  |         """
 | ||
|  |         Helper to instantiate methods at both the class and method level. | ||
|  | 
 | ||
|  |         Args: | ||
|  |             methods_list: The list of methods in the class to instantiated. | ||
|  |             typenames: List of class level template parameters, e.g. ['T']. | ||
|  |             parent: The instantiated class to which `methods_list` belongs. | ||
|  |         """
 | ||
|  |         instantiated_methods = [] | ||
|  | 
 | ||
|  |         for method in methods_list: | ||
|  |             # We creare a copy since we will modify the typenames list. | ||
|  |             method_typenames = deepcopy(typenames) | ||
|  | 
 | ||
|  |             if isinstance(method.template, parser.template.Template): | ||
|  |                 method_typenames.extend(method.template.typenames) | ||
|  | 
 | ||
|  |                 # Get all combinations of template args | ||
|  |                 for instantiations in itertools.product( | ||
|  |                         *method.template.instantiations): | ||
|  | 
 | ||
|  |                     instantiated_methods = self.instantiate( | ||
|  |                         instantiated_methods, | ||
|  |                         method, | ||
|  |                         typenames=method_typenames, | ||
|  |                         class_instantiations=parent.instantiations, | ||
|  |                         method_instantiations=list(instantiations), | ||
|  |                         parent=parent) | ||
|  | 
 | ||
|  |             else: | ||
|  |                 # If no constructor level templates, just use the class templates | ||
|  |                 instantiated_methods = self.instantiate( | ||
|  |                     instantiated_methods, | ||
|  |                     method, | ||
|  |                     typenames=method_typenames, | ||
|  |                     class_instantiations=parent.instantiations, | ||
|  |                     method_instantiations=[], | ||
|  |                     parent=parent) | ||
|  | 
 | ||
|  |         return instantiated_methods |