文本格式语言规范

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.

有一个需要特别注意的边缘情况:一个数字标记(FLOATDEC_INTOCT_INTHEX_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" ;

空白与注释

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 字段名称以一个限定域名作为前缀,例如 type.googleapis.com/

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

常规字段和扩展字段可以有标量值或消息值。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" }
}

未知字段

文本格式解析器不支持以原始字段编号代替字段名称来表示未知字段,因为六种线路类型中有三种在文本格式中的表示方式相同。一些文本格式序列化器实现会使用一种格式对未知字段进行编码,该格式使用字段编号和值的数字表示,但这本质上是有损的,因为线路类型信息被忽略了。相比之下,线路格式是无损的,因为它在每个字段标签中都包含了线路类型,格式为 (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 字段不能使用列表语法。例如,[0] 对于 optionalrequired 字段是无效的。标记为 optional 的字段可以省略或指定一次。标记为 required 的字段必须且只能指定一次。

不允许出现未在关联的 .proto 消息中指定的字段,除非该字段名称存在于消息的 reserved 字段列表中。reserved 字段,如果以任何形式(标量、列表、消息)出现,都会被文本格式简单地忽略。

值类型

当字段关联的 .proto 值类型已知时,适用以下值描述和约束。为本节之目的,我们声明以下容器元素:

signedInteger   = DecSignedInteger | OctSignedInteger | HexSignedInteger ;
unsignedInteger = DecUnsignedInteger | OctUnsignedInteger | HexUnsignedInteger ;
integer         = signedInteger | unsignedInteger ;
.proto 类型
float, double一个 FloatDecSignedIntegerDecUnsignedInteger 元素,或者一个 IdentifierSignedIdentifier 元素,其 IDENT 部分等于 "inf""infinity""nan"(不区分大小写)。溢出被视为无穷大或负无穷大。八进制和十六进制值无效。

注意:"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""true""t"1
False 值: "False""false""f"0
允许使用 01 的任何无符号整数表示形式:000x0010x1 等。
enum values一个包含枚举值名称的 Identifier 元素,或一个包含枚举值数字且范围在 -0x800000000x7FFFFFFF 之间的任何 integer 元素。指定一个不属于该字段 enum 类型定义的名称是无效的。根据具体的 protobuf 运行时实现,指定一个不属于该字段 enum 类型定义的数字可能有效也可能无效。与特定运行时实现无关的文本格式处理器(例如 IDE 支持)可以选择在提供的数字值不是有效成员时发出警告。请注意,某些在其他上下文中是有效关键字的名称,例如 "true""infinity",也是有效的枚举值名称。
message values一个 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 元素作为其值,但是使用大写的组名而不是隐含的小写字段名来指定。

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

使用上述 .proto 定义,以下文本格式是一个有效的 MessageWithGroup

MyGroup {
  my_value: 1
}

与消息字段类似,组名和值之间的 : 分隔符是可选的。

map 字段

文本格式不提供用于指定 map 字段条目的自定义语法。当在 .proto 文件中定义一个 map 字段时,会隐式定义一个包含 keyvalue 字段的 Entry 消息。Map 字段总是 repeated 的,接受多个键/值条目。

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 成员。同时指定多个成员是无效的。

message OneofExample {
  message MessageWithOneof {
    optional 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 编码。下面提供了一个 textproto 文件示例。

# 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 会告知开发工具相关的模式(schema),以便它们可以提供各种功能。

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

以编程方式使用该格式

由于各个 Protocol Buffer 实现所生成的文本格式既不一致也不规范,因此修改 TextProto 文件或生成 TextProto 输出的工具或库必须明确使用 https://github.com/protocolbuffers/txtpbfmt 来格式化其输出。