# # 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)