def generate_cpp(self): # Make our value pairs values = indent('\n'.join(['{} = {}'.format(v[0], v[1]) for v in self.values]), 8) values = ',\n'.join([v for v in values.splitlines()]) scope_name='_'.join(self.fqn.split('.')) # Make our switch statement pairs switches = indent('\n'.join(['case Value::{}: return "{}";'.format(v[0], v[0]) for v in self.values]), 8) # Make our if chain if_chain = indent('\nelse '.join(['if (str == "{}") value = Value::{};'.format(v[0], v[0]) for v in self.values])) # Get our default value default_value = dict([reversed(v) for v in self.values])[0] # Make our fancy enums header_template = dedent("""\ struct {name} : public ::message::MessageBase<{name}> {{ enum Value {{ {values} }}; Value value; // Constructors {name}(); {name}(int const& v); {name}(Value const& value); {name}(std::string const& str); {name}({protobuf_name} const& p); // Operators bool operator <({name} const& other) const; bool operator >({name} const& other) const; bool operator <=({name} const& other) const; bool operator >=({name} const& other) const; bool operator ==({name} const& other) const; bool operator !=({name} const& other) const; bool operator <({name}::Value const& other) const; bool operator >({name}::Value const& other) const; bool operator <=({name}::Value const& other) const; bool operator >=({name}::Value const& other) const; bool operator ==({name}::Value const& other) const; bool operator !=({name}::Value const& other) const; // Conversions operator Value() const; operator int() const; operator std::string() const; operator {protobuf_name}() const; friend std::ostream& operator<< (std::ostream& out, const {name}& val); }};""") impl_template = dedent("""\ typedef {fqn} T{scope_name}; {fqn}::{name}() : value(Value::{default_value}) {{}} {fqn}::{name}(int const& v) : value(static_cast<Value>(v)) {{}} {fqn}::{name}(Value const& value) : value(value) {{}} {fqn}::{name}(std::string const& str) {{ {if_chain} else throw std::runtime_error("String " + str + " did not match any enum for {name}"); }} {fqn}::{name}({protobuf_name} const& p) {{ value = static_cast<Value>(p); }} bool {fqn}::operator <({name} const& other) const {{ return value < other.value; }} bool {fqn}::operator >({name} const& other) const {{ return value > other.value; }} bool {fqn}::operator <=({name} const& other) const {{ return value <= other.value; }} bool {fqn}::operator >=({name} const& other) const {{ return value >= other.value; }} bool {fqn}::operator ==({name} const& other) const {{ return value == other.value; }} bool {fqn}::operator !=({name} const& other) const {{ return value != other.value; }} bool {fqn}::operator <({name}::Value const& other) const {{ return value < other; }} bool {fqn}::operator >({name}::Value const& other) const {{ return value > other; }} bool {fqn}::operator <=({name}::Value const& other) const {{ return value <= other; }} bool {fqn}::operator >=({name}::Value const& other) const {{ return value >= other; }} bool {fqn}::operator ==({name}::Value const& other) const {{ return value == other; }} bool {fqn}::operator !=({name}::Value const& other) const {{ return value != other; }} {fqn}::operator Value() const {{ return value; }} {fqn}::operator int() const {{ return value; }} {fqn}::operator std::string() const {{ switch(value) {{ {switches} default: throw std::runtime_error("enum {name}'s value is corrupt, unknown value stored"); }} }} {fqn}::operator {protobuf_name}() const {{ return static_cast<{protobuf_name}>(value); }} std::ostream& {namespace}::operator<< (std::ostream& out, const {fqn}& val) {{ return out << static_cast<std::string>(val); }}""") python_template = dedent("""\ // Local scope for this enum {{ auto enumclass = pybind11::class_<{fqn}, std::shared_ptr<{fqn}>>(context, "{name}") .def(pybind11::init<>()) .def(pybind11::init<int const&>()) .def(pybind11::init<{fqn}::Value const&>()) .def(pybind11::init<std::string const&>()) .def(pybind11::self < pybind11::self) .def(pybind11::self > pybind11::self) .def(pybind11::self <= pybind11::self) .def(pybind11::self >= pybind11::self) .def(pybind11::self == pybind11::self) .def(pybind11::self < {fqn}::Value()) .def(pybind11::self > {fqn}::Value()) .def(pybind11::self <= {fqn}::Value()) .def(pybind11::self >= {fqn}::Value()) .def(pybind11::self == {fqn}::Value()) .def_static("include_path", [] {{ return "{include_path}"; }}) .def("_emit", [] ({fqn}& msg, pybind11::capsule capsule) {{ // Extract our reactor from the capsule NUClear::Reactor* reactor = capsule; // Do the emit reactor->powerplant.emit_shared<NUClear::dsl::word::emit::Local>(msg.shared_from_this()); }}); pybind11::enum_<{fqn}::Value>(enumclass, "Value") {value_list} .export_values(); }}""") return header_template.format( name=self.name, protobuf_name='::'.join(('.protobuf' + self.fqn).split('.')), values=values ), impl_template.format( fqn='::'.join(self.fqn.split('.')), namespace='::'.join(self.package.split('.')), name=self.name, protobuf_name='::'.join(('.protobuf' + self.fqn).split('.')), default_value=default_value, if_chain=if_chain, switches=switches, scope_name=scope_name, ), python_template.format( fqn='::'.join(self.fqn.split('.')), name=self.name, include_path=self.include_path, value_list=indent('\n'.join('.value("{name}", {fqn}::{name})'.format(name=v[0], fqn=self.fqn.replace('.', '::')) for v in self.values), 8) )
# Write our file with open(base_file, 'w') as f: f.write( dedent("""\ #include <pybind11/pybind11.h> #include <pybind11/complex.h> #include <pybind11/stl.h> #include <pybind11/eigen.h> // Declare our functions (that we know will be made later) {function_declarations} PYBIND11_PLUGIN(message) {{ pybind11::module module("message", "NUClear message classes"); // Initialise each of the modules {function_calls} return module.ptr(); }} """).format( function_declarations='\n'.join( 'void init_message_{}(pybind11::module& module);'.format(f) for f in functions), function_calls=indent('\n'.join( 'init_message_{}(module);'.format(f) for f in functions)), ))
with open(base_file, "w") as f: f.write( dedent( """\ #include <pybind11/pybind11.h> #include <pybind11/complex.h> #include <pybind11/stl.h> #include <pybind11/eigen.h> // Declare our functions (that we know will be made later) {function_declarations} PYBIND11_PLUGIN(message) {{ pybind11::module module("message", "NUClear message classes"); // Initialise each of the modules {function_calls} return module.ptr(); }} """ ).format( function_declarations="\n".join( "void init_message_{}(pybind11::module& module);".format(f) for f in functions ), function_calls=indent("\n".join("init_message_{}(module);".format(f) for f in functions)), ) )
def generate_cpp(self): # Make our value pairs values = indent('\n'.join(['{} = {}'.format(v[0], v[1]) for v in self.values]), 8) values = ',\n'.join([v for v in values.splitlines()]) # Make our switch statement pairs switches = indent('\n'.join(['case Value::{}: return "{}";'.format(v[0], v[0]) for v in self.values]), 8) # Make our if chain if_chain = indent('\n'.join(['if (str == "{}") value = Value::{};'.format(v[0], v[0]) for v in self.values])) # Get our default value default_value = dict([reversed(v) for v in self.values])[0] # Make our fancy enums header_template = dedent("""\ struct {name} : public ::message::MessageBase<{name}> {{ enum Value {{ {values} }}; Value value; // Constructors {name}(); {name}(int const& v); {name}(Value const& value); {name}(std::string const& str); {name}({protobuf_name} const& p); // Operators bool operator <({name} const& other) const; bool operator >({name} const& other) const; bool operator <=({name} const& other) const; bool operator >=({name} const& other) const; bool operator ==({name} const& other) const; bool operator <({name}::Value const& other) const; bool operator >({name}::Value const& other) const; bool operator <=({name}::Value const& other) const; bool operator >=({name}::Value const& other) const; bool operator ==({name}::Value const& other) const; // Conversions operator Value() const; operator int() const; operator std::string() const; operator {protobuf_name}() const; }};""") impl_template = dedent("""\ {fqn}::{name}() : value(Value::{default_value}) {{}} {fqn}::{name}(int const& v) : value(static_cast<Value>(v)) {{}} {fqn}::{name}(Value const& value) : value(value) {{}} {fqn}::{name}(std::string const& str) {{ {if_chain} throw std::runtime_error("String did not match any enum for {name}"); }} {fqn}::{name}({protobuf_name} const& p) {{ value = static_cast<Value>(p); }} bool {fqn}::operator <({name} const& other) const {{ return value < other.value; }} bool {fqn}::operator >({name} const& other) const {{ return value > other.value; }} bool {fqn}::operator <=({name} const& other) const {{ return value <= other.value; }} bool {fqn}::operator >=({name} const& other) const {{ return value >= other.value; }} bool {fqn}::operator ==({name} const& other) const {{ return value == other.value; }} bool {fqn}::operator <({name}::Value const& other) const {{ return value < other; }} bool {fqn}::operator >({name}::Value const& other) const {{ return value > other; }} bool {fqn}::operator <=({name}::Value const& other) const {{ return value <= other; }} bool {fqn}::operator >=({name}::Value const& other) const {{ return value >= other; }} bool {fqn}::operator ==({name}::Value const& other) const {{ return value == other; }} {fqn}::operator Value() const {{ return value; }} {fqn}::operator int() const {{ return value; }} {fqn}::operator std::string() const {{ switch(value) {{ {switches} default: throw std::runtime_error("enum {name}'s value is corrupt, unknown value stored"); }} }} {fqn}::operator {protobuf_name}() const {{ return static_cast<{protobuf_name}>(value); }}""") python_template = dedent("""\ // Local scope for this enum {{ auto enumclass = pybind11::class_<{fqn}, std::shared_ptr<{fqn}>>(context, "{name}") .def(pybind11::init<>()) .def(pybind11::init<int const&>()) .def(pybind11::init<{fqn}::Value const&>()) .def(pybind11::init<std::string const&>()) .def(pybind11::self < pybind11::self) .def(pybind11::self > pybind11::self) .def(pybind11::self <= pybind11::self) .def(pybind11::self >= pybind11::self) .def(pybind11::self == pybind11::self) .def(pybind11::self < {fqn}::Value()) .def(pybind11::self > {fqn}::Value()) .def(pybind11::self <= {fqn}::Value()) .def(pybind11::self >= {fqn}::Value()) .def(pybind11::self == {fqn}::Value()) .def_static("include_path", [] {{ return "{include_path}"; }}); pybind11::enum_<{fqn}::Value>(enumclass, "Value") {value_list} .export_values(); }}""") return header_template.format( name=self.name, protobuf_name='::'.join(('.protobuf' + self.fqn).split('.')), values=values ), impl_template.format( fqn='::'.join(self.fqn.split('.')), name=self.name, protobuf_name='::'.join(('.protobuf' + self.fqn).split('.')), default_value=default_value, if_chain=if_chain, switches=switches ), python_template.format( fqn='::'.join(self.fqn.split('.')), name=self.name, include_path=self.include_path, value_list=indent('\n'.join('.value("{name}", {fqn}::{name})'.format(name=v[0], fqn=self.fqn.replace('.', '::')) for v in self.values), 8) )
def generate_cpp(self): header_template = dedent("""\ class {oneof_name} : public enable_shared_from_this {{ public: {oneof_name}() : {initialiser_list}, val_index(0) {{}} template <typename T, size_t index> class Proxy {{ public: Proxy({oneof_name}* oneof) : oneof(oneof) {{}} template <typename U = T> Proxy& operator=(U&& t) {{ oneof->data = std::make_shared<T>(std::forward<U>(t)); oneof->val_index = index; return *this; }} bool operator==(const Proxy<T, index>& other) const {{ return get() == other.get(); }} operator T&() {{ return get(); }} operator const T&() const {{ return get(); }} const T& get() const {{ if (oneof->val_index == index) {{ return *std::static_pointer_cast<T>(oneof->data); }} throw std::range_error("This is not the one of you were looking for."); }} T& get() {{ if (oneof->val_index == index) {{ return *std::static_pointer_cast<T>(oneof->data); }} throw std::range_error("This is not the one of you were looking for."); }} private: {oneof_name}* oneof; }}; bool operator==(const {oneof_name}& other) const {{ if (val_index == other.val_index) {{ switch (val_index) {{ {cases} default: return false; }} }} return false; }} void reset() {{ val_index = 0; data.reset(); }} {member_list} size_t val_index; private: std::shared_ptr<void> data; }}; """) impl_template = dedent("""\ """) python_template = dedent("""\ // Local scope for this message {{ // Use our context and assign a new one to a shadow auto shadow = pybind11::class_<{fqn}, std::shared_ptr<{fqn}>>(context, "{name}"); // Shadow our context with our new context and declare our subclasses auto& context = shadow; // Declare the functions on our class (which may use the ones in the subclasses) context {bindings} }}""") binding_template = dedent("""\ context.def_property("{field_name}", [](const {fqn}& oneof) -> const {type_name}& {{ return oneof.{field_name}; }}, [](const {fqn}& oneof, const {type_name}& value) -> void {{ oneof.{field_name} = value; }}); """) python_bindings = [ binding_template.format(field_name=v.name, type_name=v.cpp_type, fqn=self.fqn.replace('.', '::')) for v in self.fields ] member_list = [ 'Proxy<{}, {}> {};'.format(v.cpp_type, v.number, v.name) for v in self.fields ] initialiser_list = ['{}(this)'.format(v.name) for v in self.fields] cases = [ 'case {0}: return {1} == other.{1};'.format(v.number, v.name) for v in self.fields ] return (header_template.format( oneof_name=self.name, member_list=indent('\n'.join(member_list), 4), initialiser_list=', '.join(initialiser_list), cases=indent('\n'.join(cases), 16)), impl_template, python_template.format(bindings='\n\n'.join(python_bindings), fqn=self.fqn.replace('.', '::'), name=self.name))
def generate_cpp(self): # Make our value pairs fields = indent("\n".join(["{}".format(v.generate_cpp_header()) for v in self.fields])) # Generate our protobuf class name protobuf_type = "::".join((".protobuf" + self.fqn).split(".")) # Generate our enums c++ enums = [e.generate_cpp() for e in self.enums] enum_headers = indent("\n\n".join([e[0] for e in enums])) enum_impls = "\n\n".join([e[1] for e in enums]) enum_python = "\n\n".join([e[2] for e in enums]) # Generate our submessage c++ submessages = [s.generate_cpp() for s in self.submessages] submessage_headers = indent("\n\n".join([s[0] for s in submessages])) submessage_impls = "\n\n".join([s[1] for s in submessages]) submessage_python = "\n\n".join([s[2] for s in submessages]) # Get our function code default_constructor = self.generate_default_constructor() rule_of_five = self.generate_rule_of_five() protobuf_constructor = self.generate_protobuf_constructor() protobuf_converter = self.generate_protobuf_converter() equality_operator = self.generate_equality_operator() constructor_headers = indent( "\n\n".join([default_constructor[0], rule_of_five[0], protobuf_constructor[0], equality_operator[0]]) ) constructor_impl = "\n\n".join([default_constructor[1], protobuf_constructor[1], equality_operator[1]]) converter_headers = indent("\n\n".join([protobuf_converter[0]])) converter_impl = "\n\n".join([protobuf_converter[1]]) header_template = dedent( """\ struct {name} : public ::message::MessageBase<{name}> {{ // Protobuf type using protobuf_type = {protobuf_type}; // Enum Definitions {enums} // Submessage Definitions {submessages} // Constructors {constructors} // Converters {converters} // Fields {fields} }};""" ) impl_template = dedent( """\ // Constructors {constructors} // Converters {converters} // Subenums {enums} // Submessages {submessages}""" ) python_template = dedent( """\ // Local scope for this message {{ // Use our context and assign a new one to a shadow auto shadow = pybind11::class_<{fqn}, std::shared_ptr<{fqn}>>(context, "{name}"); // Shadow our context with our new context and declare our subclasses auto& context = shadow; {enums} {submessages} // Declare the functions on our class (which may use the ones in the subclasses) context {constructor} {message_members}; context.def_static("include_path", [] {{ return "{include_path}"; }}); // Build our emitter function that is used to emit this object context.def("_emit", [] ({fqn}& msg, pybind11::capsule capsule) {{ // Extract our reactor from the capsule NUClear::Reactor* reactor = capsule; // Do the emit reactor->powerplant.emit_shared<NUClear::dsl::word::emit::Local>(msg.shared_from_this()); }}); }}""" ) python_constructor_args = ["{}& self".format(self.fqn.replace(".", "::"))] python_constructor_args.extend(["{} const& _{}".format(t.cpp_type, t.name) for t in self.fields]) python_members = "\n".join( '.def_readwrite("{field}", &{fqn}::{field})'.format(field=f.name, fqn=self.fqn.replace(".", "::")) for f in self.fields ) python_constructor_default_args = [""] python_constructor_default_args.extend( [ 'pybind11::arg("{}") = {}'.format( t.name, t.default_value if t.default_value else "{}()".format(t.cpp_type) ) for t in self.fields ] ) python_constructor = dedent( """\ .def("__init__", [] ({args}) {{ new (&self) {name}({vars}); }}{default_args})""" ).format( name=self.fqn.replace(".", "::"), args=", ".join(python_constructor_args), vars=", ".join("_{}".format(t.name) for t in self.fields), default_args=", ".join(python_constructor_default_args), ) return ( header_template.format( name=self.name, enums=enum_headers, submessages=submessage_headers, constructors=constructor_headers, protobuf_type=protobuf_type, converters=converter_headers, fields=fields, ), impl_template.format( constructors=constructor_impl, converters=converter_impl, enums=enum_impls, submessages=submessage_impls ), python_template.format( constructor=indent(python_constructor, 8), message_members=indent(python_members, 8), include_path=self.include_path, fqn=self.fqn.replace(".", "::"), name=self.name, submessages=indent(submessage_python), enums=indent(enum_python), ), )
# Write our file with open(base_file, "w") as f: f.write( dedent("""\ #include <pybind11/pybind11.h> #include <pybind11/complex.h> #include <pybind11/stl.h> #include <pybind11/eigen.h> // Declare our functions (that we know will be made later) {function_declarations} PYBIND11_PLUGIN(message) {{ pybind11::module module("message", "NUClear message classes"); // Initialise each of the modules {function_calls} return module.ptr(); }} """).format( function_declarations="\n".join( "void init_message_{}(pybind11::module& module);".format(f) for f in functions), function_calls=indent("\n".join( "init_message_{}(module);".format(f) for f in functions)), ))
def generate_protobuf_constructor(self): # Fully qualified c++ name cpp_fqn = '::'.join(self.fqn.split('.')) # Protobuf name protobuf_name = '::'.join(('.protobuf' + self.fqn).split('.')) # If we are empty it's easy if not self.fields: return ('{}(const {}&);'.format(self.name, protobuf_name), '{}::{}(const {}&) {{}}'.format(cpp_fqn, self.name, protobuf_name)) else: lines = ['{}::{}(const {}& proto) {{'.format(cpp_fqn, self.name, protobuf_name)] for v in self.fields: if v.pointer: print('TODO HANDLE POINTER CASES') elif v.map_type: if v.type[1].bytes_type: lines.append(indent('for (auto& _v : proto.{}()) {{'.format(v.name))) lines.append(indent('{0}[_v.first].insert(std::end({0}[_v.first]), std::begin(_v.second), std::end(_v.second));'.format(v.name), 8)) lines.append(indent('}')) elif v.type[1].special_cpp_type: lines.append(indent('for (auto& _v : proto.{}()) {{'.format(v.name.lower()))) lines.append(indent('{}[_v.first] << _v.second;'.format(v.name), 8)) lines.append(indent('}')) else: # Basic and other types are handled the same lines.append(indent('{0}.insert(std::begin(proto.{1}()), std::end(proto.{1}()));'.format(v.name, v.name.lower()), 8)) elif v.repeated: if v.bytes_type: lines.append(indent('{0}.resize(proto.{0}_size());'.format(v.name.lower()))) lines.append(indent('for (size_t _i = 0; _i < {0}.size(); ++_i) {{'.format(v.name))) lines.append(indent('{0}[_i].insert(std::end({0}[_i]), std::begin(proto.{1}(_i)), std::end(proto.{1}(_i)));'.format(v.name, v.name.lower()), 8)) lines.append(indent('}')) elif v.special_cpp_type: if v.array_size > 0: lines.append(indent('for (size_t _i = 0; _i < {0}.size() && _i < size_t(proto.{1}_size()); ++_i) {{'.format(v.name, v.name.lower()))) lines.append(indent('{0}[_i] << proto.{1}(_i);'.format(v.name, v.name.lower()), 8)) lines.append(indent('}')) else: # Add the top of our for loop for the repeated field lines.append(indent('{0}.resize(proto.{1}_size());'.format(v.name, v.name.lower()))) lines.append(indent('for (size_t _i = 0; _i < {0}.size(); ++_i) {{'.format(v.name))) lines.append(indent('{0}[_i] << proto.{1}(_i);'.format(v.name, v.name.lower()), 8)) lines.append(indent('}')) else: # Basic and other types are handled the same if v.array_size > 0: lines.append(indent('for (size_t _i = 0; _i < {0}.size() && _i < size_t(proto.{1}_size()); ++_i) {{'.format(v.name, v.name.lower()))) lines.append(indent('{0}[_i] = proto.{1}(_i);'.format(v.name, v.name.lower()), 8)) lines.append(indent('}')) else: lines.append(indent('{0}.insert(std::end({0}), std::begin(proto.{1}()), std::end(proto.{1}()));'.format(v.name, v.name.lower()))) else: if v.bytes_type: lines.append(indent('{0}.insert(std::end({0}), std::begin(proto.{1}()), std::end(proto.{1}()));'.format(v.name, v.name.lower()))) elif v.special_cpp_type: lines.append(indent('{} << proto.{}();'.format(v.name, v.name.lower()))) else: # Basic and other types are handled the same lines.append(indent('{} = proto.{}();'.format(v.name, v.name.lower()))) lines.append('}') return '{}(const {}& proto);'.format(self.name, protobuf_name), '\n'.join(lines)
def generate_protobuf_constructor(self): # Fully qualified c++ name cpp_fqn = '::'.join(self.fqn.split('.')) # Protobuf name protobuf_name = '::'.join(('.protobuf' + self.fqn).split('.')) # If we are empty it's easy if not self.fields: return ( '{}(const {}&);'.format(self.name, protobuf_name), '{}::{}(const {}&) {{}}'.format(cpp_fqn, self.name, protobuf_name) ) else: lines = ['{}::{}(const {}& proto) {{'.format(cpp_fqn, self.name, protobuf_name)] for v in self.fields: if v.pointer: print('TODO HANDLE POINTER CASES') elif v.map_type: if v.type[1].bytes_type: lines.append(indent('for (auto& _v : proto.{}()) {{'.format(v.name))) lines.append( indent( '{0}[_v.first].insert(std::end({0}[_v.first]), std::begin(_v.second), std::end(_v.second));'. format(v.name), 8 ) ) lines.append(indent('}')) elif v.type[1].special_cpp_type: lines.append(indent('for (auto& _v : proto.{}()) {{'.format(v.name.lower()))) lines.append(indent('message::conversion::convert({}[_v.first], _v.second);'.format(v.name), 8)) lines.append(indent('}')) else: # Basic and other types are handled the same lines.append( indent( '{0}.insert(std::begin(proto.{1}()), std::end(proto.{1}()));'.format( v.name, v.name.lower() ), 8 ) ) elif v.repeated: if v.bytes_type: lines.append(indent('{0}.resize(proto.{0}_size());'.format(v.name.lower()))) lines.append(indent('for (size_t _i = 0; _i < {0}.size(); ++_i) {{'.format(v.name))) lines.append( indent( '{0}[_i].insert(std::end({0}[_i]), std::begin(proto.{1}(_i)), std::end(proto.{1}(_i)));'. format(v.name, v.name.lower()), 8 ) ) lines.append(indent('}')) elif v.special_cpp_type: if v.array_size > 0: lines.append( indent( 'for (size_t _i = 0; _i < {0}.size() && _i < size_t(proto.{1}_size()); ++_i) {{'. format(v.name, v.name.lower()) ) ) lines.append( indent( 'message::conversion::convert({0}[_i], proto.{1}(_i));'.format( v.name, v.name.lower() ), 8 ) ) lines.append(indent('}')) else: # Add the top of our for loop for the repeated field lines.append(indent('{0}.resize(proto.{1}_size());'.format(v.name, v.name.lower()))) lines.append(indent('for (size_t _i = 0; _i < {0}.size(); ++_i) {{'.format(v.name))) lines.append( indent( 'message::conversion::convert({0}[_i], proto.{1}(_i));'.format( v.name, v.name.lower() ), 8 ) ) lines.append(indent('}')) else: # Basic and other types are handled the same if v.array_size > 0: lines.append( indent( 'for (size_t _i = 0; _i < {0}.size() && _i < size_t(proto.{1}_size()); ++_i) {{'. format(v.name, v.name.lower()) ) ) lines.append(indent('{0}[_i] = proto.{1}(_i);'.format(v.name, v.name.lower()), 8)) lines.append(indent('}')) else: lines.append( indent( '{0}.insert(std::end({0}), std::begin(proto.{1}()), std::end(proto.{1}()));'.format( v.name, v.name.lower() ) ) ) elif v.one_of: lines.append(indent('switch (proto.{}_case()) {{').format(v.name.lower())) for oneof_field in v.oneof_fields: lines.append(indent('case {}: {{'.format(oneof_field.number), 8)) if oneof_field.bytes_type: lines.append( indent( dedent( """\ {0}.{1} = {2}; {0}.{1}.insert(std::end({0}.{1}), std::begin(proto.{3}()), std::end(proto.{3}()));""" .format( v.name, oneof_field.name, oneof_field.default_value, oneof_field.name.lower() ) ), 12 ) ) elif oneof_field.special_cpp_type: lines.append( indent( dedent( """\ {0}.{1} = {2}; message::conversion::convert({0}.{1}, proto.{3}());""".format( v.name, oneof_field.name, oneof_field.default_value, oneof_field.name.lower() ) ), 12 ) ) else: # Basic and other types are handled the same lines.append( indent( '{0}.{1} = proto.{2}();'.format(v.name, oneof_field.name, oneof_field.name.lower()), 12 ) ) lines.append(indent('} break;', 8)) lines.append(indent('default: object.reset(); break;', 8)) lines.append(indent('}')) else: if v.bytes_type: lines.append( indent( '{0}.insert(std::end({0}), std::begin(proto.{1}()), std::end(proto.{1}()));'.format( v.name, v.name.lower() ) ) ) elif v.special_cpp_type: lines.append( indent('message::conversion::convert({}, proto.{}());'.format(v.name, v.name.lower())) ) else: # Basic and other types are handled the same lines.append(indent('{} = proto.{}();'.format(v.name, v.name.lower()))) lines.append('}') return '{}(const {}& proto);'.format(self.name, protobuf_name), '\n'.join(lines)
def generate_cpp(self): define = '{}_H'.format('_'.join([s.upper() for s in self.name[:-6].strip().split('/')])) parts = self.package.split('.') ns_open = '\n'.join(['namespace {} {{'.format(x) for x in parts]) ns_close = '\n'.join('}' * len(parts)) # Generate our enums c++ enums = [e.generate_cpp() for e in self.enums] enum_headers = indent('\n\n'.join([e[0] for e in enums])) enum_impls = ('\n\n'.join([e[1] for e in enums])) enum_python = ('\n\n'.join([e[2] for e in enums])) # Generate our enums c++ messages = [m.generate_cpp() for m in self.messages] message_headers = indent('\n\n'.join([m[0] for m in messages])) message_impls = ('\n\n'.join([m[1] for m in messages])) message_python = ('\n\n'.join([m[2] for m in messages])) # By default include some useful headers includes = { '1<cstdint>', '2<string>', '2<map>', '2<vector>', '2<array>', '2<memory>', '4"{}"'.format(self.name[:-6] + '.pb.h'), '5"message/MessageBase.h"' } # We use a dirty hack here of putting a priority on each header # to make the includes be in a better order for d in self.dependencies: if d in ['Vector.proto', 'Matrix.proto']: includes.add('4"message/conversion/proto_matrix.h"') elif d in ['EnhancedMessage.proto']: pass # We don't need to do anything for these ones elif d in ['Transform.proto']: includes.add('4"message/conversion/proto_transform.h"') elif d in ['google/protobuf/timestamp.proto', 'google/protobuf/duration.proto']: includes.add('4"message/conversion/proto_time.h"') elif d in ['google/protobuf/struct.proto']: includes.add('4"utility/include/proto_struct.h"') else: includes.add('4"{}"'.format(d[:-6] + '.h')) # Don't forget to remove the first character includes = '\n'.join(['#include {}'.format(i[1:]) for i in sorted(list(includes))]) header_template = dedent("""\ #ifndef {define} #define {define} {includes} {openNamespace} // Enum Definitions {enums} // Message Definitions {messages} {closeNamespace} #endif // {define} """) impl_template = dedent("""\ {include} // Enum Implementations {enums} // Message Implementations {messages} """) python_template = dedent("""\ #include <pybind11/pybind11.h> #include <pybind11/complex.h> #include <pybind11/stl.h> #include <pybind11/chrono.h> #include <pybind11/operators.h> #include <pybind11/eigen.h> {include} void init_{filename}(pybind11::module& module) {{ // Go down to our submodule as required as context pybind11::module context = module{submodules}; {messages} {enums} }} """) python_submodules = ''.join('.def_submodule("{}")'.format(m) for m in self.fqn.split('.')[2:]) return header_template.format( define=define, includes=includes, openNamespace=ns_open, enums=enum_headers, messages=message_headers, closeNamespace=ns_close ), impl_template.format( include='#include "{}"'.format(self.name[:-6] + '.h'), enums=enum_impls, messages=message_impls ), python_template.format( include='#include "{}"'.format(self.name[:-6] + '.h'), messages=indent(message_python), enums=indent(enum_python), filename=re.sub(r'[^A-Za-z0-9]', '_', self.name), submodules=python_submodules )
def generate_cpp(self): # Make our value pairs fields = indent('\n'.join(['{}'.format(v.generate_cpp_header()) for v in self.fields])) # Generate our protobuf class name protobuf_type = '::'.join(('.protobuf' + self.fqn).split('.')) # Generate our enums c++ enums = [e.generate_cpp() for e in self.enums] enum_headers = indent('\n\n'.join([e[0] for e in enums])) enum_impls = ('\n\n'.join([e[1] for e in enums])) enum_python = ('\n\n'.join([e[2] for e in enums])) # Generate our submessage c++ submessages = [s.generate_cpp() for s in self.submessages] submessage_headers = indent('\n\n'.join([s[0] for s in submessages])) submessage_impls = ('\n\n'.join([s[1] for s in submessages])) submessage_python = ('\n\n'.join([s[2] for s in submessages])) # Get our function code default_constructor = self.generate_default_constructor() protobuf_constructor = self.generate_protobuf_constructor() protobuf_converter = self.generate_protobuf_converter() constructor_headers = indent('\n\n'.join([default_constructor[0], protobuf_constructor[0]])) constructor_impl = '\n\n'.join([default_constructor[1], protobuf_constructor[1]]) converter_headers = indent('\n\n'.join([protobuf_converter[0]])) converter_impl = '\n\n'.join([protobuf_converter[1]]) header_template = dedent("""\ struct alignas(16) {name} : public ::message::MessageBase<{name}> {{ // Protobuf type using protobuf_type = {protobuf_type}; // Enum Definitions {enums} // Submessage Definitions {submessages} // Constructors {constructors} // Converters {converters} // Fields {fields} }};""") impl_template = dedent("""\ // Constructors {constructors} // Converters {converters} // Subenums {enums} // Submessages {submessages}""") python_template = dedent("""\ // Local scope for this message {{ // Use our context and assign a new one to a shadow auto shadow = pybind11::class_<{fqn}, std::shared_ptr<{fqn}>>(context, "{name}"); // Shadow our context with our new context and declare our subclasses auto& context = shadow; {submessages} {enums} // Declare the functions on our class (which may use the ones in the subclasses) context {constructor} {message_members}; context.def_static("include_path", [] {{ return "{include_path}"; }}); }}""") python_members = '\n'.join('.def_readwrite("{field}", &{fqn}::{field})'.format(field=f.name, fqn=self.fqn.replace('.', '::')) for f in self.fields) python_constructor = dedent("""\ .def("__init__", [] ({name}& self, {args}) {{ new (&self) {name}({vars}); }}, {default_args})""").format( name=self.fqn.replace('.', '::'), args=', '.join('{} const& {}'.format(t.cpp_type, t.name) for t in self.fields), vars=', '.join(t.name for t in self.fields), default_args=', '.join('pybind11::arg("{}") = {}'.format(t.name, t.default_value if t.default_value else '{}()'.format(t.cpp_type)) for t in self.fields) ) return header_template.format( name=self.name, enums=enum_headers, submessages=submessage_headers, constructors=constructor_headers, protobuf_type=protobuf_type, converters=converter_headers, fields=fields ), impl_template.format( constructors=constructor_impl, converters=converter_impl, enums=enum_impls, submessages=submessage_impls ), python_template.format( constructor=indent(python_constructor, 8), message_members=indent(python_members, 8), include_path=self.include_path, fqn=self.fqn.replace('.', '::'), name=self.name, submessages=indent(submessage_python), enums=indent(enum_python) )
def generate_cpp(self): header_template = dedent( """\ class {oneof_name} : public enable_shared_from_this {{ public: {oneof_name}() : {initialiser_list}, val_index(0) {{}} template <typename T, size_t index> class Proxy {{ public: Proxy({oneof_name}* oneof) : oneof(oneof) {{}} template <typename U = T> Proxy& operator=(U&& t) {{ oneof->data = std::make_shared<T>(std::forward<U>(t)); oneof->val_index = index; return *this; }} bool operator==(const Proxy<T, index>& other) const {{ return get() == other.get(); }} operator T&() {{ return get(); }} operator const T&() const {{ return get(); }} const T& get() const {{ if (oneof->val_index == index) {{ return *std::static_pointer_cast<T>(oneof->data); }} throw std::range_error("This is not the one of you were looking for."); }} T& get() {{ if (oneof->val_index == index) {{ return *std::static_pointer_cast<T>(oneof->data); }} throw std::range_error("This is not the one of you were looking for."); }} private: {oneof_name}* oneof; }}; bool operator==(const {oneof_name}& other) const {{ if (val_index == other.val_index) {{ switch (val_index) {{ {cases} default: return false; }} }} return false; }} void reset() {{ val_index = 0; data.reset(); }} {member_list} size_t val_index; private: std::shared_ptr<void> data; }}; """ ) impl_template = dedent("""\ """) python_template = dedent( """\ // Local scope for this message {{ // Use our context and assign a new one to a shadow auto shadow = pybind11::class_<{fqn}, std::shared_ptr<{fqn}>>(context, "{name}"); // Shadow our context with our new context and declare our subclasses auto& context = shadow; // Declare the functions on our class (which may use the ones in the subclasses) context {bindings} }}""" ) binding_template = dedent( """\ context.def_property("{field_name}", [](const {fqn}& oneof) -> const {type_name}& {{ return oneof.{field_name}; }}, [](const {fqn}& oneof, const {type_name}& value) -> void {{ oneof.{field_name} = value; }}); """ ) python_bindings = [ binding_template.format(field_name=v.name, type_name=v.cpp_type, fqn=self.fqn.replace('.', '::')) for v in self.fields ] member_list = ['Proxy<{}, {}> {};'.format(v.cpp_type, v.number, v.name) for v in self.fields] initialiser_list = ['{}(this)'.format(v.name) for v in self.fields] cases = ['case {0}: return {1} == other.{1};'.format(v.number, v.name) for v in self.fields] return ( header_template.format( oneof_name=self.name, member_list=indent('\n'.join(member_list), 4), initialiser_list=', '.join(initialiser_list), cases=indent('\n'.join(cases), 16) ), impl_template, python_template.format( bindings='\n\n'.join(python_bindings), fqn=self.fqn.replace('.', '::'), name=self.name ) )
def generate_cpp(self): define = "{}_H".format("_".join( [s.upper() for s in self.name[:-6].strip().split("/")])) parts = self.package.split(".") ns_open = "\n".join(["namespace {} {{".format(x) for x in parts]) ns_close = "\n".join("}" * len(parts)) # Generate our enums c++ enums = [e.generate_cpp() for e in self.enums] enum_headers = indent("\n\n".join([e[0] for e in enums])) enum_impls = "\n\n".join([e[1] for e in enums]) enum_python = "\n\n".join([e[2] for e in enums]) # Generate our enums c++ messages = [m.generate_cpp() for m in self.messages] message_headers = indent("\n\n".join([m[0] for m in messages])) message_impls = "\n\n".join([m[1] for m in messages]) message_python = "\n\n".join([m[2] for m in messages]) # By default include some useful headers # yapf: disable includes = { '1<cstdint>', '2<string>', '2<array>', '2<exception>', '2<map>', '2<memory>', '2<vector>', '4"{}"'.format(self.name[:-6] + '.pb.h'), '5"message/MessageBase.h"' } # yapf: enable # We use a dirty hack here of putting a priority on each header # to make the includes be in a better order for d in self.dependencies: if d in ["Vector.proto", "Matrix.proto"]: includes.add('4"message/conversion/proto_matrix.h"') elif d in ["Neutron.proto"]: pass # We don't need to do anything for these ones elif d in [ "google/protobuf/timestamp.proto", "google/protobuf/duration.proto" ]: includes.add('4"message/conversion/proto_time.h"') else: includes.add('4"{}"'.format(d[:-6] + ".h")) # Don't forget to remove the first character includes = "\n".join( ["#include {}".format(i[1:]) for i in sorted(list(includes))]) header_template = dedent("""\ #ifndef {define} #define {define} {includes} {open_namespace} // Enum Definitions {enums} // Message Definitions {messages} {close_namespace} #endif // {define} """) impl_template = dedent("""\ {include} // Enum Implementations {enums} // Message Implementations {messages} """) python_template = dedent("""\ #include <pybind11/pybind11.h> #include <pybind11/complex.h> #include <pybind11/stl.h> #include <pybind11/chrono.h> #include <pybind11/operators.h> #include <pybind11/eigen.h> {include} void init_{filename}(pybind11::module& module) {{ // Go down to our submodule as required as context pybind11::module context = module{submodules}; {enums} {messages} }} """) python_submodules = "".join('.def_submodule("{}")'.format(m) for m in self.fqn.split(".")[2:]) return ( header_template.format( define=define, includes=includes, open_namespace=ns_open, enums=enum_headers, messages=message_headers, close_namespace=ns_close, ), impl_template.format( include='#include "{}"'.format(self.name[:-6] + ".h"), enums=enum_impls, messages=message_impls), python_template.format( include='#include "{}"'.format(self.name[:-6] + ".h"), messages=indent(message_python), enums=indent(enum_python), filename=re.sub(r"[^A-Za-z0-9]", "_", self.name), submodules=python_submodules, ), )
def generate_cpp(self): # Make our value pairs values = indent( '\n'.join(['{} = {}'.format(v[0], v[1]) for v in self.values]), 8) values = ',\n'.join([v for v in values.splitlines()]) scope_name = '_'.join(self.fqn.split('.')) # Make our switch statement pairs switches = indent( '\n'.join([ 'case Value::{}: return "{}";'.format(v[0], v[0]) for v in self.values ]), 8) # Make our if chain if_chain = indent('\nelse '.join([ 'if (str == "{}") value = Value::{};'.format(v[0], v[0]) for v in self.values ])) # Get our default value default_value = dict([reversed(v) for v in self.values])[0] # Make our fancy enums header_template = dedent("""\ struct {name} : public ::message::MessageBase<{name}> {{ enum Value {{ {values} }}; Value value; // Constructors {name}(); {name}(int const& v); {name}(Value const& value); {name}(std::string const& str); {name}({protobuf_name} const& p); // Operators bool operator <({name} const& other) const; bool operator >({name} const& other) const; bool operator <=({name} const& other) const; bool operator >=({name} const& other) const; bool operator ==({name} const& other) const; bool operator !=({name} const& other) const; bool operator <({name}::Value const& other) const; bool operator >({name}::Value const& other) const; bool operator <=({name}::Value const& other) const; bool operator >=({name}::Value const& other) const; bool operator ==({name}::Value const& other) const; bool operator !=({name}::Value const& other) const; // Conversions operator Value() const; operator int() const; operator std::string() const; operator {protobuf_name}() const; friend std::ostream& operator<< (std::ostream& out, const {name}& val); }};""") impl_template = dedent("""\ typedef {fqn} T{scope_name}; {fqn}::{name}() : value(Value::{default_value}) {{}} {fqn}::{name}(int const& v) : value(static_cast<Value>(v)) {{}} {fqn}::{name}(Value const& value) : value(value) {{}} {fqn}::{name}(std::string const& str) {{ {if_chain} else throw std::runtime_error("String " + str + " did not match any enum for {name}"); }} {fqn}::{name}({protobuf_name} const& p) {{ value = static_cast<Value>(p); }} bool {fqn}::operator <({name} const& other) const {{ return value < other.value; }} bool {fqn}::operator >({name} const& other) const {{ return value > other.value; }} bool {fqn}::operator <=({name} const& other) const {{ return value <= other.value; }} bool {fqn}::operator >=({name} const& other) const {{ return value >= other.value; }} bool {fqn}::operator ==({name} const& other) const {{ return value == other.value; }} bool {fqn}::operator !=({name} const& other) const {{ return value != other.value; }} bool {fqn}::operator <({name}::Value const& other) const {{ return value < other; }} bool {fqn}::operator >({name}::Value const& other) const {{ return value > other; }} bool {fqn}::operator <=({name}::Value const& other) const {{ return value <= other; }} bool {fqn}::operator >=({name}::Value const& other) const {{ return value >= other; }} bool {fqn}::operator ==({name}::Value const& other) const {{ return value == other; }} bool {fqn}::operator !=({name}::Value const& other) const {{ return value != other; }} {fqn}::operator Value() const {{ return value; }} {fqn}::operator int() const {{ return value; }} {fqn}::operator std::string() const {{ switch(value) {{ {switches} default: throw std::runtime_error("enum {name}'s value is corrupt, unknown value stored"); }} }} {fqn}::operator {protobuf_name}() const {{ return static_cast<{protobuf_name}>(value); }} std::ostream& {namespace}::operator<< (std::ostream& out, const {fqn}& val) {{ return out << static_cast<std::string>(val); }}""") python_template = dedent("""\ // Local scope for this enum {{ auto enumclass = pybind11::class_<{fqn}, std::shared_ptr<{fqn}>>(context, "{name}") .def(pybind11::init<>()) .def(pybind11::init<int const&>()) .def(pybind11::init<{fqn}::Value const&>()) .def(pybind11::init<std::string const&>()) .def(pybind11::self < pybind11::self) .def(pybind11::self > pybind11::self) .def(pybind11::self <= pybind11::self) .def(pybind11::self >= pybind11::self) .def(pybind11::self == pybind11::self) .def(pybind11::self < {fqn}::Value()) .def(pybind11::self > {fqn}::Value()) .def(pybind11::self <= {fqn}::Value()) .def(pybind11::self >= {fqn}::Value()) .def(pybind11::self == {fqn}::Value()) .def_static("include_path", [] {{ return "{include_path}"; }}) .def("_emit", [] ({fqn}& msg, pybind11::capsule capsule) {{ // Extract our reactor from the capsule NUClear::Reactor* reactor = capsule; // Do the emit reactor->powerplant.emit_shared<NUClear::dsl::word::emit::Local>(msg.shared_from_this()); }}); pybind11::enum_<{fqn}::Value>(enumclass, "Value") {value_list} .export_values(); }}""") return header_template.format( name=self.name, protobuf_name='::'.join(('.protobuf' + self.fqn).split('.')), values=values), impl_template.format( fqn='::'.join(self.fqn.split('.')), namespace='::'.join(self.package.split('.')), name=self.name, protobuf_name='::'.join(('.protobuf' + self.fqn).split('.')), default_value=default_value, if_chain=if_chain, switches=switches, scope_name=scope_name, ), python_template.format( fqn='::'.join(self.fqn.split('.')), name=self.name, include_path=self.include_path, value_list=indent( '\n'.join('.value("{name}", {fqn}::{name})'.format( name=v[0], fqn=self.fqn.replace('.', '::')) for v in self.values), 8))
def generate_protobuf_constructor(self): # Fully qualified c++ name cpp_fqn = "::".join(self.fqn.split(".")) # Protobuf name protobuf_name = "::".join((".protobuf" + self.fqn).split(".")) # If we are empty it's easy if not self.fields: return ( "{}(const {}&);".format(self.name, protobuf_name), "{}::{}(const {}&) {{}}".format(cpp_fqn, self.name, protobuf_name), ) else: lines = ["{}::{}(const {}& proto) {{".format(cpp_fqn, self.name, protobuf_name)] for v in self.fields: if v.pointer: print("TODO HANDLE POINTER CASES") elif v.map_type: if v.type[1].bytes_type: lines.append(indent("for (auto& _v : proto.{}()) {{".format(v.name))) lines.append( indent( "{0}[_v.first].insert(std::end({0}[_v.first]), std::begin(_v.second), std::end(_v.second));".format( v.name ), 8, ) ) lines.append(indent("}")) elif v.type[1].special_cpp_type: lines.append(indent("for (auto& _v : proto.{}()) {{".format(v.name.lower()))) lines.append(indent("message::conversion::convert({}[_v.first], _v.second);".format(v.name), 8)) lines.append(indent("}")) else: # Basic and other types are handled the same lines.append( indent( "{0}.insert(std::begin(proto.{1}()), std::end(proto.{1}()));".format( v.name, v.name.lower() ), 8, ) ) elif v.repeated: if v.bytes_type: lines.append(indent("{0}.resize(proto.{0}_size());".format(v.name.lower()))) lines.append(indent("for (size_t _i = 0; _i < {0}.size(); ++_i) {{".format(v.name))) lines.append( indent( "{0}[_i].insert(std::end({0}[_i]), std::begin(proto.{1}(_i)), std::end(proto.{1}(_i)));".format( v.name, v.name.lower() ), 8, ) ) lines.append(indent("}")) elif v.special_cpp_type: if v.array_size > 0: lines.append( indent( "for (size_t _i = 0; _i < {0}.size() && _i < size_t(proto.{1}_size()); ++_i) {{".format( v.name, v.name.lower() ) ) ) lines.append( indent( "message::conversion::convert({0}[_i], proto.{1}(_i));".format( v.name, v.name.lower() ), 8, ) ) lines.append(indent("}")) else: # Add the top of our for loop for the repeated field lines.append(indent("{0}.resize(proto.{1}_size());".format(v.name, v.name.lower()))) lines.append(indent("for (size_t _i = 0; _i < {0}.size(); ++_i) {{".format(v.name))) lines.append( indent( "message::conversion::convert({0}[_i], proto.{1}(_i));".format( v.name, v.name.lower() ), 8, ) ) lines.append(indent("}")) else: # Basic and other types are handled the same if v.array_size > 0: lines.append( indent( "for (size_t _i = 0; _i < {0}.size() && _i < size_t(proto.{1}_size()); ++_i) {{".format( v.name, v.name.lower() ) ) ) lines.append(indent("{0}[_i] = proto.{1}(_i);".format(v.name, v.name.lower()), 8)) lines.append(indent("}")) else: lines.append( indent( "{0}.insert(std::end({0}), std::begin(proto.{1}()), std::end(proto.{1}()));".format( v.name, v.name.lower() ) ) ) elif v.one_of: lines.append(indent("switch (proto.{}_case()) {{").format(v.name.lower())) for oneof_field in v.oneof_fields: lines.append(indent("case {}: {{".format(oneof_field.number), 8)) if oneof_field.bytes_type: lines.append( indent( dedent( """\ {0}.{1} = {2}; {0}.{1}.insert(std::end({0}.{1}), std::begin(proto.{3}()), std::end(proto.{3}()));""".format( v.name, oneof_field.name, oneof_field.default_value, oneof_field.name.lower(), ) ), 12, ) ) elif oneof_field.special_cpp_type: lines.append( indent( dedent( """\ {0}.{1} = {2}; message::conversion::convert({0}.{1}, proto.{3}());""".format( v.name, oneof_field.name, oneof_field.default_value, oneof_field.name.lower(), ) ), 12, ) ) else: # Basic and other types are handled the same lines.append( indent( "{0}.{1} = proto.{2}();".format(v.name, oneof_field.name, oneof_field.name.lower()), 12, ) ) lines.append(indent("} break;", 8)) lines.append(indent("default: object.reset(); break;", 8)) lines.append(indent("}")) else: if v.bytes_type: lines.append( indent( "{0}.insert(std::end({0}), std::begin(proto.{1}()), std::end(proto.{1}()));".format( v.name, v.name.lower() ) ) ) elif v.special_cpp_type: lines.append( indent("message::conversion::convert({}, proto.{}());".format(v.name, v.name.lower())) ) else: # Basic and other types are handled the same lines.append(indent("{} = proto.{}();".format(v.name, v.name.lower()))) lines.append("}") return ("{}(const {}& proto);".format(self.name, protobuf_name), "\n".join(lines))
def generate_protobuf_converter(self): # Fully qualified c++ name cpp_fqn = '::'.join(self.fqn.split('.')) # Protobuf name protobuf_name = '::'.join(('.protobuf' + self.fqn).split('.')) # If we are empty it's easy if not self.fields: return ( 'operator {0}() const;'.format(protobuf_name), '{0}::operator {1}() const {{\n return {1}();\n}}'.format(cpp_fqn, protobuf_name) ) else: lines = [ '{}::operator {}() const {{'.format(cpp_fqn, protobuf_name), indent('{} proto;'.format(protobuf_name)) ] for v in self.fields: if v.pointer: print('TODO HANDLE POINTER CASES') elif v.map_type: # Add the top of our for loop for the repeated field lines.append(indent('for (auto& _v : {}) {{'.format(v.name))) if v.type[1].bytes_type: lines.append( indent( '(*proto.mutable_{}())[_v.first].append(std::begin(_v.second), std::end(_v.second));'. format(v.name.lower()), 8 ) ) elif v.type[1].special_cpp_type: lines.append( indent( 'message::conversion::convert((*proto.mutable_{}())[_v.first], _v.second);'.format( v.name.lower() ), 8 ) ) else: # Basic and others are handled the same lines.append(indent('(*proto.mutable_{}())[_v.first] = _v.second;'.format(v.name.lower()), 8)) lines.append(indent('}')) elif v.repeated: # We don't need to handle array here specially because it's the same # Add the top of our for loop for the repeated field lines.append(indent('for (auto& _v : {}) {{'.format(v.name))) if v.bytes_type: lines.append( indent('proto.add_{}()->append(std::begin(_v), std::end(_v));'.format(v.name.lower()), 8) ) elif v.special_cpp_type: lines.append( indent('message::conversion::convert(*proto.add_{}(), _v);'.format(v.name.lower()), 8) ) elif v.basic: lines.append(indent('proto.add_{}(_v);'.format(v.name.lower()), 8)) else: lines.append(indent('*proto.add_{}() = _v;'.format(v.name.lower()), 8)) lines.append(indent('}')) elif v.one_of: lines.append(indent('switch ({}.val_index) {{').format(v.name)) for oneof_field in v.oneof_fields: lines.append(indent('case {}: {{'.format(oneof_field.number), 8)) if oneof_field.bytes_type: lines.append( indent( 'proto.mutable_{0}()->append(std::begin({1}.{2}.get()), std::end({1}.{2}.get()));'. format(oneof_field.name.lower(), v.name, oneof_field.name), 12 ) ) elif oneof_field.special_cpp_type: lines.append( indent( 'message::conversion::convert(*proto.mutable_{0}(), {1}.{2}.get());'.format( oneof_field.name.lower(), v.name, oneof_field.name ), 12 ) ) elif oneof_field.basic: lines.append( indent( 'proto.set_{0}({1}.{2}.get());'.format( oneof_field.name.lower(), v.name, oneof_field.name ), 12 ) ) else: lines.append( indent( '*proto.mutable_{0}() = {1}.{2}.get();'.format( oneof_field.name.lower(), v.name, oneof_field.name ), 12 ) ) lines.append(indent('} break;', 8)) lines.append(indent('default: break;', 8)) lines.append(indent('}')) else: if v.bytes_type: lines.append( indent( 'proto.mutable_{0}()->append(std::begin({1}), std::end({1}));'.format( v.name.lower(), v.name ), 8 ) ) elif v.special_cpp_type: lines.append( indent( 'message::conversion::convert(*proto.mutable_{}(), {});'.format(v.name.lower(), v.name) ) ) elif v.basic: lines.append(indent('proto.set_{}({});'.format(v.name.lower(), v.name))) else: lines.append(indent('*proto.mutable_{}() = {};'.format(v.name.lower(), v.name))) lines.append(indent('return proto;')) lines.append('}') return 'operator {0}() const;'.format(protobuf_name), '\n'.join(lines)
def generate_protobuf_converter(self): # Fully qualified c++ name cpp_fqn = "::".join(self.fqn.split(".")) # Protobuf name protobuf_name = "::".join((".protobuf" + self.fqn).split(".")) # If we are empty it's easy if not self.fields: return ( "operator {0}() const;".format(protobuf_name), "{0}::operator {1}() const {{\n return {1}();\n}}".format(cpp_fqn, protobuf_name), ) else: lines = [ "{}::operator {}() const {{".format(cpp_fqn, protobuf_name), indent("{} proto;".format(protobuf_name)), ] for v in self.fields: if v.pointer: print("TODO HANDLE POINTER CASES") elif v.map_type: # Add the top of our for loop for the repeated field lines.append(indent("for (auto& _v : {}) {{".format(v.name))) if v.type[1].bytes_type: lines.append( indent( "(*proto.mutable_{}())[_v.first].append(std::begin(_v.second), std::end(_v.second));".format( v.name.lower() ), 8, ) ) elif v.type[1].special_cpp_type: lines.append( indent( "message::conversion::convert((*proto.mutable_{}())[_v.first], _v.second);".format( v.name.lower() ), 8, ) ) else: # Basic and others are handled the same lines.append(indent("(*proto.mutable_{}())[_v.first] = _v.second;".format(v.name.lower()), 8)) lines.append(indent("}")) elif v.repeated: # We don't need to handle array here specially because it's the same # Add the top of our for loop for the repeated field lines.append(indent("for (auto& _v : {}) {{".format(v.name))) if v.bytes_type: lines.append( indent("proto.add_{}()->append(std::begin(_v), std::end(_v));".format(v.name.lower()), 8) ) elif v.special_cpp_type: lines.append( indent("message::conversion::convert(*proto.add_{}(), _v);".format(v.name.lower()), 8) ) elif v.basic: lines.append(indent("proto.add_{}(_v);".format(v.name.lower()), 8)) else: lines.append(indent("*proto.add_{}() = _v;".format(v.name.lower()), 8)) lines.append(indent("}")) elif v.one_of: lines.append(indent("switch ({}.val_index) {{").format(v.name)) for oneof_field in v.oneof_fields: lines.append(indent("case {}: {{".format(oneof_field.number), 8)) if oneof_field.bytes_type: lines.append( indent( "proto.mutable_{0}()->append(std::begin({1}.{2}.get()), std::end({1}.{2}.get()));".format( oneof_field.name.lower(), v.name, oneof_field.name ), 12, ) ) elif oneof_field.special_cpp_type: lines.append( indent( "message::conversion::convert(*proto.mutable_{0}(), {1}.{2}.get());".format( oneof_field.name.lower(), v.name, oneof_field.name ), 12, ) ) elif oneof_field.basic: lines.append( indent( "proto.set_{0}({1}.{2}.get());".format( oneof_field.name.lower(), v.name, oneof_field.name ), 12, ) ) else: lines.append( indent( "*proto.mutable_{0}() = {1}.{2}.get();".format( oneof_field.name.lower(), v.name, oneof_field.name ), 12, ) ) lines.append(indent("} break;", 8)) lines.append(indent("default: break;", 8)) lines.append(indent("}")) else: if v.bytes_type: lines.append( indent( "proto.mutable_{0}()->append(std::begin({1}), std::end({1}));".format( v.name.lower(), v.name ), 8, ) ) elif v.special_cpp_type: lines.append( indent( "message::conversion::convert(*proto.mutable_{}(), {});".format(v.name.lower(), v.name) ) ) elif v.basic: lines.append(indent("proto.set_{}({});".format(v.name.lower(), v.name))) else: lines.append(indent("*proto.mutable_{}() = {};".format(v.name.lower(), v.name))) lines.append(indent("return proto;")) lines.append("}") return "operator {0}() const;".format(protobuf_name), "\n".join(lines)
def generate_cpp(self): # Make our value pairs fields = indent('\n'.join(['{}'.format(v.generate_cpp_header()) for v in self.fields])) # Generate our protobuf class name protobuf_type = '::'.join(('.protobuf' + self.fqn).split('.')) # Generate our enums c++ enums = [e.generate_cpp() for e in self.enums] enum_headers = indent('\n\n'.join([e[0] for e in enums])) enum_impls = ('\n\n'.join([e[1] for e in enums])) enum_python = ('\n\n'.join([e[2] for e in enums])) # Generate our submessage c++ submessages = [s.generate_cpp() for s in self.submessages] submessage_headers = indent('\n\n'.join([s[0] for s in submessages])) submessage_impls = ('\n\n'.join([s[1] for s in submessages])) submessage_python = ('\n\n'.join([s[2] for s in submessages])) # Get our function code default_constructor = self.generate_default_constructor() rule_of_five = self.generate_rule_of_five() protobuf_constructor = self.generate_protobuf_constructor() protobuf_converter = self.generate_protobuf_converter() equality_operator = self.generate_equality_operator() constructor_headers = indent( '\n\n'.join([default_constructor[0], rule_of_five[0], protobuf_constructor[0], equality_operator[0]]) ) constructor_impl = '\n\n'.join([default_constructor[1], protobuf_constructor[1], equality_operator[1]]) converter_headers = indent('\n\n'.join([protobuf_converter[0]])) converter_impl = '\n\n'.join([protobuf_converter[1]]) header_template = dedent( """\ struct {name} : public ::message::MessageBase<{name}> {{ // Protobuf type using protobuf_type = {protobuf_type}; // Enum Definitions {enums} // Submessage Definitions {submessages} // Constructors {constructors} // Converters {converters} // Fields {fields} }};""" ) impl_template = dedent( """\ // Constructors {constructors} // Converters {converters} // Subenums {enums} // Submessages {submessages}""" ) python_template = dedent( """\ // Local scope for this message {{ // Use our context and assign a new one to a shadow auto shadow = pybind11::class_<{fqn}, std::shared_ptr<{fqn}>>(context, "{name}"); // Shadow our context with our new context and declare our subclasses auto& context = shadow; {enums} {submessages} // Declare the functions on our class (which may use the ones in the subclasses) context {constructor} {message_members}; context.def_static("include_path", [] {{ return "{include_path}"; }}); // Build our emitter function that is used to emit this object context.def("_emit", [] ({fqn}& msg, pybind11::capsule capsule) {{ // Extract our reactor from the capsule NUClear::Reactor* reactor = capsule; // Do the emit reactor->powerplant.emit_shared<NUClear::dsl::word::emit::Local>(msg.shared_from_this()); }}); }}""" ) python_constructor_args = ['{}& self'.format(self.fqn.replace('.', '::'))] python_constructor_args.extend(['{} const& _{}'.format(t.cpp_type, t.name) for t in self.fields]) python_members = '\n'.join( '.def_readwrite("{field}", &{fqn}::{field})'.format(field=f.name, fqn=self.fqn.replace('.', '::')) for f in self.fields ) python_constructor_default_args = [''] python_constructor_default_args.extend([ 'pybind11::arg("{}") = {}'.format( t.name, t.default_value if t.default_value else '{}()'.format(t.cpp_type) ) for t in self.fields ]) python_constructor = dedent( """\ .def("__init__", [] ({args}) {{ new (&self) {name}({vars}); }}{default_args})""" ).format( name=self.fqn.replace('.', '::'), args=', '.join(python_constructor_args), vars=', '.join('_{}'.format(t.name) for t in self.fields), default_args=', '.join(python_constructor_default_args) ) return header_template.format( name=self.name, enums=enum_headers, submessages=submessage_headers, constructors=constructor_headers, protobuf_type=protobuf_type, converters=converter_headers, fields=fields ), impl_template.format( constructors=constructor_impl, converters=converter_impl, enums=enum_impls, submessages=submessage_impls ), python_template.format( constructor=indent(python_constructor, 8), message_members=indent(python_members, 8), include_path=self.include_path, fqn=self.fqn.replace('.', '::'), name=self.name, submessages=indent(submessage_python), enums=indent(enum_python) )
if function not in duplicates: duplicates.append(function) functions.append(function) # Write our file with open(base_file, 'w') as f: f.write(dedent("""\ #include <pybind11/pybind11.h> #include <pybind11/complex.h> #include <pybind11/stl.h> #include <pybind11/eigen.h> // Declare our functions (that we know will be made later) {function_declarations} PYBIND11_PLUGIN(message) {{ pybind11::module module("message", "NUClear message classes"); // Initialise each of the modules {function_calls} return module.ptr(); }} """).format( function_declarations='\n'.join('void init_message_{}(pybind11::module& module);'.format(f) for f in functions), function_calls=indent('\n'.join('init_message_{}(module);'.format(f) for f in functions)), ))
def generate_protobuf_converter(self): # Fully qualified c++ name cpp_fqn = '::'.join(self.fqn.split('.')) # Protobuf name protobuf_name = '::'.join(('.protobuf' + self.fqn).split('.')) # If we are empty it's easy if not self.fields: return ( 'operator {0}() const;'.format(protobuf_name), '{0}::operator {1}() const {{\n return {1}();\n}}'.format( cpp_fqn, protobuf_name)) else: lines = [ '{}::operator {}() const {{'.format(cpp_fqn, protobuf_name), indent('{} proto;'.format(protobuf_name)) ] for v in self.fields: if v.pointer: print('TODO HANDLE POINTER CASES') elif v.map_type: # Add the top of our for loop for the repeated field lines.append( indent('for (auto& _v : {}) {{'.format(v.name))) if v.type[1].bytes_type: lines.append( indent( '(*proto.mutable_{}())[_v.first].append(std::begin(_v.second), std::end(_v.second));' .format(v.name.lower()), 8)) elif v.type[1].special_cpp_type: lines.append( indent( 'message::conversion::convert((*proto.mutable_{}())[_v.first], _v.second);' .format(v.name.lower()), 8)) else: # Basic and others are handled the same lines.append( indent( '(*proto.mutable_{}())[_v.first] = _v.second;'. format(v.name.lower()), 8)) lines.append(indent('}')) elif v.repeated: # We don't need to handle array here specially because it's the same # Add the top of our for loop for the repeated field lines.append( indent('for (auto& _v : {}) {{'.format(v.name))) if v.bytes_type: lines.append( indent( 'proto.add_{}()->append(std::begin(_v), std::end(_v));' .format(v.name.lower()), 8)) elif v.special_cpp_type: lines.append( indent( 'message::conversion::convert(*proto.add_{}(), _v);' .format(v.name.lower()), 8)) elif v.basic: lines.append( indent('proto.add_{}(_v);'.format(v.name.lower()), 8)) else: lines.append( indent( '*proto.add_{}() = _v;'.format(v.name.lower()), 8)) lines.append(indent('}')) elif v.one_of: lines.append( indent('switch ({}.val_index) {{').format(v.name)) for oneof_field in v.oneof_fields: lines.append( indent('case {}: {{'.format(oneof_field.number), 8)) if oneof_field.bytes_type: lines.append( indent( 'proto.mutable_{0}()->append(std::begin({1}.{2}.get()), std::end({1}.{2}.get()));' .format(oneof_field.name.lower(), v.name, oneof_field.name), 12)) elif oneof_field.special_cpp_type: lines.append( indent( 'message::conversion::convert(*proto.mutable_{0}(), {1}.{2}.get());' .format(oneof_field.name.lower(), v.name, oneof_field.name), 12)) elif oneof_field.basic: lines.append( indent( 'proto.set_{0}({1}.{2}.get());'.format( oneof_field.name.lower(), v.name, oneof_field.name), 12)) else: lines.append( indent( '*proto.mutable_{0}() = {1}.{2}.get();'. format(oneof_field.name.lower(), v.name, oneof_field.name), 12)) lines.append(indent('} break;', 8)) lines.append(indent('default: break;', 8)) lines.append(indent('}')) else: if v.bytes_type: lines.append( indent( 'proto.mutable_{0}()->append(std::begin({1}), std::end({1}));' .format(v.name.lower(), v.name), 8)) elif v.special_cpp_type: lines.append( indent( 'message::conversion::convert(*proto.mutable_{}(), {});' .format(v.name.lower(), v.name))) elif v.basic: lines.append( indent('proto.set_{}({});'.format( v.name.lower(), v.name))) else: lines.append( indent('*proto.mutable_{}() = {};'.format( v.name.lower(), v.name))) lines.append(indent('return proto;')) lines.append('}') return 'operator {0}() const;'.format(protobuf_name), '\n'.join( lines)