文本格式语言规范

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)以取消转义字节值 0x83(ASCII ‘S’),随后的‘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'

字段名称

包含消息的一部分的字段使用简单的标识符作为名称。扩展Any字段名称用方括号括起来并进行完全限定。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的字段必须且只能指定一次。

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

值类型

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

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

注意:"nan"应解释为静默 NaN

int32sint32sfixed32范围为-0x800000000x7FFFFFFF的任何integer元素。
int64sint64sfixed64范围为-0x80000000000000000x7FFFFFFFFFFFFFFF的任何integer元素。
uint32fixed32范围为00xFFFFFFFF的任何unsignedInteger元素。请注意,有符号值(-0)无效。
uint64fixed64范围为00xFFFFFFFFFFFFFFFF的任何unsignedInteger元素。请注意,有符号值(-0)无效。
string包含有效 UTF-8 数据的String元素。任何转义序列在取消转义后都必须形成有效的 UTF-8 字节序列。
bytesString元素,可能包含无效的 UTF-8 转义序列。
boolIdentifier元素或与以下值之一匹配的任何unsignedInteger元素。
真值:"True""true""t"1
假值:"False""false""f"0
允许使用01的任何无符号整数表示形式:000x0010x1等。
枚举值包含枚举值名称的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元素作为其值,但使用大写的组名而不是隐式的小写字段名来指定。示例

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

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

MyGroup {
  my_value: 1
}

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

map 字段

文本格式没有提供用于指定映射字段条目的自定义语法。当在.proto文件中定义map字段时,会隐式定义包含keyvalue字段的Entry消息。映射字段始终是重复的,接受多个键/值条目。示例

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字段都是可选的,如果未指定,则默认为其各自类型的零值。如果键重复,则解析后的映射中只会保留最后指定的键值。

文本协议中不维护映射的顺序。

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编码。下面提供了一个文本协议文件示例。

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