Merging 'master' into 'wrap'

release/4.3a0
Varun Agrawal 2021-03-24 16:14:55 -04:00
commit 37b5b667e2
19 changed files with 1144 additions and 974 deletions

View File

@ -33,6 +33,7 @@ jobs:
- name: Build and Test
run: |
cmake .
cd tests
# Use Pytest to run all the tests.
pytest

View File

@ -31,6 +31,8 @@ jobs:
- name: Build and Test
run: |
cmake .
cd tests
# Use Pytest to run all the tests.
pytest

4
wrap/.gitignore vendored
View File

@ -5,4 +5,6 @@ __pycache__/
*.egg-info
# Files related to code coverage stats
**/.coverage
**/.coverage
gtwrap/matlab_wrapper.tpl

View File

@ -21,6 +21,13 @@ else()
set(SCRIPT_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/lib/cmake")
endif()
# Configure the include directory for matlab.h
# This allows the #include to be either gtwrap/matlab.h, wrap/matlab.h or something custom.
if(NOT DEFINED GTWRAP_INCLUDE_NAME)
set(GTWRAP_INCLUDE_NAME "gtwrap" CACHE INTERNAL "Directory name for Matlab includes")
endif()
configure_file(${PROJECT_SOURCE_DIR}/templates/matlab_wrapper.tpl.in ${PROJECT_SOURCE_DIR}/gtwrap/matlab_wrapper.tpl)
# Install CMake scripts to the standard CMake script directory.
install(FILES cmake/gtwrapConfig.cmake cmake/MatlabWrap.cmake
cmake/PybindWrap.cmake cmake/GtwrapUtils.cmake

View File

@ -1,951 +0,0 @@
"""
GTSAM Copyright 2010-2020, Georgia Tech Research Corporation,
Atlanta, Georgia 30332-0415
All Rights Reserved
See LICENSE for the license information
Parser to get the interface of a C++ source file
Author: Duy Nguyen Ta, Fan Jiang, Matthew Sklar, Varun Agrawal, and Frank Dellaert
"""
# pylint: disable=unnecessary-lambda, unused-import, expression-not-assigned, no-else-return, protected-access, too-few-public-methods, too-many-arguments
import sys
from typing import Iterable, Union, Tuple, List
import pyparsing # type: ignore
from pyparsing import (CharsNotIn, Forward, Group, Keyword, Literal, OneOrMore,
Optional, Or, ParseException, ParseResults, ParserElement, Suppress,
Word, ZeroOrMore, alphanums, alphas, cppStyleComment,
delimitedList, empty, nums, stringEnd)
# Fix deepcopy issue with pyparsing
# Can remove once https://github.com/pyparsing/pyparsing/issues/208 is resolved.
if sys.version_info >= (3, 8):
def fixed_get_attr(self, item):
"""
Fix for monkey-patching issue with deepcopy in pyparsing.ParseResults
"""
if item == '__deepcopy__':
raise AttributeError(item)
try:
return self[item]
except KeyError:
return ""
# apply the monkey-patch
pyparsing.ParseResults.__getattr__ = fixed_get_attr
ParserElement.enablePackrat()
# rule for identifiers (e.g. variable names)
IDENT = Word(alphas + '_', alphanums + '_') ^ Word(nums)
RAW_POINTER, SHARED_POINTER, REF = map(Literal, "@*&")
LPAREN, RPAREN, LBRACE, RBRACE, COLON, SEMI_COLON = map(Suppress, "(){}:;")
LOPBRACK, ROPBRACK, COMMA, EQUAL = map(Suppress, "<>,=")
CONST, VIRTUAL, CLASS, STATIC, PAIR, TEMPLATE, TYPEDEF, INCLUDE = map(
Keyword,
[
"const",
"virtual",
"class",
"static",
"pair",
"template",
"typedef",
"#include",
],
)
NAMESPACE = Keyword("namespace")
BASIS_TYPES = map(
Keyword,
[
"void",
"bool",
"unsigned char",
"char",
"int",
"size_t",
"double",
"float",
],
)
class Typename:
"""
Generic type which can be either a basic type or a class type,
similar to C++'s `typename` aka a qualified dependent type.
Contains type name with full namespace and template arguments.
E.g.
```
gtsam::PinholeCamera<gtsam::Cal3S2>
```
will give the name as `PinholeCamera`, namespace as `gtsam`,
and template instantiations as `[gtsam::Cal3S2]`.
Args:
namespaces_and_name: A list representing the namespaces of the type
with the type being the last element.
instantiations: Template parameters to the type.
"""
namespaces_name_rule = delimitedList(IDENT, "::")
instantiation_name_rule = delimitedList(IDENT, "::")
rule = Forward()
rule << (
namespaces_name_rule("namespaces_and_name") #
+ Optional(
(LOPBRACK + delimitedList(rule, ",")("instantiations") + ROPBRACK))
).setParseAction(lambda t: Typename(t.namespaces_and_name, t.instantiations))
def __init__(self,
namespaces_and_name: ParseResults,
instantiations: Union[tuple, list, str, ParseResults] = ()):
self.name = namespaces_and_name[-1] # the name is the last element in this list
self.namespaces = namespaces_and_name[:-1]
if instantiations:
if isinstance(instantiations, Iterable):
self.instantiations = instantiations # type: ignore
else:
self.instantiations = instantiations.asList()
else:
self.instantiations = []
if self.name in ["Matrix", "Vector"] and not self.namespaces:
self.namespaces = ["gtsam"]
@staticmethod
def from_parse_result(parse_result: Union[str, list]):
"""Unpack the parsed result to get the Typename instance."""
return parse_result[0]
def __repr__(self) -> str:
return self.to_cpp()
def instantiated_name(self) -> str:
"""Get the instantiated name of the type."""
res = self.name
for instantiation in self.instantiations:
res += instantiation.instantiated_name()
return res
def to_cpp(self) -> str:
"""Generate the C++ code for wrapping."""
idx = 1 if self.namespaces and not self.namespaces[0] else 0
if self.instantiations:
cpp_name = self.name + "<{}>".format(
", ".join([inst.to_cpp() for inst in self.instantiations])
)
else:
cpp_name = self.name
return '{}{}{}'.format(
"::".join(self.namespaces[idx:]),
"::" if self.namespaces[idx:] else "",
cpp_name,
)
def __eq__(self, other) -> bool:
if isinstance(other, Typename):
return str(self) == str(other)
else:
return False
def __ne__(self, other) -> bool:
res = self.__eq__(other)
return not res
class QualifiedType:
"""Type with qualifiers, such as `const`."""
rule = (
Typename.rule("typename") #
+ Optional(SHARED_POINTER("is_shared_ptr") | RAW_POINTER("is_ptr") | REF("is_ref"))
).setParseAction(
lambda t: QualifiedType(t)
)
def __init__(self, t: ParseResults):
self.typename = Typename.from_parse_result(t.typename)
self.is_shared_ptr = t.is_shared_ptr
self.is_ptr = t.is_ptr
self.is_ref = t.is_ref
class BasisType:
"""
Basis types are the built-in types in C++ such as double, int, char, etc.
When using templates, the basis type will take on the same form as the template.
E.g.
```
template<T = {double}>
void func(const T& x);
```
will give
```
m_.def("CoolFunctionDoubleDouble",[](const double& s) {
return wrap_example::CoolFunction<double,double>(s);
}, py::arg("s"));
```
"""
rule = (
Or(BASIS_TYPES)("typename") #
+ Optional(SHARED_POINTER("is_shared_ptr") | RAW_POINTER("is_ptr") | REF("is_ref")) #
).setParseAction(lambda t: BasisType(t))
def __init__(self, t: ParseResults):
self.typename = Typename([t.typename])
self.is_ptr = t.is_ptr
self.is_shared_ptr = t.is_shared_ptr
self.is_ref = t.is_ref
class Type:
"""The type value that is parsed, e.g. void, string, size_t."""
rule = (
Optional(CONST("is_const")) #
+ (BasisType.rule("basis") | QualifiedType.rule("qualified")) # BR
).setParseAction(lambda t: Type.from_parse_result(t))
def __init__(self, typename: Typename, is_const: str, is_shared_ptr: str,
is_ptr: str, is_ref: str, is_basis: bool):
self.typename = typename
self.is_const = is_const
self.is_shared_ptr = is_shared_ptr
self.is_ptr = is_ptr
self.is_ref = is_ref
self.is_basis = is_basis
@staticmethod
def from_parse_result(t: ParseResults):
"""Return the resulting Type from parsing the source."""
if t.basis:
return Type(
typename=t.basis.typename,
is_const=t.is_const,
is_shared_ptr=t.basis.is_shared_ptr,
is_ptr=t.basis.is_ptr,
is_ref=t.basis.is_ref,
is_basis=True,
)
elif t.qualified:
return Type(
typename=t.qualified.typename,
is_const=t.is_const,
is_shared_ptr=t.qualified.is_shared_ptr,
is_ptr=t.qualified.is_ptr,
is_ref=t.qualified.is_ref,
is_basis=False,
)
else:
raise ValueError("Parse result is not a Type")
def __repr__(self) -> str:
return "{self.typename} " \
"{self.is_const}{self.is_shared_ptr}{self.is_ptr}{self.is_ref}".format(
self=self)
def to_cpp(self, use_boost: bool) -> str:
"""
Generate the C++ code for wrapping.
Treat all pointers as "const shared_ptr<T>&"
Treat Matrix and Vector as "const Matrix&" and "const Vector&" resp.
"""
shared_ptr_ns = "boost" if use_boost else "std"
if self.is_shared_ptr:
# always pass by reference: https://stackoverflow.com/a/8741626/1236990
typename = "{ns}::shared_ptr<{typename}>&".format(
ns=shared_ptr_ns, typename=self.typename.to_cpp())
elif self.is_ptr:
typename = "{typename}*".format(typename=self.typename.to_cpp())
elif self.is_ref or self.typename.name in ["Matrix", "Vector"]:
typename = typename = "{typename}&".format(
typename=self.typename.to_cpp())
else:
typename = self.typename.to_cpp()
return ("{const}{typename}".format(
const="const " if
(self.is_const
or self.typename.name in ["Matrix", "Vector"]) else "",
typename=typename))
class Argument:
"""
The type and name of a function/method argument.
E.g.
```
void sayHello(/*`s` is the method argument with type `const string&`*/ const string& s);
```
"""
rule = (Type.rule("ctype") +
IDENT("name")).setParseAction(lambda t: Argument(t.ctype, t.name))
def __init__(self, ctype: Type, name: str):
self.ctype = ctype
self.name = name
self.parent: Union[ArgumentList, None] = None
def __repr__(self) -> str:
return '{} {}'.format(self.ctype.__repr__(), self.name)
class ArgumentList:
"""
List of Argument objects for all arguments in a function.
"""
rule = Optional(delimitedList(Argument.rule)("args_list")).setParseAction(
lambda t: ArgumentList.from_parse_result(t.args_list)
)
def __init__(self, args_list: List[Argument]):
self.args_list = args_list
for arg in args_list:
arg.parent = self
self.parent: Union[Method, StaticMethod, Template, Constructor,
GlobalFunction, None] = None
@staticmethod
def from_parse_result(parse_result: ParseResults):
"""Return the result of parsing."""
if parse_result:
return ArgumentList(parse_result.asList())
else:
return ArgumentList([])
def __repr__(self) -> str:
return self.args_list.__repr__()
def __len__(self) -> int:
return len(self.args_list)
def args_names(self) -> List[str]:
"""Return a list of the names of all the arguments."""
return [arg.name for arg in self.args_list]
def to_cpp(self, use_boost: bool) -> List[str]:
"""Generate the C++ code for wrapping."""
return [arg.ctype.to_cpp(use_boost) for arg in self.args_list]
class ReturnType:
"""
Rule to parse the return type.
The return type can either be a single type or a pair such as <type1, type2>.
"""
_pair = (
PAIR.suppress() #
+ LOPBRACK #
+ Type.rule("type1") #
+ COMMA #
+ Type.rule("type2") #
+ ROPBRACK #
)
rule = (_pair ^ Type.rule("type1")).setParseAction( # BR
lambda t: ReturnType(t.type1, t.type2))
def __init__(self, type1: Type, type2: Type):
self.type1 = type1
self.type2 = type2
self.parent: Union[Method, StaticMethod, GlobalFunction, None] = None
def is_void(self) -> bool:
"""
Check if the return type is void.
"""
return self.type1.typename.name == "void" and not self.type2
def __repr__(self) -> str:
return "{}{}".format(
self.type1, (', ' + self.type2.__repr__()) if self.type2 else '')
def to_cpp(self, use_boost: bool) -> str:
"""
Generate the C++ code for wrapping.
If there are two return types, we return a pair<>,
otherwise we return the regular return type.
"""
if self.type2:
return "std::pair<{type1},{type2}>".format(
type1=self.type1.to_cpp(use_boost),
type2=self.type2.to_cpp(use_boost))
else:
return self.type1.to_cpp(use_boost)
class Template:
"""
Rule to parse templated values in the interface file.
E.g.
template<POSE> // this is the Template.
class Camera { ... };
"""
class TypenameAndInstantiations:
"""
Rule to parse the template parameters.
template<typename POSE> // POSE is the Instantiation.
"""
rule = (
IDENT("typename") #
+ Optional( #
EQUAL #
+ LBRACE #
+ ((delimitedList(Typename.rule)("instantiations"))) #
+ RBRACE #
)).setParseAction(lambda t: Template.TypenameAndInstantiations(
t.typename, t.instantiations))
def __init__(self, typename: str, instantiations: ParseResults):
self.typename = typename
if instantiations:
self.instantiations = instantiations.asList()
else:
self.instantiations = []
rule = ( # BR
TEMPLATE #
+ LOPBRACK #
+ delimitedList(TypenameAndInstantiations.rule)(
"typename_and_instantiations_list") #
+ ROPBRACK # BR
).setParseAction(
lambda t: Template(t.typename_and_instantiations_list.asList()))
def __init__(self, typename_and_instantiations_list: List[TypenameAndInstantiations]):
ti_list = typename_and_instantiations_list
self.typenames = [ti.typename for ti in ti_list]
self.instantiations = [ti.instantiations for ti in ti_list]
def __repr__(self) -> str:
return "<{0}>".format(", ".join(self.typenames))
class Method:
"""
Rule to parse a method in a class.
E.g.
```
class Hello {
void sayHello() const;
};
```
"""
rule = (
Optional(Template.rule("template")) #
+ ReturnType.rule("return_type") #
+ IDENT("name") #
+ LPAREN #
+ ArgumentList.rule("args_list") #
+ RPAREN #
+ Optional(CONST("is_const")) #
+ SEMI_COLON # BR
).setParseAction(lambda t: Method(t.template, t.name, t.return_type, t.
args_list, t.is_const))
def __init__(self,
template: str,
name: str,
return_type: ReturnType,
args: ArgumentList,
is_const: str,
parent: Union[str, "Class"] = ''):
self.template = template
self.name = name
self.return_type = return_type
self.args = args
self.is_const = is_const
self.parent = parent
def __repr__(self) -> str:
return "Method: {} {} {}({}){}".format(
self.template,
self.return_type,
self.name,
self.args,
self.is_const,
)
class StaticMethod:
"""
Rule to parse all the static methods in a class.
E.g.
```
class Hello {
static void changeGreeting();
};
```
"""
rule = (
STATIC #
+ ReturnType.rule("return_type") #
+ IDENT("name") #
+ LPAREN #
+ ArgumentList.rule("args_list") #
+ RPAREN #
+ SEMI_COLON # BR
).setParseAction(
lambda t: StaticMethod(t.name, t.return_type, t.args_list))
def __init__(self,
name: str,
return_type: ReturnType,
args: ArgumentList,
parent: Union[str, "Class"] = ''):
self.name = name
self.return_type = return_type
self.args = args
self.parent = parent
def __repr__(self) -> str:
return "static {} {}{}".format(self.return_type, self.name, self.args)
def to_cpp(self) -> str:
"""Generate the C++ code for wrapping."""
return self.name
class Constructor:
"""
Rule to parse the class constructor.
Can have 0 or more arguments.
"""
rule = (
IDENT("name") #
+ LPAREN #
+ ArgumentList.rule("args_list") #
+ RPAREN #
+ SEMI_COLON # BR
).setParseAction(lambda t: Constructor(t.name, t.args_list))
def __init__(self, name: str, args: ArgumentList, parent: Union["Class", str] =''):
self.name = name
self.args = args
self.parent = parent
def __repr__(self) -> str:
return "Constructor: {}".format(self.name)
class Property:
"""
Rule to parse the variable members of a class.
E.g.
```
class Hello {
string name; // This is a property.
};
````
"""
rule = (
Type.rule("ctype") #
+ IDENT("name") #
+ SEMI_COLON #
).setParseAction(lambda t: Property(t.ctype, t.name))
def __init__(self, ctype: Type, name: str, parent=''):
self.ctype = ctype
self.name = name
self.parent = parent
def __repr__(self) -> str:
return '{} {}'.format(self.ctype.__repr__(), self.name)
def collect_namespaces(obj):
"""
Get the chain of namespaces from the lowest to highest for the given object.
Args:
obj: Object of type Namespace, Class or InstantiatedClass.
"""
namespaces = []
ancestor = obj.parent
while ancestor and ancestor.name:
namespaces = [ancestor.name] + namespaces
ancestor = ancestor.parent
return [''] + namespaces
class Class:
"""
Rule to parse a class defined in the interface file.
E.g.
```
class Hello {
...
};
```
"""
class MethodsAndProperties:
"""
Rule for all the methods and properties within a class.
"""
rule = ZeroOrMore(
Constructor.rule ^ StaticMethod.rule ^ Method.rule ^ Property.rule
).setParseAction(lambda t: Class.MethodsAndProperties(t.asList()))
def __init__(self, methods_props: List[Union[Constructor, Method,
StaticMethod, Property]]):
self.ctors = []
self.methods = []
self.static_methods = []
self.properties = []
for m in methods_props:
if isinstance(m, Constructor):
self.ctors.append(m)
elif isinstance(m, Method):
self.methods.append(m)
elif isinstance(m, StaticMethod):
self.static_methods.append(m)
elif isinstance(m, Property):
self.properties.append(m)
_parent = COLON + Typename.rule("parent_class")
rule = (
Optional(Template.rule("template")) #
+ Optional(VIRTUAL("is_virtual")) #
+ CLASS #
+ IDENT("name") #
+ Optional(_parent) #
+ LBRACE #
+ MethodsAndProperties.rule("methods_props") #
+ RBRACE #
+ SEMI_COLON # BR
).setParseAction(lambda t: Class(
t.template,
t.is_virtual,
t.name,
t.parent_class,
t.methods_props.ctors,
t.methods_props.methods,
t.methods_props.static_methods,
t.methods_props.properties,
))
def __init__(
self,
template: Template,
is_virtual: str,
name: str,
parent_class: list,
ctors: List[Constructor],
methods: List[Method],
static_methods: List[StaticMethod],
properties: List[Property],
parent: str = '',
):
self.template = template
self.is_virtual = is_virtual
self.name = name
if parent_class:
self.parent_class = Typename.from_parse_result(parent_class)
else:
self.parent_class = ''
self.ctors = ctors
self.methods = methods
self.static_methods = static_methods
self.properties = properties
self.parent = parent
# Make sure ctors' names and class name are the same.
for ctor in self.ctors:
if ctor.name != self.name:
raise ValueError(
"Error in constructor name! {} != {}".format(
ctor.name, self.name
)
)
for ctor in self.ctors:
ctor.parent = self
for method in self.methods:
method.parent = self
for static_method in self.static_methods:
static_method.parent = self
for _property in self.properties:
_property.parent = self
def namespaces(self) -> list:
"""Get the namespaces which this class is nested under as a list."""
return collect_namespaces(self)
def __repr__(self):
return "Class: {self.name}".format(self=self)
class TypedefTemplateInstantiation:
"""
Rule for parsing typedefs (with templates) within the interface file.
E.g.
```
typedef SuperComplexName<Arg1, Arg2, Arg3> EasierName;
```
"""
rule = (
TYPEDEF + Typename.rule("typename") + IDENT("new_name") + SEMI_COLON
).setParseAction(
lambda t: TypedefTemplateInstantiation(
Typename.from_parse_result(t.typename), t.new_name
)
)
def __init__(self, typename: Typename, new_name: str, parent: str=''):
self.typename = typename
self.new_name = new_name
self.parent = parent
class Include:
"""
Rule to parse #include directives.
"""
rule = (
INCLUDE + LOPBRACK + CharsNotIn('>')("header") + ROPBRACK
).setParseAction(lambda t: Include(t.header))
def __init__(self, header: CharsNotIn, parent: str = ''):
self.header = header
self.parent = parent
def __repr__(self) -> str:
return "#include <{}>".format(self.header)
class ForwardDeclaration:
"""
Rule to parse forward declarations in the interface file.
"""
rule = (
Optional(VIRTUAL("is_virtual"))
+ CLASS
+ Typename.rule("name")
+ Optional(COLON + Typename.rule("parent_type"))
+ SEMI_COLON
).setParseAction(
lambda t: ForwardDeclaration(t.name, t.parent_type, t.is_virtual)
)
def __init__(self,
name: Typename,
parent_type: str,
is_virtual: str,
parent: str = ''):
self.name = name
if parent_type:
self.parent_type = Typename.from_parse_result(parent_type)
else:
self.parent_type = ''
self.is_virtual = is_virtual
self.parent = parent
def __repr__(self) -> str:
return "ForwardDeclaration: {} {}({})".format(self.is_virtual,
self.name, self.parent)
class GlobalFunction:
"""
Rule to parse functions defined in the global scope.
"""
rule = (
Optional(Template.rule("template"))
+ ReturnType.rule("return_type") #
+ IDENT("name") #
+ LPAREN #
+ ArgumentList.rule("args_list") #
+ RPAREN #
+ SEMI_COLON #
).setParseAction(lambda t: GlobalFunction(t.name, t.return_type, t.
args_list, t.template))
def __init__(self,
name: str,
return_type: ReturnType,
args_list: ArgumentList,
template: Template,
parent: str = ''):
self.name = name
self.return_type = return_type
self.args = args_list
self.template = template
self.parent = parent
self.return_type.parent = self
self.args.parent = self
def __repr__(self) -> str:
return "GlobalFunction: {}{}({})".format(
self.return_type, self.name, self.args
)
def to_cpp(self) -> str:
"""Generate the C++ code for wrapping."""
return self.name
def find_sub_namespace(namespace: "Namespace",
str_namespaces: List["Namespace"]) -> list:
"""
Get the namespaces nested under `namespace`, filtered by a list of namespace strings.
Args:
namespace: The top-level namespace under which to find sub-namespaces.
str_namespaces: The list of namespace strings to filter against.
"""
if not str_namespaces:
return [namespace]
sub_namespaces = (
ns for ns in namespace.content if isinstance(ns, Namespace)
)
found_namespaces = [
ns for ns in sub_namespaces if ns.name == str_namespaces[0]
]
if not found_namespaces:
return []
res = []
for found_namespace in found_namespaces:
ns = find_sub_namespace(found_namespace, str_namespaces[1:])
if ns:
res += ns
return res
class Namespace:
"""Rule for parsing a namespace in the interface file."""
rule = Forward()
rule << (
NAMESPACE #
+ IDENT("name") #
+ LBRACE #
+ ZeroOrMore( # BR
ForwardDeclaration.rule #
^ Include.rule #
^ Class.rule #
^ TypedefTemplateInstantiation.rule #
^ GlobalFunction.rule #
^ rule #
)("content") # BR
+ RBRACE #
).setParseAction(lambda t: Namespace.from_parse_result(t))
def __init__(self, name: str, content: ZeroOrMore, parent=''):
self.name = name
self.content = content
self.parent = parent
for child in self.content:
child.parent = self
@staticmethod
def from_parse_result(t: ParseResults):
"""Return the result of parsing."""
if t.content:
content = t.content.asList()
else:
content = []
return Namespace(t.name, content)
def find_class_or_function(
self, typename: Typename) -> Union[Class, GlobalFunction]:
"""
Find the Class or GlobalFunction object given its typename.
We have to traverse the tree of namespaces.
"""
found_namespaces = find_sub_namespace(self, typename.namespaces)
res = []
for namespace in found_namespaces:
classes_and_funcs = (c for c in namespace.content
if isinstance(c, (Class, GlobalFunction)))
res += [c for c in classes_and_funcs if c.name == typename.name]
if not res:
raise ValueError(
"Cannot find class {} in module!".format(typename.name)
)
elif len(res) > 1:
raise ValueError(
"Found more than one classes {} in module!".format(
typename.name
)
)
else:
return res[0]
def top_level(self) -> "Namespace":
"""Return the top leve namespace."""
if self.name == '' or self.parent == '':
return self
else:
return self.parent.top_level()
def __repr__(self) -> str:
return "Namespace: {}\n\t{}".format(self.name, self.content)
def full_namespaces(self) -> List["Namespace"]:
"""Get the full namespace list."""
ancestors = collect_namespaces(self)
if self.name:
ancestors.append(self.name)
return ancestors
class Module:
"""
Module is just a global namespace.
E.g.
```
namespace gtsam {
...
}
```
"""
rule = (
ZeroOrMore(ForwardDeclaration.rule #
^ Include.rule #
^ Class.rule #
^ TypedefTemplateInstantiation.rule #
^ GlobalFunction.rule #
^ Namespace.rule #
).setParseAction(lambda t: Namespace('', t.asList())) +
stringEnd)
rule.ignore(cppStyleComment)
@staticmethod
def parseString(s: str) -> ParseResults:
"""Parse the source string and apply the rules."""
return Module.rule.parseString(s)[0]

View File

@ -0,0 +1,43 @@
"""
GTSAM Copyright 2010-2020, Georgia Tech Research Corporation,
Atlanta, Georgia 30332-0415
All Rights Reserved
See LICENSE for the license information
Parser to get the interface of a C++ source file
Author: Duy Nguyen Ta, Fan Jiang, Matthew Sklar, Varun Agrawal, and Frank Dellaert
"""
import sys
import pyparsing
from .classes import *
from .declaration import *
from .function import *
from .module import *
from .namespace import *
from .template import *
from .tokens import *
from .type import *
# Fix deepcopy issue with pyparsing
# Can remove once https://github.com/pyparsing/pyparsing/issues/208 is resolved.
if sys.version_info >= (3, 8):
def fixed_get_attr(self, item):
"""
Fix for monkey-patching issue with deepcopy in pyparsing.ParseResults
"""
if item == '__deepcopy__':
raise AttributeError(item)
try:
return self[item]
except KeyError:
return ""
# apply the monkey-patch
pyparsing.ParseResults.__getattr__ = fixed_get_attr
pyparsing.ParserElement.enablePackrat()

View File

@ -0,0 +1,282 @@
"""
GTSAM Copyright 2010-2020, Georgia Tech Research Corporation,
Atlanta, Georgia 30332-0415
All Rights Reserved
See LICENSE for the license information
Parser classes and rules for parsing C++ classes.
Author: Duy Nguyen Ta, Fan Jiang, Matthew Sklar, Varun Agrawal, and Frank Dellaert
"""
from typing import List, Union
from pyparsing import Optional, ZeroOrMore
from .function import ArgumentList, ReturnType
from .template import Template
from .tokens import (CLASS, COLON, CONST, IDENT, LBRACE, LPAREN, RBRACE,
RPAREN, SEMI_COLON, STATIC, VIRTUAL)
from .type import Type, Typename
class Method:
"""
Rule to parse a method in a class.
E.g.
```
class Hello {
void sayHello() const;
};
```
"""
rule = (
Optional(Template.rule("template")) #
+ ReturnType.rule("return_type") #
+ IDENT("name") #
+ LPAREN #
+ ArgumentList.rule("args_list") #
+ RPAREN #
+ Optional(CONST("is_const")) #
+ SEMI_COLON # BR
).setParseAction(lambda t: Method(t.template, t.name, t.return_type, t.
args_list, t.is_const))
def __init__(self,
template: str,
name: str,
return_type: ReturnType,
args: ArgumentList,
is_const: str,
parent: Union[str, "Class"] = ''):
self.template = template
self.name = name
self.return_type = return_type
self.args = args
self.is_const = is_const
self.parent = parent
def __repr__(self) -> str:
return "Method: {} {} {}({}){}".format(
self.template,
self.return_type,
self.name,
self.args,
self.is_const,
)
class StaticMethod:
"""
Rule to parse all the static methods in a class.
E.g.
```
class Hello {
static void changeGreeting();
};
```
"""
rule = (
STATIC #
+ ReturnType.rule("return_type") #
+ IDENT("name") #
+ LPAREN #
+ ArgumentList.rule("args_list") #
+ RPAREN #
+ SEMI_COLON # BR
).setParseAction(
lambda t: StaticMethod(t.name, t.return_type, t.args_list))
def __init__(self,
name: str,
return_type: ReturnType,
args: ArgumentList,
parent: Union[str, "Class"] = ''):
self.name = name
self.return_type = return_type
self.args = args
self.parent = parent
def __repr__(self) -> str:
return "static {} {}{}".format(self.return_type, self.name, self.args)
def to_cpp(self) -> str:
"""Generate the C++ code for wrapping."""
return self.name
class Constructor:
"""
Rule to parse the class constructor.
Can have 0 or more arguments.
"""
rule = (
IDENT("name") #
+ LPAREN #
+ ArgumentList.rule("args_list") #
+ RPAREN #
+ SEMI_COLON # BR
).setParseAction(lambda t: Constructor(t.name, t.args_list))
def __init__(self,
name: str,
args: ArgumentList,
parent: Union["Class", str] = ''):
self.name = name
self.args = args
self.parent = parent
def __repr__(self) -> str:
return "Constructor: {}".format(self.name)
class Property:
"""
Rule to parse the variable members of a class.
E.g.
```
class Hello {
string name; // This is a property.
};
````
"""
rule = (
Type.rule("ctype") #
+ IDENT("name") #
+ SEMI_COLON #
).setParseAction(lambda t: Property(t.ctype, t.name))
def __init__(self, ctype: Type, name: str, parent=''):
self.ctype = ctype
self.name = name
self.parent = parent
def __repr__(self) -> str:
return '{} {}'.format(self.ctype.__repr__(), self.name)
def collect_namespaces(obj):
"""
Get the chain of namespaces from the lowest to highest for the given object.
Args:
obj: Object of type Namespace, Class or InstantiatedClass.
"""
namespaces = []
ancestor = obj.parent
while ancestor and ancestor.name:
namespaces = [ancestor.name] + namespaces
ancestor = ancestor.parent
return [''] + namespaces
class Class:
"""
Rule to parse a class defined in the interface file.
E.g.
```
class Hello {
...
};
```
"""
class MethodsAndProperties:
"""
Rule for all the methods and properties within a class.
"""
rule = ZeroOrMore(Constructor.rule ^ StaticMethod.rule ^ Method.rule
^ Property.rule).setParseAction(
lambda t: Class.MethodsAndProperties(t.asList()))
def __init__(self, methods_props: List[Union[Constructor, Method,
StaticMethod, Property]]):
self.ctors = []
self.methods = []
self.static_methods = []
self.properties = []
for m in methods_props:
if isinstance(m, Constructor):
self.ctors.append(m)
elif isinstance(m, Method):
self.methods.append(m)
elif isinstance(m, StaticMethod):
self.static_methods.append(m)
elif isinstance(m, Property):
self.properties.append(m)
_parent = COLON + Typename.rule("parent_class")
rule = (
Optional(Template.rule("template")) #
+ Optional(VIRTUAL("is_virtual")) #
+ CLASS #
+ IDENT("name") #
+ Optional(_parent) #
+ LBRACE #
+ MethodsAndProperties.rule("methods_props") #
+ RBRACE #
+ SEMI_COLON # BR
).setParseAction(lambda t: Class(
t.template,
t.is_virtual,
t.name,
t.parent_class,
t.methods_props.ctors,
t.methods_props.methods,
t.methods_props.static_methods,
t.methods_props.properties,
))
def __init__(
self,
template: Template,
is_virtual: str,
name: str,
parent_class: list,
ctors: List[Constructor],
methods: List[Method],
static_methods: List[StaticMethod],
properties: List[Property],
parent: str = '',
):
self.template = template
self.is_virtual = is_virtual
self.name = name
if parent_class:
self.parent_class = Typename.from_parse_result(parent_class)
else:
self.parent_class = ''
self.ctors = ctors
self.methods = methods
self.static_methods = static_methods
self.properties = properties
self.parent = parent
# Make sure ctors' names and class name are the same.
for ctor in self.ctors:
if ctor.name != self.name:
raise ValueError("Error in constructor name! {} != {}".format(
ctor.name, self.name))
for ctor in self.ctors:
ctor.parent = self
for method in self.methods:
method.parent = self
for static_method in self.static_methods:
static_method.parent = self
for _property in self.properties:
_property.parent = self
def namespaces(self) -> list:
"""Get the namespaces which this class is nested under as a list."""
return collect_namespaces(self)
def __repr__(self):
return "Class: {self.name}".format(self=self)

View File

@ -0,0 +1,60 @@
"""
GTSAM Copyright 2010-2020, Georgia Tech Research Corporation,
Atlanta, Georgia 30332-0415
All Rights Reserved
See LICENSE for the license information
Classes and rules for declarations such as includes and forward declarations.
Author: Duy Nguyen Ta, Fan Jiang, Matthew Sklar, Varun Agrawal, and Frank Dellaert
"""
from pyparsing import CharsNotIn, Optional
from .tokens import (CLASS, COLON, INCLUDE, LOPBRACK, ROPBRACK, SEMI_COLON,
VIRTUAL)
from .type import Typename
class Include:
"""
Rule to parse #include directives.
"""
rule = (INCLUDE + LOPBRACK + CharsNotIn('>')("header") +
ROPBRACK).setParseAction(lambda t: Include(t.header))
def __init__(self, header: CharsNotIn, parent: str = ''):
self.header = header
self.parent = parent
def __repr__(self) -> str:
return "#include <{}>".format(self.header)
class ForwardDeclaration:
"""
Rule to parse forward declarations in the interface file.
"""
rule = (Optional(VIRTUAL("is_virtual")) + CLASS + Typename.rule("name") +
Optional(COLON + Typename.rule("parent_type")) +
SEMI_COLON).setParseAction(lambda t: ForwardDeclaration(
t.name, t.parent_type, t.is_virtual))
def __init__(self,
name: Typename,
parent_type: str,
is_virtual: str,
parent: str = ''):
self.name = name
if parent_type:
self.parent_type = Typename.from_parse_result(parent_type)
else:
self.parent_type = ''
self.is_virtual = is_virtual
self.parent = parent
def __repr__(self) -> str:
return "ForwardDeclaration: {} {}({})".format(self.is_virtual,
self.name, self.parent)

View File

@ -0,0 +1,166 @@
"""
GTSAM Copyright 2010-2020, Georgia Tech Research Corporation,
Atlanta, Georgia 30332-0415
All Rights Reserved
See LICENSE for the license information
Parser classes and rules for parsing C++ functions.
Author: Duy Nguyen Ta, Fan Jiang, Matthew Sklar, Varun Agrawal, and Frank Dellaert
"""
from typing import List, Union
from pyparsing import Optional, ParseResults, delimitedList
from .template import Template
from .tokens import (COMMA, IDENT, LOPBRACK, LPAREN, PAIR, ROPBRACK, RPAREN,
SEMI_COLON)
from .type import Type
class Argument:
"""
The type and name of a function/method argument.
E.g.
```
void sayHello(/*`s` is the method argument with type `const string&`*/ const string& s);
```
"""
rule = (Type.rule("ctype") +
IDENT("name")).setParseAction(lambda t: Argument(t.ctype, t.name))
def __init__(self, ctype: Type, name: str):
self.ctype = ctype
self.name = name
self.parent: Union[ArgumentList, None] = None
def __repr__(self) -> str:
return '{} {}'.format(self.ctype.__repr__(), self.name)
class ArgumentList:
"""
List of Argument objects for all arguments in a function.
"""
rule = Optional(delimitedList(Argument.rule)("args_list")).setParseAction(
lambda t: ArgumentList.from_parse_result(t.args_list))
def __init__(self, args_list: List[Argument]):
self.args_list = args_list
for arg in args_list:
arg.parent = self
# The parent object which contains the argument list
# E.g. Method, StaticMethod, Template, Constructor, GlobalFunction
self.parent = None
@staticmethod
def from_parse_result(parse_result: ParseResults):
"""Return the result of parsing."""
if parse_result:
return ArgumentList(parse_result.asList())
else:
return ArgumentList([])
def __repr__(self) -> str:
return self.args_list.__repr__()
def __len__(self) -> int:
return len(self.args_list)
def args_names(self) -> List[str]:
"""Return a list of the names of all the arguments."""
return [arg.name for arg in self.args_list]
def to_cpp(self, use_boost: bool) -> List[str]:
"""Generate the C++ code for wrapping."""
return [arg.ctype.to_cpp(use_boost) for arg in self.args_list]
class ReturnType:
"""
Rule to parse the return type.
The return type can either be a single type or a pair such as <type1, type2>.
"""
_pair = (
PAIR.suppress() #
+ LOPBRACK #
+ Type.rule("type1") #
+ COMMA #
+ Type.rule("type2") #
+ ROPBRACK #
)
rule = (_pair ^ Type.rule("type1")).setParseAction( # BR
lambda t: ReturnType(t.type1, t.type2))
def __init__(self, type1: Type, type2: Type):
self.type1 = type1
self.type2 = type2
# The parent object which contains the return type
# E.g. Method, StaticMethod, Template, Constructor, GlobalFunction
self.parent = None
def is_void(self) -> bool:
"""
Check if the return type is void.
"""
return self.type1.typename.name == "void" and not self.type2
def __repr__(self) -> str:
return "{}{}".format(
self.type1, (', ' + self.type2.__repr__()) if self.type2 else '')
def to_cpp(self, use_boost: bool) -> str:
"""
Generate the C++ code for wrapping.
If there are two return types, we return a pair<>,
otherwise we return the regular return type.
"""
if self.type2:
return "std::pair<{type1},{type2}>".format(
type1=self.type1.to_cpp(use_boost),
type2=self.type2.to_cpp(use_boost))
else:
return self.type1.to_cpp(use_boost)
class GlobalFunction:
"""
Rule to parse functions defined in the global scope.
"""
rule = (
Optional(Template.rule("template")) + ReturnType.rule("return_type") #
+ IDENT("name") #
+ LPAREN #
+ ArgumentList.rule("args_list") #
+ RPAREN #
+ SEMI_COLON #
).setParseAction(lambda t: GlobalFunction(t.name, t.return_type, t.
args_list, t.template))
def __init__(self,
name: str,
return_type: ReturnType,
args_list: ArgumentList,
template: Template,
parent: str = ''):
self.name = name
self.return_type = return_type
self.args = args_list
self.template = template
self.parent = parent
self.return_type.parent = self
self.args.parent = self
def __repr__(self) -> str:
return "GlobalFunction: {}{}({})".format(self.return_type, self.name,
self.args)
def to_cpp(self) -> str:
"""Generate the C++ code for wrapping."""
return self.name

View File

@ -0,0 +1,55 @@
"""
GTSAM Copyright 2010-2020, Georgia Tech Research Corporation,
Atlanta, Georgia 30332-0415
All Rights Reserved
See LICENSE for the license information
Rules and classes for parsing a module.
Author: Duy Nguyen Ta, Fan Jiang, Matthew Sklar, Varun Agrawal, and Frank Dellaert
"""
# pylint: disable=unnecessary-lambda, unused-import, expression-not-assigned, no-else-return, protected-access, too-few-public-methods, too-many-arguments
import sys
import pyparsing # type: ignore
from pyparsing import (ParserElement, ParseResults, ZeroOrMore,
cppStyleComment, stringEnd)
from .classes import Class
from .declaration import ForwardDeclaration, Include
from .function import GlobalFunction
from .namespace import Namespace
from .template import TypedefTemplateInstantiation
class Module:
"""
Module is just a global namespace.
E.g.
```
namespace gtsam {
...
}
```
"""
rule = (
ZeroOrMore(ForwardDeclaration.rule #
^ Include.rule #
^ Class.rule #
^ TypedefTemplateInstantiation.rule #
^ GlobalFunction.rule #
^ Namespace.rule #
).setParseAction(lambda t: Namespace('', t.asList())) +
stringEnd)
rule.ignore(cppStyleComment)
@staticmethod
def parseString(s: str) -> ParseResults:
"""Parse the source string and apply the rules."""
return Module.rule.parseString(s)[0]

View File

@ -0,0 +1,128 @@
"""
GTSAM Copyright 2010-2020, Georgia Tech Research Corporation,
Atlanta, Georgia 30332-0415
All Rights Reserved
See LICENSE for the license information
Classes and rules to parse a namespace.
Author: Duy Nguyen Ta, Fan Jiang, Matthew Sklar, Varun Agrawal, and Frank Dellaert
"""
# pylint: disable=unnecessary-lambda, expression-not-assigned
from typing import List, Union
from pyparsing import Forward, ParseResults, ZeroOrMore
from .classes import Class, collect_namespaces
from .declaration import ForwardDeclaration, Include
from .function import GlobalFunction
from .template import TypedefTemplateInstantiation
from .tokens import IDENT, LBRACE, NAMESPACE, RBRACE
from .type import Typename
def find_sub_namespace(namespace: "Namespace",
str_namespaces: List["Namespace"]) -> list:
"""
Get the namespaces nested under `namespace`, filtered by a list of namespace strings.
Args:
namespace: The top-level namespace under which to find sub-namespaces.
str_namespaces: The list of namespace strings to filter against.
"""
if not str_namespaces:
return [namespace]
sub_namespaces = (ns for ns in namespace.content
if isinstance(ns, Namespace))
found_namespaces = [
ns for ns in sub_namespaces if ns.name == str_namespaces[0]
]
if not found_namespaces:
return []
res = []
for found_namespace in found_namespaces:
ns = find_sub_namespace(found_namespace, str_namespaces[1:])
if ns:
res += ns
return res
class Namespace:
"""Rule for parsing a namespace in the interface file."""
rule = Forward()
rule << (
NAMESPACE #
+ IDENT("name") #
+ LBRACE #
+ ZeroOrMore( # BR
ForwardDeclaration.rule #
^ Include.rule #
^ Class.rule #
^ TypedefTemplateInstantiation.rule #
^ GlobalFunction.rule #
^ rule #
)("content") # BR
+ RBRACE #
).setParseAction(lambda t: Namespace.from_parse_result(t))
def __init__(self, name: str, content: ZeroOrMore, parent=''):
self.name = name
self.content = content
self.parent = parent
for child in self.content:
child.parent = self
@staticmethod
def from_parse_result(t: ParseResults):
"""Return the result of parsing."""
if t.content:
content = t.content.asList()
else:
content = []
return Namespace(t.name, content)
def find_class_or_function(
self, typename: Typename) -> Union[Class, GlobalFunction]:
"""
Find the Class or GlobalFunction object given its typename.
We have to traverse the tree of namespaces.
"""
found_namespaces = find_sub_namespace(self, typename.namespaces)
res = []
for namespace in found_namespaces:
classes_and_funcs = (c for c in namespace.content
if isinstance(c, (Class, GlobalFunction)))
res += [c for c in classes_and_funcs if c.name == typename.name]
if not res:
raise ValueError("Cannot find class {} in module!".format(
typename.name))
elif len(res) > 1:
raise ValueError(
"Found more than one classes {} in module!".format(
typename.name))
else:
return res[0]
def top_level(self) -> "Namespace":
"""Return the top leve namespace."""
if self.name == '' or self.parent == '':
return self
else:
return self.parent.top_level()
def __repr__(self) -> str:
return "Namespace: {}\n\t{}".format(self.name, self.content)
def full_namespaces(self) -> List["Namespace"]:
"""Get the full namespace list."""
ancestors = collect_namespaces(self)
if self.name:
ancestors.append(self.name)
return ancestors

View File

@ -0,0 +1,90 @@
"""
GTSAM Copyright 2010-2020, Georgia Tech Research Corporation,
Atlanta, Georgia 30332-0415
All Rights Reserved
See LICENSE for the license information
Classes and rules for parsing C++ templates and typedefs for template instantiations.
Author: Duy Nguyen Ta, Fan Jiang, Matthew Sklar, Varun Agrawal, and Frank Dellaert
"""
from typing import List
from pyparsing import Optional, ParseResults, delimitedList
from .tokens import (EQUAL, IDENT, LBRACE, LOPBRACK, RBRACE, ROPBRACK,
SEMI_COLON, TEMPLATE, TYPEDEF)
from .type import Typename
class Template:
"""
Rule to parse templated values in the interface file.
E.g.
template<POSE> // this is the Template.
class Camera { ... };
"""
class TypenameAndInstantiations:
"""
Rule to parse the template parameters.
template<typename POSE> // POSE is the Instantiation.
"""
rule = (
IDENT("typename") #
+ Optional( #
EQUAL #
+ LBRACE #
+ ((delimitedList(Typename.rule)("instantiations"))) #
+ RBRACE #
)).setParseAction(lambda t: Template.TypenameAndInstantiations(
t.typename, t.instantiations))
def __init__(self, typename: str, instantiations: ParseResults):
self.typename = typename
if instantiations:
self.instantiations = instantiations.asList()
else:
self.instantiations = []
rule = ( # BR
TEMPLATE #
+ LOPBRACK #
+ delimitedList(TypenameAndInstantiations.rule)(
"typename_and_instantiations_list") #
+ ROPBRACK # BR
).setParseAction(
lambda t: Template(t.typename_and_instantiations_list.asList()))
def __init__(
self,
typename_and_instantiations_list: List[TypenameAndInstantiations]):
ti_list = typename_and_instantiations_list
self.typenames = [ti.typename for ti in ti_list]
self.instantiations = [ti.instantiations for ti in ti_list]
def __repr__(self) -> str:
return "<{0}>".format(", ".join(self.typenames))
class TypedefTemplateInstantiation:
"""
Rule for parsing typedefs (with templates) within the interface file.
E.g.
```
typedef SuperComplexName<Arg1, Arg2, Arg3> EasierName;
```
"""
rule = (TYPEDEF + Typename.rule("typename") + IDENT("new_name") +
SEMI_COLON).setParseAction(lambda t: TypedefTemplateInstantiation(
Typename.from_parse_result(t.typename), t.new_name))
def __init__(self, typename: Typename, new_name: str, parent: str = ''):
self.typename = typename
self.new_name = new_name
self.parent = parent

View File

@ -0,0 +1,48 @@
"""
GTSAM Copyright 2010-2020, Georgia Tech Research Corporation,
Atlanta, Georgia 30332-0415
All Rights Reserved
See LICENSE for the license information
All the token definitions.
Author: Duy Nguyen Ta, Fan Jiang, Matthew Sklar, Varun Agrawal, and Frank Dellaert
"""
from pyparsing import Keyword, Literal, Suppress, Word, alphanums, alphas, nums
# rule for identifiers (e.g. variable names)
IDENT = Word(alphas + '_', alphanums + '_') ^ Word(nums)
RAW_POINTER, SHARED_POINTER, REF = map(Literal, "@*&")
LPAREN, RPAREN, LBRACE, RBRACE, COLON, SEMI_COLON = map(Suppress, "(){}:;")
LOPBRACK, ROPBRACK, COMMA, EQUAL = map(Suppress, "<>,=")
CONST, VIRTUAL, CLASS, STATIC, PAIR, TEMPLATE, TYPEDEF, INCLUDE = map(
Keyword,
[
"const",
"virtual",
"class",
"static",
"pair",
"template",
"typedef",
"#include",
],
)
NAMESPACE = Keyword("namespace")
BASIS_TYPES = map(
Keyword,
[
"void",
"bool",
"unsigned char",
"char",
"int",
"size_t",
"double",
"float",
],
)

View File

@ -0,0 +1,232 @@
"""
GTSAM Copyright 2010-2020, Georgia Tech Research Corporation,
Atlanta, Georgia 30332-0415
All Rights Reserved
See LICENSE for the license information
Define the parser rules and classes for various C++ types.
Author: Duy Nguyen Ta, Fan Jiang, Matthew Sklar, Varun Agrawal, and Frank Dellaert
"""
# pylint: disable=unnecessary-lambda, expression-not-assigned
from typing import Iterable, Union
from pyparsing import Forward, Optional, Or, ParseResults, delimitedList
from .tokens import (BASIS_TYPES, CONST, IDENT, LOPBRACK, RAW_POINTER, REF,
ROPBRACK, SHARED_POINTER)
class Typename:
"""
Generic type which can be either a basic type or a class type,
similar to C++'s `typename` aka a qualified dependent type.
Contains type name with full namespace and template arguments.
E.g.
```
gtsam::PinholeCamera<gtsam::Cal3S2>
```
will give the name as `PinholeCamera`, namespace as `gtsam`,
and template instantiations as `[gtsam::Cal3S2]`.
Args:
namespaces_and_name: A list representing the namespaces of the type
with the type being the last element.
instantiations: Template parameters to the type.
"""
namespaces_name_rule = delimitedList(IDENT, "::")
instantiation_name_rule = delimitedList(IDENT, "::")
rule = Forward()
rule << (
namespaces_name_rule("namespaces_and_name") #
+ Optional(
(LOPBRACK + delimitedList(rule, ",")
("instantiations") + ROPBRACK))).setParseAction(
lambda t: Typename(t.namespaces_and_name, t.instantiations))
def __init__(self,
namespaces_and_name: ParseResults,
instantiations: Union[tuple, list, str, ParseResults] = ()):
self.name = namespaces_and_name[
-1] # the name is the last element in this list
self.namespaces = namespaces_and_name[:-1]
if instantiations:
if isinstance(instantiations, Iterable):
self.instantiations = instantiations # type: ignore
else:
self.instantiations = instantiations.asList()
else:
self.instantiations = []
if self.name in ["Matrix", "Vector"] and not self.namespaces:
self.namespaces = ["gtsam"]
@staticmethod
def from_parse_result(parse_result: Union[str, list]):
"""Unpack the parsed result to get the Typename instance."""
return parse_result[0]
def __repr__(self) -> str:
return self.to_cpp()
def instantiated_name(self) -> str:
"""Get the instantiated name of the type."""
res = self.name
for instantiation in self.instantiations:
res += instantiation.instantiated_name()
return res
def to_cpp(self) -> str:
"""Generate the C++ code for wrapping."""
idx = 1 if self.namespaces and not self.namespaces[0] else 0
if self.instantiations:
cpp_name = self.name + "<{}>".format(", ".join(
[inst.to_cpp() for inst in self.instantiations]))
else:
cpp_name = self.name
return '{}{}{}'.format(
"::".join(self.namespaces[idx:]),
"::" if self.namespaces[idx:] else "",
cpp_name,
)
def __eq__(self, other) -> bool:
if isinstance(other, Typename):
return str(self) == str(other)
else:
return False
def __ne__(self, other) -> bool:
res = self.__eq__(other)
return not res
class QualifiedType:
"""Type with qualifiers, such as `const`."""
rule = (
Typename.rule("typename") #
+ Optional(
SHARED_POINTER("is_shared_ptr") | RAW_POINTER("is_ptr")
| REF("is_ref"))).setParseAction(lambda t: QualifiedType(t))
def __init__(self, t: ParseResults):
self.typename = Typename.from_parse_result(t.typename)
self.is_shared_ptr = t.is_shared_ptr
self.is_ptr = t.is_ptr
self.is_ref = t.is_ref
class BasisType:
"""
Basis types are the built-in types in C++ such as double, int, char, etc.
When using templates, the basis type will take on the same form as the template.
E.g.
```
template<T = {double}>
void func(const T& x);
```
will give
```
m_.def("CoolFunctionDoubleDouble",[](const double& s) {
return wrap_example::CoolFunction<double,double>(s);
}, py::arg("s"));
```
"""
rule = (
Or(BASIS_TYPES)("typename") #
+ Optional(
SHARED_POINTER("is_shared_ptr") | RAW_POINTER("is_ptr")
| REF("is_ref")) #
).setParseAction(lambda t: BasisType(t))
def __init__(self, t: ParseResults):
self.typename = Typename([t.typename])
self.is_ptr = t.is_ptr
self.is_shared_ptr = t.is_shared_ptr
self.is_ref = t.is_ref
class Type:
"""The type value that is parsed, e.g. void, string, size_t."""
rule = (
Optional(CONST("is_const")) #
+ (BasisType.rule("basis") | QualifiedType.rule("qualified")) # BR
).setParseAction(lambda t: Type.from_parse_result(t))
def __init__(self, typename: Typename, is_const: str, is_shared_ptr: str,
is_ptr: str, is_ref: str, is_basis: bool):
self.typename = typename
self.is_const = is_const
self.is_shared_ptr = is_shared_ptr
self.is_ptr = is_ptr
self.is_ref = is_ref
self.is_basis = is_basis
@staticmethod
def from_parse_result(t: ParseResults):
"""Return the resulting Type from parsing the source."""
if t.basis:
return Type(
typename=t.basis.typename,
is_const=t.is_const,
is_shared_ptr=t.basis.is_shared_ptr,
is_ptr=t.basis.is_ptr,
is_ref=t.basis.is_ref,
is_basis=True,
)
elif t.qualified:
return Type(
typename=t.qualified.typename,
is_const=t.is_const,
is_shared_ptr=t.qualified.is_shared_ptr,
is_ptr=t.qualified.is_ptr,
is_ref=t.qualified.is_ref,
is_basis=False,
)
else:
raise ValueError("Parse result is not a Type")
def __repr__(self) -> str:
return "{self.typename} " \
"{self.is_const}{self.is_shared_ptr}{self.is_ptr}{self.is_ref}".format(
self=self)
def to_cpp(self, use_boost: bool) -> str:
"""
Generate the C++ code for wrapping.
Treat all pointers as "const shared_ptr<T>&"
Treat Matrix and Vector as "const Matrix&" and "const Vector&" resp.
"""
shared_ptr_ns = "boost" if use_boost else "std"
if self.is_shared_ptr:
# always pass by reference: https://stackoverflow.com/a/8741626/1236990
typename = "{ns}::shared_ptr<{typename}>&".format(
ns=shared_ptr_ns, typename=self.typename.to_cpp())
elif self.is_ptr:
typename = "{typename}*".format(typename=self.typename.to_cpp())
elif self.is_ref or self.typename.name in ["Matrix", "Vector"]:
typename = typename = "{typename}&".format(
typename=self.typename.to_cpp())
else:
typename = self.typename.to_cpp()
return ("{const}{typename}".format(
const="const " if
(self.is_const
or self.typename.name in ["Matrix", "Vector"]) else "",
typename=typename))

View File

@ -76,6 +76,10 @@ class MatlabWrapper(object):
# Files and their content
content: List[str] = []
# Ensure the template file is always picked up from the correct directory.
dir_path = osp.dirname(osp.realpath(__file__))
wrapper_file_template = osp.join(dir_path, "matlab_wrapper.tpl")
def __init__(self,
module,
module_name,
@ -664,10 +668,8 @@ class MatlabWrapper(object):
"""Generate the C++ file for the wrapper."""
file_name = self._wrapper_name() + '.cpp'
wrapper_file = textwrap.dedent('''\
# include <gtwrap/matlab.h>
# include <map>
''')
with open(self.wrapper_file_template) as f:
wrapper_file = f.read()
return file_name, wrapper_file

View File

@ -13,7 +13,6 @@ Author: Duy Nguyen Ta, Fan Jiang, Matthew Sklar, Varun Agrawal, and Frank Dellae
# pylint: disable=too-many-arguments, too-many-instance-attributes, no-self-use, no-else-return, too-many-arguments, unused-format-string-argument, line-too-long
import re
import textwrap
import gtwrap.interface_parser as parser
import gtwrap.template_instantiator as instantiator

View File

@ -0,0 +1,2 @@
# include <${GTWRAP_INCLUDE_NAME}/matlab.h>
# include <map>

View File

@ -16,16 +16,13 @@ import os
import sys
import unittest
from pyparsing import ParseException
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from gtwrap.interface_parser import (ArgumentList, Class, Constructor,
ForwardDeclaration, GlobalFunction,
Include, Method, Module, Namespace,
ReturnType, StaticMethod, Type,
TypedefTemplateInstantiation, Typename,
find_sub_namespace)
TypedefTemplateInstantiation, Typename)
class TestInterfaceParser(unittest.TestCase):
@ -35,7 +32,8 @@ class TestInterfaceParser(unittest.TestCase):
typename = Typename.rule.parseString("size_t")[0]
self.assertEqual("size_t", typename.name)
typename = Typename.rule.parseString("gtsam::PinholeCamera<gtsam::Cal3S2>")[0]
typename = Typename.rule.parseString(
"gtsam::PinholeCamera<gtsam::Cal3S2>")[0]
self.assertEqual("PinholeCamera", typename.name)
self.assertEqual(["gtsam"], typename.namespaces)
self.assertEqual("Cal3S2", typename.instantiations[0].name)

View File

@ -13,7 +13,9 @@ import sys
import unittest
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.append(os.path.normpath(os.path.abspath(os.path.join(__file__, '../../../build/wrap'))))
sys.path.append(
os.path.normpath(
os.path.abspath(os.path.join(__file__, '../../../build/wrap'))))
import gtwrap.interface_parser as parser
import gtwrap.template_instantiator as instantiator
@ -38,14 +40,12 @@ class TestWrap(unittest.TestCase):
module_template = template_file.read()
# Create Pybind wrapper instance
wrapper = PybindWrapper(
module=module,
module_name=module_name,
use_boost=False,
top_module_namespaces=[''],
ignore_classes=[''],
module_template=module_template
)
wrapper = PybindWrapper(module=module,
module_name=module_name,
use_boost=False,
top_module_namespaces=[''],
ignore_classes=[''],
module_template=module_template)
cc_content = wrapper.wrap()
@ -70,7 +70,8 @@ class TestWrap(unittest.TestCase):
output = self.wrap_content(content, 'geometry_py', 'actual-python')
expected = path.join(self.TEST_DIR, 'expected-python/geometry_pybind.cpp')
expected = path.join(self.TEST_DIR,
'expected-python/geometry_pybind.cpp')
success = filecmp.cmp(output, expected)
if not success:
@ -86,14 +87,17 @@ class TestWrap(unittest.TestCase):
with open(os.path.join(self.TEST_DIR, 'testNamespaces.h'), 'r') as f:
content = f.read()
output = self.wrap_content(content, 'testNamespaces_py', 'actual-python')
output = self.wrap_content(content, 'testNamespaces_py',
'actual-python')
expected = path.join(self.TEST_DIR, 'expected-python/testNamespaces_py.cpp')
expected = path.join(self.TEST_DIR,
'expected-python/testNamespaces_py.cpp')
success = filecmp.cmp(output, expected)
if not success:
os.system("diff {} {}".format(output, expected))
self.assertTrue(success)
if __name__ == '__main__':
unittest.main()