Skip to content
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ configure_package_config_file(
INSTALL_INCLUDE_DIR)

# Set all the install paths
set(GTWRAP_CMAKE_INSTALL_DIR $${INSTALL_CMAKE_DIR})
set(GTWRAP_CMAKE_INSTALL_DIR ${INSTALL_CMAKE_DIR})
set(GTWRAP_LIB_INSTALL_DIR ${INSTALL_LIB_DIR})
set(GTWRAP_BIN_INSTALL_DIR ${INSTALL_BIN_DIR})
set(GTWRAP_INCLUDE_INSTALL_DIR ${INSTALL_INCLUDE_DIR})
Expand Down
4 changes: 1 addition & 3 deletions gtwrap/interface_parser/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ def cpp_typename(self):
Return a Typename with the namespaces and cpp name of this
class.
"""
namespaces_name = self.namespaces()
namespaces_name.append(self.name)
return Typename(namespaces_name)
return Typename(self.name, self.namespaces())

def __repr__(self):
return "Enum: {0}".format(self.name)
3 changes: 1 addition & 2 deletions gtwrap/interface_parser/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@

from typing import Any, Iterable, List, Union

from pyparsing import (Literal, Optional, ParseResults, # type: ignore
delimitedList)
from pyparsing import Literal, Optional, ParseResults, delimitedList

from .template import Template
from .tokens import (COMMA, DEFAULT_ARG, EQUAL, IDENT, LOPBRACK, LPAREN, PAIR,
Expand Down
89 changes: 62 additions & 27 deletions gtwrap/interface_parser/type.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,14 @@ class Typename:
namespaces_name_rule = delimitedList(IDENT, "::")
rule = (
namespaces_name_rule("namespaces_and_name") #
).setParseAction(lambda t: Typename(t))
).setParseAction(lambda t: Typename.from_parse_result(t))

def __init__(self,
t: ParseResults,
name: str,
namespaces: list[str],
instantiations: Sequence[ParseResults] = ()):
self.name = t[-1] # the name is the last element in this list
self.namespaces = t[:-1]
self.name = name
self.namespaces = namespaces

# If the first namespace is empty string, just get rid of it.
if self.namespaces and self.namespaces[0] == '':
Expand All @@ -63,12 +64,38 @@ def __init__(self,
self.instantiations = []

@staticmethod
def from_parse_result(parse_result: Union[str, list]):
def from_parse_result(parse_result: list):
"""Unpack the parsed result to get the Typename instance."""
return parse_result[0]
name = parse_result[-1] # the name is the last element in this list
namespaces = parse_result[:-1]
return Typename(name, namespaces)

def __repr__(self) -> str:
return self.to_cpp()
if self.get_template_args():
templates = f"<{self.get_template_args()}>"
else:
templates = ""

if len(self.namespaces) > 0:
namespaces = "::".join(self.namespaces) + "::"
else:
namespaces = ""

return f"{namespaces}{self.name}{templates}"

def get_template_args(self) -> str:
"""Return the template args as a string, e.g. <double, gtsam::Pose3>."""
return ", ".join([inst.to_cpp() for inst in self.instantiations])

def templated_name(self) -> str:
"""Return the name without namespace and with the template instantiations if any."""
if self.instantiations:
templates = self.get_template_args()
name = f"{self.name}<{templates}>"
else:
name = self.name

return name

def instantiated_name(self) -> str:
"""Get the instantiated name of the type."""
Expand All @@ -84,8 +111,7 @@ def qualified_name(self):
def to_cpp(self) -> str:
"""Generate the C++ code for wrapping."""
if self.instantiations:
cpp_name = self.name + "<{}>".format(", ".join(
[inst.to_cpp() for inst in self.instantiations]))
cpp_name = self.name + f"<{self.get_template_args()}>"
else:
cpp_name = self.name
return '{}{}{}'.format(
Expand Down Expand Up @@ -129,7 +155,7 @@ class BasicType:
rule = (Or(BASIC_TYPES)("typename")).setParseAction(lambda t: BasicType(t))

def __init__(self, t: ParseResults):
self.typename = Typename(t)
self.typename = Typename.from_parse_result(t)


class CustomType:
Expand All @@ -148,7 +174,7 @@ class CustomType:
rule = (Typename.rule("typename")).setParseAction(lambda t: CustomType(t))

def __init__(self, t: ParseResults):
self.typename = Typename(t)
self.typename = Typename.from_parse_result(t)


class Type:
Expand Down Expand Up @@ -226,18 +252,16 @@ def to_cpp(self) -> str:
"""

