文字格式語言規範

Protocol buffer 文字格式語言定義了一種文字形式表示 protobuf 資料的語法,這對於配置檔案或測試非常有用。

此格式與 .proto 模式中的文字格式不同。本文件包含使用 ISO/IEC 14977 EBNF 中指定的語法的參考文件。

示例

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

解析概述

此規範中的語言元素分為詞法和語法類別。詞法元素必須與輸入文字精確匹配,而語法元素可以由可選的 WHITESPACECOMMENT 標記分隔。

例如,帶符號浮點值由兩個語法元素組成:符號(-)和 FLOAT 字面量。符號和數字之間可以存在可選的空白和註釋,但不能在數字內部。示例

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.

有一個需要特別注意的邊緣情況:數字標記(FLOAT, DEC_INT, OCT_INT, 或 HEX_INT)後面不能直接跟 IDENT 標記。示例

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'.

詞法元素

下面描述的詞法元素分為兩類:大寫的主要元素和純小寫的片段。只有主要元素包含在語法分析期間使用的標記輸出流中;片段僅用於簡化主要元素的構建。

解析輸入文字時,最長匹配的主要元素獲勝。示例

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.

字元

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" ;

遵循 RFC 3986: Uniform Resource Identifier (URI) 的有限集 URL 字元

url_unreserved  = letter | dec | "-" | "." | "~" | "_"
url_sub_delim   = "!" | "$" | "&" | "(" | ")"
                | "*" | "+" | "," | ";" | "="
url_pct_encoded = "%" hex hex
url_char        = url_unreserved | url_sub_delim | url_pct_encoded

空白和註釋

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

識別符號

IDENT = letter, { letter | dec } ;

數字文字

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" ) ;

可以透過使用 Ff 字尾將十進位制整數轉換為浮點值。示例

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

字串字面量

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 *)

八進位制轉義序列最多消耗三個八進位制數字。額外的數字將被原樣傳遞。例如,在反轉義輸入 \1234 時,解析器會消耗三個八進位制數字(123)來反轉義位元組值 0x53(ASCII ‘S’,十進位制為 83),後面的 ‘4’ 將作為位元組值 0x34(ASCII ‘4’)原樣傳遞。為確保正確解析,請使用 3 個八進位制數字表示八進位制轉義序列,並根據需要使用前導零,例如:\000, \001, \063, \377。當非數字字元跟隨數字字元時,會消耗少於三個數字,例如 \5Hello

十六進位制轉義序列最多消耗兩個十六進位制數字。例如,在反轉義 \x213 時,解析器僅消耗前兩個數字(21)來反轉義位元組值 0x21(ASCII ‘!’)。為確保正確解析,請使用 2 個十六進位制數字表示十六進位制轉義序列,並根據需要使用前導零,例如:\x00, \x01, \xFF。當非十六進位制字元跟隨數字字元時,會消耗少於兩個數字,例如 \xFHello\x3world

僅為型別為 bytes 的欄位使用位元組級別的轉義。雖然可以在型別為 string 的欄位中使用位元組級別的轉義,但這些轉義序列必須能夠形成有效的 UTF-8 序列。使用位元組級別的轉義來表示 UTF-8 序列很容易出錯。對於 string 型別欄位的不可列印字元和換行符,請優先使用 Unicode 轉義序列。

較長的字串可以分解為多個用引號括起來的字串,分佈在連續的行上。例如

  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 碼點根據 Unicode 13 A-1 表擴充套件 BNF 進行解釋,並編碼為 UTF-8。

語法元素

訊息

訊息是欄位的集合。文字格式檔案是單一的訊息。

Message = { Field } ;

文字

欄位文字值可以是數字、字串或識別符號,例如 true 或列舉值。

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 ;

單個字串值可以由多個用可選空白分隔的帶引號的部分組成。示例

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

欄位名

包含訊息欄位使用簡單的 Identifiers 作為名稱。 ExtensionAny 欄位名用方括號括起來並完全限定。 Any 欄位名是 URI 字尾引用,意味著它們前面帶有限定的授權方和可選路徑,例如 type.googleapis.com/

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

文字格式序列化器不應在 ExtensionNameAnyName 的方括號之間寫入任何空白字元或註釋。解析器應跳過 ExtensionNameAnyName 方括號之間的任何空白字元和註釋。這包括分隔名稱的空白和註釋。

常規欄位和擴充套件欄位可以是標量或訊息值。 Any 欄位始終是訊息。示例

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" }
}

未知欄位

由於文字格式中有三種線型(wire type)表示方式相同,因此文字格式解析器無法支援用原始欄位號而不是欄位名錶示的未知欄位。一些文字格式序列化實現使用一種格式來編碼未知欄位,該格式使用欄位號和值的數字表示,但這本質上是會丟失資訊的,因為線型資訊被忽略了。相比之下,線格式是非丟失的,因為它在每個欄位標籤中都包含線型,格式為 (field_number << 3) | wire_type。有關編碼的更多資訊,請參閱 編碼 主題。

如果沒有訊息模式中的欄位型別資訊,則無法將該值正確編碼為線格式的 proto 訊息。

欄位

欄位值可以是字面量(字串、數字或識別符號),也可以是巢狀訊息。

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 ;

對於標量欄位,欄位名和值之間的 : 分隔符是必需的,但對於訊息欄位(包括列表)是可選的。示例

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

訊息欄位的值可以用花括號或尖括號括起來

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

標記為 repeated 的欄位可以透過重複欄位、使用特殊的 [] 列表語法,或兩者的某種組合來指定多個值。值的順序會保留。示例

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

等同於

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

