template_instantiator.py
Go to the documentation of this file.
1 """Code to help instantiate templated classes, methods and functions."""
2 
3 # pylint: disable=too-many-arguments, too-many-instance-attributes, no-self-use, no-else-return, too-many-arguments, unused-format-string-argument, unused-variable
4 
5 import itertools
6 from copy import deepcopy
7 from typing import List
8 
9 import gtwrap.interface_parser as parser
10 
11 
12 def instantiate_type(ctype: parser.Type,
13  template_typenames: List[str],
14  instantiations: List[parser.Typename],
15  cpp_typename: parser.Typename,
16  instantiated_class=None):
17  """
18  Instantiate template typename for @p ctype.
19 
20  Args:
21  instiated_class (InstantiatedClass):
22 
23  @return If ctype's name is in the @p template_typenames, return the
24  corresponding type to replace in @p instantiations.
25  If ctype name is `This`, return the new typename @p `cpp_typename`.
26  Otherwise, return the original ctype.
27  """
28  # make a deep copy so that there is no overwriting of original template params
29  ctype = deepcopy(ctype)
30 
31  # Check if the return type has template parameters
32  if len(ctype.typename.instantiations) > 0:
33  for idx, instantiation in enumerate(ctype.typename.instantiations):
34  if instantiation.name in template_typenames:
35  template_idx = template_typenames.index(instantiation.name)
36  ctype.typename.instantiations[idx] = instantiations[
37  template_idx]
38 
39  return ctype
40 
41  str_arg_typename = str(ctype.typename)
42 
43  if str_arg_typename in template_typenames:
44  idx = template_typenames.index(str_arg_typename)
45  return parser.Type(
46  typename=instantiations[idx],
47  is_const=ctype.is_const,
48  is_shared_ptr=ctype.is_shared_ptr,
49  is_ptr=ctype.is_ptr,
50  is_ref=ctype.is_ref,
51  is_basic=ctype.is_basic,
52  )
53  elif str_arg_typename == 'This':
54  if instantiated_class:
55  name = instantiated_class.original.name
56  namespaces_name = instantiated_class.namespaces()
57  namespaces_name.append(name)
58  # print("INST: {}, {}, CPP: {}, CLS: {}".format(
59  # ctype, instantiations, cpp_typename, instantiated_class.instantiations
60  # ), file=sys.stderr)
61  cpp_typename = parser.Typename(
62  namespaces_name,
63  instantiations=instantiated_class.instantiations)
64 
65  return parser.Type(
66  typename=cpp_typename,
67  is_const=ctype.is_const,
68  is_shared_ptr=ctype.is_shared_ptr,
69  is_ptr=ctype.is_ptr,
70  is_ref=ctype.is_ref,
71  is_basic=ctype.is_basic,
72  )
73  else:
74  return ctype
75 
76 
77 def instantiate_args_list(args_list, template_typenames, instantiations,
78  cpp_typename):
79  """
80  Instantiate template typenames in an argument list.
81  Type with name `This` will be replaced by @p `cpp_typename`.
82 
83  @param[in] args_list A list of `parser.Argument` to instantiate.
84  @param[in] template_typenames List of template typenames to instantiate,
85  e.g. ['T', 'U', 'V'].
86  @param[in] instantiations List of specific types to instantiate, each
87  associated with each template typename. Each type is a parser.Typename,
88  including its name and full namespaces.
89  @param[in] cpp_typename Full-namespace cpp class name of this instantiation
90  to replace for arguments of type named `This`.
91  @return A new list of parser.Argument which types are replaced with their
92  instantiations.
93  """
94  instantiated_args = []
95  for arg in args_list:
96  new_type = instantiate_type(arg.ctype, template_typenames,
97  instantiations, cpp_typename)
98  default = [arg.default] if isinstance(arg, parser.Argument) else ''
99  instantiated_args.append(parser.Argument(name=arg.name,
100  ctype=new_type,
101  default=default))
102  return instantiated_args
103 
104 
105 def instantiate_return_type(return_type,
106  template_typenames,
107  instantiations,
108  cpp_typename,
109  instantiated_class=None):
110  """Instantiate the return type."""
111  new_type1 = instantiate_type(return_type.type1,
112  template_typenames,
113  instantiations,
114  cpp_typename,
115  instantiated_class=instantiated_class)
116  if return_type.type2:
117  new_type2 = instantiate_type(return_type.type2,
118  template_typenames,
119  instantiations,
120  cpp_typename,
121  instantiated_class=instantiated_class)
122  else:
123  new_type2 = ''
124  return parser.ReturnType(new_type1, new_type2)
125 
126 
127 def instantiate_name(original_name, instantiations):
128  """
129  Concatenate instantiated types with an @p original name to form a new
130  instantiated name.
131  TODO(duy): To avoid conflicts, we should include the instantiation's
132  namespaces, but I find that too verbose.
133  """
134  inst_name = ''
135  instantiated_names = []
136  for inst in instantiations:
137  # Ensure the first character of the type is capitalized
138  name = inst.instantiated_name()
139  # Using `capitalize` on the complete name causes other caps to be lower case
140  instantiated_names.append(name.replace(name[0], name[0].capitalize()))
141 
142  return "{}{}".format(original_name, "".join(instantiated_names))
143 
144 
145 class InstantiatedGlobalFunction(parser.GlobalFunction):
146  """
147  Instantiate global functions.
148 
149  E.g.
150  template<T = {double}>
151  T add(const T& x, const T& y);
152  """
153  def __init__(self, original, instantiations=(), new_name=''):
154  self.original = original
155  self.instantiations = instantiations
156  self.template = ''
157  self.parent = original.parent
158 
159  if not original.template:
160  self.name = original.name
161  self.return_type = original.return_type
162  self.args = original.args
163  else:
164  self.name = instantiate_name(
165  original.name, instantiations) if not new_name else new_name
166  self.return_type = instantiate_return_type(
167  original.return_type,
168  self.original.template.typenames,
169  self.instantiations,
170  # Keyword type name `This` should already be replaced in the
171  # previous class template instantiation round.
172  cpp_typename='',
173  )
174  instantiated_args = instantiate_args_list(
175  original.args.args_list,
176  self.original.template.typenames,
177  self.instantiations,
178  # Keyword type name `This` should already be replaced in the
179  # previous class template instantiation round.
180  cpp_typename='',
181  )
182  self.args = parser.ArgumentList(instantiated_args)
183 
184  super().__init__(self.name,
185  self.return_type,
186  self.args,
187  self.template,
188  parent=self.parent)
189 
190  def to_cpp(self):
191  """Generate the C++ code for wrapping."""
192  if self.original.template:
193  instantiated_names = [
194  inst.instantiated_name() for inst in self.instantiations
195  ]
196  ret = "{}<{}>".format(self.original.name,
197  ",".join(instantiated_names))
198  else:
199  ret = self.original.name
200  return ret
201 
202  def __repr__(self):
203  return "Instantiated {}".format(
204  super(InstantiatedGlobalFunction, self).__repr__())
205 
206 
207 class InstantiatedMethod(parser.Method):
208  """
209  We can only instantiate template methods with a single template parameter.
210  """
211  def __init__(self, original, instantiations: List[parser.Typename] = ''):
212  self.original = original
213  self.instantiations = instantiations
214  self.template = ''
215  self.is_const = original.is_const
216  self.parent = original.parent
217 
218  # Check for typenames if templated.
219  # This way, we can gracefully handle bot templated and non-templated methois.
220  typenames = self.original.template.typenames if self.original.template else []
221  self.name = instantiate_name(original.name, self.instantiations)
222  self.return_type = instantiate_return_type(
223  original.return_type,
224  typenames,
225  self.instantiations,
226  # Keyword type name `This` should already be replaced in the
227  # previous class template instantiation round.
228  cpp_typename='',
229  )
230 
231  instantiated_args = instantiate_args_list(
232  original.args.args_list,
233  typenames,
234  self.instantiations,
235  # Keyword type name `This` should already be replaced in the
236  # previous class template instantiation round.
237  cpp_typename='',
238  )
239  self.args = parser.ArgumentList(instantiated_args)
240 
241  super().__init__(self.template,
242  self.name,
243  self.return_type,
244  self.args,
245  self.is_const,
246  parent=self.parent)
247 
248  def to_cpp(self):
249  """Generate the C++ code for wrapping."""
250  if self.original.template:
251  # to_cpp will handle all the namespacing and templating
252  instantiation_list = [x.to_cpp() for x in self.instantiations]
253  # now can simply combine the instantiations, separated by commas
254  ret = "{}<{}>".format(self.original.name,
255  ",".join(instantiation_list))
256  else:
257  ret = self.original.name
258  return ret
259 
260  def __repr__(self):
261  return "Instantiated {}".format(
262  super(InstantiatedMethod, self).__repr__())
263 
264 
265 class InstantiatedClass(parser.Class):
266  """
267  Instantiate the class defined in the interface file.
268  """
269  def __init__(self, original: parser.Class, instantiations=(), new_name=''):
270  """
271  Template <T, U>
272  Instantiations: [T1, U1]
273  """
274  self.original = original
275  self.instantiations = instantiations
276 
277  self.template = ''
278  self.is_virtual = original.is_virtual
279  self.parent_class = original.parent_class
280  self.parent = original.parent
281 
282  # If the class is templated, check if the number of provided instantiations
283  # match the number of templates, else it's only a partial instantiation which is bad.
284  if original.template:
285  assert len(original.template.typenames) == len(
286  instantiations), "Typenames and instantiations mismatch!"
287 
288  # Get the instantiated name of the class. E.g. FuncDouble
289  self.name = instantiate_name(
290  original.name, instantiations) if not new_name else new_name
291 
292  # Check for typenames if templated.
293  # By passing in typenames, we can gracefully handle both templated and non-templated classes
294  # This will allow the `This` keyword to be used in both templated and non-templated classes.
295  typenames = self.original.template.typenames if self.original.template else []
296 
297  # Instantiate the constructors, static methods, properties, respectively.
298  self.ctors = self.instantiate_ctors(typenames)
299  self.static_methods = self.instantiate_static_methods(typenames)
300  self.properties = self.instantiate_properties(typenames)
301 
302  # Instantiate all operator overloads
303  self.operators = self.instantiate_operators(typenames)
304 
305  # Set enums
306  self.enums = original.enums
307 
308  # Instantiate all instance methods
309  instantiated_methods = \
310  self.instantiate_class_templates_in_methods(typenames)
311 
312  # Second instantiation round to instantiate templated methods.
313  # This is done in case both the class and the method are templated.
314  self.methods = []
315  for method in instantiated_methods:
316  if not method.template:
317  self.methods.append(InstantiatedMethod(method, ''))
318  else:
319  instantiations = []
320  # Get all combinations of template parameters
321  for instantiations in itertools.product(
322  *method.template.instantiations):
323  self.methods.append(
324  InstantiatedMethod(method, instantiations))
325 
326  super().__init__(
327  self.template,
328  self.is_virtual,
329  self.name,
330  [self.parent_class],
331  self.ctors,
332  self.methods,
333  self.static_methods,
334  self.properties,
335  self.operators,
336  self.enums,
337  parent=self.parent,
338  )
339 
340  def __repr__(self):
341  return "{virtual} class {name} [{cpp_class}]: {parent_class}\n"\
342  "{ctors}\n{static_methods}\n{methods}".format(
343  virtual="virtual" if self.is_virtual else '',
344  name=self.name,
345  cpp_class=self.cpp_class(),
346  parent_class=self.parent,
347  ctors="\n".join([repr(ctor) for ctor in self.ctors]),
348  methods="\n".join([repr(m) for m in self.methods]),
349  static_methods="\n".join([repr(m)
350  for m in self.static_methods]),
351  operators="\n".join([repr(op) for op in self.operators])
352  )
353 
354  def instantiate_ctors(self, typenames):
355  """
356  Instantiate the class constructors.
357 
358  Args:
359  typenames: List of template types to instantiate.
360 
361  Return: List of constructors instantiated with provided template args.
362  """
363  instantiated_ctors = []
364 
365  for ctor in self.original.ctors:
366  instantiated_args = instantiate_args_list(
367  ctor.args.args_list,
368  typenames,
369  self.instantiations,
370  self.cpp_typename(),
371  )
372  instantiated_ctors.append(
373  parser.Constructor(
374  name=self.name,
375  args=parser.ArgumentList(instantiated_args),
376  parent=self,
377  ))
378  return instantiated_ctors
379 
380  def instantiate_static_methods(self, typenames):
381  """
382  Instantiate static methods in the class.
383 
384  Args:
385  typenames: List of template types to instantiate.
386 
387  Return: List of static methods instantiated with provided template args.
388  """
389  instantiated_static_methods = []
390  for static_method in self.original.static_methods:
391  instantiated_args = instantiate_args_list(
392  static_method.args.args_list, typenames, self.instantiations,
393  self.cpp_typename())
394  instantiated_static_methods.append(
395  parser.StaticMethod(
396  name=static_method.name,
397  return_type=instantiate_return_type(
398  static_method.return_type,
399  typenames,
400  self.instantiations,
401  self.cpp_typename(),
402  instantiated_class=self),
403  args=parser.ArgumentList(instantiated_args),
404  parent=self,
405  ))
406  return instantiated_static_methods
407 
408  def instantiate_class_templates_in_methods(self, typenames):
409  """
410  This function only instantiates the class-level templates in the methods.
411  Template methods are instantiated in InstantiatedMethod in the second
412  round.
413 
414  E.g.
415  ```
416  template<T={string}>
417  class Greeter{
418  void sayHello(T& name);
419  };
420 
421  Args:
422  typenames: List of template types to instantiate.
423 
424  Return: List of methods instantiated with provided template args on the class.
425  """
426  class_instantiated_methods = []
427  for method in self.original.methods:
428  instantiated_args = instantiate_args_list(
429  method.args.args_list,
430  typenames,
431  self.instantiations,
432  self.cpp_typename(),
433  )
434  class_instantiated_methods.append(
435  parser.Method(
436  template=method.template,
437  name=method.name,
438  return_type=instantiate_return_type(
439  method.return_type,
440  typenames,
441  self.instantiations,
442  self.cpp_typename(),
443  ),
444  args=parser.ArgumentList(instantiated_args),
445  is_const=method.is_const,
446  parent=self,
447  ))
448  return class_instantiated_methods
449 
450  def instantiate_operators(self, typenames):
451  """
452  Instantiate the class-level template in the operator overload.
453 
454  Args:
455  typenames: List of template types to instantiate.
456 
457  Return: List of methods instantiated with provided template args on the class.
458  """
459  instantiated_operators = []
460  for operator in self.original.operators:
461  instantiated_args = instantiate_args_list(
462  operator.args.args_list,
463  typenames,
464  self.instantiations,
465  self.cpp_typename(),
466  )
467  instantiated_operators.append(
468  parser.Operator(
469  name=operator.name,
470  operator=operator.operator,
471  return_type=instantiate_return_type(
472  operator.return_type,
473  typenames,
474  self.instantiations,
475  self.cpp_typename(),
476  ),
477  args=parser.ArgumentList(instantiated_args),
478  is_const=operator.is_const,
479  parent=self,
480  ))
481  return instantiated_operators
482 
483  def instantiate_properties(self, typenames):
484  """
485  Instantiate the class properties.
486 
487  Args:
488  typenames: List of template types to instantiate.
489 
490  Return: List of properties instantiated with provided template args.
491  """
492  instantiated_properties = instantiate_args_list(
493  self.original.properties,
494  typenames,
495  self.instantiations,
496  self.cpp_typename(),
497  )
498  return instantiated_properties
499 
500  def cpp_class(self):
501  """Generate the C++ code for wrapping."""
502  return self.cpp_typename().to_cpp()
503 
504  def cpp_typename(self):
505  """
506  Return a parser.Typename including namespaces and cpp name of this
507  class.
508  """
509  if self.original.template:
510  name = "{}<{}>".format(
511  self.original.name,
512  ", ".join([inst.to_cpp() for inst in self.instantiations]))
513  else:
514  name = self.original.name
515  namespaces_name = self.namespaces()
516  namespaces_name.append(name)
517  return parser.Typename(namespaces_name)
518 
519 
520 def instantiate_namespace_inplace(namespace):
521  """
522  Instantiate the classes and other elements in the `namespace` content and
523  assign it back to the namespace content attribute.
524 
525  @param[in/out] namespace The namespace whose content will be replaced with
526  the instantiated content.
527  """
528  instantiated_content = []
529  typedef_content = []
530 
531  for element in namespace.content:
532  if isinstance(element, parser.Class):
533  original_class = element
534  if not original_class.template:
535  instantiated_content.append(
536  InstantiatedClass(original_class, []))
537  else:
538  # This case is for when the templates have enumerated instantiations.
539 
540  # Use itertools to get all possible combinations of instantiations
541  # Works even if one template does not have an instantiation list
542  for instantiations in itertools.product(
543  *original_class.template.instantiations):
544  instantiated_content.append(
545  InstantiatedClass(original_class,
546  list(instantiations)))
547 
548  elif isinstance(element, parser.GlobalFunction):
549  original_func = element
550  if not original_func.template:
551  instantiated_content.append(
552  InstantiatedGlobalFunction(original_func, []))
553  else:
554  # Use itertools to get all possible combinations of instantiations
555  # Works even if one template does not have an instantiation list
556  for instantiations in itertools.product(
557  *original_func.template.instantiations):
558  instantiated_content.append(
559  InstantiatedGlobalFunction(original_func,
560  list(instantiations)))
561 
562  elif isinstance(element, parser.TypedefTemplateInstantiation):
563  # This is for the case where `typedef` statements are used
564  # to specify the template parameters.
565  typedef_inst = element
566  top_level = namespace.top_level()
567  original_element = top_level.find_class_or_function(
568  typedef_inst.typename)
569 
570  # Check if element is a typedef'd class or function.
571  if isinstance(original_element, parser.Class):
572  typedef_content.append(
573  InstantiatedClass(original_element,
574  typedef_inst.typename.instantiations,
575  typedef_inst.new_name))
576  elif isinstance(original_element, parser.GlobalFunction):
577  typedef_content.append(
578  InstantiatedGlobalFunction(
579  original_element, typedef_inst.typename.instantiations,
580  typedef_inst.new_name))
581 
582  elif isinstance(element, parser.Namespace):
583  instantiate_namespace_inplace(element)
584  instantiated_content.append(element)
585  else:
586  instantiated_content.append(element)
587 
588  instantiated_content.extend(typedef_content)
589  namespace.content = instantiated_content
Definition: pytypes.h:928
def instantiate_args_list(args_list, template_typenames, instantiations, cpp_typename)
size_t len(handle h)
Definition: pytypes.h:1514


gtsam
Author(s):
autogenerated on Sat May 8 2021 02:45:07