Merge pull request #747 from borglab/feature/wrap-update

Wrapper Update
release/4.3a0
Varun Agrawal 2021-04-19 18:24:58 -04:00 committed by GitHub
commit e9202b836f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 125 additions and 44 deletions

View File

@ -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.

View File

@ -19,6 +19,7 @@ from .template import Template
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:
@ -136,32 +137,6 @@ class Constructor:
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.
@ -256,12 +231,12 @@ class Class:
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 = []
@ -274,7 +249,7 @@ class Class:
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)
@ -311,7 +286,7 @@ class Class:
ctors: List[Constructor],
methods: List[Method],
static_methods: List[StaticMethod],
properties: List[Property],
properties: List[Variable],
operators: List[Operator],
parent: str = '',
):

View File

@ -23,6 +23,7 @@ from .declaration import ForwardDeclaration, Include
from .function import GlobalFunction
from .namespace import Namespace
from .template import TypedefTemplateInstantiation
from .variable import Variable
class Module:
@ -43,6 +44,7 @@ class Module:
^ Class.rule #
^ TypedefTemplateInstantiation.rule #
^ GlobalFunction.rule #
^ Variable.rule #
^ Namespace.rule #
).setParseAction(lambda t: Namespace('', t.asList())) +
stringEnd)

View File

@ -22,6 +22,7 @@ from .function import GlobalFunction
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",
@ -67,6 +68,7 @@ class Namespace:
^ Class.rule #
^ TypedefTemplateInstantiation.rule #
^ GlobalFunction.rule #
^ Variable.rule #
^ rule #
)("content") # BR
+ RBRACE #

View File

@ -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)

View File

@ -186,6 +186,16 @@ class PybindWrapper:
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 = ""
@ -341,6 +351,13 @@ class PybindWrapper:
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 = [

View File

@ -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"

View File

@ -17,7 +17,7 @@ class ClassB {
// check namespace handling
Vector aGlobalFunction();
}
} // namespace ns1
#include <path/to/ns2.h>
namespace ns2 {
@ -38,7 +38,7 @@ class ClassB {
ClassB();
};
}
} // namespace ns3
class ClassC {
ClassC();
@ -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;

View File

@ -18,12 +18,10 @@ import unittest
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):
@ -199,7 +197,8 @@ class TestInterfaceParser(unittest.TestCase):
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
@ -422,7 +421,8 @@ class TestInterfaceParser(unittest.TestCase):
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."""
@ -449,6 +449,23 @@ class TestInterfaceParser(unittest.TestCase):
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("""
@ -505,17 +522,21 @@ class TestInterfaceParser(unittest.TestCase):
};
}
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()