repeated 欄位不能使用列表語法。例如,[0] 對於 optionalrequired 欄位無效。標記為 optional 的欄位可以省略或指定一次。標記為 required 的欄位必須指定一次。required 是 proto2 的舊功能,在 proto3 中不可用。對於使用 features.field_presence = LEGACY_REQUIRED 的 Edition 中的訊息,提供向後相容性。

除非欄位名存在於訊息的 reserved 欄位列表中,否則不允許使用在關聯的 .proto 訊息中未指定的欄位。 reserved 欄位(如果以任何形式存在,包括標量、列表、訊息)將被文字格式忽略。

值型別

當已知欄位的關聯 .proto 值型別時,將應用以下值描述和約束。為了本節的方便,我們宣告以下容器元素

signedInteger   = DecSignedInteger | OctSignedInteger | HexSignedInteger ;
unsignedInteger = DecUnsignedInteger | OctUnsignedInteger | HexUnsignedInteger ;
integer         = signedInteger | unsignedInteger ;
.proto 型別
float, doubleFloatDecSignedIntegerDecUnsignedInteger 元素,或者 IDENT 部分等於“inf”“infinity”“nan”(不區分大小寫)的 IdentifierSignedIdentifier 元素。溢位被視為無窮大或負無窮大。八進位制和十六進位制值無效。

注意:“nan” 應解釋為 安靜 NaN

int32, sint32, sfixed32範圍在 -0x800000000x7FFFFFFF 之間的任何 integer 元素。
int64, sint64, sfixed64範圍在 -0x80000000000000000x7FFFFFFFFFFFFFFF 之間的任何 integer 元素。
uint32, fixed32範圍在 00xFFFFFFFF 之間的任何 unsignedInteger 元素。請注意,負數(-0)無效。
uint64, fixed64範圍在 00xFFFFFFFFFFFFFFFF 之間的任何 unsignedInteger 元素。請注意,負數(-0)無效。
string包含有效 UTF-8 資料的 String 元素。任何轉義序列在反轉義後必須形成有效的 UTF-8 位元組序列。
bytes一個 String 元素,可能包括無效的 UTF-8 轉義序列。
bool一個 Identifier 元素或匹配以下值之一的任何 unsignedInteger 元素。
真值: “True”, “true”, “t”, 1
假值: “False”, “false”, “f”, 0
允許使用 01 的任何無符號整數表示:00, 0x0, 01, 0x1 等。
列舉值一個包含列舉值名稱的 Identifier 元素,或範圍在 -0x800000000x7FFFFFFF 之間的任何包含列舉值編號的 integer 元素。指定不是欄位 enum 型別定義成員的名稱是無效的。根據特定的 protobuf 執行時實現,指定不是欄位 enum 型別定義成員的數字可能有效,也可能無效。未繫結到特定執行時實現的文字格式處理器(如 IDE 支援)可以選擇在提供的數字值不是有效成員時發出警告。請注意,某些在其他上下文中是有效關鍵字的名稱,例如 “true”“infinity”,也是有效的列舉值名稱。
訊息值一個 MessageValue 元素。

擴充套件欄位

擴充套件欄位使用其限定名稱指定。示例

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

擴充套件欄位通常在其他 .proto 檔案中定義。文字格式語言不提供指定定義擴充套件欄位檔案位置的機制;相反,解析器必須事先知道它們的位置。

Any 欄位

文字格式使用一種特殊的語法(類似於擴充套件欄位)來支援 google.protobuf.Any 知名型別的擴充套件形式。示例

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"
  }
}

在此示例中,any_value 是型別為 google.protobuf.Any 的欄位,它儲存了一個序列化的 com.example.SomeType 訊息,其中包含 field1: hello

group 欄位

在文字格式中,group 欄位使用普通的 MessageValue 元素作為其值,但使用大寫的組名而不是隱式的全小寫欄位名來指定。示例

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

使用上面的 .proto 定義,以下文字格式是有效的 MessageWithGroup

MyGroup {
  my_value: 1
}

與 Message 欄位類似,組名和值之間的 : 分隔符是可選的。

此功能包含在 Editions 中以實現向後相容。通常 DELIMITED 欄位會像普通訊息一樣序列化。以下顯示了 Editions 的行為

edition = "2024";

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

.proto 檔案的內容將像 proto2 group 一樣序列化

GroupLike {
  foo: 2;
}

map 欄位

文字格式不提供用於指定 map 欄位條目的自定義語法。當 map 欄位在 .proto 檔案中定義時,會定義一個隱式的 Entry 訊息,其中包含 keyvalue 欄位。Map 欄位始終是重複的,接受多個鍵/值條目。示例

// Editions
edition = "2024";

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

使用上面的 .proto 定義,以下文字格式是有效的 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 }
]

keyvalue 欄位都是可選的,如果未指定,則預設為各自型別的零值。如果鍵重複,則在解析後的 map 中僅保留最後指定的鍵值。

map 的順序在 textprotos 中不保留。

oneof 欄位

雖然文字格式中沒有與 oneof 欄位相關的特殊語法,但一次只能指定一個 oneof 成員。同時指定多個成員是無效的。示例

// 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;
}

上面的 .proto 定義導致以下文字格式行為

# 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"
}

文字格式檔案

文字格式檔案使用 .txtpb 檔名字尾,幷包含一個 Message。文字格式檔案編碼為 UTF-8。下面提供了一個文字 proto 檔案示例。

# 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" ]

頭部註釋 proto-fileproto-message 會告知開發者工具有關模式的資訊,以便它們可以提供各種功能。

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

以程式設計方式處理格式

由於各個 Protocol Buffer 實現發出的文字格式不一致或不規範,因此修改 TextProto 檔案或輸出 TextProto 的工具或庫必須明確使用 https://github.com/protocolbuffers/txtpbfmt 來格式化其輸出。