From 56d060b4c9ea1b1d14ed497b71859f358d13b9a7 Mon Sep 17 00:00:00 2001 From: Varun Agrawal Date: Mon, 19 Apr 2021 16:09:06 -0400 Subject: [PATCH] Squashed 'wrap/' changes from b80bc63cf..903694b77 903694b77 Merge pull request #92 from borglab/fix/global-variables abb74dd26 added support for default args, more tests, and docs cfa104257 Merge pull request #83 from borglab/feature/globalVariables fdd7b8cad fixes d4ceb63c6 add correct namespaces to global variable values 925c02c82 global variables works af62fdef7 unit test for global variable 3d3f3f3c9 add "Variable" to the global parsing rule ecfeb2025 rename "Property" to "Variable" and move into separate file git-subtree-dir: wrap git-subtree-split: 903694b777c4c25bd9cc82f8d3950b3bbc33d8f2 --- DOCS.md | 7 +++ gtwrap/interface_parser/classes.py | 35 ++------------ gtwrap/interface_parser/module.py | 2 + gtwrap/interface_parser/namespace.py | 2 + gtwrap/interface_parser/variable.py | 53 +++++++++++++++++++++ gtwrap/pybind_wrapper.py | 17 +++++++ tests/expected/python/namespaces_pybind.cpp | 2 + tests/fixtures/namespaces.i | 10 ++-- tests/test_interface_parser.py | 41 ++++++++++++---- 9 files changed, 125 insertions(+), 44 deletions(-) create mode 100644 gtwrap/interface_parser/variable.py 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()