文本格式语言规范

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 来格式化其输出。