EmbeddedProto 4.0.0b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- EmbeddedProto/EmbeddedProto.py +122 -0
- EmbeddedProto/Features.py +255 -0
- EmbeddedProto/Field.py +1295 -0
- EmbeddedProto/Oneof.py +74 -0
- EmbeddedProto/ProtoFile.py +210 -0
- EmbeddedProto/TypeDefinitions.py +367 -0
- EmbeddedProto/__init__.py +0 -0
- EmbeddedProto/__main__.py +28 -0
- EmbeddedProto/config.py +173 -0
- EmbeddedProto/custom_header.py +513 -0
- EmbeddedProto/embedded_proto_options.proto +62 -0
- EmbeddedProto/embedded_proto_options_pb2.py +37 -0
- EmbeddedProto/field_options.py +344 -0
- EmbeddedProto/main.py +316 -0
- EmbeddedProto/src/EmbeddedProto/BytesStringCallback.h +476 -0
- EmbeddedProto/src/EmbeddedProto/Defines.h +118 -0
- EmbeddedProto/src/EmbeddedProto/EmptyArray.h +81 -0
- EmbeddedProto/src/EmbeddedProto/Errors.h +53 -0
- EmbeddedProto/src/EmbeddedProto/FieldStringBytes.h +793 -0
- EmbeddedProto/src/EmbeddedProto/Fields.h +832 -0
- EmbeddedProto/src/EmbeddedProto/Functional.h +412 -0
- EmbeddedProto/src/EmbeddedProto/MessageCallback.h +688 -0
- EmbeddedProto/src/EmbeddedProto/MessageInterface.h +792 -0
- EmbeddedProto/src/EmbeddedProto/MessageSizeCalculator.h +109 -0
- EmbeddedProto/src/EmbeddedProto/MessageState.h +207 -0
- EmbeddedProto/src/EmbeddedProto/ReadBufferFixedSize.h +204 -0
- EmbeddedProto/src/EmbeddedProto/ReadBufferInterface.h +181 -0
- EmbeddedProto/src/EmbeddedProto/ReadBufferSection.h +232 -0
- EmbeddedProto/src/EmbeddedProto/RepeatedField.h +1053 -0
- EmbeddedProto/src/EmbeddedProto/RepeatedFieldCallback.h +432 -0
- EmbeddedProto/src/EmbeddedProto/RepeatedFieldFixedSize.h +367 -0
- EmbeddedProto/src/EmbeddedProto/Version.h +39 -0
- EmbeddedProto/src/EmbeddedProto/WireFormatter.h +852 -0
- EmbeddedProto/src/EmbeddedProto/WriteBufferFixedSize.h +114 -0
- EmbeddedProto/src/EmbeddedProto/WriteBufferInterface.h +134 -0
- EmbeddedProto/src/EmbeddedProto.h +55 -0
- EmbeddedProto/templates/FieldBasic_Deserialize.h.jinja2 +26 -0
- EmbeddedProto/templates/FieldBasic_GetSet.h.jinja2 +89 -0
- EmbeddedProto/templates/FieldBytes_GetSet.h.jinja2 +86 -0
- EmbeddedProto/templates/FieldEnum_Deserialize.h.jinja2 +50 -0
- EmbeddedProto/templates/FieldEnum_GetSet.h.jinja2 +93 -0
- EmbeddedProto/templates/FieldErrorRecursive_GetSet.h.jinja2 +29 -0
- EmbeddedProto/templates/FieldMap_GetSet.h.jinja2 +203 -0
- EmbeddedProto/templates/FieldMsg_Deserialize.h.jinja2 +44 -0
- EmbeddedProto/templates/FieldMsg_GetSet.h.jinja2 +93 -0
- EmbeddedProto/templates/FieldRepeated_GetSet.h.jinja2 +94 -0
- EmbeddedProto/templates/FieldString_GetSet.h.jinja2 +86 -0
- EmbeddedProto/templates/Field_DeserializePartial.h.jinja2 +33 -0
- EmbeddedProto/templates/Field_Serialize.h.jinja2 +112 -0
- EmbeddedProto/templates/Field_SerializePartial.h.jinja2 +119 -0
- EmbeddedProto/templates/Header.h.jinja2 +108 -0
- EmbeddedProto/templates/TypeDefEnum.h.jinja2 +45 -0
- EmbeddedProto/templates/TypeDefMsg.h.jinja2 +791 -0
- EmbeddedProto/templates/TypeOneof.h.jinja2 +178 -0
- EmbeddedProto/version.json +3 -0
- embeddedproto-4.0.0b1.dist-info/METADATA +141 -0
- embeddedproto-4.0.0b1.dist-info/RECORD +63 -0
- embeddedproto-4.0.0b1.dist-info/WHEEL +5 -0
- embeddedproto-4.0.0b1.dist-info/entry_points.txt +3 -0
- embeddedproto-4.0.0b1.dist-info/licenses/LICENSE +30 -0
- embeddedproto-4.0.0b1.dist-info/licenses/LICENSES/GPL-3.0-only.txt +674 -0
- embeddedproto-4.0.0b1.dist-info/licenses/LICENSES/LicenseRef-EmbeddedProto-Commercial.txt +12 -0
- embeddedproto-4.0.0b1.dist-info/top_level.txt +1 -0
EmbeddedProto/Oneof.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (C) 2020-2026 Embedded AMS B.V. - All Rights Reserved
|
|
3
|
+
#
|
|
4
|
+
# This file is part of Embedded Proto.
|
|
5
|
+
#
|
|
6
|
+
# Embedded Proto is dual licensed. You may use it under the terms of the
|
|
7
|
+
# GNU General Public License version 3 (GPLv3) as published by the Free
|
|
8
|
+
# Software Foundation, or under a commercial license from Embedded AMS B.V.
|
|
9
|
+
#
|
|
10
|
+
# Under the GPLv3 you must release the source code of
|
|
11
|
+
# any product you distribute that includes Embedded Proto or code
|
|
12
|
+
# generated by it. A commercial license removes that obligation.
|
|
13
|
+
# See <https://embeddedproto.com/pricing/>.
|
|
14
|
+
#
|
|
15
|
+
# Embedded Proto is distributed WITHOUT ANY WARRANTY; without even the
|
|
16
|
+
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
17
|
+
# See the LICENSE file in the root of the repository for details.
|
|
18
|
+
#
|
|
19
|
+
# SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-EmbeddedProto-Commercial
|
|
20
|
+
#
|
|
21
|
+
# Embedded AMS B.V., Hoorn, the Netherlands - info at EmbeddedProto dot com
|
|
22
|
+
#
|
|
23
|
+
|
|
24
|
+
from .Field import Field
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Oneof:
|
|
28
|
+
def __init__(self, oneof_proto_descriptor, index, msg_descriptor, parent_msg):
|
|
29
|
+
# A reference to the OneofDescriptorProto object which defines this field.
|
|
30
|
+
self.descriptor = oneof_proto_descriptor
|
|
31
|
+
|
|
32
|
+
# A reference to the parent message in which this oneof is defined.
|
|
33
|
+
self.parent = parent_msg
|
|
34
|
+
|
|
35
|
+
self.fields = []
|
|
36
|
+
# Loop over all the fields in this oneof
|
|
37
|
+
for f in msg_descriptor.field:
|
|
38
|
+
if f.HasField('oneof_index') and index == f.oneof_index:
|
|
39
|
+
new_field = Field.factory(f, self.parent, oneof=self)
|
|
40
|
+
self.fields.append(new_field)
|
|
41
|
+
|
|
42
|
+
def get_name(self):
|
|
43
|
+
return self.descriptor.name
|
|
44
|
+
|
|
45
|
+
def get_variable_name(self):
|
|
46
|
+
return self.get_name() + "_"
|
|
47
|
+
|
|
48
|
+
def get_which_oneof(self):
|
|
49
|
+
return "which_" + self.get_name()
|
|
50
|
+
|
|
51
|
+
def get_which_method(self):
|
|
52
|
+
return "get_" + self.get_which_oneof() + "()"
|
|
53
|
+
|
|
54
|
+
def get_fields(self):
|
|
55
|
+
return self.fields
|
|
56
|
+
|
|
57
|
+
def match_field_with_definitions(self, all_types_definitions):
|
|
58
|
+
for field in self.fields:
|
|
59
|
+
field.match_field_with_definitions(all_types_definitions)
|
|
60
|
+
|
|
61
|
+
def register_template_parameters(self):
|
|
62
|
+
all_parameters_registered = True
|
|
63
|
+
for field in self.fields:
|
|
64
|
+
all_parameters_registered = field.register_template_parameters() and all_parameters_registered
|
|
65
|
+
return all_parameters_registered
|
|
66
|
+
|
|
67
|
+
# Returns true if in oneof.init the new& function needs to be call to initialize already allocated memory.
|
|
68
|
+
def oneof_allocation_required(self):
|
|
69
|
+
result = False
|
|
70
|
+
for field in self.fields:
|
|
71
|
+
result = field.oneof_allocation_required()
|
|
72
|
+
if result:
|
|
73
|
+
break
|
|
74
|
+
return result
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (C) 2020-2026 Embedded AMS B.V. - All Rights Reserved
|
|
3
|
+
#
|
|
4
|
+
# This file is part of Embedded Proto.
|
|
5
|
+
#
|
|
6
|
+
# Embedded Proto is dual licensed. You may use it under the terms of the
|
|
7
|
+
# GNU General Public License version 3 (GPLv3) as published by the Free
|
|
8
|
+
# Software Foundation, or under a commercial license from Embedded AMS B.V.
|
|
9
|
+
#
|
|
10
|
+
# Under the GPLv3 you must release the source code of
|
|
11
|
+
# any product you distribute that includes Embedded Proto or code
|
|
12
|
+
# generated by it. A commercial license removes that obligation.
|
|
13
|
+
# See <https://embeddedproto.com/pricing/>.
|
|
14
|
+
#
|
|
15
|
+
# Embedded Proto is distributed WITHOUT ANY WARRANTY; without even the
|
|
16
|
+
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
17
|
+
# See the LICENSE file in the root of the repository for details.
|
|
18
|
+
#
|
|
19
|
+
# SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-EmbeddedProto-Commercial
|
|
20
|
+
#
|
|
21
|
+
# Embedded AMS B.V., Hoorn, the Netherlands - info at EmbeddedProto dot com
|
|
22
|
+
#
|
|
23
|
+
|
|
24
|
+
from .TypeDefinitions import *
|
|
25
|
+
from .Features import FeatureResolver
|
|
26
|
+
from . import field_options
|
|
27
|
+
import os
|
|
28
|
+
import sys
|
|
29
|
+
from toposort import CircularDependencyError, toposort_flatten
|
|
30
|
+
from google.protobuf import descriptor_pb2
|
|
31
|
+
from google.protobuf.descriptor_pb2 import FieldDescriptorProto
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Proto files for which no C++ code is generated. Our own options file only carries generator settings. The google
|
|
35
|
+
# descriptor file only carries the definitions required to declare custom options, those definitions are used by protoc
|
|
36
|
+
# and other plugins, not by the embedded target.
|
|
37
|
+
EXCLUDED_PROTO_FILES = ("embedded_proto_options.proto", "google/protobuf/descriptor.proto")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# Return true if no C++ code should be generated for the given proto file name.
|
|
41
|
+
def is_excluded_proto_file(proto_filename):
|
|
42
|
+
return any(excluded in proto_filename for excluded in EXCLUDED_PROTO_FILES)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# -----------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
def toposort_add_msg(msg, namespace, dependency_data):
|
|
48
|
+
|
|
49
|
+
local_definitions = []
|
|
50
|
+
local_namespace = namespace + "." + msg.name
|
|
51
|
+
|
|
52
|
+
dependencies = {namespace}
|
|
53
|
+
|
|
54
|
+
for nested_msg in msg.nested_type:
|
|
55
|
+
dependency_data = toposort_add_msg(nested_msg, local_namespace, dependency_data)
|
|
56
|
+
full_msg_type = local_namespace + "." + nested_msg.name
|
|
57
|
+
local_definitions.append(full_msg_type)
|
|
58
|
+
for dep in dependency_data[full_msg_type]:
|
|
59
|
+
# Add requirements on other namespaces.
|
|
60
|
+
if not dep.startswith(local_namespace):
|
|
61
|
+
dependencies.add(dep)
|
|
62
|
+
|
|
63
|
+
for nested_enum in msg.enum_type:
|
|
64
|
+
full_enum_type = local_namespace + "." + nested_enum.name
|
|
65
|
+
dependency_data[full_enum_type] = {local_namespace}
|
|
66
|
+
local_definitions.append(full_enum_type)
|
|
67
|
+
|
|
68
|
+
for f in msg.field:
|
|
69
|
+
if ((FieldDescriptorProto.TYPE_MESSAGE == f.type) or (FieldDescriptorProto.TYPE_ENUM == f.type)) \
|
|
70
|
+
and (f.type_name not in local_definitions) \
|
|
71
|
+
and not f.type_name.startswith(local_namespace + "."):
|
|
72
|
+
dependencies.add(f.type_name)
|
|
73
|
+
|
|
74
|
+
# If we have any dependencies add them
|
|
75
|
+
if 0 < len(dependencies):
|
|
76
|
+
dependency_data[local_namespace] = dependencies
|
|
77
|
+
|
|
78
|
+
return dependency_data
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# -----------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
class ProtoFile:
|
|
84
|
+
def __init__(self, proto_descriptor, options_file=None):
|
|
85
|
+
self.descriptor = proto_descriptor
|
|
86
|
+
|
|
87
|
+
# The external field options file, shared by every field in this run. None when no file was given.
|
|
88
|
+
self.options_file = options_file
|
|
89
|
+
|
|
90
|
+
if "proto2" == proto_descriptor.syntax:
|
|
91
|
+
raise Exception(proto_descriptor.name + ": Sorry, proto2 is not supported, please use proto3.")
|
|
92
|
+
|
|
93
|
+
# Select the edition feature profile for this file and resolve the
|
|
94
|
+
# file-scope feature set. proto3 maps to the proto3 profile so the existing
|
|
95
|
+
# generation path is a special case of the same machinery.
|
|
96
|
+
self.feature_resolver = FeatureResolver(proto_descriptor)
|
|
97
|
+
self.file_features = self.feature_resolver.file_features
|
|
98
|
+
|
|
99
|
+
# These file names are the ones used for creating the C++ files.
|
|
100
|
+
self.filename_with_folder = os.path.splitext(proto_descriptor.name)[0]
|
|
101
|
+
self.filename_without_folder = os.path.basename(self.filename_with_folder)
|
|
102
|
+
|
|
103
|
+
# Construct the base scope used in this file.
|
|
104
|
+
self.scope = None
|
|
105
|
+
if self.descriptor.package:
|
|
106
|
+
package_list = self.descriptor.package.split(".")
|
|
107
|
+
# The first element is the base scope.
|
|
108
|
+
self.scope = Scope(package_list[0], None)
|
|
109
|
+
# Next add additional scopes nesting the previous one.
|
|
110
|
+
for package in package_list[1:]:
|
|
111
|
+
self.scope = Scope(package, self.scope)
|
|
112
|
+
|
|
113
|
+
self.enum_definitions = [EnumDefinition(enum, self.scope, self.feature_resolver, self.file_features)
|
|
114
|
+
for enum in self.descriptor.enum_type]
|
|
115
|
+
self.msg_definitions = [MessageDefinition(msg, self.scope, self.feature_resolver, self.file_features,
|
|
116
|
+
self.options_file)
|
|
117
|
+
for msg in self.descriptor.message_type]
|
|
118
|
+
|
|
119
|
+
self.all_parameters_registered = False
|
|
120
|
+
|
|
121
|
+
# Sort the message definitions such that the dependencies work out.
|
|
122
|
+
dependency_data = {}
|
|
123
|
+
toposort_namespace = ""
|
|
124
|
+
if self.descriptor.package:
|
|
125
|
+
toposort_namespace = "." + self.descriptor.package
|
|
126
|
+
|
|
127
|
+
for msg in self.descriptor.message_type:
|
|
128
|
+
dependency_data = toposort_add_msg(msg, toposort_namespace, dependency_data)
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
# Sort the message in the order they should appear in the code.
|
|
132
|
+
message_order = toposort_flatten(dependency_data)
|
|
133
|
+
|
|
134
|
+
# If we are in a namespace this appears in the list and should be removed.
|
|
135
|
+
my_scope = ""
|
|
136
|
+
if self.scope and (0 < len(message_order)):
|
|
137
|
+
my_scope = "." + self.scope.get_scope_str().replace("::", ".")
|
|
138
|
+
message_order.remove(my_scope)
|
|
139
|
+
|
|
140
|
+
# Based on the desired order assign each message definition an index.
|
|
141
|
+
for index, msg_name in enumerate(message_order):
|
|
142
|
+
for msg_def in self.msg_definitions:
|
|
143
|
+
if msg_def.name == msg_name.replace(my_scope + ".", ''):
|
|
144
|
+
msg_def.sorted_index = index
|
|
145
|
+
break
|
|
146
|
+
|
|
147
|
+
# Next sort the messages based on their index.
|
|
148
|
+
self.msg_definitions.sort(key=lambda msg: msg.sorted_index)
|
|
149
|
+
|
|
150
|
+
# Sort al the nested message definitions.
|
|
151
|
+
for msg in self.msg_definitions:
|
|
152
|
+
msg.sort_nested_msg_definitions(message_order)
|
|
153
|
+
|
|
154
|
+
except CircularDependencyError as e:
|
|
155
|
+
raise Exception("There are possible circular dependencies in the message definitions of "
|
|
156
|
+
+ proto_descriptor.name + ". Embedded Proto is not able to support this. "
|
|
157
|
+
"Please remove these dependencies.")
|
|
158
|
+
|
|
159
|
+
def get_dependencies(self):
|
|
160
|
+
imported_dependencies = []
|
|
161
|
+
if self.descriptor.dependency:
|
|
162
|
+
extension = self.options_file.header_extension() if self.options_file is not None else field_options.DEFAULT_HEADER_EXTENSION
|
|
163
|
+
imported_dependencies = [os.path.splitext(dependency)[0] + extension for dependency in
|
|
164
|
+
self.descriptor.dependency if not is_excluded_proto_file(dependency)]
|
|
165
|
+
return imported_dependencies
|
|
166
|
+
|
|
167
|
+
def get_namespaces(self):
|
|
168
|
+
if self.scope:
|
|
169
|
+
result = self.scope.get_list_of_scope_str()
|
|
170
|
+
else:
|
|
171
|
+
result = []
|
|
172
|
+
return result
|
|
173
|
+
|
|
174
|
+
def get_header_guard(self):
|
|
175
|
+
return self.filename_with_folder.replace("/", "_").upper()
|
|
176
|
+
|
|
177
|
+
# Obtain a dictionary with references to all nested enums and messages
|
|
178
|
+
def get_all_nested_types(self):
|
|
179
|
+
nested_types = {"enums": [], "messages": []}
|
|
180
|
+
nested_types["enums"].extend(self.enum_definitions)
|
|
181
|
+
for msg in self.msg_definitions:
|
|
182
|
+
nt = msg.get_all_nested_types()
|
|
183
|
+
nested_types["enums"].extend(nt["enums"])
|
|
184
|
+
nested_types["messages"].extend(nt["messages"])
|
|
185
|
+
nested_types["messages"].append(msg)
|
|
186
|
+
|
|
187
|
+
return nested_types
|
|
188
|
+
|
|
189
|
+
def match_fields_with_definitions(self, all_types_definitions):
|
|
190
|
+
for msg in self.msg_definitions:
|
|
191
|
+
msg.match_fields_with_definitions(all_types_definitions)
|
|
192
|
+
|
|
193
|
+
def register_template_parameters(self):
|
|
194
|
+
all_parameters_registered = True
|
|
195
|
+
for msg in self.msg_definitions:
|
|
196
|
+
all_parameters_registered = msg.register_template_parameters() and all_parameters_registered
|
|
197
|
+
return all_parameters_registered
|
|
198
|
+
|
|
199
|
+
def render(self, jinja_environment):
|
|
200
|
+
template_file = "Header.h.jinja2"
|
|
201
|
+
template = jinja_environment.get_template(template_file)
|
|
202
|
+
file_str = template.render(proto_file=self, environment=jinja_environment)
|
|
203
|
+
return file_str
|
|
204
|
+
|
|
205
|
+
def print_template_data(self, indent):
|
|
206
|
+
print(indent + "File: " + self.filename_without_folder)
|
|
207
|
+
if self.msg_definitions:
|
|
208
|
+
for msg in self.msg_definitions:
|
|
209
|
+
msg.print_template_data(indent + "\t")
|
|
210
|
+
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (C) 2020-2026 Embedded AMS B.V. - All Rights Reserved
|
|
3
|
+
#
|
|
4
|
+
# This file is part of Embedded Proto.
|
|
5
|
+
#
|
|
6
|
+
# Embedded Proto is dual licensed. You may use it under the terms of the
|
|
7
|
+
# GNU General Public License version 3 (GPLv3) as published by the Free
|
|
8
|
+
# Software Foundation, or under a commercial license from Embedded AMS B.V.
|
|
9
|
+
#
|
|
10
|
+
# Under the GPLv3 you must release the source code of
|
|
11
|
+
# any product you distribute that includes Embedded Proto or code
|
|
12
|
+
# generated by it. A commercial license removes that obligation.
|
|
13
|
+
# See <https://embeddedproto.com/pricing/>.
|
|
14
|
+
#
|
|
15
|
+
# Embedded Proto is distributed WITHOUT ANY WARRANTY; without even the
|
|
16
|
+
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
17
|
+
# See the LICENSE file in the root of the repository for details.
|
|
18
|
+
#
|
|
19
|
+
# SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-EmbeddedProto-Commercial
|
|
20
|
+
#
|
|
21
|
+
# Embedded AMS B.V., Hoorn, the Netherlands - info at EmbeddedProto dot com
|
|
22
|
+
#
|
|
23
|
+
|
|
24
|
+
from .Field import Field
|
|
25
|
+
from .Oneof import Oneof
|
|
26
|
+
import jinja2
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# This class deal with the scope in which definitions and field are located. It is used to keep track of template
|
|
30
|
+
# parameters required for a given scope.
|
|
31
|
+
class Scope:
|
|
32
|
+
def __init__(self, scope_name, parent):
|
|
33
|
+
# The name of this scope/namespace
|
|
34
|
+
self.name = scope_name
|
|
35
|
+
|
|
36
|
+
# A reference to the scope above this scope. If we are at the top level this will be None.
|
|
37
|
+
self.parent = parent
|
|
38
|
+
|
|
39
|
+
# Start of without any children.
|
|
40
|
+
self.child_scopes = []
|
|
41
|
+
|
|
42
|
+
# Register this object with it's parent.
|
|
43
|
+
if self.parent:
|
|
44
|
+
self.parent.child_scopes.append(self)
|
|
45
|
+
|
|
46
|
+
self.fields_with_templates = []
|
|
47
|
+
|
|
48
|
+
# This function is used
|
|
49
|
+
def get_list_of_scope_str(self):
|
|
50
|
+
if self.parent:
|
|
51
|
+
result = self.parent.get_list_of_scope_str()
|
|
52
|
+
result.append(self.name)
|
|
53
|
+
else:
|
|
54
|
+
result = [self.name]
|
|
55
|
+
return result
|
|
56
|
+
|
|
57
|
+
# When searching for the definition of a field this function returns a scope string equal to the type defined by
|
|
58
|
+
# protobuf.
|
|
59
|
+
def get_scope_str(self):
|
|
60
|
+
if self.parent:
|
|
61
|
+
scope_str = self.parent.get_scope_str() + "::" + self.name
|
|
62
|
+
else:
|
|
63
|
+
scope_str = self.name
|
|
64
|
+
|
|
65
|
+
return scope_str
|
|
66
|
+
|
|
67
|
+
def register_template_parameters(self, field):
|
|
68
|
+
# Guard against duplicate registration. This can happen because FieldRepeated now unconditionally registers
|
|
69
|
+
# itself, and a nested message field may trigger a second registration through its own register chain.
|
|
70
|
+
if field not in self.fields_with_templates:
|
|
71
|
+
self.fields_with_templates.append(field)
|
|
72
|
+
|
|
73
|
+
# Return the list of template parameters required for this scope alone.
|
|
74
|
+
def get_template_parameters(self):
|
|
75
|
+
# Collect all template parameters from every field that registered with this scope.
|
|
76
|
+
result = []
|
|
77
|
+
for field in self.fields_with_templates:
|
|
78
|
+
result.extend(field.get_template_parameters())
|
|
79
|
+
|
|
80
|
+
# Multiple fields may contribute parameters with the same name (e.g. when a field registers both through
|
|
81
|
+
# the repeated path and the nested message path). De-duplicate by name, keeping first-seen order.
|
|
82
|
+
unique = []
|
|
83
|
+
seen = set()
|
|
84
|
+
for param in result:
|
|
85
|
+
if param["name"] not in seen:
|
|
86
|
+
unique.append(param)
|
|
87
|
+
seen.add(param["name"])
|
|
88
|
+
|
|
89
|
+
# In C++ template parameter lists, parameters with default values must appear after those without. Separate
|
|
90
|
+
# them to enforce this ordering.
|
|
91
|
+
non_default = [param for param in unique if "default" not in param]
|
|
92
|
+
defaulted = [param for param in unique if "default" in param]
|
|
93
|
+
|
|
94
|
+
# Backward compatibility: if a message only has defaulted template parameters (e.g. a message where all
|
|
95
|
+
# repeated fields have a fixed maxLength and only contribute the storage type parameter), suppress them
|
|
96
|
+
# entirely. This prevents turning a previously non-templated message into a template class, which would
|
|
97
|
+
# break existing user code that instantiates it without template arguments.
|
|
98
|
+
if not non_default:
|
|
99
|
+
return []
|
|
100
|
+
|
|
101
|
+
return non_default + defaulted
|
|
102
|
+
|
|
103
|
+
# Return a full list of the scope, parent scopes and their templates
|
|
104
|
+
def get(self):
|
|
105
|
+
result = []
|
|
106
|
+
if self.parent:
|
|
107
|
+
result.extend(self.parent.get())
|
|
108
|
+
result.extend([{"name": self.name, "templates": self.get_template_parameters()}])
|
|
109
|
+
return result
|
|
110
|
+
|
|
111
|
+
# -----------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class TypeDefinition:
|
|
115
|
+
def __init__(self, proto_descriptor, parent_scope, template_filename,
|
|
116
|
+
feature_resolver=None, enclosing_features=None):
|
|
117
|
+
self.descriptor = proto_descriptor
|
|
118
|
+
self.name = proto_descriptor.name
|
|
119
|
+
self.scope = Scope(self.name, parent_scope)
|
|
120
|
+
self.template_file = template_filename
|
|
121
|
+
|
|
122
|
+
# Editions feature resolution. The resolver picks the edition profile; the
|
|
123
|
+
# enclosing features are the resolved feature set of the parent scope (file
|
|
124
|
+
# or enclosing message). This type's own explicit overrides are merged on
|
|
125
|
+
# top to obtain its resolved feature set. proto3 files use the proto3
|
|
126
|
+
# profile so this is always populated.
|
|
127
|
+
self.feature_resolver = feature_resolver
|
|
128
|
+
if feature_resolver is not None:
|
|
129
|
+
base = enclosing_features if enclosing_features is not None \
|
|
130
|
+
else feature_resolver.file_features
|
|
131
|
+
self.features = feature_resolver.merge(base, proto_descriptor.options,
|
|
132
|
+
self.scope.get_scope_str())
|
|
133
|
+
else:
|
|
134
|
+
self.features = None
|
|
135
|
+
|
|
136
|
+
def get_name(self):
|
|
137
|
+
return self.name
|
|
138
|
+
|
|
139
|
+
def render(self, jinja_environment):
|
|
140
|
+
template = jinja_environment.get_template(self.template_file)
|
|
141
|
+
render_result = template.render(typedef=self, environment=jinja_environment)
|
|
142
|
+
return render_result
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# -----------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
class EnumDefinition(TypeDefinition):
|
|
148
|
+
def __init__(self, proto_descriptor, parent_scope, feature_resolver=None, enclosing_features=None):
|
|
149
|
+
super().__init__(proto_descriptor, parent_scope, "TypeDefEnum.h.jinja2",
|
|
150
|
+
feature_resolver, enclosing_features)
|
|
151
|
+
|
|
152
|
+
# Loop through the values defined in the enum.
|
|
153
|
+
def values(self):
|
|
154
|
+
for value in self.descriptor.value:
|
|
155
|
+
yield value
|
|
156
|
+
|
|
157
|
+
# Whether this enum is CLOSED (editions enum_type feature). Closed enums
|
|
158
|
+
# validate decoded values and drop unknown ones; open enums store any value.
|
|
159
|
+
def is_closed(self):
|
|
160
|
+
from .Features import EnumType
|
|
161
|
+
if self.features is not None:
|
|
162
|
+
return EnumType.CLOSED == self.features["enum_type"]
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
# The distinct value numbers declared in this enum (aliases collapsed), used to
|
|
166
|
+
# generate the CLOSED-enum validation helper without duplicate switch labels.
|
|
167
|
+
def unique_value_numbers(self):
|
|
168
|
+
seen = []
|
|
169
|
+
for value in self.descriptor.value:
|
|
170
|
+
if value.number not in seen:
|
|
171
|
+
seen.append(value.number)
|
|
172
|
+
return seen
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# -----------------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
class MessageDefinition(TypeDefinition):
|
|
178
|
+
def __init__(self, proto_descriptor, parent_scope, feature_resolver=None, enclosing_features=None,
|
|
179
|
+
options_file=None):
|
|
180
|
+
super().__init__(proto_descriptor, parent_scope, "TypeDefMsg.h.jinja2",
|
|
181
|
+
feature_resolver, enclosing_features)
|
|
182
|
+
|
|
183
|
+
# The external field options file. The fields of this message read their options from it, they reach it
|
|
184
|
+
# through their parent message the same way they reach the feature resolver.
|
|
185
|
+
self.options_file = options_file
|
|
186
|
+
|
|
187
|
+
self.nested_enum_definitions = [EnumDefinition(enum, self.scope, self.feature_resolver, self.features)
|
|
188
|
+
for enum in self.descriptor.enum_type]
|
|
189
|
+
self.nested_msg_definitions = [MessageDefinition(msg, self.scope, self.feature_resolver, self.features,
|
|
190
|
+
self.options_file)
|
|
191
|
+
for msg in self.descriptor.nested_type]
|
|
192
|
+
|
|
193
|
+
# Store the id numbers of all the fields to create the ID enum.
|
|
194
|
+
self.field_ids = []
|
|
195
|
+
|
|
196
|
+
# This message contains optional fields, or not.
|
|
197
|
+
self.optional_fields = []
|
|
198
|
+
|
|
199
|
+
# Store all the variable fields in this message.
|
|
200
|
+
self.fields = []
|
|
201
|
+
for f in self.descriptor.field:
|
|
202
|
+
if (not f.HasField('oneof_index')) or f.proto3_optional:
|
|
203
|
+
new_field = Field.factory(f, self)
|
|
204
|
+
self.fields.append(new_field)
|
|
205
|
+
self.field_ids.append((new_field.variable_id, new_field.variable_id_name))
|
|
206
|
+
|
|
207
|
+
# Store for which fields presence needs to be tracked. This now
|
|
208
|
+
# follows the resolved editions field_presence feature (EXPLICIT
|
|
209
|
+
# fields are optional) of which proto3_optional is a special case.
|
|
210
|
+
if new_field.optional:
|
|
211
|
+
self.optional_fields.append(new_field)
|
|
212
|
+
|
|
213
|
+
# Store all the oneof definitions in this message.
|
|
214
|
+
self.oneofs = []
|
|
215
|
+
for index, oneof in enumerate(self.descriptor.oneof_decl):
|
|
216
|
+
new_oneof = Oneof(oneof, index, proto_descriptor, self)
|
|
217
|
+
oneof_fields = new_oneof.get_fields()
|
|
218
|
+
# For backwards compatibility proto3 optional fields are also include as oneof's with a single field. These
|
|
219
|
+
# dummy oneof's should not actually end up in code. Lets filter them out.
|
|
220
|
+
if oneof_fields[0].optional:
|
|
221
|
+
continue
|
|
222
|
+
else:
|
|
223
|
+
self.oneofs.append(new_oneof)
|
|
224
|
+
for oneof_field in new_oneof.get_fields():
|
|
225
|
+
self.field_ids.append((oneof_field.variable_id, oneof_field.variable_id_name))
|
|
226
|
+
|
|
227
|
+
# Sort the field id's such they will appear in order in the id enum.
|
|
228
|
+
self.field_ids.sort()
|
|
229
|
+
|
|
230
|
+
# Not all template parameters for this message definition have been registered with the scope. This is
|
|
231
|
+
# most likely due to a message defined later in the proto file.
|
|
232
|
+
self.all_parameters_registered = False
|
|
233
|
+
|
|
234
|
+
# Does this message definition contains fields with template parameters.
|
|
235
|
+
self.contains_template_parameters = False
|
|
236
|
+
|
|
237
|
+
# The index where in the source file this definition should be placed. This is later set by the ProtoFile class
|
|
238
|
+
# when the dependencies on other messages has been sorted.
|
|
239
|
+
self.sorted_index = 0
|
|
240
|
+
|
|
241
|
+
# Sort the nested message defintions based on the dependencies found by the sort algorithms.
|
|
242
|
+
def sort_nested_msg_definitions(self, message_order):
|
|
243
|
+
my_scope = "." + self.scope.get_scope_str().replace("::", ".")
|
|
244
|
+
|
|
245
|
+
for index, msg_name in enumerate(message_order):
|
|
246
|
+
for msg_def in self.nested_msg_definitions:
|
|
247
|
+
if msg_def.name == msg_name.replace(my_scope + ".", ''):
|
|
248
|
+
msg_def.sorted_index = index
|
|
249
|
+
break
|
|
250
|
+
|
|
251
|
+
# Next sort the messages based on their index.
|
|
252
|
+
self.nested_msg_definitions.sort(key=lambda msg: msg.sorted_index)
|
|
253
|
+
|
|
254
|
+
# Sort al the nested message definitions in the nested message definitions.
|
|
255
|
+
for nested_msg in self.nested_msg_definitions:
|
|
256
|
+
nested_msg.sort_nested_msg_definitions(message_order)
|
|
257
|
+
|
|
258
|
+
# Obtain a dictionary with references to all nested enums and messages
|
|
259
|
+
def get_all_nested_types(self):
|
|
260
|
+
nested_types = {"enums": [], "messages": []}
|
|
261
|
+
nested_types["enums"].extend(self.nested_enum_definitions)
|
|
262
|
+
for msg in self.nested_msg_definitions:
|
|
263
|
+
nt = msg.get_all_nested_types()
|
|
264
|
+
nested_types["enums"].extend(nt["enums"])
|
|
265
|
+
nested_types["messages"].extend(nt["messages"])
|
|
266
|
+
nested_types["messages"].append(msg)
|
|
267
|
+
|
|
268
|
+
return nested_types
|
|
269
|
+
|
|
270
|
+
def match_fields_with_definitions(self, all_types_definitions):
|
|
271
|
+
# Resolve the types of the nested messages.
|
|
272
|
+
for msg in self.nested_msg_definitions:
|
|
273
|
+
msg.match_fields_with_definitions(all_types_definitions)
|
|
274
|
+
|
|
275
|
+
# Resolve the types of the fields defined in this message.
|
|
276
|
+
for field in self.fields:
|
|
277
|
+
field.match_field_with_definitions(all_types_definitions)
|
|
278
|
+
|
|
279
|
+
for oneof in self.oneofs:
|
|
280
|
+
oneof.match_field_with_definitions(all_types_definitions)
|
|
281
|
+
|
|
282
|
+
# TODO This function will fail to return True if this definitions contains it self as a nested field.
|
|
283
|
+
def register_template_parameters(self):
|
|
284
|
+
self.all_parameters_registered = True
|
|
285
|
+
# First resolve the template parameters for all nested message definitions.
|
|
286
|
+
for nested_msg in self.nested_msg_definitions:
|
|
287
|
+
self.all_parameters_registered = nested_msg.register_template_parameters() \
|
|
288
|
+
and self.all_parameters_registered
|
|
289
|
+
|
|
290
|
+
# Next see if we our self have any fields that have template parameters.
|
|
291
|
+
for field in self.fields:
|
|
292
|
+
self.all_parameters_registered = field.register_template_parameters() and self.all_parameters_registered
|
|
293
|
+
|
|
294
|
+
for oneof in self.oneofs:
|
|
295
|
+
self.all_parameters_registered = oneof.register_template_parameters() and self.all_parameters_registered
|
|
296
|
+
|
|
297
|
+
return self.all_parameters_registered
|
|
298
|
+
|
|
299
|
+
def register_child_with_template(self, child):
|
|
300
|
+
self.scope.register_template_parameters(child)
|
|
301
|
+
self.contains_template_parameters = True
|
|
302
|
+
|
|
303
|
+
def get_templates(self):
|
|
304
|
+
return self.scope.get_template_parameters()
|
|
305
|
+
|
|
306
|
+
def get_type(self):
|
|
307
|
+
return self.scope.get_scope_str()
|
|
308
|
+
|
|
309
|
+
# The full protobuf name of this message, package and enclosing messages included, as it appears in the type url of
|
|
310
|
+
# a google.protobuf.Any.
|
|
311
|
+
def get_full_name(self):
|
|
312
|
+
return self.scope.get_scope_str().replace("::", ".")
|
|
313
|
+
|
|
314
|
+
def get_state_depth(self):
|
|
315
|
+
"""
|
|
316
|
+
Calculate the required state depth for this message.
|
|
317
|
+
|
|
318
|
+
Returns:
|
|
319
|
+
int: Minimum 1, plus maximum depth of nested message fields.
|
|
320
|
+
|
|
321
|
+
Examples:
|
|
322
|
+
- message Simple { int32 a = 1; } -> depth = 1
|
|
323
|
+
- message A { B b = 1; } where B has no nested -> depth = 2
|
|
324
|
+
- message A { B b = 1; } where B { C c = 1; } -> depth = 3
|
|
325
|
+
"""
|
|
326
|
+
max_nested_depth = 0
|
|
327
|
+
|
|
328
|
+
for field in self.fields:
|
|
329
|
+
# Check if this is a message field
|
|
330
|
+
if hasattr(field, 'definition') and field.definition is not None:
|
|
331
|
+
if isinstance(field.definition, MessageDefinition):
|
|
332
|
+
nested_depth = field.definition.get_state_depth()
|
|
333
|
+
if nested_depth > max_nested_depth:
|
|
334
|
+
max_nested_depth = nested_depth
|
|
335
|
+
# Check for repeated message fields
|
|
336
|
+
elif hasattr(field, 'actual_type') and hasattr(field.actual_type, 'definition'):
|
|
337
|
+
if field.actual_type.definition is not None and isinstance(field.actual_type.definition, MessageDefinition):
|
|
338
|
+
nested_depth = field.actual_type.definition.get_state_depth()
|
|
339
|
+
if nested_depth > max_nested_depth:
|
|
340
|
+
max_nested_depth = nested_depth
|
|
341
|
+
|
|
342
|
+
# Check oneof fields as well
|
|
343
|
+
for oneof in self.oneofs:
|
|
344
|
+
for field in oneof.get_fields():
|
|
345
|
+
if hasattr(field, 'definition') and field.definition is not None:
|
|
346
|
+
if isinstance(field.definition, MessageDefinition):
|
|
347
|
+
nested_depth = field.definition.get_state_depth()
|
|
348
|
+
if nested_depth > max_nested_depth:
|
|
349
|
+
max_nested_depth = nested_depth
|
|
350
|
+
elif hasattr(field, 'actual_type') and hasattr(field.actual_type, 'definition'):
|
|
351
|
+
if field.actual_type.definition is not None and isinstance(field.actual_type.definition, MessageDefinition):
|
|
352
|
+
nested_depth = field.actual_type.definition.get_state_depth()
|
|
353
|
+
if nested_depth > max_nested_depth:
|
|
354
|
+
max_nested_depth = nested_depth
|
|
355
|
+
|
|
356
|
+
return 1 + max_nested_depth # +1 for this message itself
|
|
357
|
+
|
|
358
|
+
def print_template_data(self, indent):
|
|
359
|
+
print(indent + "Message definition: " + self.name)
|
|
360
|
+
if self.nested_msg_definitions:
|
|
361
|
+
for msg in self.nested_msg_definitions:
|
|
362
|
+
msg.print_template_data(indent + "\t")
|
|
363
|
+
|
|
364
|
+
if self.fields:
|
|
365
|
+
for field in self.fields:
|
|
366
|
+
print(indent + "Field: " + field.name, end='')
|
|
367
|
+
print(field.get_template_parameters())
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (C) 2020-2026 Embedded AMS B.V. - All Rights Reserved
|
|
3
|
+
#
|
|
4
|
+
# This file is part of Embedded Proto.
|
|
5
|
+
#
|
|
6
|
+
# Embedded Proto is dual licensed. You may use it under the terms of the
|
|
7
|
+
# GNU General Public License version 3 (GPLv3) as published by the Free
|
|
8
|
+
# Software Foundation, or under a commercial license from Embedded AMS B.V.
|
|
9
|
+
#
|
|
10
|
+
# Under the GPLv3 you must release the source code of
|
|
11
|
+
# any product you distribute that includes Embedded Proto or code
|
|
12
|
+
# generated by it. A commercial license removes that obligation.
|
|
13
|
+
# See <https://embeddedproto.com/pricing/>.
|
|
14
|
+
#
|
|
15
|
+
# Embedded Proto is distributed WITHOUT ANY WARRANTY; without even the
|
|
16
|
+
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
17
|
+
# See the LICENSE file in the root of the repository for details.
|
|
18
|
+
#
|
|
19
|
+
# SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-EmbeddedProto-Commercial
|
|
20
|
+
#
|
|
21
|
+
# Embedded AMS B.V., Hoorn, the Netherlands - info at EmbeddedProto dot com
|
|
22
|
+
#
|
|
23
|
+
|
|
24
|
+
from .main import main
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if __name__ == '__main__':
|
|
28
|
+
main()
|