Protocol Buffers Support - Generated Chapel Code

This page describes exactly what Chapel code the protocol buffer compiler generates for protocol definitions using proto3 syntax. Please see Protocol Buffers Support in Chapel for information about how to use the protocol buffer support. Additionally, this document assumes familiarity with the proto3 language guide.

Note

The aim of this documentation is to provide a better understanding of the generated code. The user is not expected to modify the generated file.

Compiler Invocation

The protocol buffer compiler produces Chapel output when invoked with the --chpl_out command-line flag. The parameter to the --chpl_out option is the directory where you want the compiler to write your Chapel output. The compiler creates a single source file for each .proto file input, having a .chpl extension.

Only proto3 messages are supported by the Chapel code generator. Ensure that each .proto file begins with a declaration of:

syntax = "proto3";

Output Module Name

The name of the output file/module will be same as the package name. If the package name is not specified, the module takes the name of the proto file with all non-alphanumeric characters replaced by an underscore.

For example a file called address.proto without the package specifier will result in an output file called address.chpl with the generated code wrapped in a module of the same name. (Full implementation is not shown here.)

address.proto

syntax = "proto3";

message messageName {
...
}

address.chpl

module address {
  record messageName {
  ...
  }
}

The same proto file with a package declaration package myPackage; will result in a file called myPackage.chpl.

address.proto

syntax = "proto3";

package myPackage;

message messageName {
...
}

myPackage.chpl

module myPackage {
  record messageName {
  ...
  }
}

Messages

Given a simple message declaration:

message Foo {
  int32 num = 1;
}

The protocol buffer compiler generates a record called Foo, which has message field initializers and serialization/parsing methods for wire-type encoding. (Full implementation is not shown here.)

record Foo {

  /*
    Returns the package name of the proto file. If not declared the method returns
    an empty string.
  */
  proc packageName param { return "packageName"; }

  /*
    Returns the name of the proto message the record is derived from.
  */
  proc messageName param { return "Foo"; }

  /*
    Record fields will be generated corresponding to each proto message field.
  */
  var num: int(32);

  /*
    Used to store encoded byte stream of unknown fields encountered while parsing.

    As per proto3 documentation, unknown fields should be preserved and appended
    to the generated message byte stream.
  */
  var unknownFieldStream: bytes = "";

  /*
    User exposed method for serializing data to protobuf wire format.

    This is a wrapper method to the actual method.
  */
  proc serialize(ch) throws { ... }

  /*
    Contains the actual implementation for serializing data.

    Calls the `Append` functions of the user support library. It appends the
    `unknownFieldStream` at the end of the message.

    This should end up as a private method when supported, so a user should not
    call it directly.
  */
  proc _serialize(binCh) throws { ... }

  /*
    User exposed method for parsing data from protobuf wire format.

    This is a wrapper method to the actual method.
  */
  proc deserialize(ch) throws { ... }

  /*
    Contains the actual implementation for parsing data.

    Calls the `Consume` functions of the user support library. Appends unknown
    fields encountered to the `unknownFieldStream` variable.

    This should end up as a private method when supported, so a user should not
    call it directly.
  */
  proc _deserialize(binCh) throws { ... }

}

Any Message Type

For Any messages, you can call pack to pack a specified message into the current Any message, or unpack to unpack the current Any message to a specified message. Corresponding to an any message type field the plugin will generate a record field of Any type.

// Field "a"
var a: Any;

Fields

The protocol buffer compiler generates a Chapel record field for each field defined within a message. Methods equivalent to get and set in other languages are implicitly generated by the Chapel compiler, so do not need to be generated by the protocol buffer compiler.

Scalar Value Types

A scalar message field can have one of the following types, the table shows the type specified in the .proto file, and the corresponding generated Chapel type:

.proto Type

Chapel Type

double

real(64)

float

real(32)

int32

int(32)

int64

int(64)

uint32

uint(32)

uint64

uint(64)

sint32

int(32)

sint64

int(64)

fixed32

uint(32)

fixed64

uint(64)

sfixed32

int(32)

sfixed64

int(64)

bool

bool

string

string

bytes

bytes

Singular Fields

Every singular message field generates a record field variable of an appropriate Chapel type. Fetching a value from a field which hasn’t been explicitly set will return the default chapel value for that type. For example, a boolean field a will generate a variable of bool type, with default value of false:

// Field "a"
var a: bool;

Repeated Fields

Every repeated message field generates a list type. Fetching a value from a field which hasn’t been explicitly set will return an empty list. For example, a repeated string field a will generate a list of type string:

// Field "a"
var a: list(string);

Map Fields

Given this message definition:

message Foo {
  map<int32, bool> mapfield = 1;
}

The plugin will generate a Chapel map(int(32), bool) type field:

// Field "mapfield"
var mapfield: map(int(32), bool);

Enumerations

Given an enumeration definition like:

enum Color {
  RED = 0;
  GREEN = 5;
  BLUE = 1234;
}

The protocol buffer compiler will generate a Chapel enum type called Color with the same set of values.

The Color proto enum above would therefore become the following Chapel code:

enum Color {
  RED = 0,
  GREEN = 5,
  BLUE = 1234,
}

Oneof

Given a message with a oneof:

message Foo {
  oneof test_oneof {
     string name = 1;
     int32 serial_number = 2;
  }
}

The Chapel record corresponding to Foo will have name_ and serial_number_ fields along with explicit get/set type methods:

// Field "name"
var name_: string;
proc name {
  ...
}
proc ref name ref {
  ...
}

// Field "serial_number"
var serial_number_: int(32);
proc serial_number {
  ...
}
proc ref serial_number ref {
  ...
}

The explicit methods are declared to allow the user to set at most one of the fields in a oneof at a time. For example:

messageObj.name = "chapel";
messageObj.serial_number = 23;

Setting the value of serial_number after name will set name to its default value(”” in case of string).

Nested Types

A message can be declared inside another message. For example:

message Foo {
  message Bar {
    ...
  }
}

In this case, or if a message contains a nested enum declaration, the compiler will generate module level record/enum per nested type. This generated record or enum will have a name prefixed by the parent message name:

record Foo {
 ...
}

// Nested Types
record Foo_Bar {
  ...
}

Note

Nested records or declaration of enums in records are currently not supported in Chapel. Once we have support for these, we can declare nested types in the parent record and thus avoid the name prefix.