Sprachspezifikation für Textformate

Die Protocol Buffer Text Format Language definiert eine Syntax für die Darstellung von protobuf-Daten in Textform, was oft für Konfigurationen oder Tests nützlich ist.

Dieses Format unterscheidet sich von dem Textformat innerhalb eines .proto-Schemas. Dieses Dokument enthält Referenzdokumentation, die die Syntax gemäß ISO/IEC 14977 EBNF verwendet.

Beispiel

convolution_benchmark {
  label: "NHWC_128x20x20x56x160"
  input {
    dimension: [128, 56, 20, 20]
    data_type: DATA_HALF
    format: TENSOR_NHWC
  }
}

Übersicht über das Parsen

Die in dieser Spezifikation beschriebenen Sprachelemente sind in lexikalische und syntaktische Kategorien unterteilt. Lexikalische Elemente müssen exakt mit dem Eingabetext übereinstimmen, wie beschrieben, aber syntaktische Elemente können durch optionale WHITESPACE- und COMMENT-Token getrennt sein.

Zum Beispiel besteht ein vorzeichenbehafteter Gleitkommawert aus zwei syntaktischen Elementen: dem Vorzeichen (-) und dem FLOAT-Literal. Optionale Leerzeichen und Kommentare können zwischen dem Vorzeichen und der Zahl stehen, aber nicht innerhalb der Zahl. Beispiel

value: -2.0   # Valid: no additional whitespace.
value: - 2.0  # Valid: whitespace between '-' and '2.0'.
value: -
  # comment
  2.0         # Valid: whitespace and comments between '-' and '2.0'.
value: 2 . 0  # Invalid: the floating point period is part of the lexical
              # element, so no additional whitespace is allowed.

Es gibt einen Sonderfall, der besondere Aufmerksamkeit erfordert: Ein Zahlentoken (FLOAT, DEC_INT, OCT_INT oder HEX_INT) darf nicht unmittelbar von einem IDENT-Token gefolgt werden. Beispiel

foo: 10 bar: 20           # Valid: whitespace separates '10' and 'bar'
foo: 10,bar: 20           # Valid: ',' separates '10' and 'bar'
foo: 10[com.foo.ext]: 20  # Valid: '10' is followed immediately by '[', which is
                          # not an identifier.
foo: 10bar: 20            # Invalid: no space between '10' and identifier 'bar'.

Lexikalische Elemente

Die unten beschriebenen lexikalischen Elemente fallen in zwei Kategorien: primäre Elemente in Großbuchstaben und Fragmente in Kleinbuchstaben. Nur primäre Elemente werden in den Token-Ausgabestrom aufgenommen, der während der syntaktischen Analyse verwendet wird; Fragmente existieren nur, um die Konstruktion primärer Elemente zu vereinfachen.

Beim Parsen von Eingabetext gewinnt das am längsten übereinstimmende primäre Element. Beispiel

value: 10   # '10' is parsed as a DEC_INT token.
value: 10f  # '10f' is parsed as a FLOAT token, despite containing '10' which
            # would also match DEC_INT. In this case, FLOAT matches a longer
            # subsequence of the input.

Zeichen

char    = ? Any non-NUL unicode character ? ;
newline = ? ASCII #10 (line feed) ? ;

letter = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M"
       | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z"
       | "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m"
       | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z"
       | "_" ;

oct = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" ;
dec = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
hex = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
    | "A" | "B" | "C" | "D" | "E" | "F"
    | "a" | "b" | "c" | "d" | "e" | "f" ;

Leerzeichen und Kommentare

COMMENT    = "#", { char - newline }, [ newline ] ;
WHITESPACE = " "
           | newline
           | ? ASCII #9  (horizontal tab) ?
           | ? ASCII #11 (vertical tab) ?
           | ? ASCII #12 (form feed) ?
           | ? ASCII #13 (carriage return) ? ;

Bezeichner

IDENT = letter, { letter | dec } ;

Numerische Literale

dec_lit   = "0"
          | ( dec - "0" ), { dec } ;
