diff --git a/DOCS.md b/DOCS.md index c0c4310fa..a5ca3fb0c 100644 --- a/DOCS.md +++ b/DOCS.md @@ -91,6 +91,13 @@ The python wrapper supports keyword arguments for functions/methods. Hence, the ```cpp template ``` +- 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. diff --git a/gtwrap/interface_parser/classes.py b/gtwrap/interface_parser/classes.py index 255c26137..9c83821b8 100644 --- a/gtwrap/interface_parser/classes.py +++ b/gtwrap/interface_parser/classes.py @@ -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 = '', ): diff --git a/gtwrap/interface_parser/module.py b/gtwrap/interface_parser/module.py index 5619c1f56..2a564ec9b 100644 --- a/gtwrap/interface_parser/module.py +++ b/gtwrap/interface_parser/module.py @@ -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) diff --git a/gtwrap/interface_parser/namespace.py b/gtwrap/interface_parser/namespace.py index da505d5f9..502064a2f 100644 --- a/gtwrap/interface_parser/namespace.py +++ b/gtwrap/interface_parser/namespace.py @@ -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 # diff --git a/gtwrap/interface_parser/variable.py b/gtwrap/interface_parser/variable.py new file mode 100644 index 000000000..80dd5030b --- /dev/null +++ b/gtwrap/interface_parser/variable.py @@ -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) diff --git a/gtwrap/pybind_wrapper.py b/gtwrap/pybind_wrapper.py index 801e691c6..88bd05a49 100755 --- a/gtwrap/pybind_wrapper.py +++ b/gtwrap/pybind_wrapper.py @@ -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 = [ diff --git a/tests/expected/python/namespaces_pybind.cpp b/tests/expected/python/namespaces_pybind.cpp index e0e21d93b..b09fe36eb 100644 --- a/tests/expected/python/namespaces_pybind.cpp +++ b/tests/expected/python/namespaces_pybind.cpp @@ -50,12 +50,14 @@ PYBIND11_MODULE(namespaces_py, m_) { py::class_>(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_>(m_, "ClassD") .def(py::init<>()); + m_.attr("aGlobalVar") = aGlobalVar; #include "python/specializations.h" diff --git a/tests/fixtures/namespaces.i b/tests/fixtures/namespaces.i index a9b23cad1..5c277801d 100644 --- a/tests/fixtures/namespaces.i +++ b/tests/fixtures/namespaces.i @@ -17,7 +17,7 @@ class ClassB { // check namespace handling Vector aGlobalFunction(); -} +} // namespace ns1 #include 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; diff --git a/tests/test_interface_parser.py b/tests/test_interface_parser.py index 1b9d5e711..28b645201 100644 --- a/tests/test_interface_parser.py +++ b/tests/test_interface_parser.py @@ -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') # 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()