545 lines
21 KiB
Python
545 lines
21 KiB
Python
"""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_basis=ctype.is_basis,
|
|
)
|
|
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_basis=ctype.is_basis,
|
|
)
|
|
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)
|
|
instantiated_args.append(parser.Argument(name=arg.name,
|
|
ctype=new_type))
|
|
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 = {double}>
|
|
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, instantiations=(), new_name=''):
|
|
"""
|
|
Template <T, U>
|
|
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
|
|
# and instance methods, respectively.
|
|
self.ctors = self.instantiate_ctors(typenames)
|
|
self.static_methods = self.instantiate_static_methods(typenames)
|
|
self.properties = self.instantiate_properties(typenames)
|
|
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,
|
|
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([ctor.__repr__() for ctor in self.ctors]),
|
|
methods="\n".join([m.__repr__() for m in self.methods]),
|
|
static_methods="\n".join([m.__repr__()
|
|
for m in self.static_methods]),
|
|
)
|
|
|
|
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<T={string}>
|
|
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_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
|