Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrapper Update #747

Merged
merged 2 commits into from
Apr 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions wrap/DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ The python wrapper supports keyword arguments for functions/methods. Hence, the
```cpp
template<T, R, S>
```
- Global variables
- Similar to global functions, the wrapper supports global variables as well.
- Currently we only support primitive types, such as `double`, `int`, `string`, etc.
- E.g.
```cpp
const double kGravity = -9.81;
```

- Using classes defined in other modules
- If you are using a class `OtherClass` not wrapped in an interface file, add `class OtherClass;` as a forward declaration to avoid a dependency error. `OtherClass` should be in the same project.
Expand Down
35 changes: 5 additions & 30 deletions wrap/gtwrap/interface_parser/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from .tokens import (CLASS, COLON, CONST, IDENT, LBRACE, LPAREN, RBRACE,
RPAREN, SEMI_COLON, STATIC, VIRTUAL, OPERATOR)
from .type import TemplatedType, Type, Typename
from .variable import Variable


class Method:
Expand Down Expand Up @@ -136,32 +137,6 @@ 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 ^ TemplatedType.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[0] # ParseResult is a list
self.name = name
self.parent = parent

def __repr__(self) -> str:
return '{} {}'.format(self.ctype.__repr__(), self.name)


class Operator:
"""
Rule for parsing operator overloads.
Expand Down Expand Up @@ -256,12 +231,12 @@ class Members:
Rule for all the members within a class.
"""
rule = ZeroOrMore(Constructor.rule ^ StaticMethod.rule ^ Method.rule
^ Property.rule ^ Operator.rule).setParseAction(
^ Variable.rule ^ Operator.rule).setParseAction(
lambda t: Class.Members(t.asList()))

def __init__(self,
members: List[Union[Constructor, Method, StaticMethod,
Property, Operator]]):
Variable, Operator]]):
self.ctors = []
self.methods = []
self.static_methods = []
Expand All @@ -274,7 +249,7 @@ def __init__(self,
self.methods.append(m)
elif isinstance(m, StaticMethod):
self.static_methods.append(m)
elif isinstance(m, Property):
elif isinstance(m, Variable):
self.properties.append(m)
elif isinstance(m, Operator):
self.operators.append(m)
Expand Down Expand Up @@ -311,7 +286,7 @@ def __init__(
ctors: List[Constructor],
methods: List[Method],
static_methods: List[StaticMethod],
properties: List[Property],
properties: List[Variable],
operators: List[Operator],
parent: str = '',
):
Expand Down
2 changes: 2 additions & 0 deletions wrap/gtwrap/interface_parser/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from .function import GlobalFunction
from .namespace import Namespace
from .template import TypedefTemplateInstantiation
from .variable import Variable


class Module:
Expand All @@ -43,6 +44,7 @@ class Module:
^ Class.rule #
^ TypedefTemplateInstantiation.rule #
^ GlobalFunction.rule #
^ Variable.rule #
^ Namespace.rule #
).setParseAction(lambda t: Namespace('', t.asList())) +
stringEnd)
Expand Down
2 changes: 2 additions & 0 deletions wrap/gtwrap/interface_parser/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from .template import TypedefTemplateInstantiation
from .tokens import IDENT, LBRACE, NAMESPACE, RBRACE
from .type import Typename
from .variable import Variable


def find_sub_namespace(namespace: "Namespace",
Expand Down Expand Up @@ -67,6 +68,7 @@ class Namespace:
^ Class.rule #
^ TypedefTemplateInstantiation.rule #
^ GlobalFunction.rule #
^ Variable.rule #
^ rule #
)("content") # BR
+ RBRACE #
Expand Down
53 changes: 53 additions & 0 deletions wrap/gtwrap/interface_parser/variable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""
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++ variables.

Author: Varun Agrawal, Gerry Chen
"""

from pyparsing import Optional, ParseResults

from .tokens import DEFAULT_ARG, EQUAL, IDENT, SEMI_COLON, STATIC
from .type import TemplatedType, Type


class Variable:
"""
Rule to parse variables.
Variables are a combination of Type/TemplatedType and the variable identifier.