float_lit = ".", dec, { dec }, [ exp ]
          | dec_lit, ".", { dec }, [ exp ]
          | dec_lit, exp ;
exp       = ( "E" | "e" ), [ "+" | "-" ], dec, { dec } ;

DEC_INT   = dec_lit
OCT_INT   = "0", oct, { oct } ;
HEX_INT   = "0", ( "X" | "x" ), hex, { hex } ;
FLOAT     = float_lit, [ "F" | "f" ]
          | dec_lit,   ( "F" | "f" ) ;

Dezimale Ganzzahlen können durch die Suffixe F und f als Gleitkommazahlen interpretiert werden. Beispiel

foo: 10    # This is an integer value.
foo: 10f   # This is a floating-point value.
foo: 1.0f  # Also optional for floating-point literals.

Zeichenketten-Literale

STRING = single_string | double_string ;
single_string = "'", { escape | char - "'" - newline - "\" }, "'" ;
double_string = '"', { escape | char - '"' - newline - "\" }, '"' ;

escape = "\a"                        (* ASCII #7  (bell)                 *)
       | "\b"                        (* ASCII #8  (backspace)            *)
       | "\f"                        (* ASCII #12 (form feed)            *)
       | "\n"                        (* ASCII #10 (line feed)            *)
       | "\r"                        (* ASCII #13 (carriage return)      *)
       | "\t"                        (* ASCII #9  (horizontal tab)       *)
       | "\v"                        (* ASCII #11 (vertical tab)         *)
       | "\?"                        (* ASCII #63 (question mark)        *)
       | "\\"                        (* ASCII #92 (backslash)            *)
       | "\'"                        (* ASCII #39 (apostrophe)           *)
       | '\"'                        (* ASCII #34 (quote)                *)
       | "\", oct, [ oct, [ oct ] ]  (* octal escaped byte value         *)
       | "\x", hex, [ hex ]          (* hexadecimal escaped byte value   *)
       | "\u", hex, hex, hex, hex    (* Unicode code point up to 0xffff  *)
       | "\U000",
         hex, hex, hex, hex, hex     (* Unicode code point up to 0xfffff *)
       | "\U0010",
         hex, hex, hex, hex ;        (* Unicode code point between 0x100000 and 0x10ffff *)

Oktale Escape-Sequenzen verbrauchen bis zu drei Oktalziffern. Zusätzliche Ziffern werden unescaped weitergegeben. Zum Beispiel wird beim Entschlüsseln der Eingabe \1234 der Parser drei Oktalziffern (123) verbrauchen, um den Byte-Wert 0x53 (ASCII 'S', 83 dezimal) zu entschlüsseln, und das nachfolgende '4' wird als Byte-Wert 0x34 (ASCII '4') weitergegeben. Um eine korrekte Analyse sicherzustellen, drücken Sie oktale Escape-Sequenzen mit 3 Oktalziffern aus und verwenden Sie bei Bedarf führende Nullen, wie z. B.: \000, \001, \063, \377. Weniger als drei Ziffern werden verbraucht, wenn auf die numerischen Zeichen ein nicht-numerisches Zeichen folgt, wie z. B. \5Hello.

Hexadezimale Escape-Sequenzen verbrauchen bis zu zwei Hexadezimalziffern. Zum Beispiel verbraucht der Parser beim Entschlüsseln von \x213 nur die ersten beiden Ziffern (21), um den Byte-Wert 0x21 (ASCII '!') zu entschlüsseln. Um eine korrekte Analyse sicherzustellen, drücken Sie hexadezimale Escape-Sequenzen mit 2 Hexadezimalziffern aus und verwenden Sie bei Bedarf führende Nullen, wie z. B.: \x00, \x01, \xFF. Weniger als zwei Ziffern werden verbraucht, wenn auf das numerische Zeichen ein nicht-hexadezimales Zeichen folgt, wie z. B. \xFHello oder \x3world.

Verwenden Sie die byteweise Maskierung nur für Felder vom Typ bytes. Obwohl es möglich ist, die byteweise Maskierung in Feldern vom Typ string zu verwenden, müssen diese Maskierungen gültige UTF-8-Sequenzen bilden. Die Verwendung der byteweisen Maskierung zur Darstellung von UTF-8-Sequenzen ist fehleranfällig. Bevorzugen Sie Unicode-Maskierungen für nicht druckbare Zeichen und zeilenbrechende Zeichen in Literalen für Felder vom Typ string.

Längere Zeichenketten können in mehrere Anführungszeichen gesetzte Zeichenketten auf aufeinanderfolgenden Zeilen aufgeteilt werden. Beispiel

  quote:
      "When we got into office, the thing that surprised me most was to find "
      "that things were just as bad as we'd been saying they were.\n\n"
      "  -- John F. Kennedy"

Unicode-Codepunkte werden gemäß Unicode 13 Tabelle A-1 Extended BNF interpretiert und als UTF-8 kodiert.

Syntaxelemente

Nachricht

Eine Nachricht ist eine Sammlung von Feldern. Eine Textformatdatei ist eine einzelne Nachricht.

Message = { Field } ;

Literale

Feldliterale können Zahlen, Zeichenketten oder Bezeichner wie true oder Aufzählungswerte sein.

String             = STRING, { STRING } ;
Float              = [ "-" ], FLOAT ;
Identifier         = IDENT ;
SignedIdentifier   = "-", IDENT ;   (* For example, "-inf" *)
DecSignedInteger   = "-", DEC_INT ;
OctSignedInteger   = "-", OCT_INT ;
HexSignedInteger   = "-", HEX_INT ;
DecUnsignedInteger = DEC_INT ;
OctUnsignedInteger = OCT_INT ;
HexUnsignedInteger = HEX_INT ;

Ein einzelner Zeichenkettenwert kann aus mehreren durch optionale Leerzeichen getrennten Teilen mit Anführungszeichen bestehen. Beispiel

a_string: "first part" 'second part'
          "third part"
no_whitespace: "first""second"'third''fourth'

Feldnamen

Felder, die Teil der enthaltenden Nachricht sind, verwenden einfache Identifier als Namen. Extension- und Any-Feldnamen werden in eckige Klammern gesetzt und vollständig qualifiziert. Any-Feldnamen sind mit einem qualifizierenden Domänennamen versehen, z. B. type.googleapis.com/.

FieldName     = ExtensionName | AnyName | IDENT ;
ExtensionName = "[", TypeName, "]" ;
AnyName       = "[", Domain, "/", TypeName, "]" ;
TypeName      = IDENT, { ".", IDENT } ;
Domain        = IDENT, { ".", IDENT } ;

Reguläre Felder und Erweiterungsfelder können skalare oder Nachrichtenwerte haben. Any-Felder sind immer Nachrichten. Beispiel

reg_scalar: 10
reg_message { foo: "bar" }

[com.foo.ext.scalar]​: 10
[com.foo.ext.message] { foo: "bar" }

any_value {
  [type.googleapis.com/com.foo.any] { foo: "bar" }
}

Unbekannte Felder

Textformat-Parser können keine unbekannten Felder unterstützen, die als rohe Feldnummern anstelle von Feldnamen dargestellt werden, da drei der sechs Drahttypen auf die gleiche Weise im Textformat dargestellt werden. Einige Textformat-Serialisierungsimplementierungen kodieren unbekannte Felder mit einem Format, das eine Feldnummer und eine numerische Darstellung des Werts verwendet, aber dies ist inhärent verlustbehaftet, da die Wire-Type-Information ignoriert wird. Zum Vergleich: Das Drahtformat ist verlustfrei, da es den Wire-Type in jedem Feld-Tag als (field_number << 3) | wire_type enthält. Weitere Informationen zur Kodierung finden Sie im Thema Encoding.

Ohne Informationen über den Feldtyp aus dem Nachrichtenschema kann der Wert nicht korrekt in eine Wire-Format-Proto-Nachricht kodiert werden.

Felder

Feldwerte können Literale (Zeichenketten, Zahlen oder Bezeichner) oder verschachtelte Nachrichten sein.

Field        = ScalarField | MessageField ;
MessageField = FieldName, [ ":" ], ( MessageValue | MessageList ) [ ";" | "," ];
ScalarField  = FieldName, ":",     ( ScalarValue  | ScalarList  ) [ ";" | "," ];
MessageList  = "[", [ MessageValue, { ",", MessageValue } ], "]" ;
ScalarList   = "[", [ ScalarValue,  { ",", ScalarValue  } ], "]" ;
MessageValue = "{", Message, "}" | "<", Message, ">" ;
ScalarValue  = String
             | Float
             | Identifier
             | SignedIdentifier
             | DecSignedInteger
             | OctSignedInteger
             | HexSignedInteger
             | DecUnsignedInteger
             | OctUnsignedInteger
             | HexUnsignedInteger ;

Der :-Trennzeichen zwischen Feldname und Wert ist für Skalarfelder erforderlich, für Nachrichtenfelder (einschließlich Listen) jedoch optional. Beispiel

scalar: 10          # Valid
scalar  10          # Invalid
scalars: [1, 2, 3]  # Valid
scalars  [1, 2, 3]  # Invalid
message: {}         # Valid
message  {}         # Valid
messages: [{}, {}]  # Valid
messages  [{}, {}]  # Valid

Werte von Nachrichtenfeldern können von geschweiften oder spitzen Klammern umgeben sein

message: { foo: "bar" }
message: < foo: "bar" >

Felder, die als repeated markiert sind, können mehrere Werte haben, die durch Wiederholung des Feldes, unter Verwendung der speziellen []-Listen-Syntax oder einer Kombination aus beidem angegeben werden. Die Reihenfolge der Werte wird beibehalten. Beispiel

repeated_field: 1
repeated_field: 2
repeated_field: [3, 4, 5]
repeated_field: 6
repeated_field: [7, 8, 9]

ist gleichwertig zu

repeated_field: [1, 2, 3, 4, 5, 6, 7, 8, 9]

Nicht-repeated Felder dürfen die Listensyntax nicht verwenden. Zum Beispiel ist [0] für optional oder required Felder ungültig. Felder, die als optional markiert sind, können weggelassen oder einmal angegeben werden. Felder, die als required markiert sind, müssen genau einmal angegeben werden. required ist ein Legacy-Feature von proto2 und in proto3 nicht verfügbar. Abwärtskompatibilität ist für Nachrichten in Editionen unter Verwendung von features.field_presence = LEGACY_REQUIRED verfügbar.

Felder, die nicht in der zugehörigen .proto-Nachricht angegeben sind, sind nicht erlaubt, es sei denn, der Feldname ist in der Liste reserved der Nachricht enthalten. reserved-Felder werden, falls in irgendeiner Form vorhanden (skalar, Liste, Nachricht), vom Textformat einfach ignoriert.

Werttypen

Wenn der zugehörige .proto-Werttyp eines Feldes bekannt ist, gelten die folgenden Wertbeschreibungen und Einschränkungen. Für die Zwecke dieses Abschnitts deklarieren wir die folgenden Containerelemente

signedInteger   = DecSignedInteger | OctSignedInteger | HexSignedInteger ;
unsignedInteger = DecUnsignedInteger | OctUnsignedInteger | HexUnsignedInteger ;
integer         = signedInteger | unsignedInteger ;
.proto-TypWerte
float, doubleEin Float-, DecSignedInteger- oder DecUnsignedInteger-Element oder ein Identifier- oder SignedIdentifier-Element, dessen IDENT-Teil gleich "inf", "infinity" oder "nan" ist (Groß-/Kleinschreibung wird nicht beachtet). Überläufe werden als Unendlichkeit oder -Unendlichkeit behandelt. Oktale und hexadezimale Werte sind nicht gültig.

Hinweis: "nan" sollte als Quiet NaN interpretiert werden.

int32, sint32, sfixed32Jedes der integer-Elemente im Bereich -0x80000000 bis 0x7FFFFFFF.
int64, sint64, sfixed64Jedes der integer-Elemente im Bereich -0x8000000000000000 bis 0x7FFFFFFFFFFFFFFF.
uint32, fixed32Jedes der unsignedInteger-Elemente im Bereich 0 bis 0xFFFFFFFF. Beachten Sie, dass vorzeichenbehaftete Werte (-0) nicht gültig sind.
uint64, fixed64Jedes der unsignedInteger-Elemente im Bereich 0 bis 0xFFFFFFFFFFFFFFFF. Beachten Sie, dass vorzeichenbehaftete Werte (-0) nicht gültig sind.
stringEin String-Element, das gültige UTF-8-Daten enthält. Alle Escape-Sequenzen müssen nach dem Entschlüsseln gültige UTF-8-Byte-Sequenzen bilden.
bytesEin String-Element, das möglicherweise ungültige UTF-8-Escape-Sequenzen enthält.
boolEin Identifier-Element oder eines der unsignedInteger-Elemente, das einem der folgenden Werte entspricht.
Wahrwerte: "True", "true", "t", 1
Falschwerte: "False", "false", "f", 0
Jede vorzeichenlose Ganzzahl-Darstellung von 0 oder 1 ist zulässig: 00, 0x0, 01, 0x1 usw.
Enum-WerteEin Identifier-Element, das einen Enum-Wertnamen enthält, oder eines der integer-Elemente im Bereich -0x80000000 bis 0x7FFFFFFF, das eine Enum-Wertnummer enthält. Es ist nicht zulässig, einen Namen anzugeben, der kein Mitglied der enum-Typdefinition des Feldes ist. Abhängig von der jeweiligen Protokollpuffer-Laufzeitimplementierung kann es zulässig sein oder auch nicht, eine Nummer anzugeben, die kein Mitglied der enum-Typdefinition des Feldes ist. Textformatprozessoren, die nicht an eine bestimmte Laufzeitimplementierung gebunden sind (wie z. B. IDE-Unterstützung), können wählen, eine Warnung auszugeben, wenn ein bereitgestellter numerischer Wert kein gültiges Mitglied ist. Beachten Sie, dass bestimmte Namen, die in anderen Kontexten gültige Schlüsselwörter sind, wie z. B. "true" oder "infinity", auch gültige Enum-Wertnamen sind.
NachrichtenwerteEin MessageValue-Element.

Erweiterungsfelder

Erweiterungsfelder werden unter Verwendung ihrer qualifizierten Namen angegeben. Beispiel

local_field: 10
[com.example.ext_field]​: 20

Erweiterungsfelder werden im Allgemeinen in anderen .proto-Dateien definiert. Die Textformat-Sprache bietet keinen Mechanismus zur Angabe der Speicherorte von Dateien, die Erweiterungsfelder definieren; stattdessen muss der Parser vorherige Kenntnis von deren Speicherorten haben.

Any-Felder

Textformat unterstützt eine erweiterte Form des google.protobuf.Any-Well-Known-Typs mit einer speziellen Syntax, die Erweiterungsfeldern ähnelt. Beispiel

local_field: 10

# An Any value using regular fields.
any_value {
  type_url: "type.googleapis.com/com.example.SomeType"
  value: "\x0a\x05hello"  # serialized bytes of com.example.SomeType
}

# The same value using Any expansion
any_value {
  [type.googleapis.com/com.example.SomeType] {
    field1: "hello"
  }
}

In diesem Beispiel ist any_value ein Feld vom Typ google.protobuf.Any und speichert eine serialisierte com.example.SomeType-Nachricht, die field1: hello enthält.

group-Felder

Im Textformat verwendet ein group-Feld ein normales MessageValue-Element als Wert, wird aber unter Verwendung des großgeschriebenen Gruppennamens anstelle des impliziten kleingeschriebenen Feldnamens angegeben. Beispiel

// proto2
message MessageWithGroup {
  optional group MyGroup = 1 {
    optional int32 my_value = 1;
  }
}

Mit der obigen .proto-Definition ist das folgende Textformat eine gültige MessageWithGroup

MyGroup {
  my_value: 1
}

Ähnlich wie bei Nachrichtenfeldern ist der :-Trennzeichen zwischen Gruppenname und Wert optional.

Diese Funktionalität ist in den Editionen aus Gründen der Abwärtskompatibilität enthalten. Normalerweise werden DELIMITED-Felder wie normale Nachrichten serialisiert. Das Folgende zeigt das Verhalten mit Editionen

edition = "2024";

message Parent {
  message GroupLike {
    int32 foo = 1;
  }
  GroupLike grouplike = 1 [features.message_encoding = DELIMITED];
}

Der Inhalt dieser .proto-Datei wird wie eine proto2-Gruppe serialisiert

GroupLike {
  foo: 2;
}

map-Felder

Textformat bietet keine benutzerdefinierte Syntax zur Angabe von Map-Feldern. Wenn ein map-Feld in einer .proto-Datei definiert ist, wird eine implizite Entry-Nachricht definiert, die key- und value-Felder enthält. Map-Felder sind immer wiederholt und akzeptieren mehrere Schlüssel/Wert-Einträge. Beispiel

// Editions
edition = "2024";

message MessageWithMap {
  map<string, int32> my_map = 1;
}

Mit der obigen .proto-Definition ist das folgende Textformat eine gültige MessageWithMap

my_map { key: "entry1" value: 1 }
my_map { key: "entry2" value: 2 }

# You can also use the list syntax
my_map: [
  { key: "entry3" value: 3 },
  { key: "entry4" value: 4 }
]

Sowohl die key- als auch die value-Felder sind optional und haben standardmäßig den Nullwert ihres jeweiligen Typs, wenn sie nicht angegeben werden. Wenn ein Schlüssel dupliziert wird, wird nur der zuletzt angegebene Wert in einer geparsten Map beibehalten.

Die Reihenfolge von Maps wird in Textprotos nicht beibehalten.

oneof-Felder

Obwohl es im Textformat keine spezielle Syntax im Zusammenhang mit oneof-Feldern gibt, darf nur ein oneof-Mitglied gleichzeitig angegeben werden. Die gleichzeitige Angabe mehrerer Mitglieder ist nicht gültig. Beispiel

// Editions
edition = "2024";

message OneofExample {
  message MessageWithOneof {
    string not_part_of_oneof = 1;
    oneof Example {
      string first_oneof_field = 2;
      string second_oneof_field = 3;
    }
  }
  repeated MessageWithOneof message = 1;
}

Die obige .proto-Definition führt zu folgendem Textformat-Verhalten

# Valid: only one field from the Example oneof is set.
message {
  not_part_of_oneof: "always valid"
  first_oneof_field: "valid by itself"
}

# Valid: the other oneof field is set.
message {
  not_part_of_oneof: "always valid"
  second_oneof_field: "valid by itself"
}

# Invalid: multiple fields from the Example oneof are set.
message {
  not_part_of_oneof: "always valid"
  first_oneof_field: "not valid"
  second_oneof_field: "not valid"
}

Textformatdateien

Eine Textformatdatei verwendet die Dateiendung .txtpb und enthält eine einzelne Message. Textformatdateien sind UTF-8-kodiert. Eine Beispiel-Textproto-Datei ist unten aufgeführt.

# This is an example of Protocol Buffer's text format.
# Unlike .proto files, only shell-style line comments are supported.

name: "John Smith"

pet {
  kind: DOG
  name: "Fluffy"
  tail_wagginess: 0.65f
}

pet <
  kind: LIZARD
  name: "Lizzy"
  legs: 4
>

string_value_with_escape: "valid \n escape"
repeated_values: [ "one", "two", "three" ]

Die Header-Kommentare proto-file und proto-message informieren Entwicklertools über das Schema, damit diese verschiedene Funktionen bereitstellen können.

# proto-file: some/proto/my_file.proto
# proto-message: MyMessage

Arbeiten mit dem Format im Programm

Da einzelne Protokollpuffer-Implementierungen weder ein konsistentes noch ein kanonisches Textformat ausgeben, müssen Tools oder Bibliotheken, die TextProto-Dateien ändern oder TextProto-Ausgaben erstellen, explizit https://github.com/protocolbuffers/txtpbfmt verwenden, um ihre Ausgabe zu formatieren.