if self.is_shared_ptr:
typename = "std::shared_ptr<{typename}>".format(
typename=self.get_typename())
typename = f"std::shared_ptr<{self.get_typename()}>"
elif self.is_ptr:
typename = "{typename}*".format(typename=self.typename.to_cpp())
typename = f"{self.typename.to_cpp()}*"
elif self.is_ref:
typename = typename = "{typename}&".format(
typename=self.get_typename())
typename = f"{self.get_typename()}&"
else:
typename = self.get_typename()

return ("{const}{typename}".format(
const="const " if self.is_const else "", typename=typename))
const = "const " if self.is_const else ""
return f"{const}{typename}"


class TemplatedType:
Expand Down Expand Up @@ -265,7 +289,7 @@ def __init__(self, typename: Typename, template_params: List[Type],
is_const: str, is_shared_ptr: str, is_ptr: str, is_ref: str):
instantiations = [param.typename for param in template_params]
# Recreate the typename but with the template params as instantiations.
self.typename = Typename(typename.namespaces + [typename.name],
self.typename = Typename(typename.name, typename.namespaces,
instantiations)

self.template_params = template_params
Expand All @@ -278,22 +302,33 @@ def __init__(self, typename: Typename, template_params: List[Type],
@staticmethod
def from_parse_result(t: ParseResults):
"""Get the TemplatedType from the parser results."""
return TemplatedType(t.typename, t.template_params, t.is_const,
t.is_shared_ptr, t.is_ptr, t.is_ref)
return TemplatedType(t.typename, t.template_params.as_list(),
t.is_const, t.is_shared_ptr, t.is_ptr, t.is_ref)

def __repr__(self):
return "TemplatedType({typename.namespaces}::{typename.name})".format(
typename=self.typename)
return "TemplatedType({typename.namespaces}::{typename.name}<{template_params}>)".format(
typename=self.typename, template_params=self.template_params)

def get_template_params(self):
"""
Get the template args for the type as a string.
E.g. for
```
template <T = {double}, U = {string}>
class Random(){};
```
it returns `<double, string>`.

"""
# Use Type.to_cpp to do the heavy lifting for the template parameters.
return ", ".join([t.to_cpp() for t in self.template_params])

def get_typename(self):
"""
Get the typename of this type without any qualifiers.
E.g. for `const std::vector<double>& indices` this will return `std::vector<double>`.
"""
# Use Type.to_cpp to do the heavy lifting for the template parameters.
template_args = ", ".join([t.to_cpp() for t in self.template_params])

return f"{self.typename.qualified_name()}<{template_args}>"
return f"{self.typename.qualified_name()}<{self.get_template_params()}>"

def to_cpp(self):
"""
Expand Down
64 changes: 35 additions & 29 deletions gtwrap/pybind_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@

import gtwrap.interface_parser as parser
import gtwrap.template_instantiator as instantiator

from gtwrap.interface_parser.function import ArgumentList
from gtwrap.xml_parser.xml_parser import XMLDocParser


class PybindWrapper:
"""
Class to generate binding code for Pybind11 specifically.
Expand All @@ -30,7 +31,7 @@ def __init__(self,
module_name,
top_module_namespaces='',
use_boost_serialization=False,
ignore_classes=(),
ignore_classes=(),
module_template="",
xml_source=""):
self.module_name = module_name
Expand Down Expand Up @@ -76,7 +77,7 @@ def _py_args_names(self, args):
else:
return ''

def _method_args_signature(self, args):
def _method_args_signature(self, args: ArgumentList):
"""Generate the argument types and names as per the method signature."""
cpp_types = args.to_cpp()
names = args.names()
Expand Down Expand Up @@ -260,37 +261,42 @@ def _wrap_method(self,
args_names=', '.join(args_names),
))

ret = ('{prefix}.{cdef}("{py_method}",'
'[]({opt_self}{opt_comma}{args_signature_with_names}){{'
'{function_call}'
'}}'
'{py_args_names}{docstring}){suffix}'.format(
prefix=prefix,
cdef="def_static" if is_static else "def",
py_method=py_method,
opt_self="{cpp_class}* self".format(
cpp_class=cpp_class) if is_method else "",
opt_comma=', ' if is_method and args_names else '',
args_signature_with_names=args_signature_with_names,
function_call=function_call,
py_args_names=py_args_names,
suffix=suffix,
# Try to get the function's docstring from the Doxygen XML.
# If extract_docstring errors or fails to find a docstring, it just prints a warning.
# The incantation repr(...)[1:-1].replace('"', r'\"') replaces newlines with \n
# and " with \" so that the docstring can be put into a C++ string on a single line.
docstring=', "' + repr(self.xml_parser.extract_docstring(self.xml_source, cpp_class, cpp_method, method.args.names()))[1:-1].replace('"', r'\"') + '"'
if self.xml_source != "" else "",
))
result = (
'{prefix}.{cdef}("{py_method}",'
'[]({opt_self}{opt_comma}{args_signature_with_names}){{'
'{function_call}'
'}}'
'{py_args_names}{docstring}){suffix}'.format(
prefix=prefix,
cdef="def_static" if is_static else "def",
py_method=py_method,
opt_self="{cpp_class}* self".format(
cpp_class=cpp_class) if is_method else "",
opt_comma=', '
if is_method and args_signature_with_names else '',
args_signature_with_names=args_signature_with_names,
function_call=function_call,
py_args_names=py_args_names,
suffix=suffix,
# Try to get the function's docstring from the Doxygen XML.
# If extract_docstring errors or fails to find a docstring, it just prints a warning.
# The incantation repr(...)[1:-1].replace('"', r'\"') replaces newlines with \n
# and " with \" so that the docstring can be put into a C++ string on a single line.
docstring=', "' + repr(
self.xml_parser.extract_docstring(
self.xml_source, cpp_class, cpp_method,
method.args.names()))[1:-1].replace('"', r'\"') +
'"' if self.xml_source != "" else "",
))

# Create __repr__ override
# We allow all arguments to .print() and let the compiler handle type mismatches.
if method.name == 'print':
ret = self._wrap_print(ret, method, cpp_class, args_names,
args_signature_with_names, py_args_names,
prefix, suffix)
result = self._wrap_print(result, method, cpp_class, args_names,
args_signature_with_names, py_args_names,
prefix, suffix)

return ret
return result

def wrap_dunder_methods(self,
methods,
Expand Down
20 changes: 11 additions & 9 deletions gtwrap/template_instantiator/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import gtwrap.interface_parser as parser
from gtwrap.template_instantiator.constructor import InstantiatedConstructor
from gtwrap.template_instantiator.helpers import (InstantiationHelper,
from gtwrap.template_instantiator.helpers import (InstantiatedMember,
InstantiationHelper,
instantiate_args_list,
instantiate_name,
instantiate_return_type,
Expand Down Expand Up @@ -57,7 +58,7 @@ def __init__(self, original: parser.Class, instantiations=(), new_name=''):

# Instantiate all instance methods
self.methods = self.instantiate_methods(typenames)

self.dunder_methods = original.dunder_methods

super().__init__(
Expand Down Expand Up @@ -99,9 +100,11 @@ def instantiate_parent_class(self, typenames):
"""

if isinstance(self.original.parent_class, parser.type.TemplatedType):
return instantiate_type(
self.original.parent_class, typenames, self.instantiations,
parser.Typename(self.namespaces())).typename
namespaces = self.namespaces()
typename = parser.Typename(name=namespaces[-1],
namespaces=namespaces[:-1])
return instantiate_type(self.original.parent_class, typenames,
self.instantiations, typename).typename
else:
return self.original.parent_class

Expand Down Expand Up @@ -140,7 +143,7 @@ def instantiate_static_methods(self, typenames):

return instantiated_static_methods

def instantiate_methods(self, typenames):
def instantiate_methods(self, typenames) -> list[InstantiatedMember]:
"""
Instantiate regular methods in the class.

Expand Down Expand Up @@ -225,9 +228,8 @@ def cpp_typename(self):
", ".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)

return parser.Typename(name=name, namespaces=self.namespaces())

def to_cpp(self):
"""Generate the C++ code for wrapping."""
Expand Down
4 changes: 1 addition & 3 deletions gtwrap/template_instantiator/declaration.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,8 @@ def to_cpp(self):
]
name = "{}<{}>".format(self.original.name,
",".join(instantiated_names))
namespaces_name = self.namespaces()
namespaces_name.append(name)
# Leverage Typename to generate the fully qualified C++ name
return parser.Typename(namespaces_name).to_cpp()
return parser.Typename(name=name, namespaces=self.namespaces()).to_cpp()

def __repr__(self):
return "Instantiated {}".format(
Expand Down
2 changes: 1 addition & 1 deletion gtwrap/template_instantiator/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,4 @@ def to_cpp(self):
return ret

def __repr__(self):
return f"Instantiated {super().__repr__}"
return f"Instantiated {super().__repr__()}"
Loading
Loading