E.g.
```
class Hello {
string name; // This is a property variable.
};

Vector3 kGravity; // This is a global variable.
````
"""
rule = ((Type.rule ^ TemplatedType.rule)("ctype") #
+ IDENT("name") #
#TODO(Varun) Add support for non-basic types
+ Optional(EQUAL + (DEFAULT_ARG))("default") #
+ SEMI_COLON #
).setParseAction(lambda t: Variable(t.ctype, t.name, t.default))

def __init__(self,
ctype: Type,
name: str,
default: ParseResults = None,
parent=''):
self.ctype = ctype[0] # ParseResult is a list
self.name = name
if default:
self.default = default[0]

self.parent = parent

def __repr__(self) -> str:
return '{} {}'.format(self.ctype.__repr__(), self.name)
17 changes: 17 additions & 0 deletions wrap/gtwrap/pybind_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,16 @@ def wrap_methods(self, methods, cpp_class, prefix='\n' + ' ' * 8, suffix=''):

return res

def wrap_variable(self, module, module_var, variable, prefix='\n' + ' ' * 8):
"""Wrap a variable that's not part of a class (i.e. global)
"""
return '{prefix}{module_var}.attr("{variable_name}") = {module}{variable_name};'.format(
prefix=prefix,
module=module,
module_var=module_var,
variable_name=variable.name
)

def wrap_properties(self, properties, cpp_class, prefix='\n' + ' ' * 8):
"""Wrap all the properties in the `cpp_class`."""
res = ""
Expand Down Expand Up @@ -341,6 +351,13 @@ def wrap_namespace(self, namespace):
includes += includes_namespace
elif isinstance(element, instantiator.InstantiatedClass):
wrapped += self.wrap_instantiated_class(element)
elif isinstance(element, parser.Variable):
wrapped += self.wrap_variable(
module=self._add_namespaces('', namespaces),
module_var=module_var,
variable=element,
prefix='\n' + ' ' * 4
)

# Global functions.
all_funcs = [
Expand Down
2 changes: 2 additions & 0 deletions wrap/tests/expected/python/namespaces_pybind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,14 @@ PYBIND11_MODULE(namespaces_py, m_) {
py::class_<ns2::ClassC, std::shared_ptr<ns2::ClassC>>(m_ns2, "ClassC")
.def(py::init<>());

m_ns2.attr("aNs2Var") = ns2::aNs2Var;
m_ns2.def("aGlobalFunction",[](){return ns2::aGlobalFunction();});
m_ns2.def("overloadedGlobalFunction",[](const ns1::ClassA& a){return ns2::overloadedGlobalFunction(a);}, py::arg("a"));
m_ns2.def("overloadedGlobalFunction",[](const ns1::ClassA& a, double b){return ns2::overloadedGlobalFunction(a, b);}, py::arg("a"), py::arg("b"));
py::class_<ClassD, std::shared_ptr<ClassD>>(m_, "ClassD")
.def(py::init<>());

m_.attr("aGlobalVar") = aGlobalVar;

#include "python/specializations.h"

Expand Down
10 changes: 6 additions & 4 deletions wrap/tests/fixtures/namespaces.i
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class ClassB {
// check namespace handling
Vector aGlobalFunction();

}
} // namespace ns1

#include <path/to/ns2.h>
namespace ns2 {
Expand All @@ -38,7 +38,7 @@ class ClassB {
ClassB();
};

}
} // namespace ns3

class ClassC {
ClassC();
Expand All @@ -51,10 +51,12 @@ Vector aGlobalFunction();
ns1::ClassA overloadedGlobalFunction(const ns1::ClassA& a);
ns1::ClassA overloadedGlobalFunction(const ns1::ClassA& a, double b);

} //\namespace ns2
int aNs2Var;

} // namespace ns2

class ClassD {
ClassD();
};


int aGlobalVar;
41 changes: 31 additions & 10 deletions wrap/tests/test_interface_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@

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,
Operator, ReturnType, StaticMethod,
TemplatedType, Type,
TypedefTemplateInstantiation, Typename)
from gtwrap.interface_parser import (
ArgumentList, Class, Constructor, ForwardDeclaration, GlobalFunction,
Include, Method, Module, Namespace, Operator, ReturnType, StaticMethod,
TemplatedType, Type, TypedefTemplateInstantiation, Typename, Variable)


class TestInterfaceParser(unittest.TestCase):
Expand Down Expand Up @@ -199,7 +197,8 @@ def test_default_arguments(self):
self.assertEqual(args[3].default, 3.1415)

# Test non-basic type
self.assertEqual(repr(args[4].default.typename), 'gtsam::DefaultKeyFormatter')
self.assertEqual(repr(args[4].default.typename),
'gtsam::DefaultKeyFormatter')
# Test templated type
self.assertEqual(repr(args[5].default.typename), 'std::vector<size_t>')
# Test for allowing list as default argument
Expand Down Expand Up @@ -422,7 +421,8 @@ def test_class_inheritance(self):
self.assertEqual("BetweenFactor", ret.parent_class.name)
self.assertEqual(["gtsam"], ret.parent_class.namespaces)
self.assertEqual("Pose3", ret.parent_class.instantiations[0].name)
self.assertEqual(["gtsam"], ret.parent_class.instantiations[0].namespaces)
self.assertEqual(["gtsam"],
ret.parent_class.instantiations[0].namespaces)

def test_include(self):
"""Test for include statements."""
Expand All @@ -449,6 +449,23 @@ def test_function(self):
self.assertEqual("Values", func.return_type.type1.typename.name)
self.assertEqual(3, len(func.args))

def test_global_variable(self):
"""Test for global variable."""
variable = Variable.rule.parseString("string kGravity;")[0]
self.assertEqual(variable.name, "kGravity")
self.assertEqual(variable.ctype.typename.name, "string")

variable = Variable.rule.parseString("string kGravity = 9.81;")[0]
self.assertEqual(variable.name, "kGravity")
self.assertEqual(variable.ctype.typename.name, "string")
self.assertEqual(variable.default, 9.81)

variable = Variable.rule.parseString("const string kGravity = 9.81;")[0]
self.assertEqual(variable.name, "kGravity")
self.assertEqual(variable.ctype.typename.name, "string")
self.assertTrue(variable.ctype.is_const)
self.assertEqual(variable.default, 9.81)

def test_namespace(self):
"""Test for namespace parsing."""
namespace = Namespace.rule.parseString("""
Expand Down Expand Up @@ -505,17 +522,21 @@ class Class12b {

};
}
int oneVar;
}

class Global{
};
int globalVar;
""")

# print("module: ", module)
# print(dir(module.content[0].name))
self.assertEqual(["one", "Global"], [x.name for x in module.content])
self.assertEqual(["two", "two_dummy", "two"],
self.assertEqual(["one", "Global", "globalVar"],
[x.name for x in module.content])
self.assertEqual(["two", "two_dummy", "two", "oneVar"],
[x.name for x in module.content[0].content])


if __name__ == '__main__':
unittest.main()