Support dynamic allocation for string, bytes and message fields.

This is turned on by passing -p to nanopb_generator.py or setting the
(nanopb).pointer option for a .proto field.

git-svn-id: https://svn.kapsi.fi/jpa/nanopb-dev@1081 e3a754e5-d11d-0410-8d38-ebb782a927b9
This commit is contained in:
Michael Poole
2011-12-20 03:30:52 +00:00
committed by Petteri Aimonen
parent 8e5337e9ef
commit c66c6b43c4
14 changed files with 403 additions and 48 deletions

View File

@@ -1,6 +1,7 @@
// Custom options for defining:
// - Maximum size of string/bytes
// - Maximum number of elements in array
// - Pointer or in-line representation of non-scalar fields
//
// These are used by nanopb to generate statically allocable structures
// for memory-limited environments.
@@ -10,6 +11,7 @@ import "google/protobuf/descriptor.proto";
message NanoPBOptions {
optional int32 max_size = 1;
optional int32 max_count = 2;
optional bool pointer = 3;
}
// Protocol Buffers extension number registry

View File

@@ -3,6 +3,7 @@
import google.protobuf.descriptor_pb2 as descriptor
import nanopb_pb2
import os.path
from optparse import OptionParser
# Values are tuple (c type, pb ltype)
FieldD = descriptor.FieldDescriptorProto
@@ -21,6 +22,12 @@ datatypes = {
FieldD.TYPE_UINT32: ('uint32_t', 'PB_LTYPE_VARINT'),
FieldD.TYPE_UINT64: ('uint64_t', 'PB_LTYPE_VARINT')
}
pointable_types = frozenset([
FieldD.TYPE_STRING,
FieldD.TYPE_BYTES,
FieldD.TYPE_MESSAGE
])
options = None
class Names:
'''Keeps a set of nested names and formats them to C identifier.
@@ -70,6 +77,8 @@ class Field:
self.max_size = None
self.max_count = None
self.array_decl = ""
self.is_pointer = options.pointer
self.is_array = False
# Parse nanopb-specific field options
if desc.options.HasExtension(nanopb_pb2.nanopb):
@@ -78,6 +87,11 @@ class Field:
self.max_size = ext.max_size
if ext.HasField("max_count"):
self.max_count = ext.max_count
if ext.HasField("pointer"):
self.is_pointer = ext.pointer
if desc.type not in pointable_types:
raise NotImplementedError('Cannot make %s.%s a pointer'
% (struct_name, desc.name))
if desc.HasField('default_value'):
self.default = desc.default_value
@@ -96,8 +110,11 @@ class Field:
else:
self.htype = 'PB_HTYPE_ARRAY'
self.array_decl = '[%d]' % self.max_count
self.is_array = True
else:
raise NotImplementedError(desc.label)
if self.is_pointer:
self.htype += ' | PB_HTYPE_POINTER'
# Decide LTYPE and CTYPE
# LTYPE is the low-order nibble of nanopb field description,
@@ -112,14 +129,18 @@ class Field:
self.default = self.ctype + self.default
elif desc.type == FieldD.TYPE_STRING:
self.ltype = 'PB_LTYPE_STRING'
if self.max_size is None:
if self.is_pointer:
self.ctype = 'char'
elif self.max_size is None:
is_callback = True
else:
self.ctype = 'char'
self.array_decl += '[%d]' % self.max_size
elif desc.type == FieldD.TYPE_BYTES:
self.ltype = 'PB_LTYPE_BYTES'
if self.max_size is None:
if self.is_pointer:
self.ctype = 'pb_bytes_t'
elif self.max_size is None:
is_callback = True
else:
self.ctype = self.struct_name + self.name + 't'
@@ -138,11 +159,14 @@ class Field:
return cmp(self.tag, other.tag)
def __str__(self):
if self.htype == 'PB_HTYPE_ARRAY':
if self.is_array:
result = ' size_t ' + self.name + '_count;\n'
else:
result = ''
result += ' %s %s%s;' % (self.ctype, self.name, self.array_decl)
if self.is_pointer and self.ctype != 'pb_bytes_t':
result += ' %s *%s%s;' % (self.ctype, self.name, self.array_decl)
else:
result += ' %s %s%s;' % (self.ctype, self.name, self.array_decl)
return result
def types(self):
@@ -205,17 +229,16 @@ class Field:
else:
result += ' pb_delta_end(%s, %s, %s),' % (self.struct_name, self.name, prev_field_name)
if self.htype == 'PB_HTYPE_ARRAY':
if self.is_array:
result += '\n pb_delta(%s, %s_count, %s),' % (self.struct_name, self.name, self.name)
else:
result += ' 0,'
if self.htype == 'PB_HTYPE_ARRAY':
if self.is_array:
result += '\n pb_membersize(%s, %s[0]),' % (self.struct_name, self.name)
result += ('\n pb_membersize(%s, %s) / pb_membersize(%s, %s[0]),'
% (self.struct_name, self.name, self.struct_name, self.name))
elif self.htype != 'PB_HTYPE_CALLBACK' and self.ltype == 'PB_LTYPE_BYTES':
elif self.htype != 'PB_HTYPE_CALLBACK' and not self.is_pointer and self.ltype == 'PB_LTYPE_BYTES':
result += '\n pb_membersize(%s, bytes),' % self.ctype
result += ' 0,'
else:
@@ -240,13 +263,14 @@ class Message:
def get_dependencies(self):
'''Get list of type names that this structure refers to.'''
return [str(field.ctype) for field in self.fields]
return [str(field.ctype) for field in self.fields if not
(field.is_pointer or field.htype == 'PB_HTYPE_CALLBACK')]
def __str__(self):
result = 'typedef struct {\n'
result = 'struct %s {\n' % self.name
result += ' uint8_t has_fields[%d];\n' % ((len(self.fields) + 7) / 8)
result += '\n'.join([str(f) for f in self.ordered_fields])
result += '\n} %s;' % self.name
result += '\n};'
return result
def types(self):
@@ -257,6 +281,9 @@ class Message:
result += types + '\n'
return result
def typedef(self):
return 'typedef struct %s %s;' % (self.name, self.name)
def default_decl(self, declaration_only = False):
result = ""
for field in self.fields:
@@ -273,7 +300,7 @@ class Message:
def message_definition(self):
result = 'const %s_msg_t %s_real_msg = {\n' % (self.name, self.name)
result += ' %d,\n' % len(self.fields)
result += ' %d, sizeof(%s),\n' % (len(self.fields), self.name)
result += ' {\n\n'
prev = None
@@ -379,6 +406,11 @@ def generate_header(headername, enums, messages):
for enum in enums:
yield str(enum) + '\n\n'
yield '/* Struct typedefs */\n'
for msg in messages:
yield msg.typedef() + '\n'
yield '\n'
yield '/* Struct definitions */\n'
for msg in sort_dependencies(messages):
yield msg.types()
@@ -418,18 +450,25 @@ if __name__ == '__main__':
import sys
import os.path
if len(sys.argv) != 2:
print "Usage: " + sys.argv[0] + " file.pb"
print "where file.pb has been compiled from .proto by:"
print "protoc -ofile.pb file.proto"
print "Output fill be written to file.pb.h and file.pb.c"
sys.exit(1)
data = open(sys.argv[1]).read()
parser = OptionParser(usage="Usage: %prog [options] file.pb",
epilog=
"""file.pb should be compiled from file.proto by:
protoc -ofile.pb file.proto
""")
parser.add_option("-p", "--pointer", dest="pointer",
action="store_true", default=False,
help="generate pointers for non-scalar fields")
(options, args) = parser.parse_args()
if len(args) != 1:
parser.error("Please provide exactly one file.pb argument")
data = open(args[0]).read()
fdesc = descriptor.FileDescriptorSet.FromString(data)
enums, messages = parse_file(fdesc.file[0])
noext = os.path.splitext(sys.argv[1])[0]
noext = os.path.splitext(args[0])[0]
headername = noext + '.pb.h'
sourcename = noext + '.pb.c'
headerbasename = os.path.basename(headername)

View File

@@ -7,10 +7,12 @@ from google.protobuf import descriptor_pb2
# @@protoc_insertion_point(imports)
import google.protobuf.descriptor_pb2
DESCRIPTOR = descriptor.FileDescriptor(
name='nanopb.proto',
package='',
serialized_pb='\n\x0cnanopb.proto\x1a google/protobuf/descriptor.proto\"4\n\rNanoPBOptions\x12\x10\n\x08max_size\x18\x01 \x01(\x05\x12\x11\n\tmax_count\x18\x02 \x01(\x05:>\n\x06nanopb\x12\x1d.google.protobuf.FieldOptions\x18\xf2\x07 \x01(\x0b\x32\x0e.NanoPBOptions')
serialized_pb='\n\x0cnanopb.proto\x1a google/protobuf/descriptor.proto\"E\n\rNanoPBOptions\x12\x10\n\x08max_size\x18\x01 \x01(\x05\x12\x11\n\tmax_count\x18\x02 \x01(\x05\x12\x0f\n\x07pointer\x18\x03 \x01(\x08:>\n\x06nanopb\x12\x1d.google.protobuf.FieldOptions\x18\xf2\x07 \x01(\x0b\x32\x0e.NanoPBOptions')
NANOPB_FIELD_NUMBER = 1010
@@ -44,6 +46,13 @@ _NANOPBOPTIONS = descriptor.Descriptor(
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
descriptor.FieldDescriptor(
name='pointer', full_name='NanoPBOptions.pointer', index=2,
number=3, type=8, cpp_type=7, label=1,
has_default_value=False, default_value=False,
message_type=None, enum_type=None, containing_type=None,
is_extension=False, extension_scope=None,
options=None),
],
extensions=[
],
@@ -54,11 +63,10 @@ _NANOPBOPTIONS = descriptor.Descriptor(
is_extendable=False,
extension_ranges=[],
serialized_start=50,
serialized_end=102,
serialized_end=119,
)
import google.protobuf.descriptor_pb2
DESCRIPTOR.message_types_by_name['NanoPBOptions'] = _NANOPBOPTIONS
class NanoPBOptions(message.Message):
__metaclass__ = reflection.GeneratedProtocolMessageType