A protobuf wire-stream is a byte string that is comprised of zero or more of the above multi-byte wire-stream primitives. Templates are lists of Prolog terms. Each term corresponds to a production rule in the DCG. The purpose of the template is to provide a recipe and value set for encoding and decoding a particular message. Each term in the template has an arity of two. The term's functor is the local "host type". Argument 1 is its tag (field number), which must always be ground, and argument 2 is its associated value, which may or may not be ground.
A protobuf "message" is a list of fields and is encoded in the
template as
protobufs([Field1, Field2, ...])
, where each field is of
the form Type(Tag,Value
and Type
can be any
scalar or compound type.
Scalar fields are encoded as Type(Tag,Value)
. For
example, if a field is defined in a .proto file by
optional string some_field = 10
, then it could be encoded
by
string(10,"some field's contents")
or by
atom(10, 'some field\'s contents')
.
Repeated fields are done by
repeated(Tag,Type([Value1,Value2,...])
, where Type
is any type.
Embedded messages are done by
embedded(Tag,protobuf([Field1,Field2,...]))
(this is the
same =protobuf(...)
= as is used at the top level).
Repeated embedded messages are done by
repeated_embedded(Tag,protobuf([Field1,Field2,...]),Fields)
,
which gets repeated items and combines them into a list. For example,
repeated_embedded(Tag, protobuf([string(1,_Key),string(2,_Value)]), Fields)
could unify Fields
to [protobuf([string(1,"key1"),string(2,"value1")]), protobuf([string(1,"key2"),string(2,"value2")])]
.
Note that the variables in the protobuf
part of the term do
not get instantiated: they are similar to the Template
in findall/3
and similar.
Note: It is an error to attempt to encode a message using a template that is not ground. Decoding a message into a template that has unbound variables has the effect of unifying the variables with their corresponding values in the wire-stream.
Assume a .proto definition:
message Command { optional string msg_type = 1; optional string command = 2; optional int32 x = 3; optional int32 y = 4; }
Map a Prolog structure to a Protocol Buffer:
%! command(+Term, -Proto) is det. % Map a Prolog term to a corresponding protobuf term. command(add(X,Y), Proto) :- freeze(X, must_be(integer, X)), % for debugging freeze(Y, must_be(integer, Y)), % for debugging Proto = protobuf([atom(1, command), atom(2, add), integer(3, X), integer(4, Y) ]).
Later on:
... prepare X, Y for command/2 ... command(add(X,Y), Proto), protobuf_message(Proto, WireCodes), % send the message open('filename', write, Stream, [encoding(octet),type(binary)]), format(Stream, '~s', [WireCodes]), close(Stream)
Proto
is the protobuf template. Each template describes
exactly one message. WireCodes
is the wire-stream, which
encodes bytes (values between 0 and 255 inclusive). If you are
interworking with other systems and languages, then the protobuf
templates that you supply to
protobuf_message/2 must
be equivalent to those described in the .proto file that is used on the
other side.