wrapper.py
Go to the documentation of this file.
1 """
2 Code to use the parsed results and convert it to a format
3 that Matlab's MEX compiler can use.
4 """
5 
6 # pylint: disable=too-many-lines, no-self-use, too-many-arguments, too-many-branches, too-many-statements, consider-using-f-string, unspecified-encoding
7 
8 import copy
9 import os
10 import os.path as osp
11 import textwrap
12 from functools import partial, reduce
13 from typing import Dict, Iterable, List, Union
14 
15 import gtwrap.interface_parser as parser
16 import gtwrap.template_instantiator as instantiator
17 from gtwrap.interface_parser.function import ArgumentList
18 from gtwrap.matlab_wrapper.mixins import CheckMixin, FormatMixin
19 from gtwrap.matlab_wrapper.templates import WrapperTemplate
20 from gtwrap.template_instantiator.classes import InstantiatedClass
21 
22 
24  """ Wrap the given C++ code into Matlab.
25 
26  Attributes
27  module: the C++ module being wrapped
28  module_name: name of the C++ module being wrapped
29  top_module_namespace: C++ namespace for the top module (default '')
30  ignore_classes: A list of classes to ignore (default [])
31  """
32 
33  def __init__(self,
34  module_name,
35  top_module_namespace='',
36  ignore_classes=(),
37  use_boost_serialization=False):
38  super().__init__()
39 
40  self.module_name = module_name
41  self.top_module_namespace = top_module_namespace
42  self.ignore_classes = ignore_classes
43  self.verbose = False
44  self.use_boost_serialization = use_boost_serialization
45 
46  # Map the data type to its Matlab class.
47  # Found in Argument.cpp in old wrapper
48  self.data_type = {
49  'string': 'char',
50  'char': 'char',
51  'unsigned char': 'unsigned char',
52  'Vector': 'double',
53  'Matrix': 'double',
54  'int': 'numeric',
55  'size_t': 'numeric',
56  'bool': 'logical'
57  }
58  # Map the data type into the type used in Matlab methods.
59  # Found in matlab.h in old wrapper
60  self.data_type_param = {
61  'string': 'char',
62  'char': 'char',
63  'unsigned char': 'unsigned char',
64  'size_t': 'int',
65  'int': 'int',
66  'double': 'double',
67  'Point2': 'double',
68  'Point3': 'double',
69  'Vector': 'double',
70  'Matrix': 'double',
71  'bool': 'bool'
72  }
73  # The amount of times the wrapper has created a call to geometry_wrapper
74  self.wrapper_id = 0
75  # Map each wrapper id to its collector function namespace, class, type, and string format
76  self.wrapper_map: Dict = {}
77  # Set of all the includes in the namespace
78  self.includes: List[parser.Include] = []
79  # Set of all classes in the namespace
80  self.classes: List[Union[parser.Class,
81  instantiator.InstantiatedClass]] = []
82  self.classes_elems: Dict[Union[parser.Class,
83  instantiator.InstantiatedClass],
84  int] = {}
85  # Id for ordering global functions in the wrapper
87  # Files and their content
88  self.content: List[str] = []
89 
90  # Ensure the template file is always picked up from the correct directory.
91  dir_path = osp.dirname(osp.realpath(__file__))
92  with open(osp.join(dir_path, "matlab_wrapper.tpl")) as f:
93  self.wrapper_file_headers = f.read()
94 
95  def add_class(self, instantiated_class):
96  """Add `instantiated_class` to the list of classes."""
97  if self.classes_elems.get(instantiated_class) is None:
98  self.classes_elems[instantiated_class] = 0
99  self.classes.append(instantiated_class)
100 
101  def _update_wrapper_id(self,
102  collector_function=None,
103  id_diff=0,
104  function_name: str = None):
105  """
106  Get and define wrapper ids.
107  Generates the map of id -> collector function.
108 
109  Args:
110  collector_function: tuple storing info about the wrapper function
111  (namespace, class instance, function name, function object)
112  id_diff: constant to add to the id in the map
113  function_name: Optional custom function_name.
114 
115  Returns:
116  the current wrapper id
117  """
118  if collector_function is not None:
119  is_instantiated_class = isinstance(collector_function[1],
120  instantiator.InstantiatedClass)
121 
122  if function_name is None:
123  if is_instantiated_class:
124  function_name = collector_function[0] + \
125  collector_function[1].name + '_' + collector_function[2]
126  else:
127  function_name = collector_function[1].name
128 
129  self.wrapper_map[self.wrapper_id] = (
130  collector_function[0], collector_function[1],
131  collector_function[2], function_name + '_' +
132  str(self.wrapper_id + id_diff), collector_function[3])
133 
134  self.wrapper_id += 1
135 
136  return self.wrapper_id - 1
137 
138  def _qualified_name(self, names):
139  return 'handle' if names == '' else names
140 
141  def _insert_spaces(self, x, y):
142  """Insert spaces at the beginning of each line
143 
144  Args:
145  x: the statement currently generated
146  y: the addition to add to the statement
147  """
148  return x + '\n' + ('' if y == '' else ' ') + y
149 
150  @staticmethod
151  def _expand_default_arguments(method, save_backup=True):
152  """Recursively expand all possibilities for optional default arguments.
153  We create "overload" functions with fewer arguments, but since we have to "remember" what
154  the default arguments are for later, we make a backup.
155  """
156 
157  def args_copy(args):
158  return ArgumentList([copy.copy(arg) for arg in args.list()])
159 
160  def method_copy(method):
161  method2 = copy.copy(method)
162  method2.args = args_copy(method.args)
163  method2.args.backup = method.args.backup
164  return method2
165 
166  if save_backup:
167  method.args.backup = args_copy(method.args)
168  method = method_copy(method)
169  for arg in reversed(method.args.list()):
170  if arg.default is not None:
171  arg.default = None
172  methodWithArg = method_copy(method)
173  method.args.list().remove(arg)
174  return [
175  methodWithArg,
176  *MatlabWrapper._expand_default_arguments(method,
177  save_backup=False)
178  ]
179  break
180  assert all(arg.default is None for arg in method.args.list()), \
181  'In parsing method {:}: Arguments with default values cannot appear before ones ' \
182  'without default values.'.format(method.name)
183  return [method]
184 
185  def _group_methods(self, methods):
186  """Group overloaded methods together"""
187  method_map = {}
188  method_out = []
189 
190  for method in methods:
191  method_index = method_map.get(method.name)
192 
193  if method_index is None:
194  method_map[method.name] = len(method_out)
195  method_out.append(
196  MatlabWrapper._expand_default_arguments(method))
197  else:
198  method_out[
199  method_index] += MatlabWrapper._expand_default_arguments(
200  method)
201 
202  return method_out
203 
204  def _wrap_args(self, args):
205  """Wrap an interface_parser.ArgumentList into a list of arguments.
206 
207  Returns:
208  A string representation of the arguments. For example:
209  'int x, double y'
210  """
211  arg_wrap = ''
212 
213  for i, arg in enumerate(args.list(), 1):
214  c_type = self._format_type_name(arg.ctype.typename,
215  include_namespace=False)
216 
217  arg_wrap += '{c_type} {arg_name}{comma}'.format(
218  c_type=c_type,
219  arg_name=arg.name,
220  comma='' if i == len(args.list()) else ', ')
221 
222  return arg_wrap
223 
224  def _wrap_variable_arguments(self, args, wrap_datatypes=True):
225  """ Wrap an interface_parser.ArgumentList into a statement of argument
226  checks.
227 
228  Returns:
229  A string representation of a variable arguments for an if
230  statement. For example:
231  ' && isa(varargin{1},'double') && isa(varargin{2},'numeric')'
232  """
233  var_arg_wrap = ''
234 
235  for i, arg in enumerate(args.list(), 1):
236  name = arg.ctype.typename.name
237  if name in self.not_check_type:
238  continue
239 
240  check_type = self.data_type_param.get(name)
241 
242  if self.data_type.get(check_type):
243  check_type = self.data_type[check_type]
244 
245  if check_type is None:
246  check_type = self._format_type_name(
247  arg.ctype.typename,
248  separator='.',
249  is_constructor=not wrap_datatypes)
250 
251  var_arg_wrap += " && isa(varargin{{{num}}},'{data_type}')".format(
252  num=i, data_type=check_type)
253  if name == 'Vector':
254  var_arg_wrap += ' && size(varargin{{{num}}},2)==1'.format(
255  num=i)
256  if name == 'Point2':
257  var_arg_wrap += ' && size(varargin{{{num}}},1)==2'.format(
258  num=i)
259  var_arg_wrap += ' && size(varargin{{{num}}},2)==1'.format(
260  num=i)
261  if name == 'Point3':
262  var_arg_wrap += ' && size(varargin{{{num}}},1)==3'.format(
263  num=i)
264  var_arg_wrap += ' && size(varargin{{{num}}},2)==1'.format(
265  num=i)
266 
267  return var_arg_wrap
268 
270  """ Wrap an interface_parser.ArgumentList into a list of argument
271  variables.
272 
273  Returns:
274  A string representation of a list of variable arguments.
275  For example:
276  'varargin{1}, varargin{2}, varargin{3}'
277  """
278  var_list_wrap = ''
279  first = True
280 
281  for i in range(1, len(args.list()) + 1):
282  if first:
283  var_list_wrap += 'varargin{{{}}}'.format(i)
284  first = False
285  else:
286  var_list_wrap += ', varargin{{{}}}'.format(i)
287 
288  return var_list_wrap
289 
290  def _wrap_method_check_statement(self, args: parser.ArgumentList):
291  """
292  Wrap the given arguments into either just a varargout call or a
293  call in an if statement that checks if the parameters are accurate.
294 
295  TODO Update this method so that default arguments are supported.
296  """
297  arg_id = 1
298 
299  param_count = len(args)
300  check_statement = 'if length(varargin) == {param_count}'.format(
301  param_count=param_count)
302 
303  for _, arg in enumerate(args.list()):
304  name = arg.ctype.typename.name
305 
306  if name in self.not_check_type:
307  arg_id += 1
308  continue
309 
310  check_type = self.data_type_param.get(name)
311 
312  if self.data_type.get(check_type):
313  check_type = self.data_type[check_type]
314 
315  if check_type is None:
316  check_type = self._format_type_name(arg.ctype.typename,
317  separator='.')
318 
319  check_statement += " && isa(varargin{{{id}}},'{ctype}')".format(
320  id=arg_id, ctype=check_type)
321 
322  if name == 'Vector':
323  check_statement += ' && size(varargin{{{num}}},2)==1'.format(
324  num=arg_id)
325  if name == 'Point2':
326  check_statement += ' && size(varargin{{{num}}},1)==2'.format(
327  num=arg_id)
328  check_statement += ' && size(varargin{{{num}}},2)==1'.format(
329  num=arg_id)
330  if name == 'Point3':
331  check_statement += ' && size(varargin{{{num}}},1)==3'.format(
332  num=arg_id)
333  check_statement += ' && size(varargin{{{num}}},2)==1'.format(
334  num=arg_id)
335 
336  arg_id += 1
337 
338  check_statement = check_statement \
339  if check_statement == '' \
340  else check_statement + '\n'
341 
342  return check_statement
343 
344  def _unwrap_argument(self, arg, arg_id=0, instantiated_class=None):
345  ctype_camel = self._format_type_name(arg.ctype.typename, separator='')
346  ctype_sep = self._format_type_name(arg.ctype.typename)
347 
348  if instantiated_class and \
349  self.is_enum(arg.ctype, instantiated_class):
350  enum_type = f"{arg.ctype.typename}"
351  arg_type = f"{enum_type}"
352  unwrap = f'unwrap_enum<{enum_type}>(in[{arg_id}]);'
353 
354  elif self.is_ref(arg.ctype): # and not constructor:
355  arg_type = "{ctype}&".format(ctype=ctype_sep)
356  unwrap = '*unwrap_shared_ptr< {ctype} >(in[{id}], "ptr_{ctype_camel}");'.format(
357  ctype=ctype_sep, ctype_camel=ctype_camel, id=arg_id)
358 
359  elif self.is_ptr(arg.ctype) and \
360  arg.ctype.typename.name not in self.ignore_namespace:
361 
362  arg_type = "{ctype_sep}*".format(ctype_sep=ctype_sep)
363  unwrap = 'unwrap_ptr< {ctype_sep} >(in[{id}], "ptr_{ctype}");'.format(
364  ctype_sep=ctype_sep, ctype=ctype_camel, id=arg_id)
365 
366  elif (self.is_shared_ptr(arg.ctype) or self.can_be_pointer(arg.ctype)) and \
367  arg.ctype.typename.name not in self.ignore_namespace:
368 
369  arg_type = "std::shared_ptr<{ctype_sep}>".format(
370  ctype_sep=ctype_sep)
371  unwrap = 'unwrap_shared_ptr< {ctype_sep} >(in[{id}], "ptr_{ctype}");'.format(
372  ctype_sep=ctype_sep, ctype=ctype_camel, id=arg_id)
373 
374  else:
375  arg_type = "{ctype}".format(ctype=arg.ctype.typename.name)
376  unwrap = 'unwrap< {ctype} >(in[{id}]);'.format(
377  ctype=arg.ctype.typename.name, id=arg_id)
378 
379  return arg_type, unwrap
380 
381  def _wrapper_unwrap_arguments(self,
382  args,
383  arg_id=0,
384  instantiated_class=None):
385  """Format the interface_parser.Arguments.
386 
387  Examples:
388  ((a), unsigned char a = unwrap< unsigned char >(in[1]);),
389  ((a), Test& t = *unwrap_shared_ptr< Test >(in[1], "ptr_Test");),
390  ((a), std::shared_ptr<Test> p1 = unwrap_shared_ptr< Test >(in[1], "ptr_Test");)
391  """
392  body_args = ''
393 
394  for arg in args.list():
395  arg_type, unwrap = self._unwrap_argument(
396  arg, arg_id, instantiated_class=instantiated_class)
397 
398  body_args += textwrap.indent(textwrap.dedent('''\
399  {arg_type} {name} = {unwrap}
400  '''.format(arg_type=arg_type, name=arg.name,
401  unwrap=unwrap)),
402  prefix=' ')
403  arg_id += 1
404 
405  params = ''
406  explicit_arg_names = [arg.name for arg in args.list()]
407  # when returning the params list, we need to re-include the default args.
408  for arg in args.backup.list():
409  if params != '':
410  params += ','
411 
412  if (arg.default is not None) and (arg.name
413  not in explicit_arg_names):
414  params += arg.default
415  continue
416 
417  if not self.is_ref(arg.ctype) and (self.is_shared_ptr(arg.ctype) or \
418  self.is_ptr(arg.ctype) or self.can_be_pointer(arg.ctype)) and \
419  not self.is_enum(arg.ctype, instantiated_class) and \
420  arg.ctype.typename.name not in self.ignore_namespace:
421  if arg.ctype.is_shared_ptr:
422  call_type = arg.ctype.is_shared_ptr
423  else:
424  call_type = arg.ctype.is_ptr
425  if call_type == "":
426  params += "*"
427  params += arg.name
428 
429  return params, body_args
430 
431  @staticmethod
432  def _return_count(return_type):
433  """The amount of objects returned by the given
434  interface_parser.ReturnType.
435  """
436  return 1 if return_type.type2 == '' else 2
437 
438  def _wrapper_name(self):
439  """Determine the name of wrapper function."""
440  return self.module_name + '_wrapper'
441 
442  def class_serialize_comment(self, class_name, static_methods):
443  """Generate comments for serialize methods."""
444  comment_wrap = ''
445  static_methods = sorted(static_methods, key=lambda name: name.name)
446 
447  for static_method in static_methods:
448  if comment_wrap == '':
449  comment_wrap = '%-------Static Methods-------\n'
450 
451  comment_wrap += '%{name}({args}) : returns {return_type}\n'.format(
452  name=static_method.name,
453  args=self._wrap_args(static_method.args),
454  return_type=self._format_return_type(static_method.return_type,
455  include_namespace=True))
456 
457  comment_wrap += textwrap.dedent('''\
458  %
459  %-------Serialization Interface-------
460  %string_serialize() : returns string
461  %string_deserialize(string serialized) : returns {class_name}
462  %
463  ''').format(class_name=class_name)
464 
465  return comment_wrap
466 
467  def class_comment(self, instantiated_class):
468  """Generate comments for the given class in Matlab.
469 
470  Args
471  instantiated_class: the class being wrapped
472  ctors: a list of the constructors in the class
473  methods: a list of the methods in the class
474  """
475  class_name = instantiated_class.name
476  ctors = instantiated_class.ctors
477  properties = instantiated_class.properties
478  methods = instantiated_class.methods
479  static_methods = instantiated_class.static_methods
480 
481  comment = textwrap.dedent('''\
482  %class {class_name}, see Doxygen page for details
483  %at https://gtsam.org/doxygen/
484  ''').format(class_name=class_name)
485 
486  if len(ctors) != 0:
487  comment += '%\n%-------Constructors-------\n'
488 
489  # Write constructors
490  for ctor in ctors:
491  comment += '%{ctor_name}({args})\n'.format(ctor_name=ctor.name,
492  args=self._wrap_args(
493  ctor.args))
494 
495  if len(properties) != 0:
496  comment += '%\n' \
497  '%-------Properties-------\n'
498  for propty in properties:
499  comment += '%{}\n'.format(propty.name)
500 
501  if len(methods) != 0:
502  comment += '%\n' \
503  '%-------Methods-------\n'
504 
505  methods = sorted(methods, key=lambda name: name.name)
506 
507  # Write methods
508  for method in methods:
509  if method.name in self.whitelist:
510  continue
511  if method.name in self.ignore_methods:
512  continue
513 
514  comment += '%{name}({args})'.format(name=method.name,
515  args=self._wrap_args(
516  method.args))
517 
518  if method.return_type.type2 == '':
519  return_type = self._format_type_name(
520  method.return_type.type1.typename)
521  else:
522  return_type = 'pair< {type1}, {type2} >'.format(
523  type1=self._format_type_name(
524  method.return_type.type1.typename),
525  type2=self._format_type_name(
526  method.return_type.type2.typename))
527 
528  comment += ' : returns {return_type}\n'.format(
529  return_type=return_type)
530 
531  comment += '%\n'
532 
533  if len(static_methods) != 0:
534  comment += self.class_serialize_comment(class_name, static_methods)
535 
536  return comment
537 
538  def wrap_method(self, methods):
539  """
540  Wrap methods in the body of a class.
541  """
542  if not isinstance(methods, list):
543  methods = [methods]
544 
545  return ''
546 
547  def wrap_methods(self, methods, global_funcs=False, global_ns=None):
548  """
549  Wrap a sequence of methods/functions. Groups methods with the same names
550  together.
551  If global_funcs is True then output every method into its own file.
552  """
553  output = ''
554  methods = self._group_methods(methods)
555 
556  for method in methods:
557  if method in self.ignore_methods:
558  continue
559 
560  if global_funcs:
561  method_text = self.wrap_global_function(method)
562  self.content.append(("".join([
563  '+' + x + '/' for x in global_ns.full_namespaces()[1:]
564  ])[:-1], [(method[0].name + '.m', method_text)]))
565  else:
566  method_text = self.wrap_method(method)
567  output += ''
568 
569  return output
570 
571  def wrap_global_function(self, function):
572  """Wrap the given global function."""
573  if not isinstance(function, list):
574  function = [function]
575 
576  function_name = function[0].name
577 
578  # Get all combinations of parameters
579  param_wrap = ''
580 
581  for i, overload in enumerate(function):
582  param_wrap += ' if' if i == 0 else ' elseif'
583  param_wrap += ' length(varargin) == '
584 
585  if len(overload.args.list()) == 0:
586  param_wrap += '0\n'
587  else:
588  param_wrap += str(len(overload.args.list())) \
589  + self._wrap_variable_arguments(overload.args, False) + '\n'
590 
591  # Determine format of return and varargout statements
592  return_type_formatted = self._format_return_type(
593  overload.return_type, include_namespace=True, separator=".")
594  varargout = self._format_varargout(overload.return_type,
595  return_type_formatted)
596 
597  param_wrap += textwrap.indent(textwrap.dedent('''\
598  {varargout}{module_name}_wrapper({num}, varargin{{:}});
599  ''').format(varargout=varargout,
600  module_name=self.module_name,
601  num=self._update_wrapper_id(
602  collector_function=(function[0].parent.name,
603  function[i], 'global_function',
604  None))),
605  prefix=' ')
606 
607  param_wrap += textwrap.indent(textwrap.dedent('''\
608  else
609  error('Arguments do not match any overload of function {func_name}');
610  end''').format(func_name=function_name),
611  prefix=' ')
612 
613  global_function = textwrap.indent(textwrap.dedent('''\
614  function varargout = {m_method}(varargin)
615  {statements}
616  end
617  ''').format(m_method=function_name, statements=param_wrap),
618  prefix='')
619 
620  return global_function
621 
622  def wrap_class_constructors(self, namespace_name, inst_class, parent_name,
623  ctors, is_virtual):
624  """Wrap class constructor.
625 
626  Args:
627  namespace_name: the name of the namespace ('' if it does not exist)
628  inst_class: instance of the class
629  parent_name: the name of the parent class if it exists
630  ctors: the interface_parser.Constructor in the class
631  is_virtual: whether the class is part of a virtual inheritance
632  chain
633  """
634  has_parent = parent_name != ''
635  class_name = inst_class.name
636  if has_parent:
637  parent_name = self._format_type_name(parent_name, separator=".")
638  if not isinstance(ctors, Iterable):
639  ctors = [ctors]
640 
641  ctors = sum((MatlabWrapper._expand_default_arguments(ctor)
642  for ctor in ctors), [])
643 
644  methods_wrap = textwrap.indent(textwrap.dedent("""\
645  methods
646  function obj = {class_name}(varargin)
647  """).format(class_name=class_name),
648  prefix='')
649 
650  if is_virtual:
651  methods_wrap += " if (nargin == 2 || (nargin == 3 && strcmp(varargin{3}, 'void')))"
652  else:
653  methods_wrap += ' if nargin == 2'
654 
655  methods_wrap += " && isa(varargin{1}, 'uint64')"
656  methods_wrap += " && varargin{1} == uint64(5139824614673773682)\n"
657 
658  if is_virtual:
659  methods_wrap += textwrap.indent(textwrap.dedent('''\
660  if nargin == 2
661  my_ptr = varargin{{2}};
662  else
663  my_ptr = {wrapper_name}({id}, varargin{{2}});
664  end
665  ''').format(wrapper_name=self._wrapper_name(),
666  id=self._update_wrapper_id() + 1),
667  prefix=' ')
668  else:
669  methods_wrap += ' my_ptr = varargin{2};\n'
670 
671  collector_base_id = self._update_wrapper_id(
672  (namespace_name, inst_class, 'collectorInsertAndMakeBase', None),
673  id_diff=-1 if is_virtual else 0)
674 
675  methods_wrap += ' {ptr}{wrapper_name}({id}, my_ptr);\n' \
676  .format(
677  ptr='base_ptr = ' if has_parent else '',
678  wrapper_name=self._wrapper_name(),
679  id=collector_base_id - (1 if is_virtual else 0))
680 
681  for ctor in ctors:
682  wrapper_return = '[ my_ptr, base_ptr ] = ' \
683  if has_parent \
684  else 'my_ptr = '
685 
686  methods_wrap += textwrap.indent(textwrap.dedent('''\
687  elseif nargin == {len}{varargin}
688  {ptr}{wrapper}({num}{comma}{var_arg});
689  ''').format(len=len(ctor.args.list()),
690  varargin=self._wrap_variable_arguments(
691  ctor.args, False),
692  ptr=wrapper_return,
693  wrapper=self._wrapper_name(),
694  num=self._update_wrapper_id(
695  (namespace_name, inst_class, 'constructor', ctor)),
696  comma='' if len(ctor.args.list()) == 0 else ', ',
697  var_arg=self._wrap_list_variable_arguments(ctor.args)),
698  prefix=' ')
699 
700  base_obj = ''
701 
702  if has_parent:
703  base_obj = ' obj = obj@{parent_name}(uint64(5139824614673773682), base_ptr);'.format(
704  parent_name=parent_name)
705 
706  if base_obj:
707  base_obj = '\n' + base_obj
708 
709  methods_wrap += textwrap.indent(textwrap.dedent('''\
710  else
711  error('Arguments do not match any overload of {class_name_doc} constructor');
712  end{base_obj}
713  obj.ptr_{class_name} = my_ptr;
714  end\n
715  ''').format(namespace=namespace_name,
716  d='' if namespace_name == '' else '.',
717  class_name_doc=self._format_class_name(inst_class,
718  separator="."),
719  class_name=self._format_class_name(inst_class,
720  separator=""),
721  base_obj=base_obj),
722  prefix=' ')
723 
724  return methods_wrap
725 
726  def wrap_properties_block(self, class_name, inst_class):
727  """Generate Matlab properties block of the class.
728 
729  E.g.
730  ```
731  properties
732  ptr_gtsamISAM2Params = 0
733  relinearizeSkip
734  end
735  ```
736 
737  Args:
738  class_name: Class name with namespace to assign unique pointer.
739  inst_class: The instantiated class whose properties we want to wrap.
740 
741  Returns:
742  str: The `properties` block in a Matlab `classdef`.
743  """
744  # Get the property names and make into newline separated block
745  class_pointer = " ptr_{class_name} = 0".format(class_name=class_name)
746 
747  if len(inst_class.properties) > 0:
748  properties = '\n' + "\n".join(
749  [" {}".format(p.name) for p in inst_class.properties])
750  else:
751  properties = ''
752 
753  properties = class_pointer + properties
754  properties_block = textwrap.dedent('''\
755  properties
756  {properties}
757  end
758  ''').format(properties=properties)
759  return properties_block
760 
761  def wrap_class_properties(self, namespace_name: str,
762  inst_class: InstantiatedClass):
763  """Generate wrappers for the setters & getters of class properties.
764 
765  Args:
766  inst_class: The instantiated class whose properties we wish to wrap.
767  """
768  properties = []
769  for propty in inst_class.properties:
770  # These are the setters and getters in the .m file
771  function_name = namespace_name + inst_class.name + '_get_' + propty.name
772  getter = """
773  function varargout = get.{name}(this)
774  {varargout} = {wrapper}({num}, this);
775  this.{name} = {varargout};
776  end
777  """.format(name=propty.name,
778  varargout='varargout{1}',
779  wrapper=self._wrapper_name(),
780  num=self._update_wrapper_id(
781  (namespace_name, inst_class, propty.name, propty),
782  function_name=function_name))
783  properties.append(getter)
784 
785  # Setter doesn't need varargin since it needs just one input.
786  function_name = namespace_name + inst_class.name + '_set_' + propty.name
787  setter = """
788  function set.{name}(this, value)
789  obj.{name} = value;
790  {wrapper}({num}, this, value);
791  end
792  """.format(name=propty.name,
793  wrapper=self._wrapper_name(),
794  num=self._update_wrapper_id(
795  (namespace_name, inst_class, propty.name, propty),
796  function_name=function_name))
797  properties.append(setter)
798 
799  return properties
800 
801  def wrap_class_deconstructor(self, namespace_name, inst_class):
802  """Generate the delete function for the Matlab class."""
803  class_name = inst_class.name
804 
805  methods_text = textwrap.indent(textwrap.dedent("""\
806  function delete(obj)
807  {wrapper}({num}, obj.ptr_{class_name});
808  end\n
809  """).format(num=self._update_wrapper_id(
810  (namespace_name, inst_class, 'deconstructor', None)),
811  wrapper=self._wrapper_name(),
812  class_name="".join(inst_class.parent.full_namespaces()) +
813  class_name),
814  prefix=' ')
815 
816  return methods_text
817 
819  """Generate the display function for the Matlab class."""
820  return textwrap.indent(textwrap.dedent("""\
821  function display(obj), obj.print(''); end
822  %DISPLAY Calls print on the object
823  function disp(obj), obj.display; end
824  %DISP Calls print on the object
825  """),
826  prefix=' ')
827 
828  def _group_class_methods(self, methods):
829  """Group overloaded methods together"""
830  return self._group_methods(methods)
831 
832  @classmethod
833  def _format_varargout(cls, return_type, return_type_formatted):
834  """Determine format of return and varargout statements"""
835  if cls._return_count(return_type) == 1:
836  varargout = '' \
837  if return_type_formatted == 'void' \
838  else 'varargout{1} = '
839  else:
840  varargout = '[ varargout{1} varargout{2} ] = '
841 
842  return varargout
843 
844  def wrap_class_methods(self,
845  namespace_name,
846  inst_class,
847  methods,
848  serialize=(False, )):
849  """Wrap the methods in the class.
850 
851  Args:
852  namespace_name: the name of the class's namespace
853  inst_class: the instantiated class whose methods to wrap
854  methods: the methods to wrap in the order to wrap them
855  serialize: mutable param storing if one of the methods is serialize
856  """
857  method_text = ''
858 
859  methods = self._group_class_methods(methods)
860 
861  # Convert to list so that it is mutable
862  if isinstance(serialize, tuple):
863  serialize = list(serialize)
864 
865  for method in methods:
866  method_name = method[0].name
867  if method_name in self.whitelist and method_name != 'serialize':
868  continue
869  if method_name in self.ignore_methods:
870  continue
871 
872  if method_name == 'serialize':
873  if self.use_boost_serialization:
874  serialize[0] = True
875  method_text += self.wrap_class_serialize_method(
876  namespace_name, inst_class)
877 
878  else:
879  # Generate method code
880  method_text += textwrap.indent(textwrap.dedent("""\
881  function varargout = {method_name}(this, varargin)
882  """).format(caps_name=method_name.upper(),
883  method_name=method_name),
884  prefix='')
885 
886  for overload in method:
887  method_text += textwrap.indent(textwrap.dedent("""\
888  % {caps_name} usage: {method_name}(""").format(
889  caps_name=method_name.upper(),
890  method_name=method_name),
891  prefix=' ')
892 
893  # Determine format of return and varargout statements
894  return_type_formatted = self._format_return_type(
895  overload.return_type,
896  include_namespace=True,
897  separator=".")
898  varargout = self._format_varargout(overload.return_type,
899  return_type_formatted)
900 
901  check_statement = self._wrap_method_check_statement(
902  overload.args)
903  class_name = namespace_name + ('' if namespace_name == ''
904  else '.') + inst_class.name
905 
906  end_statement = '' \
907  if check_statement == '' \
908  else textwrap.indent(textwrap.dedent("""\
909  return
910  end
911  """).format(
912  class_name=class_name,
913  method_name=overload.original.name), prefix=' ')
914 
915  method_text += textwrap.dedent("""\
916  {method_args}) : returns {return_type}
917  % Doxygen can be found at https://gtsam.org/doxygen/
918  {check_statement}{spacing}{varargout}{wrapper}({num}, this, varargin{{:}});
919  {end_statement}""").format(
920  method_args=self._wrap_args(overload.args),
921  return_type=return_type_formatted,
922  num=self._update_wrapper_id(
923  (namespace_name, inst_class,
924  overload.original.name, overload)),
925  check_statement=check_statement,
926  spacing='' if check_statement == '' else ' ',
927  varargout=varargout,
928  wrapper=self._wrapper_name(),
929  end_statement=end_statement)
930 
931  final_statement = textwrap.indent(textwrap.dedent("""\
932  error('Arguments do not match any overload of function {class_name}.{method_name}');
933  """.format(class_name=class_name, method_name=method_name)),
934  prefix=' ')
935  method_text += final_statement + 'end\n\n'
936 
937  return method_text
938 
939  def wrap_static_methods(self, namespace_name, instantiated_class,
940  serialize):
941  """
942  Wrap the static methods in the class.
943  """
944  class_name = instantiated_class.name
945 
946  method_text = 'methods(Static = true)\n'
947  static_methods = sorted(instantiated_class.static_methods,
948  key=lambda name: name.name)
949 
950  static_methods = self._group_class_methods(static_methods)
951 
952  for static_method in static_methods:
953  format_name = list(static_method[0].name)
954  format_name[0] = format_name[0]
955 
956  if static_method[0].name in self.ignore_methods:
957  continue
958 
959  method_text += textwrap.indent(textwrap.dedent('''\
960  function varargout = {name}(varargin)
961  '''.format(name=''.join(format_name))),
962  prefix=" ")
963 
964  for static_overload in static_method:
965  check_statement = self._wrap_method_check_statement(
966  static_overload.args)
967 
968  end_statement = '' \
969  if check_statement == '' \
970  else textwrap.indent(textwrap.dedent("""
971  return
972  end
973  """), prefix='')
974  method_text += textwrap.indent(textwrap.dedent('''\
975  % {name_caps} usage: {name_upper_case}({args}) : returns {return_type}
976  % Doxygen can be found at https://gtsam.org/doxygen/
977  {check_statement}{spacing}varargout{{1}} = {wrapper}({id}, varargin{{:}});{end_statement}
978  ''').format(
979  name=''.join(format_name),
980  name_caps=static_overload.name.upper(),
981  name_upper_case=static_overload.name,
982  args=self._wrap_args(static_overload.args),
983  return_type=self._format_return_type(
984  static_overload.return_type,
985  include_namespace=True,
986  separator="."),
987  length=len(static_overload.args.list()),
988  var_args_list=self._wrap_variable_arguments(
989  static_overload.args),
990  check_statement=check_statement,
991  spacing='' if check_statement == '' else ' ',
992  wrapper=self._wrapper_name(),
993  id=self._update_wrapper_id(
994  (namespace_name, instantiated_class,
995  static_overload.name, static_overload)),
996  class_name=instantiated_class.name,
997  end_statement=end_statement),
998  prefix=' ')
999 
1000  # If the arguments don't match any of the checks above,
1001  # throw an error with the class and method name.
1002  method_text += textwrap.indent(textwrap.dedent("""\
1003  error('Arguments do not match any overload of function {class_name}.{method_name}');
1004  """.format(class_name=class_name,
1005  method_name=static_overload.name)),
1006  prefix=' ')
1007 
1008  method_text += textwrap.indent(textwrap.dedent("""\
1009  end\n
1010  """),
1011  prefix=" ")
1012 
1013  if serialize and self.use_boost_serialization:
1014  method_text += WrapperTemplate.matlab_deserialize.format(
1015  class_name=namespace_name + '.' + instantiated_class.name,
1016  wrapper=self._wrapper_name(),
1017  id=self._update_wrapper_id(
1018  (namespace_name, instantiated_class, 'string_deserialize',
1019  'deserialize')))
1020 
1021  return method_text
1022 
1023  def wrap_instantiated_class(self,
1024  instantiated_class,
1025  namespace_name: str = ''):
1026  """Generate comments and code for given class.
1027 
1028  Args:
1029  instantiated_class: template_instantiator.InstantiatedClass
1030  instance storing the class to wrap
1031  namespace_name: the name of the namespace if there is one
1032  """
1033  file_name = self._clean_class_name(instantiated_class)
1034  namespace_file_name = namespace_name + file_name
1035 
1036  uninstantiated_name = "::".join(instantiated_class.namespaces()
1037  [1:]) + "::" + instantiated_class.name
1038  if uninstantiated_name in self.ignore_classes:
1039  return None
1040 
1041  # Class docstring/comment
1042  content_text = self.class_comment(instantiated_class)
1043  content_text += self.wrap_methods(instantiated_class.methods)
1044 
1045  # Class definition
1046  # if namespace_name:
1047  # print("nsname: {}, file_name_: {}, filename: {}"
1048  # .format(namespace_name,
1049  # self._clean_class_name(instantiated_class), file_name)
1050  # , file=sys.stderr)
1051  content_text += 'classdef {class_name} < {parent}\n'.format(
1052  class_name=file_name,
1053  parent=str(self._qualified_name(
1054  instantiated_class.parent_class)).replace("::", "."))
1055 
1056  # Class properties
1057  content_text += ' ' + reduce(
1058  self._insert_spaces,
1059  self.wrap_properties_block(namespace_file_name,
1060  instantiated_class).splitlines()) + '\n'
1061 
1062  # Class constructor
1063  content_text += ' ' + reduce(
1064  self._insert_spaces,
1066  namespace_name,
1067  instantiated_class,
1068  instantiated_class.parent_class,
1069  instantiated_class.ctors,
1070  instantiated_class.is_virtual,
1071  ).splitlines()) + '\n'
1072 
1073  # Delete function
1074  content_text += ' ' + reduce(
1075  self._insert_spaces,
1077  namespace_name, instantiated_class).splitlines()) + '\n'
1078 
1079  # Display function
1080  content_text += ' ' + reduce(
1081  self._insert_spaces,
1082  self.wrap_class_display().splitlines()) + '\n'
1083 
1084  # Class methods
1085  serialize = [False]
1086 
1087  if len(instantiated_class.methods) != 0:
1088  methods = sorted(instantiated_class.methods,
1089  key=lambda name: name.name)
1090  class_methods_wrapped = self.wrap_class_methods(
1091  namespace_name,
1092  instantiated_class,
1093  methods,
1094  serialize=serialize).splitlines()
1095  if len(class_methods_wrapped) > 0:
1096  content_text += ' ' + reduce(
1097  lambda x, y: x + '\n' + ('' if y == '' else ' ') + y,
1098  class_methods_wrapped) + '\n'
1099 
1100  # Class properties
1101  if len(instantiated_class.properties) != 0:
1102  property_accessors = self.wrap_class_properties(
1103  namespace_name, instantiated_class)
1104  content_text += textwrap.indent(textwrap.dedent(
1105  "".join(property_accessors)),
1106  prefix=' ')
1107 
1108  content_text += ' end' # End the `methods` block
1109 
1110  # Static class methods
1111  content_text += '\n\n ' + reduce(
1112  self._insert_spaces,
1113  self.wrap_static_methods(namespace_name, instantiated_class,
1114  serialize[0]).splitlines()) + '\n' + \
1115  ' end\n'
1116 
1117  # Close the classdef
1118  content_text += textwrap.dedent('''\
1119  end
1120  ''')
1121 
1122  # Enums
1123  # Place enums into the correct submodule so we can access them
1124  # e.g. gtsam.Class.Enum.A
1125  for enum in instantiated_class.enums:
1126  enum_text = self.wrap_enum(enum)
1127  if namespace_name != '':
1128  submodule = f"+{namespace_name}/"
1129  else:
1130  submodule = ""
1131  submodule += f"+{instantiated_class.name}"
1132  self.content.append((submodule, [enum_text]))
1133 
1134  return file_name + '.m', content_text
1135 
1136  def wrap_enum(self, enum):
1137  """
1138  Wrap an enum definition as a Matlab class.
1139 
1140  Args:
1141  enum: The interface_parser.Enum instance
1142  """
1143  file_name = enum.name + '.m'
1144  enum_template = textwrap.dedent("""\
1145  classdef {0} < uint32
1146  enumeration
1147  {1}
1148  end
1149  end
1150  """)
1151  enumerators = "\n ".join([
1152  f"{enumerator.name}({idx})"
1153  for idx, enumerator in enumerate(enum.enumerators)
1154  ])
1155 
1156  content = enum_template.format(enum.name, enumerators)
1157  return file_name, content
1158 
1159  def wrap_namespace(self, namespace, add_mex_file=True):
1160  """Wrap a namespace by wrapping all of its components.
1161 
1162  Args:
1163  namespace: the interface_parser.namespace instance of the namespace
1164  add_cpp_file: Flag indicating whether the mex file should be added
1165  """
1166  namespaces = namespace.full_namespaces()
1167  inner_namespace = namespace.name != ''
1168  wrapped = []
1169 
1170  top_level_scope = []
1171  inner_namespace_scope = []
1172 
1173  for element in namespace.content:
1174  if isinstance(element, parser.Include):
1175  self.includes.append(element)
1176 
1177  elif isinstance(element, parser.Namespace):
1178  self.wrap_namespace(element, False)
1179 
1180  elif isinstance(element, parser.Enum):
1181  file, content = self.wrap_enum(element)
1182  if inner_namespace:
1183  module = "".join([
1184  '+' + x + '/' for x in namespace.full_namespaces()[1:]
1185  ])[:-1]
1186  inner_namespace_scope.append((module, [(file, content)]))
1187  else:
1188  top_level_scope.append((file, content))
1189 
1190  elif isinstance(element, instantiator.InstantiatedClass):
1191  self.add_class(element)
1192 
1193  if inner_namespace:
1194  class_text = self.wrap_instantiated_class(
1195  element, "".join(namespace.full_namespaces()))
1196 
1197  if not class_text is None:
1198  inner_namespace_scope.append(("".join([
1199  '+' + x + '/'
1200  for x in namespace.full_namespaces()[1:]
1201  ])[:-1], [(class_text[0], class_text[1])]))
1202  else:
1203  class_text = self.wrap_instantiated_class(element)
1204  top_level_scope.append((class_text[0], class_text[1]))
1205 
1206  self.content.extend(top_level_scope)
1207 
1208  if inner_namespace:
1209  self.content.append(inner_namespace_scope)
1210 
1211  if add_mex_file:
1212  cpp_filename = self._wrapper_name() + '.cpp'
1213  self.content.append((cpp_filename, self.wrapper_file_headers))
1214 
1215  # Global functions
1216  all_funcs = [
1217  func for func in namespace.content
1218  if isinstance(func, parser.GlobalFunction)
1219  ]
1220 
1221  self.wrap_methods(all_funcs, True, global_ns=namespace)
1222 
1223  return wrapped
1224 
1226  return_type_name,
1227  shared_obj,
1228  func_id,
1229  new_line=True):
1230  """Wrap the collector function which returns a shared pointer."""
1231  new_line = '\n' if new_line else ''
1232 
1233  return WrapperTemplate.collector_function_shared_return.format(
1234  name=self._format_type_name(return_type_name,
1235  include_namespace=False),
1236  shared_obj=shared_obj,
1237  id=func_id,
1238  new_line=new_line)
1239 
1240  def wrap_collector_function_return_types(self, return_type, func_id):
1241  """
1242  Wrap the return type of the collector function when a std::pair is returned.
1243  """
1244  return_type_text = ' out[' + str(func_id) + '] = '
1245  pair_value = 'first' if func_id == 0 else 'second'
1246  new_line = '\n' if func_id == 0 else ''
1247 
1248  if self.is_shared_ptr(return_type) or self.is_ptr(return_type) or \
1249  self.can_be_pointer(return_type):
1250  shared_obj = 'pairResult.' + pair_value
1251 
1252  if not (return_type.is_shared_ptr or return_type.is_ptr):
1253  shared_obj = 'std::make_shared<{name}>({shared_obj})' \
1254  .format(name=self._format_type_name(return_type.typename),
1255  shared_obj='pairResult.' + pair_value)
1256 
1257  if return_type.typename.name in self.ignore_namespace:
1258  return_type_text = self.wrap_collector_function_shared_return(
1259  return_type.typename, shared_obj, func_id, func_id == 0)
1260  else:
1261  return_type_text += 'wrap_shared_ptr({0},"{1}", false);{new_line}' \
1262  .format(shared_obj,
1263  self._format_type_name(return_type.typename,
1264  separator='.'),
1265  new_line=new_line)
1266  else:
1267  return_type_text += 'wrap< {0} >(pairResult.{1});{2}'.format(
1268  self._format_type_name(return_type.typename, separator='.'),
1269  pair_value, new_line)
1270 
1271  return return_type_text
1272 
1273  def _collector_return(self,
1274  obj: str,
1275  ctype: parser.Type,
1276  instantiated_class: InstantiatedClass = None):
1277  """Helper method to get the final statement before the return in the collector function."""
1278  expanded = ''
1279 
1280  if instantiated_class and \
1281  self.is_enum(ctype, instantiated_class):
1282  if self.is_class_enum(ctype, instantiated_class):
1283  class_name = ".".join(instantiated_class.namespaces()[1:] +
1284  [instantiated_class.name])
1285  else:
1286  # Get the full namespace
1287  class_name = ".".join(instantiated_class.parent.full_namespaces()[1:])
1288 
1289  if class_name != "":
1290  class_name += '.'
1291 
1292  enum_type = f"{class_name}{ctype.typename.name}"
1293  expanded = textwrap.indent(
1294  f'out[0] = wrap_enum({obj},\"{enum_type}\");', prefix=' ')
1295 
1296  elif self.is_shared_ptr(ctype) or self.is_ptr(ctype) or \
1297  self.can_be_pointer(ctype):
1298  sep_method_name = partial(self._format_type_name,
1299  ctype.typename,
1300  include_namespace=True)
1301 
1302  if ctype.typename.name in self.ignore_namespace:
1303  expanded += self.wrap_collector_function_shared_return(
1304  ctype.typename, obj, 0, new_line=False)
1305 
1306  if ctype.is_shared_ptr or ctype.is_ptr:
1307  shared_obj = '{obj},"{method_name_sep}"'.format(
1308  obj=obj, method_name_sep=sep_method_name('.'))
1309  else:
1310  method_name_sep_dot = sep_method_name('.')
1311 
1312  # Specialize for std::optional so we access the underlying member
1313  #TODO(Varun) How do we handle std::optional as a Mex type?
1314  if isinstance(ctype, parser.TemplatedType) and \
1315  "std::optional" == str(ctype.typename)[:13]:
1316  obj = f"*{obj}"
1317  type_name = ctype.template_params[0].typename
1318  method_name_sep_dot = ".".join(
1319  type_name.namespaces) + f".{type_name.name}"
1320 
1321 
1322  shared_obj_template = 'std::make_shared<{method_name_sep_col}>({obj}),' \
1323  '"{method_name_sep_dot}"'
1324  shared_obj = shared_obj_template \
1325  .format(method_name_sep_col=sep_method_name(),
1326  method_name_sep_dot=method_name_sep_dot,
1327  obj=obj)
1328 
1329  if ctype.typename.name not in self.ignore_namespace:
1330  expanded += textwrap.indent(
1331  'out[0] = wrap_shared_ptr({0}, false);'.format(shared_obj),
1332  prefix=' ')
1333  else:
1334  expanded += ' out[0] = wrap< {0} >({1});'.format(
1335  ctype.typename.name, obj)
1336 
1337  return expanded
1338 
1339  def wrap_collector_function_return(self, method, instantiated_class=None):
1340  """
1341  Wrap the complete return type of the function.
1342  """
1343  expanded = ''
1344 
1345  params = self._wrapper_unwrap_arguments(
1346  method.args, arg_id=1, instantiated_class=instantiated_class)[0]
1347 
1348  return_1 = method.return_type.type1
1349  return_count = self._return_count(method.return_type)
1350  return_1_name = method.return_type.type1.typename.name
1351  obj_start = ''
1352 
1353  if isinstance(method, instantiator.InstantiatedMethod):
1354  method_name = method.to_cpp()
1355  obj_start = 'obj->'
1356 
1357  if method.instantiations:
1358  # method_name += '<{}>'.format(
1359  # self._format_type_name(method.instantiations))
1360  method = method.to_cpp()
1361 
1362  elif isinstance(method, instantiator.InstantiatedStaticMethod):
1363  method_name = self._format_static_method(method, '::')
1364  method_name += method.original.name
1365 
1366  elif isinstance(method, parser.GlobalFunction):
1367  method_name = self._format_global_function(method, '::')
1368  method_name += method.name
1369 
1370  else:
1371  if isinstance(method.parent, instantiator.InstantiatedClass):
1372  method_name = method.parent.to_cpp() + "::"
1373  else:
1374  method_name = self._format_static_method(method, '::')
1375  method_name += method.name
1376 
1377  obj = ' ' if return_1_name == 'void' else ''
1378  obj += '{}{}({})'.format(obj_start, method_name, params)
1379 
1380  if return_1_name != 'void':
1381  if return_count == 1:
1382  expanded += self._collector_return(
1383  obj, return_1, instantiated_class=instantiated_class)
1384 
1385  elif return_count == 2:
1386  return_2 = method.return_type.type2
1387 
1388  expanded += ' auto pairResult = {};\n'.format(obj)
1389  expanded += self.wrap_collector_function_return_types(
1390  return_1, 0)
1391  expanded += self.wrap_collector_function_return_types(
1392  return_2, 1)
1393  else:
1394  expanded += obj + ';'
1395 
1396  return expanded
1397 
1399  self,
1400  class_property: parser.Variable,
1401  instantiated_class: InstantiatedClass = None):
1402  """Get the last collector function statement before return for a property."""
1403  property_name = class_property.name
1404  obj = 'obj->{}'.format(property_name)
1405 
1406  return self._collector_return(obj,
1407  class_property.ctype,
1408  instantiated_class=instantiated_class)
1409 
1410  def wrap_collector_function_upcast_from_void(self, class_name, func_id,
1411  cpp_name):
1412  """
1413  Add function to upcast type from void type.
1414  """
1415  return WrapperTemplate.collector_function_upcast_from_void.format(
1416  class_name=class_name, cpp_name=cpp_name, id=func_id)
1417 
1418  def generate_collector_function(self, func_id):
1419  """
1420  Generate the complete collector function that goes into the wrapper.cpp file.
1421 
1422  A collector function is the Mex function used to interact between
1423  the C++ object and the Matlab .m files.
1424  """
1425  collector_func = self.wrapper_map.get(func_id)
1426 
1427  if collector_func is None:
1428  return ''
1429 
1430  method_name = collector_func[3]
1431 
1432  collector_function = "void {}" \
1433  "(int nargout, mxArray *out[], int nargin, const mxArray *in[])\n".format(method_name)
1434 
1435  if isinstance(collector_func[1], instantiator.InstantiatedClass):
1436  body = '{\n'
1437 
1438  extra = collector_func[4]
1439 
1440  class_name = collector_func[0] + collector_func[1].name
1441  class_name_separated = collector_func[1].to_cpp()
1442  is_method = isinstance(extra, parser.Method)
1443  is_static_method = isinstance(extra, parser.StaticMethod)
1444  is_property = isinstance(extra, parser.Variable)
1445 
1446  if collector_func[2] == 'collectorInsertAndMakeBase':
1447  body += textwrap.indent(textwrap.dedent('''\
1448  mexAtExit(&_deleteAllObjects);
1449  typedef std::shared_ptr<{class_name_sep}> Shared;\n
1450  Shared *self = *reinterpret_cast<Shared**> (mxGetData(in[0]));
1451  collector_{class_name}.insert(self);
1452  ''').format(class_name_sep=class_name_separated,
1453  class_name=class_name),
1454  prefix=' ')
1455 
1456  if collector_func[1].parent_class:
1457  body += textwrap.indent(textwrap.dedent('''
1458  typedef std::shared_ptr<{}> SharedBase;
1459  out[0] = mxCreateNumericMatrix(1, 1, mxUINT32OR64_CLASS, mxREAL);
1460  *reinterpret_cast<SharedBase**>(mxGetData(out[0])) = new SharedBase(*self);
1461  ''').format(collector_func[1].parent_class),
1462  prefix=' ')
1463 
1464  elif collector_func[2] == 'constructor':
1465  base = ''
1466  params, body_args = self._wrapper_unwrap_arguments(
1467  extra.args, instantiated_class=collector_func[1])
1468 
1469  if collector_func[1].parent_class:
1470  base += textwrap.indent(textwrap.dedent('''
1471  typedef std::shared_ptr<{}> SharedBase;
1472  out[1] = mxCreateNumericMatrix(1, 1, mxUINT32OR64_CLASS, mxREAL);
1473  *reinterpret_cast<SharedBase**>(mxGetData(out[1])) = new SharedBase(*self);
1474  ''').format(collector_func[1].parent_class),
1475  prefix=' ')
1476 
1477  body += textwrap.dedent('''\
1478  mexAtExit(&_deleteAllObjects);
1479  typedef std::shared_ptr<{class_name_sep}> Shared;\n
1480  {body_args} Shared *self = new Shared(new {class_name_sep}({params}));
1481  collector_{class_name}.insert(self);
1482  out[0] = mxCreateNumericMatrix(1, 1, mxUINT32OR64_CLASS, mxREAL);
1483  *reinterpret_cast<Shared**> (mxGetData(out[0])) = self;
1484  {base}''').format(class_name_sep=class_name_separated,
1485  body_args=body_args,
1486  params=params,
1487  class_name=class_name,
1488  base=base)
1489 
1490  elif collector_func[2] == 'deconstructor':
1491  body += textwrap.indent(textwrap.dedent('''\
1492  typedef std::shared_ptr<{class_name_sep}> Shared;
1493  checkArguments("delete_{class_name}",nargout,nargin,1);
1494  Shared *self = *reinterpret_cast<Shared**>(mxGetData(in[0]));
1495  Collector_{class_name}::iterator item;
1496  item = collector_{class_name}.find(self);
1497  if(item != collector_{class_name}.end()) {{
1498  collector_{class_name}.erase(item);
1499  }}
1500  delete self;
1501  ''').format(class_name_sep=class_name_separated,
1502  class_name=class_name),
1503  prefix=' ')
1504 
1505  elif extra == 'serialize':
1506  if self.use_boost_serialization:
1507  body += self.wrap_collector_function_serialize(
1508  collector_func[1].name,
1509  full_name=collector_func[1].to_cpp(),
1510  namespace=collector_func[0])
1511 
1512  elif extra == 'deserialize':
1513  if self.use_boost_serialization:
1515  collector_func[1].name,
1516  full_name=collector_func[1].to_cpp(),
1517  namespace=collector_func[0])
1518 
1519  elif is_method or is_static_method:
1520  method_name = ''
1521 
1522  if is_static_method:
1523  method_name = self._format_static_method(extra, '.')
1524 
1525  method_name += extra.name
1526 
1527  _, body_args = self._wrapper_unwrap_arguments(
1528  extra.args,
1529  arg_id=1 if is_method else 0,
1530  instantiated_class=collector_func[1])
1531 
1532  return_body = self.wrap_collector_function_return(
1533  extra, collector_func[1])
1534 
1535  shared_obj = ''
1536 
1537  if is_method:
1538  shared_obj = ' auto obj = unwrap_shared_ptr<{class_name_sep}>' \
1539  '(in[0], "ptr_{class_name}");\n'.format(
1540  class_name_sep=class_name_separated,
1541  class_name=class_name)
1542 
1543  body += ' checkArguments("{method_name}",nargout,nargin{min1},' \
1544  '{num_args});\n' \
1545  '{shared_obj}' \
1546  '{body_args}' \
1547  '{return_body}\n'.format(
1548  min1='-1' if is_method else '',
1549  shared_obj=shared_obj,
1550  method_name=method_name,
1551  num_args=len(extra.args.list()),
1552  body_args=body_args,
1553  return_body=return_body)
1554 
1555  elif is_property:
1556  shared_obj = ' auto obj = unwrap_shared_ptr<{class_name_sep}>' \
1557  '(in[0], "ptr_{class_name}");\n'.format(
1558  class_name_sep=class_name_separated,
1559  class_name=class_name)
1560 
1561  # Unpack the property from mxArray
1562  property_type, unwrap = self._unwrap_argument(
1563  extra, arg_id=1, instantiated_class=collector_func[1])
1564  unpack_property = textwrap.indent(textwrap.dedent('''\
1565  {arg_type} {name} = {unwrap}
1566  '''.format(arg_type=property_type,
1567  name=extra.name,
1568  unwrap=unwrap)),
1569  prefix=' ')
1570 
1571  # Getter
1572  if "_get_" in method_name:
1573  return_body = self.wrap_collector_property_return(
1574  extra, instantiated_class=collector_func[1])
1575 
1576  getter = ' checkArguments("{property_name}",nargout,nargin{min1},' \
1577  '{num_args});\n' \
1578  '{shared_obj}' \
1579  '{return_body}\n'.format(
1580  property_name=extra.name,
1581  min1='-1',
1582  num_args=0,
1583  shared_obj=shared_obj,
1584  return_body=return_body)
1585 
1586  body += getter
1587 
1588  # Setter
1589  if "_set_" in method_name:
1590  is_ptr_type = self.can_be_pointer(extra.ctype) and \
1591  not self.is_enum(extra.ctype, collector_func[1])
1592  return_body = ' obj->{0} = {1}{0};'.format(
1593  extra.name, '*' if is_ptr_type else '')
1594 
1595  setter = ' checkArguments("{property_name}",nargout,nargin{min1},' \
1596  '{num_args});\n' \
1597  '{shared_obj}' \
1598  '{unpack_property}' \
1599  '{return_body}\n'.format(
1600  property_name=extra.name,
1601  min1='-1',
1602  num_args=1,
1603  shared_obj=shared_obj,
1604  unpack_property=unpack_property,
1605  return_body=return_body)
1606 
1607  body += setter
1608 
1609  body += '}\n'
1610 
1611  if extra not in ['serialize', 'deserialize']:
1612  body += '\n'
1613 
1614  collector_function += body
1615 
1616  else:
1617  body = textwrap.dedent('''\
1618  {{
1619  checkArguments("{function_name}",nargout,nargin,{len});
1620  ''').format(function_name=collector_func[1].name,
1621  id=self.global_function_id,
1622  len=len(collector_func[1].args.list()))
1623 
1624  body += self._wrapper_unwrap_arguments(collector_func[1].args)[1]
1625  body += self.wrap_collector_function_return(
1626  collector_func[1]) + '\n}\n'
1627 
1628  collector_function += body
1629 
1630  self.global_function_id += 1
1631 
1632  return collector_function
1633 
1634  def mex_function(self):
1635  """
1636  Generate the wrapped MEX function.
1637  """
1638  cases = ''
1639  next_case = None
1640 
1641  for wrapper_id in range(self.wrapper_id):
1642  id_val = self.wrapper_map.get(wrapper_id)
1643  set_next_case = False
1644 
1645  if id_val is None:
1646  id_val = self.wrapper_map.get(wrapper_id + 1)
1647 
1648  if id_val is None:
1649  continue
1650 
1651  set_next_case = True
1652 
1653  cases += textwrap.indent(textwrap.dedent('''\
1654  case {}:
1655  {}(nargout, out, nargin-1, in+1);
1656  break;
1657  ''').format(wrapper_id, next_case if next_case else id_val[3]),
1658  prefix=' ')
1659 
1660  if set_next_case:
1661  next_case = '{}_upcastFromVoid_{}'.format(
1662  id_val[1].name, wrapper_id + 1)
1663  else:
1664  next_case = None
1665 
1666  mex_function = WrapperTemplate.mex_function.format(
1667  module_name=self.module_name, cases=cases)
1668 
1669  return mex_function
1670 
1671  def get_class_name(self, cls):
1672  """Get the name of the class `cls` taking template instantiations into account."""
1673  if cls.instantiations:
1674  class_name_sep = cls.name
1675  else:
1676  class_name_sep = cls.to_cpp()
1677 
1678  class_name = self._format_class_name(cls)
1679 
1680  return class_name, class_name_sep
1681 
1683  """
1684  Generate the preamble of the wrapper file, which includes
1685  the Boost exports, typedefs for collectors, and
1686  the _deleteAllObjects and _RTTIRegister functions.
1687  """
1688  delete_objs = ''
1689  typedef_instances = []
1690  boost_class_export_guid = ''
1691  typedef_collectors = ''
1692  rtti_classes = ''
1693 
1694  for cls in self.classes:
1695  # Check if class is in ignore list.
1696  # If so, then skip
1697  uninstantiated_name = "::".join(cls.namespaces()[1:] + [cls.name])
1698  if uninstantiated_name in self.ignore_classes:
1699  continue
1700 
1701  class_name, class_name_sep = self.get_class_name(cls)
1702 
1703  # If a class has instantiations, then declare the typedef for each instance
1704  if cls.instantiations:
1705  cls_insts = ''
1706  for i, inst in enumerate(cls.instantiations):
1707  if i != 0:
1708  cls_insts += ', '
1709 
1710  cls_insts += self._format_type_name(inst)
1711 
1712  typedef_instances.append('typedef {original_class_name} {class_name_sep};' \
1713  .format(original_class_name=cls.to_cpp(),
1714  class_name_sep=cls.name))
1715 
1716  # Get the Boost exports for serialization
1717  if self.use_boost_serialization and \
1718  cls.original.namespaces() and self._has_serialization(cls):
1719  boost_class_export_guid += 'BOOST_CLASS_EXPORT_GUID({}, "{}");\n'.format(
1720  class_name_sep, class_name)
1721 
1722  # Typedef and declare the collector objects.
1723  typedef_collectors += WrapperTemplate.typdef_collectors.format(
1724  class_name_sep=class_name_sep, class_name=class_name)
1725 
1726  # Generate the _deleteAllObjects method
1727  delete_objs += WrapperTemplate.delete_obj.format(
1728  class_name=class_name)
1729 
1730  if cls.is_virtual:
1731  class_name, class_name_sep = self.get_class_name(cls)
1732  rtti_classes += ' types.insert(std::make_pair(typeid({}).name(), "{}"));\n' \
1733  .format(class_name_sep, class_name)
1734 
1735  # Generate the typedef instances string
1736  typedef_instances = "\n".join(typedef_instances)
1737 
1738  # Generate the full deleteAllObjects function
1739  delete_all_objs = WrapperTemplate.delete_all_objects.format(
1740  delete_objs=delete_objs)
1741 
1742  # Generate the full RTTIRegister function
1743  rtti_register = WrapperTemplate.rtti_register.format(
1744  module_name=self.module_name, rtti_classes=rtti_classes)
1745 
1746  return typedef_instances, boost_class_export_guid, \
1747  typedef_collectors, delete_all_objs, rtti_register
1748 
1749  def generate_wrapper(self, namespace):
1750  """Generate the c++ wrapper."""
1751  assert namespace, "Namespace if empty"
1752 
1753  # Generate the header includes
1754  includes_list = sorted(self.includes,
1755  key=lambda include: include.header)
1756 
1757  # If boost serialization is enabled, include serialization headers
1758  if self.use_boost_serialization:
1759  boost_headers = WrapperTemplate.boost_headers
1760  else:
1761  boost_headers = ""
1762 
1763  includes = textwrap.dedent("""\
1764  {wrapper_file_headers}
1765  {boost_headers}
1766  {includes_list}
1767  """).format(wrapper_file_headers=self.wrapper_file_headers.strip(),
1768  boost_headers=boost_headers,
1769  includes_list='\n'.join(map(str, includes_list)))
1770 
1771  preamble = self.generate_preamble()
1772  typedef_instances, boost_class_export_guid, \
1773  typedef_collectors, delete_all_objs, \
1774  rtti_register = preamble
1775 
1776  ptr_ctor_frag = ''
1777  set_next_case = False
1778 
1779  for idx in range(self.wrapper_id):
1780  id_val = self.wrapper_map.get(idx)
1781  queue_set_next_case = set_next_case
1782 
1783  set_next_case = False
1784 
1785  if id_val is None:
1786  id_val = self.wrapper_map.get(idx + 1)
1787 
1788  if id_val is None:
1789  continue
1790 
1791  set_next_case = True
1792 
1793  ptr_ctor_frag += self.generate_collector_function(idx)
1794 
1795  if queue_set_next_case:
1796  ptr_ctor_frag += self.wrap_collector_function_upcast_from_void(
1797  id_val[1].name, idx, id_val[1].to_cpp())
1798 
1799  wrapper_file = textwrap.dedent('''\
1800  {includes}
1801  {typedef_instances}
1802  {boost_class_export_guid}
1803  {typedefs_collectors}
1804  {delete_all_objs}
1805  {rtti_register}
1806  {pointer_constructor_fragment}{mex_function}''') \
1807  .format(includes=includes,
1808  typedef_instances=typedef_instances,
1809  boost_class_export_guid=boost_class_export_guid,
1810  typedefs_collectors=typedef_collectors,
1811  delete_all_objs=delete_all_objs,
1812  rtti_register=rtti_register,
1813  pointer_constructor_fragment=ptr_ctor_frag,
1814  mex_function=self.mex_function())
1815 
1816  self.content.append((self._wrapper_name() + '.cpp', wrapper_file))
1817 
1818  def wrap_class_serialize_method(self, namespace_name, inst_class):
1819  """
1820  Wrap the serizalize method of the class.
1821  """
1822  class_name = inst_class.name
1823  wrapper_id = self._update_wrapper_id(
1824  (namespace_name, inst_class, 'string_serialize', 'serialize'))
1825 
1826  return WrapperTemplate.class_serialize_method.format(
1827  wrapper=self._wrapper_name(),
1828  wrapper_id=wrapper_id,
1829  class_name=namespace_name + '.' + class_name)
1830 
1832  class_name,
1833  full_name='',
1834  namespace=''):
1835  """
1836  Wrap the serizalize collector function.
1837  """
1838  return WrapperTemplate.collector_function_serialize.format(
1839  class_name=class_name, full_name=full_name, namespace=namespace)
1840 
1842  class_name,
1843  full_name='',
1844  namespace=''):
1845  """
1846  Wrap the deserizalize collector function.
1847  """
1848  return WrapperTemplate.collector_function_deserialize.format(
1849  class_name=class_name, full_name=full_name, namespace=namespace)
1850 
1851  def generate_content(self, cc_content, path):
1852  """
1853  Generate files and folders from matlab wrapper content.
1854 
1855  Args:
1856  cc_content: The content to generate formatted as
1857  (file_name, file_content) or
1858  (folder_name, [(file_name, file_content)])
1859  path: The path to the files parent folder within the main folder
1860  """
1861  for c in cc_content:
1862  if isinstance(c, list):
1863  if len(c) == 0:
1864  continue
1865 
1866  path_to_folder = osp.join(path, c[0][0])
1867 
1868  if not osp.isdir(path_to_folder):
1869  try:
1870  os.makedirs(path_to_folder, exist_ok=True)
1871  except OSError:
1872  pass
1873 
1874  for sub_content in c:
1875  self.generate_content(sub_content[1], path_to_folder)
1876 
1877  elif isinstance(c[1], list):
1878  path_to_folder = osp.join(path, c[0])
1879 
1880  if not osp.isdir(path_to_folder):
1881  try:
1882  os.makedirs(path_to_folder, exist_ok=True)
1883  except OSError:
1884  pass
1885  for sub_content in c[1]:
1886  path_to_file = osp.join(path_to_folder, sub_content[0])
1887  with open(path_to_file, 'w') as f:
1888  f.write(sub_content[1])
1889  else:
1890  path_to_file = osp.join(path, c[0])
1891 
1892  if not osp.isdir(path_to_file):
1893  try:
1894  os.mkdir(path)
1895  except OSError:
1896  pass
1897 
1898  with open(path_to_file, 'w') as f:
1899  f.write(c[1])
1900 
1901  def wrap(self, files, path):
1902  """High level function to wrap the project."""
1903  content = ""
1904  modules = {}
1905  for file in files:
1906  with open(file, 'r') as f:
1907  content += f.read()
1908 
1909  # Parse the contents of the interface file
1910  parsed_result = parser.Module.parseString(content)
1911 
1912  # Instantiate the module
1913  module = instantiator.instantiate_namespace(parsed_result)
1914 
1915  if module.name in modules:
1916  modules[
1917  module.name].content[0].content += module.content[0].content
1918  else:
1919  modules[module.name] = module
1920 
1921  for module in modules.values():
1922  # Wrap the full namespace
1923  self.wrap_namespace(module)
1924  self.generate_wrapper(module)
1925 
1926  # Generate the corresponding .m and .cpp files
1927  self.generate_content(self.content, path)
1928 
1929  return self.content
def wrap_class_deconstructor(self, namespace_name, inst_class)
Definition: wrapper.py:801
static const Eigen::internal::all_t all
def wrap_methods(self, methods, global_funcs=False, global_ns=None)
Definition: wrapper.py:547
def wrap_class_methods(self, namespace_name, inst_class, methods, serialize=(False,))
Definition: wrapper.py:848
def wrap_properties_block(self, class_name, inst_class)
Definition: wrapper.py:726
def wrap_collector_function_serialize(self, class_name, full_name='', namespace='')
Definition: wrapper.py:1834
def wrap_collector_function_return_types(self, return_type, func_id)
Definition: wrapper.py:1240
def __init__(self, module_name, top_module_namespace='', ignore_classes=(), use_boost_serialization=False)
Definition: wrapper.py:37
bool isinstance(handle obj)
Definition: pytypes.h:700
def wrap_collector_function_deserialize(self, class_name, full_name='', namespace='')
Definition: wrapper.py:1844
def add_class(self, instantiated_class)
Definition: wrapper.py:95
def generate_collector_function(self, func_id)
Definition: wrapper.py:1418
def _wrapper_unwrap_arguments(self, args, arg_id=0, instantiated_class=None)
Definition: wrapper.py:384
def generate_content(self, cc_content, path)
Definition: wrapper.py:1851
def wrap_class_serialize_method(self, namespace_name, inst_class)
Definition: wrapper.py:1818
def class_comment(self, instantiated_class)
Definition: wrapper.py:467
def class_serialize_comment(self, class_name, static_methods)
Definition: wrapper.py:442
def _wrap_variable_arguments(self, args, wrap_datatypes=True)
Definition: wrapper.py:224
def _unwrap_argument(self, arg, arg_id=0, instantiated_class=None)
Definition: wrapper.py:344
Definition: pytypes.h:1403
def wrap_collector_function_return(self, method, instantiated_class=None)
Definition: wrapper.py:1339
def wrap_collector_function_shared_return(self, return_type_name, shared_obj, func_id, new_line=True)
Definition: wrapper.py:1229
def wrap_class_constructors(self, namespace_name, inst_class, parent_name, ctors, is_virtual)
Definition: wrapper.py:623
def wrap_static_methods(self, namespace_name, instantiated_class, serialize)
Definition: wrapper.py:940
def wrap_namespace(self, namespace, add_mex_file=True)
Definition: wrapper.py:1159
Definition: pytypes.h:1979
def _expand_default_arguments(method, save_backup=True)
Definition: wrapper.py:151
Double_ range(const Point2_ &p, const Point2_ &q)
std::string format(const std::string &str, const std::vector< std::string > &find, const std::vector< std::string > &replace)
def wrap_global_function(self, function)
Definition: wrapper.py:571
def wrap_collector_function_upcast_from_void(self, class_name, func_id, cpp_name)
Definition: wrapper.py:1411
Container::iterator get(Container &c, Position position)
size_t len(handle h)
Get the length of a Python object.
Definition: pytypes.h:2244
def _format_varargout(cls, return_type, return_type_formatted)
Definition: wrapper.py:833


gtsam
Author(s):
autogenerated on Tue Jul 4 2023 02:40:46