Merging 'master' into 'wrap'

release/4.3a0
Varun Agrawal 2021-04-19 16:09:06 -04:00
commit f1cb194ce4
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 ```cpp
template<T, R, S> 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 - 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. - 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, from .tokens import (CLASS, COLON, CONST, IDENT, LBRACE, LPAREN, RBRACE,
RPAREN, SEMI_COLON, STATIC, VIRTUAL, OPERATOR) RPAREN, SEMI_COLON, STATIC, VIRTUAL, OPERATOR)
from .type import TemplatedType, Type, Typename from .type import TemplatedType, Type, Typename
from .variable import Variable
class Method: class Method:
@ -136,32 +137,6 @@ class Constructor:
return "Constructor: {}".format(self.name) 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: class Operator:
""" """
Rule for parsing operator overloads. Rule for parsing operator overloads.
@ -256,12 +231,12 @@ class Class:
Rule for all the members within a class. Rule for all the members within a class.
""" """
rule = ZeroOrMore(Constructor.rule ^ StaticMethod.rule ^ Method.rule rule = ZeroOrMore(Constructor.rule ^ StaticMethod.rule ^ Method.rule
^ Property.rule ^ Operator.rule).setParseAction( ^ Variable.rule ^ Operator.rule).setParseAction(
lambda t: Class.Members(t.asList())) lambda t: Class.Members(t.asList()))
def __init__(self, def __init__(self,
members: List[Union[Constructor, Method, StaticMethod, members: List[Union[Constructor, Method, StaticMethod,
Property, Operator]]): Variable, Operator]]):
self.ctors = [] self.ctors = []
self.methods = [] self.methods = []
self.static_methods = [] self.static_methods = []
@ -274,7 +249,7 @@ class Class:
self.methods.append(m) self.methods.append(m)
elif isinstance(m, StaticMethod): elif isinstance(m, StaticMethod):
self.static_methods.append(m) self.static_methods.append(m)
elif isinstance(m, Property): elif isinstance(m, Variable):
self.properties.append(m) self.properties.append(m)
elif isinstance(m, Operator): elif isinstance(m, Operator):
self.operators.append(m) self.operators.append(m)
@ -311,7 +286,7 @@ class Class:
ctors: List[Constructor], ctors: List[Constructor],
methods: List[Method], methods: List[Method],
static_methods: List[StaticMethod], static_methods: List[StaticMethod],
properties: List[Property], properties: List[Variable],
operators: List[Operator], operators: List[Operator],
parent: str = '', parent: str = '',
): ):

View File

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

View File

@ -22,6 +22,7 @@ from .function import GlobalFunction
from .template import TypedefTemplateInstantiation from .template import TypedefTemplateInstantiation
from .tokens import IDENT, LBRACE, NAMESPACE, RBRACE from .tokens import IDENT, LBRACE, NAMESPACE, RBRACE
from .type import Typename from .type import Typename
from .variable import Variable
def find_sub_namespace(namespace: "Namespace", def find_sub_namespace(namespace: "Namespace",
@ -67,6 +68,7 @@ class Namespace:
^ Class.rule # ^ Class.rule #
^ TypedefTemplateInstantiation.rule # ^ TypedefTemplateInstantiation.rule #
^ GlobalFunction.rule # ^ GlobalFunction.rule #
^ Variable.rule #
^ rule # ^ rule #
)("content") # BR )("content") # BR
+ RBRACE # + 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 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): def wrap_properties(self, properties, cpp_class, prefix='\n' + ' ' * 8):
"""Wrap all the properties in the `cpp_class`.""" """Wrap all the properties in the `cpp_class`."""
res = "" res = ""
@ -341,6 +351,13 @@ class PybindWrapper:
includes += includes_namespace includes += includes_namespace
elif isinstance(element, instantiator.InstantiatedClass): elif isinstance(element, instantiator.InstantiatedClass):
wrapped += self.wrap_instantiated_class(element) 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. # Global functions.
all_funcs = [ 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") py::class_<ns2::ClassC, std::shared_ptr<ns2::ClassC>>(m_ns2, "ClassC")
.def(py::init<>()); .def(py::init<>());
m_ns2.attr("aNs2Var") = ns2::aNs2Var;
m_ns2.def("aGlobalFunction",[](){return ns2::aGlobalFunction();}); 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){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")); 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") py::class_<ClassD, std::shared_ptr<ClassD>>(m_, "ClassD")
.def(py::init<>()); .def(py::init<>());
m_.attr("aGlobalVar") = aGlobalVar;
#include "python/specializations.h" #include "python/specializations.h"

View File

@ -17,7 +17,7 @@ class ClassB {
// check namespace handling // check namespace handling
Vector aGlobalFunction(); Vector aGlobalFunction();
} } // namespace ns1
#include <path/to/ns2.h> #include <path/to/ns2.h>
namespace ns2 { namespace ns2 {
@ -38,7 +38,7 @@ class ClassB {
ClassB(); ClassB();
}; };
} } // namespace ns3
class ClassC { class ClassC {
ClassC(); ClassC();
@ -51,10 +51,12 @@ Vector aGlobalFunction();
ns1::ClassA overloadedGlobalFunction(const ns1::ClassA& a); ns1::ClassA overloadedGlobalFunction(const ns1::ClassA& a);
ns1::ClassA overloadedGlobalFunction(const ns1::ClassA& a, double b); ns1::ClassA overloadedGlobalFunction(const ns1::ClassA& a, double b);
} //\namespace ns2 int aNs2Var;
} // namespace ns2
class ClassD { class ClassD {
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__)))) sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from gtwrap.interface_parser import (ArgumentList, Class, Constructor, from gtwrap.interface_parser import (
ForwardDeclaration, GlobalFunction, ArgumentList, Class, Constructor, ForwardDeclaration, GlobalFunction,
Include, Method, Module, Namespace, Include, Method, Module, Namespace, Operator, ReturnType, StaticMethod,
Operator, ReturnType, StaticMethod, TemplatedType, Type, TypedefTemplateInstantiation, Typename, Variable)
TemplatedType, Type,
TypedefTemplateInstantiation, Typename)
class TestInterfaceParser(unittest.TestCase): class TestInterfaceParser(unittest.TestCase):
@ -199,7 +197,8 @@ class TestInterfaceParser(unittest.TestCase):
self.assertEqual(args[3].default, 3.1415) self.assertEqual(args[3].default, 3.1415)
# Test non-basic type # 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 # Test templated type
self.assertEqual(repr(args[5].default.typename), 'std::vector<size_t>') self.assertEqual(repr(args[5].default.typename), 'std::vector<size_t>')
# Test for allowing list as default argument # Test for allowing list as default argument
@ -422,7 +421,8 @@ class TestInterfaceParser(unittest.TestCase):
self.assertEqual("BetweenFactor", ret.parent_class.name) self.assertEqual("BetweenFactor", ret.parent_class.name)
self.assertEqual(["gtsam"], ret.parent_class.namespaces) self.assertEqual(["gtsam"], ret.parent_class.namespaces)
self.assertEqual("Pose3", ret.parent_class.instantiations[0].name) 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): def test_include(self):
"""Test for include statements.""" """Test for include statements."""
@ -449,6 +449,23 @@ class TestInterfaceParser(unittest.TestCase):
self.assertEqual("Values", func.return_type.type1.typename.name) self.assertEqual("Values", func.return_type.type1.typename.name)
self.assertEqual(3, len(func.args)) 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): def test_namespace(self):
"""Test for namespace parsing.""" """Test for namespace parsing."""
namespace = Namespace.rule.parseString(""" namespace = Namespace.rule.parseString("""
@ -505,17 +522,21 @@ class TestInterfaceParser(unittest.TestCase):
}; };
} }
int oneVar;
} }
class Global{ class Global{
}; };
int globalVar;
""") """)
# print("module: ", module) # print("module: ", module)
# print(dir(module.content[0].name)) # print(dir(module.content[0].name))
self.assertEqual(["one", "Global"], [x.name for x in module.content]) self.assertEqual(["one", "Global", "globalVar"],
self.assertEqual(["two", "two_dummy", "two"], [x.name for x in module.content])
self.assertEqual(["two", "two_dummy", "two", "oneVar"],
[x.name for x in module.content[0].content]) [x.name for x in module.content[0].content])
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()