#
# Copyright (C) 2020 Embedded AMS B.V. - All Rights Reserved
#
# This file is part of Embedded Proto.
#
# Embedded Proto is open source software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation, version 3 of the license.
#
# Embedded Proto is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Embedded Proto. If not, see .
#
# For commercial and closed source application please visit:
# .
#
# Embedded AMS B.V.
# Info:
# info at EmbeddedProto dot com
#
# Postal address:
# Johan Huizingalaan 763a
# 1066 VH, Amsterdam
# the Netherlands
#
from google.protobuf.descriptor_pb2 import FieldDescriptorProto
import copy
# This class is the base class for any kind of field used in protobuf messages.
class Field:
def __init__(self, proto_descriptor, parent_msg, template_filename, oneof=None):
# A reference to the FieldDescriptorProto object which defines this field.
self.descriptor = proto_descriptor
# A reference to the parent message in which this field is defined.
self.parent = parent_msg
# If this field is part of a oneof this is the reference to it.
self.oneof = oneof
self.name = self.descriptor.name
self.variable_name = self.name + "_"
self.variable_id_name = self.name.upper()
self.variable_id = self.descriptor.number
self.template_file = template_filename
self.of_type_enum = FieldDescriptorProto.TYPE_ENUM == proto_descriptor.type
@staticmethod
# This function create the appropriate field object for a variable defined in the message.
# The descriptor and parent message parameters are required parameters, all field need them to be created. The oneof
# parameter is only required for fields which are part of a oneof. The parameter is the reference to the oneof
# object.
# The last parameter is not to be used manually. It is set when we are already in a FieldNested class.
def factory(proto_descriptor, parent_msg, oneof=None, already_nested=False):
if (FieldDescriptorProto.LABEL_REPEATED == proto_descriptor.label) and not already_nested:
result = FieldRepeated(proto_descriptor, parent_msg, oneof)
elif FieldDescriptorProto.TYPE_MESSAGE == proto_descriptor.type:
result = FieldMessage(proto_descriptor, parent_msg, oneof)
elif FieldDescriptorProto.TYPE_ENUM == proto_descriptor.type:
result = FieldEnum(proto_descriptor, parent_msg, oneof)
elif FieldDescriptorProto.TYPE_STRING == proto_descriptor.type:
result = FieldString(proto_descriptor, parent_msg, oneof)
elif FieldDescriptorProto.TYPE_BYTES == proto_descriptor.type:
result = FieldBytes(proto_descriptor, parent_msg, oneof)
else:
result = FieldBasic(proto_descriptor, parent_msg, oneof)
return result
def get_wire_type_str(self):
return ""
def get_type(self):
return ""
def get_short_type(self):
return ""
def get_default_value(self):
return ""
def get_name(self):
return self.name
def get_variable_name(self):
var_name = ""
if self.oneof:
var_name = self.oneof.get_variable_name() + "."
var_name += self.variable_name
return var_name
def get_variable_id_name(self):
return self.variable_id_name
# Returns a list with a dictionaries for each template parameter this field had. The dictionary holds the parameter
# name and its type.
def get_template_parameters(self):
# For the field that do not have any templates return an empty list.
return []
def match_field_with_definitions(self, all_types_definitions):
pass
def register_template_parameters(self):
return True
# Returns true if in oneof.init the new& function needs to be call to initialize already allocated memory.
def oneof_allocation_required(self):
return (type(self) is FieldMessage) or (type(self) is FieldRepeated) or (type(self) is FieldString) or \
(type(self) is FieldBytes)
def get_oneof_name(self):
return self.oneof.get_name()
def get_which_oneof(self):
return self.oneof.get_which_oneof()
# Get the scope relevant compared to the scope this field is used in.
def get_reduced_scope(self):
parent_scope = self.parent.scope.get()
def_scope = self.definition.scope.get()
start_index = 0
for ds, ps in zip(def_scope[:-1], parent_scope):
if ds == ps:
start_index += 1
else:
break
reduced_scope = def_scope[start_index:]
return reduced_scope
def render(self, filename, jinja_environment):
template = jinja_environment.get_template(filename)
rendered_str = template.render(field=self, environment=jinja_environment)
return rendered_str
# -----------------------------------------------------------------------------
# This class is used to define any type of basic field.
class FieldBasic(Field):
# A dictionary to convert the wire type into a default value.
type_to_default_value = {FieldDescriptorProto.TYPE_DOUBLE: "0.0",
FieldDescriptorProto.TYPE_FLOAT: "0.0",
FieldDescriptorProto.TYPE_INT64: "0",
FieldDescriptorProto.TYPE_UINT64: "0U",
FieldDescriptorProto.TYPE_INT32: "0",
FieldDescriptorProto.TYPE_FIXED64: "0U",
FieldDescriptorProto.TYPE_FIXED32: "0U",
FieldDescriptorProto.TYPE_BOOL: "false",
FieldDescriptorProto.TYPE_UINT32: "0U",
FieldDescriptorProto.TYPE_SFIXED32: "0",
FieldDescriptorProto.TYPE_SFIXED64: "0",
FieldDescriptorProto.TYPE_SINT32: "0",
FieldDescriptorProto.TYPE_SINT64: "0"}
# A dictionary to convert the protobuf wire type into a C++ type.
type_to_cpp_type = {FieldDescriptorProto.TYPE_DOUBLE: "EmbeddedProto::doublefixed",
FieldDescriptorProto.TYPE_FLOAT: "EmbeddedProto::floatfixed",
FieldDescriptorProto.TYPE_INT64: "EmbeddedProto::int64",
FieldDescriptorProto.TYPE_UINT64: "EmbeddedProto::uint64",
FieldDescriptorProto.TYPE_INT32: "EmbeddedProto::int32",
FieldDescriptorProto.TYPE_FIXED64: "EmbeddedProto::fixed64",
FieldDescriptorProto.TYPE_FIXED32: "EmbeddedProto::fixed32",
FieldDescriptorProto.TYPE_BOOL: "EmbeddedProto::boolean",
FieldDescriptorProto.TYPE_UINT32: "EmbeddedProto::uint32",
FieldDescriptorProto.TYPE_SFIXED32: "EmbeddedProto::sfixed32",
FieldDescriptorProto.TYPE_SFIXED64: "EmbeddedProto::sfixed64",
FieldDescriptorProto.TYPE_SINT32: "EmbeddedProto::sint32",
FieldDescriptorProto.TYPE_SINT64: "EmbeddedProto::sint64"}
# A dictionary to convert the wire type number into a wire type string.
type_to_wire_type = {FieldDescriptorProto.TYPE_INT32: "VARINT",
FieldDescriptorProto.TYPE_INT64: "VARINT",
FieldDescriptorProto.TYPE_UINT32: "VARINT",
FieldDescriptorProto.TYPE_UINT64: "VARINT",
FieldDescriptorProto.TYPE_SINT32: "VARINT",
FieldDescriptorProto.TYPE_SINT64: "VARINT",
FieldDescriptorProto.TYPE_BOOL: "VARINT",
FieldDescriptorProto.TYPE_FIXED64: "FIXED64",
FieldDescriptorProto.TYPE_SFIXED64: "FIXED64",
FieldDescriptorProto.TYPE_DOUBLE: "FIXED64",
FieldDescriptorProto.TYPE_FIXED32: "FIXED32",
FieldDescriptorProto.TYPE_FLOAT: "FIXED32",
FieldDescriptorProto.TYPE_SFIXED32: "FIXED32"}
def __init__(self, proto_descriptor, parent_msg, oneof=None):
super().__init__(proto_descriptor, parent_msg, "FieldBasic.h", oneof)
def get_wire_type_str(self):
return self.type_to_wire_type[self.descriptor.type]
def get_type(self):
return self.type_to_cpp_type[self.descriptor.type]
def get_short_type(self):
return self.get_type().split("::")[-1]
def get_default_value(self):
return self.type_to_default_value[self.descriptor.type]
def render_get_set(self, jinja_env):
return self.render("FieldBasic_GetSet.h", jinja_environment=jinja_env)
def render_serialize(self, jinja_env):
return self.render("FieldBasic_Serialize.h", jinja_environment=jinja_env)
def render_deserialize(self, jinja_env):
return self.render("FieldBasic_Deserialize.h", jinja_environment=jinja_env)
# -----------------------------------------------------------------------------
# This class defines a string field
class FieldString(Field):
def __init__(self, proto_descriptor, parent_msg, oneof=None):
super().__init__(proto_descriptor, parent_msg, "FieldString.h", oneof)
# This is the name given to the template parameter for the length.
self.template_param_str = self.variable_name + "LENGTH"
def get_wire_type_str(self):
return "LENGTH_DELIMITED"
def get_type(self):
return "::EmbeddedProto::FieldString<" + self.template_param_str + ">"
def get_short_type(self):
return "FieldString"
def get_template_parameters(self):
return [{"name": self.template_param_str, "type": "uint32_t"}]
def register_template_parameters(self):
self.parent.scope.register_template_parameters(self)
return True
def render_get_set(self, jinja_env):
return self.render("FieldString_GetSet.h", jinja_environment=jinja_env)
def render_serialize(self, jinja_env):
return self.render("FieldRepeated_Serialize.h", jinja_environment=jinja_env)
def render_deserialize(self, jinja_env):
return self.render("FieldBasic_Deserialize.h", jinja_environment=jinja_env)
# -----------------------------------------------------------------------------
# This class defines a bytes array field
class FieldBytes(Field):
def __init__(self, proto_descriptor, parent_msg, oneof=None):
super().__init__(proto_descriptor, parent_msg, "FieldBytes.h", oneof)
# This is the name given to the template parameter for the length.
self.template_param_str = self.variable_name + "LENGTH"
def get_wire_type_str(self):
return "LENGTH_DELIMITED"
def get_type(self):
return "::EmbeddedProto::FieldBytes<" + self.template_param_str + ">"
def get_short_type(self):
return "FieldBytes"
def get_template_parameters(self):
return [{"name": self.template_param_str, "type": "uint32_t"}]
def register_template_parameters(self):
self.parent.register_child_with_template(self)
return True
def render_get_set(self, jinja_env):
return self.render("FieldBytes_GetSet.h", jinja_environment=jinja_env)
def render_serialize(self, jinja_env):
return self.render("FieldRepeated_Serialize.h", jinja_environment=jinja_env)
def render_deserialize(self, jinja_env):
return self.render("FieldBasic_Deserialize.h", jinja_environment=jinja_env)
# -----------------------------------------------------------------------------
# This class is used to wrap around any enum used as a field.
class FieldEnum(Field):
def __init__(self, proto_descriptor, parent_msg, oneof=None):
super().__init__(proto_descriptor, parent_msg, "FieldEnum.h", oneof)
# Reserve a member variable for the reference to the enum definition used for this field.
self.definition = None
def get_wire_type_str(self):
return "VARINT"
def get_type(self):
if not self.definition:
# When the actual definition is unknown use the protobuf type.
type_name = self.descriptor.type_name if "." != self.descriptor.type_name[0] else self.descriptor.type_name[1:]
type_name = type_name.replace(".", "::")
else:
scopes = self.get_reduced_scope()
type_name = ""
for scope in scopes:
if scope["templates"]:
raise Exception("You are trying to use a field with the type: \"" + self.descriptor.type_name +
"\". It is defined in different scope as where you are using it. But the scope of "
"definition includes template parameters for repeated, string or byte fields. It "
"is there for not possible to define the field where you are using it as we do not "
"know the template value. Try defining the field in the main scope or the one you "
"are using it in.")
type_name += scope["name"] + "::"
# Remove the last ::
type_name = type_name[:-2]
return type_name
def get_short_type(self):
return self.get_type().split("::")[-1]
def get_default_value(self):
return "static_cast<" + self.get_type() + ">(0)"
def match_field_with_definitions(self, all_types_definitions):
found = False
my_type = self.get_type()
for enum_defs in all_types_definitions["enums"]:
other_scope = enum_defs.scope.get_scope_str()
if my_type == other_scope:
self.definition = enum_defs
found = True
break
if not found:
raise Exception("Unable to find the definition of this enum: " + self.name)
def render_get_set(self, jinja_env):
return self.render("FieldEnum_GetSet.h", jinja_environment=jinja_env)
def render_serialize(self, jinja_env):
return self.render("FieldEnum_Serialize.h", jinja_environment=jinja_env)
def render_deserialize(self, jinja_env):
return self.render("FieldEnum_Deserialize.h", jinja_environment=jinja_env)
# -----------------------------------------------------------------------------
# This class is used to wrap around any type of message used as a field.
class FieldMessage(Field):
def __init__(self, proto_descriptor, parent_msg, oneof=None):
super().__init__(proto_descriptor, parent_msg, "FieldMsg.h", oneof)
# Reserve a member variable for the reference to the message definition used for this field.
self.definition = None
def get_wire_type_str(self):
return "LENGTH_DELIMITED"
def get_type(self):
if not self.definition:
# When the actual definition is unknown use the protobuf type.
type_name = self.descriptor.type_name if "." != self.descriptor.type_name[0] else self.descriptor.type_name[1:]
type_name = type_name.replace(".", "::")
else:
scopes = self.get_reduced_scope()
type_name = ""
for scope in scopes:
type_name += scope["name"] + "::"
# Remove the last ::
type_name = type_name[:-2]
tmpl_param = self.get_template_parameters()
if tmpl_param:
type_name += "<"
for param in tmpl_param:
type_name += param["name"] + ", "
type_name = type_name[:-2] + ">"
return type_name
def get_short_type(self):
return self.get_type().split("::")[-1]
def get_default_value(self):
# Just call the default constructor.
return ""
def get_template_parameters(self):
# Get the template names used by the definition.
templates = copy.deepcopy(self.definition.get_templates())
# Next add our variable name to make them unique.
for tmp in templates:
tmp["name"] = self.variable_name + tmp["name"]
return templates
def match_field_with_definitions(self, all_types_definitions):
found = False
my_type = self.get_type()
for msg_defs in all_types_definitions["messages"]:
other_scope = msg_defs.scope.get_scope_str()
if my_type == other_scope:
self.definition = msg_defs
found = True
break
if not found:
raise Exception("Unable to find the definition of this message: " + self.name)
def register_template_parameters(self):
if self.definition.all_parameters_registered:
if self.definition.contains_template_parameters:
self.parent.register_child_with_template(self)
return True
else:
return False
# Get the whole scope of the definition of this field.
def get_scope(self):
return self.definition.scope.get()
def render_get_set(self, jinja_env):
return self.render("FieldMsg_GetSet.h", jinja_environment=jinja_env)
def render_serialize(self, jinja_env):
return self.render("FieldMsg_Serialize.h", jinja_environment=jinja_env)
def render_deserialize(self, jinja_env):
return self.render("FieldMsg_Deserialize.h", jinja_environment=jinja_env)
# -----------------------------------------------------------------------------
# This class wraps around any other type of field which is repeated.
class FieldRepeated(Field):
def __init__(self, proto_descriptor, parent_msg, oneof=None):
super().__init__(proto_descriptor, parent_msg, "FieldRepeated.h", oneof)
# To make use of the field object actual type create one of their objects.
self.actual_type = Field.factory(proto_descriptor, parent_msg, oneof, already_nested=True)
# This is the name given to the template parameter for the length.
self.template_param_str = self.variable_name + "REP_LENGTH"
def get_wire_type_str(self):
return "LENGTH_DELIMITED"
def get_type(self):
return "::EmbeddedProto::RepeatedFieldFixedSize<" + self.actual_type.get_type() + ", " + \
self.template_param_str + ">"
def get_short_type(self):
return "::EmbeddedProto::RepeatedFieldFixedSize<" + self.actual_type.get_short_type() + ", " + \
self.template_param_str + ">"
# As this is a repeated field we need a function to get the type we are repeating.
def get_base_type(self):
return self.actual_type.get_type()
def get_template_parameters(self):
result = [{"name": self.template_param_str, "type": "uint32_t"}]
result.extend(self.actual_type.get_template_parameters())
return result
def match_field_with_definitions(self, all_types_definitions):
self.actual_type.match_field_with_definitions(all_types_definitions)
def register_template_parameters(self):
self.parent.register_child_with_template(self)
return True
def render_get_set(self, jinja_env):
return self.render("FieldRepeated_GetSet.h", jinja_environment=jinja_env)
def render_serialize(self, jinja_env):
return self.render("FieldRepeated_Serialize.h", jinja_environment=jinja_env)
def render_deserialize(self, jinja_env):
return self.render("FieldBasic_Deserialize.h", jinja_environment=jinja_env)