+def make_identifier(headername):
+ '''Make #ifndef identifier that contains uppercase A-Z and digits 0-9'''
+ result = ""
+ for c in headername.upper():
+ if c.isalnum():
+ result += c
+ else:
+ result += '_'
+ return result
+
+class ProtoFile:
+ def __init__(self, fdesc, file_options):
+ '''Takes a FileDescriptorProto and parses it.'''
+ self.fdesc = fdesc
+ self.file_options = file_options
+ self.dependencies = {}
+ self.parse()
+
+ # Some of types used in this file probably come from the file itself.
+ # Thus it has implicit dependency on itself.
+ self.add_dependency(self)
+
+ def parse(self):
+ self.enums = []
+ self.messages = []
+ self.extensions = []
+
+ if self.fdesc.package:
+ base_name = Names(self.fdesc.package.split('.'))
+ else:
+ base_name = Names()
+
+ for enum in self.fdesc.enum_type:
+ enum_options = get_nanopb_suboptions(enum, self.file_options, base_name + enum.name)
+ self.enums.append(Enum(base_name, enum, enum_options))
+
+ for names, message in iterate_messages(self.fdesc, base_name):
+ message_options = get_nanopb_suboptions(message, self.file_options, names)
+
+ if message_options.skip_message:
+ continue
+
+ self.messages.append(Message(names, message, message_options))
+ for enum in message.enum_type:
+ enum_options = get_nanopb_suboptions(enum, message_options, names + enum.name)
+ self.enums.append(Enum(names, enum, enum_options))
+
+ for names, extension in iterate_extensions(self.fdesc, base_name):
+ field_options = get_nanopb_suboptions(extension, self.file_options, names + extension.name)
+ if field_options.type != nanopb_pb2.FT_IGNORE:
+ self.extensions.append(ExtensionField(names, extension, field_options))
+
+ def add_dependency(self, other):
+ for enum in other.enums:
+ self.dependencies[str(enum.names)] = enum
+
+ for msg in other.messages:
+ self.dependencies[str(msg.name)] = msg
+
+ # Fix field default values where enum short names are used.
+ for enum in other.enums:
+ if not enum.options.long_names:
+ for message in self.messages:
+ for field in message.fields:
+ if field.default in enum.value_longnames:
+ idx = enum.value_longnames.index(field.default)
+ field.default = enum.values[idx][0]
+
+ # Fix field data types where enums have negative values.
+ for enum in other.enums:
+ if not enum.has_negative():
+ for message in self.messages:
+ for field in message.fields:
+ if field.pbtype == 'ENUM' and field.ctype == enum.names:
+ field.pbtype = 'UENUM'
+
+ def generate_header(self, includes, headername, options):
+ '''Generate content for a header file.
+ Generates strings, which should be concatenated and stored to file.
+ '''
+
+ yield '/* Automatically generated nanopb header */\n'
+ if options.notimestamp:
+ yield '/* Generated by %s */\n\n' % (nanopb_version)
+ else:
+ yield '/* Generated by %s at %s. */\n\n' % (nanopb_version, time.asctime())
+
+ if self.fdesc.package:
+ symbol = make_identifier(self.fdesc.package + '_' + headername)
+ else:
+ symbol = make_identifier(headername)
+ yield '#ifndef PB_%s_INCLUDED\n' % symbol
+ yield '#define PB_%s_INCLUDED\n' % symbol
+ try:
+ yield options.libformat % ('pb.h')
+ except TypeError:
+ # no %s specified - use whatever was passed in as options.libformat
+ yield options.libformat
+ yield '\n'
+
+ for incfile in includes:
+ noext = os.path.splitext(incfile)[0]
+ yield options.genformat % (noext + options.extension + '.h')
+ yield '\n'
+
+ yield '/* @@protoc_insertion_point(includes) */\n'
+
+ yield '#if PB_PROTO_HEADER_VERSION != 30\n'
+ yield '#error Regenerate this file with the current version of nanopb generator.\n'
+ yield '#endif\n'
+ yield '\n'
+
+ yield '#ifdef __cplusplus\n'
+ yield 'extern "C" {\n'
+ yield '#endif\n\n'
+
+ if self.enums:
+ yield '/* Enum definitions */\n'
+ for enum in self.enums:
+ yield str(enum) + '\n\n'
+
+ if self.messages:
+ yield '/* Struct definitions */\n'
+ for msg in sort_dependencies(self.messages):
+ yield msg.types()
+ yield str(msg) + '\n\n'
+
+ if self.extensions:
+ yield '/* Extensions */\n'
+ for extension in self.extensions:
+ yield extension.extension_decl()
+ yield '\n'
+
+ if self.messages:
+ yield '/* Default values for struct fields */\n'
+ for msg in self.messages:
+ yield msg.default_decl(True)
+ yield '\n'
+
+ yield '/* Initializer values for message structs */\n'
+ for msg in self.messages:
+ identifier = '%s_init_default' % msg.name
+ yield '#define %-40s %s\n' % (identifier, msg.get_initializer(False))
+ for msg in self.messages:
+ identifier = '%s_init_zero' % msg.name
+ yield '#define %-40s %s\n' % (identifier, msg.get_initializer(True))
+ yield '\n'
+
+ yield '/* Field tags (for use in manual encoding/decoding) */\n'
+ for msg in sort_dependencies(self.messages):
+ for field in msg.fields:
+ yield field.tags()
+ for extension in self.extensions:
+ yield extension.tags()
+ yield '\n'
+
+ yield '/* Struct field encoding specification for nanopb */\n'
+ for msg in self.messages:
+ yield msg.fields_declaration() + '\n'
+ yield '\n'
+
+ yield '/* Maximum encoded size of messages (where known) */\n'
+ for msg in self.messages:
+ msize = msg.encoded_size(self.dependencies)
+ identifier = '%s_size' % msg.name
+ if msize is not None:
+ yield '#define %-40s %s\n' % (identifier, msize)
+ else:
+ yield '/* %s depends on runtime parameters */\n' % identifier
+ yield '\n'
+
+ yield '/* Message IDs (where set with "msgid" option) */\n'
+
+ yield '#ifdef PB_MSGID\n'
+ for msg in self.messages:
+ if hasattr(msg,'msgid'):
+ yield '#define PB_MSG_%d %s\n' % (msg.msgid, msg.name)
+ yield '\n'
+
+ symbol = make_identifier(headername.split('.')[0])
+ yield '#define %s_MESSAGES \\\n' % symbol
+
+ for msg in self.messages:
+ m = "-1"
+ msize = msg.encoded_size(self.dependencies)
+ if msize is not None:
+ m = msize
+ if hasattr(msg,'msgid'):
+ yield '\tPB_MSG(%d,%s,%s) \\\n' % (msg.msgid, m, msg.name)
+ yield '\n'
+
+ for msg in self.messages:
+ if hasattr(msg,'msgid'):
+ yield '#define %s_msgid %d\n' % (msg.name, msg.msgid)
+ yield '\n'
+
+ yield '#endif\n\n'
+
+ yield '#ifdef __cplusplus\n'
+ yield '} /* extern "C" */\n'
+ yield '#endif\n'
+
+ # End of header
+ yield '/* @@protoc_insertion_point(eof) */\n'
+ yield '\n#endif\n'
+
+ def generate_source(self, headername, options):
+ '''Generate content for a source file.'''
+
+ yield '/* Automatically generated nanopb constant definitions */\n'
+ if options.notimestamp:
+ yield '/* Generated by %s */\n\n' % (nanopb_version)
+ else:
+ yield '/* Generated by %s at %s. */\n\n' % (nanopb_version, time.asctime())
+ yield options.genformat % (headername)
+ yield '\n'
+ yield '/* @@protoc_insertion_point(includes) */\n'
+
+ yield '#if PB_PROTO_HEADER_VERSION != 30\n'
+ yield '#error Regenerate this file with the current version of nanopb generator.\n'
+ yield '#endif\n'
+ yield '\n'
+
+ for msg in self.messages:
+ yield msg.default_decl(False)
+
+ yield '\n\n'
+
+ for msg in self.messages:
+ yield msg.fields_definition() + '\n\n'
+
+ for ext in self.extensions:
+ yield ext.extension_def() + '\n'
+
+ for enum in self.enums:
+ yield enum.enum_to_string_definition() + '\n'
+
+ # Add checks for numeric limits
+ if self.messages:
+ largest_msg = max(self.messages, key = lambda m: m.count_required_fields())
+ largest_count = largest_msg.count_required_fields()
+ if largest_count > 64:
+ yield '\n/* Check that missing required fields will be properly detected */\n'
+ yield '#if PB_MAX_REQUIRED_FIELDS < %d\n' % largest_count
+ yield '#error Properly detecting missing required fields in %s requires \\\n' % largest_msg.name
+ yield ' setting PB_MAX_REQUIRED_FIELDS to %d or more.\n' % largest_count
+ yield '#endif\n'
+
+ max_field = FieldMaxSize()
+ checks_msgnames = []
+ for msg in self.messages:
+ checks_msgnames.append(msg.name)
+ for field in msg.fields:
+ max_field.extend(field.largest_field_value())
+
+ worst = max_field.worst
+ worst_field = max_field.worst_field
+ checks = max_field.checks
+
+ if worst > 255 or checks:
+ yield '\n/* Check that field information fits in pb_field_t */\n'
+
+ if worst > 65535 or checks:
+ yield '#if !defined(PB_FIELD_32BIT)\n'
+ if worst > 65535:
+ yield '#error Field descriptor for %s is too large. Define PB_FIELD_32BIT to fix this.\n' % worst_field
+ else:
+ assertion = ' && '.join(str(c) + ' < 65536' for c in checks)
+ msgs = '_'.join(str(n) for n in checks_msgnames)
+ yield '/* If you get an error here, it means that you need to define PB_FIELD_32BIT\n'
+ yield ' * compile-time option. You can do that in pb.h or on compiler command line.\n'
+ yield ' * \n'
+ yield ' * The reason you need to do this is that some of your messages contain tag\n'
+ yield ' * numbers or field sizes that are larger than what can fit in 8 or 16 bit\n'
+ yield ' * field descriptors.\n'
+ yield ' */\n'
+ yield 'PB_STATIC_ASSERT((%s), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_%s)\n'%(assertion,msgs)
+ yield '#endif\n\n'
+
+ if worst < 65536:
+ yield '#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT)\n'
+ if worst > 255:
+ yield '#error Field descriptor for %s is too large. Define PB_FIELD_16BIT to fix this.\n' % worst_field
+ else:
+ assertion = ' && '.join(str(c) + ' < 256' for c in checks)
+ msgs = '_'.join(str(n) for n in checks_msgnames)
+ yield '/* If you get an error here, it means that you need to define PB_FIELD_16BIT\n'
+ yield ' * compile-time option. You can do that in pb.h or on compiler command line.\n'
+ yield ' * \n'
+ yield ' * The reason you need to do this is that some of your messages contain tag\n'
+ yield ' * numbers or field sizes that are larger than what can fit in the default\n'
+ yield ' * 8 bit descriptors.\n'
+ yield ' */\n'
+ yield 'PB_STATIC_ASSERT((%s), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_%s)\n'%(assertion,msgs)
+ yield '#endif\n\n'
+
+ # Add check for sizeof(double)
+ has_double = False
+ for msg in self.messages:
+ for field in msg.fields:
+ if field.ctype == 'double':
+ has_double = True
+
+ if has_double:
+ yield '\n'
+ yield '/* On some platforms (such as AVR), double is really float.\n'
+ yield ' * These are not directly supported by nanopb, but see example_avr_double.\n'
+ yield ' * To get rid of this error, remove any double fields from your .proto.\n'
+ yield ' */\n'
+ yield 'PB_STATIC_ASSERT(sizeof(double) == 8, DOUBLE_MUST_BE_8_BYTES)\n'
+
+ yield '\n'
+ yield '/* @@protoc_insertion_point(eof) */\n'
+
+# ---------------------------------------------------------------------------
+# Options parsing for the .proto files
+# ---------------------------------------------------------------------------
+
+from fnmatch import fnmatch
+
+def read_options_file(infile):
+ '''Parse a separate options file to list:
+ [(namemask, options), ...]