def forward_declare( node: ProtoNode, root: ProtoNode, output: OutputFile, ) -> None: """Generates code forward-declaring entities in a message's namespace.""" if node.type() != ProtoNode.Type.MESSAGE: return namespace = node.cpp_namespace(root) output.write_line() output.write_line(f'namespace {namespace} {{') # Define an enum defining each of the message's fields and their numbers. output.write_line('enum class Fields {') with output.indent(): for field in node.fields(): output.write_line(f'{field.enum_name()} = {field.number()},') output.write_line('};') # Declare the message's encoder class and all of its enums. output.write_line() output.write_line('class Encoder;') for child in node.children(): if child.type() == ProtoNode.Type.ENUM: output.write_line() generate_code_for_enum(child, node, output) output.write_line(f'}} // namespace {namespace}')
def generate_code_for_enum(enum: ProtoNode, root: ProtoNode, output: OutputFile) -> None: """Creates a C++ enum for a proto enum.""" assert enum.type() == ProtoNode.Type.ENUM output.write_line(f'enum class {enum.cpp_namespace(root)} {{') with output.indent(): for name, number in enum.values(): output.write_line(f'{name} = {number},') output.write_line('};')
def generate_code_for_package(file_descriptor_proto, package: ProtoNode, output: OutputFile) -> None: """Generates code for a single .pb.h file corresponding to a .proto file.""" assert package.type() == ProtoNode.Type.PACKAGE output.write_line(f'// {os.path.basename(output.name())} automatically ' f'generated by {PLUGIN_NAME} {PLUGIN_VERSION}') output.write_line('#pragma once\n') output.write_line('#include <cstddef>') output.write_line('#include <cstdint>\n') output.write_line('#include "pw_protobuf/codegen.h"') for imported_file in file_descriptor_proto.dependency: generated_header = _proto_filename_to_generated_header(imported_file) output.write_line(f'#include "{generated_header}"') if package.cpp_namespace(): file_namespace = package.cpp_namespace() if file_namespace.startswith('::'): file_namespace = file_namespace[2:] output.write_line(f'\nnamespace {file_namespace} {{') for node in package: forward_declare(node, package, output) # Define all top-level enums. for node in package.children(): if node.type() == ProtoNode.Type.ENUM: output.write_line() generate_code_for_enum(node, package, output) # Run through all messages in the file, generating a class for each. for node in package: if node.type() == ProtoNode.Type.MESSAGE: output.write_line() generate_code_for_message(node, package, output) # Run a second pass through the classes, this time defining all of the # methods which were previously only declared. for node in package: if node.type() == ProtoNode.Type.MESSAGE: define_not_in_class_methods(node, package, output) if package.cpp_namespace(): output.write_line(f'\n}} // namespace {package.cpp_namespace()}')
def generate_code_for_message( message: ProtoNode, root: ProtoNode, output: OutputFile, ) -> None: """Creates a C++ class for a protobuf message.""" assert message.type() == ProtoNode.Type.MESSAGE # Message classes inherit from the base proto message class in codegen.h # and use its constructor. base_class = f'{PROTOBUF_NAMESPACE}::{BASE_PROTO_CLASS}' output.write_line( f'class {message.cpp_namespace(root)}::Encoder : public {base_class} {{' ) output.write_line(' public:') with output.indent(): output.write_line(f'using {BASE_PROTO_CLASS}::{BASE_PROTO_CLASS};') # Generate methods for each of the message's fields. for field in message.fields(): for method_class in PROTO_FIELD_METHODS[field.type()]: method = method_class(field, message, root) if not method.should_appear(): continue output.write_line() method_signature = ( f'{method.return_type()} ' f'{method.name()}({method.param_string()})') if not method.in_class_definition(): # Method will be defined outside of the class at the end of # the file. output.write_line(f'{method_signature};') continue output.write_line(f'{method_signature} {{') with output.indent(): for line in method.body(): output.write_line(line) output.write_line('}') output.write_line('};')
def add_message_fields( root: ProtoNode, message: ProtoNode, proto_message, ) -> None: """Adds fields from a protobuf message descriptor to a message node.""" assert message.type() == ProtoNode.Type.MESSAGE for field in proto_message.field: if field.type_name: # The "type_name" member contains the global .proto path of the # field's type object, for example ".pw.protobuf.test.KeyValuePair". # Since only a single proto file is currently supported, the root # node has the value of the file's package ("pw.protobuf.test"). # This must be stripped from the path to find the desired node # within the tree. # # TODO(frolv): Once multiple files are supported, the root node # should refer to the global namespace, and this should no longer # be needed. path = field.type_name if path[0] == '.': path = path[1:] if path.startswith(root.name()): relative_path = path[len(root.name()):].lstrip('.') else: relative_path = path type_node = root.find(relative_path) else: type_node = None repeated = \ field.label == descriptor_pb2.FieldDescriptorProto.LABEL_REPEATED message.add_field( ProtoMessageField( field.name, field.number, field.type, type_node, repeated, ))
def define_not_in_class_methods(message: ProtoNode, root: ProtoNode, output: OutputFile) -> None: """Defines methods for a message class that were previously declared.""" assert message.type() == ProtoNode.Type.MESSAGE for field in message.fields(): for method_class in PROTO_FIELD_METHODS[field.type()]: method = method_class(field, message, root) if not method.should_appear() or method.in_class_definition(): continue output.write_line() class_name = f'{message.cpp_namespace(root)}::Encoder' method_signature = ( f'inline {method.return_type(from_root=True)} ' f'{class_name}::{method.name()}({method.param_string()})') output.write_line(f'{method_signature} {{') with output.indent(): for line in method.body(): output.write_line(line) output.write_line('}')
def add_message_fields(global_root: ProtoNode, package_root: ProtoNode, message: ProtoNode, proto_message) -> None: """Adds fields from a protobuf message descriptor to a message node.""" assert message.type() == ProtoNode.Type.MESSAGE for field in proto_message.field: if field.type_name: # The "type_name" member contains the global .proto path of the # field's type object, for example ".pw.protobuf.test.KeyValuePair". # Try to find the node for this object within the current context. if field.type_name[0] == '.': # Fully qualified path. root_relative_path = field.type_name[1:] search_root = global_root else: root_relative_path = field.type_name search_root = package_root type_node = search_root.find(root_relative_path) if type_node is None: # Create nodes for field types that don't exist within this # compilation context, such as those imported from other .proto # files. type_node = create_external_nodes(search_root, root_relative_path) else: type_node = None repeated = \ field.label == descriptor_pb2.FieldDescriptorProto.LABEL_REPEATED message.add_field( ProtoMessageField( field.name, field.number, field.type, type_node, repeated, ))
def add_enum_fields(enum: ProtoNode, proto_enum) -> None: """Adds fields from a protobuf enum descriptor to an enum node.""" assert enum.type() == ProtoNode.Type.ENUM for value in proto_enum.value: enum.add_value(value.name, value.number)