Protocol Buffersのテキスト形式

様々なシリアライズ形式やデータベース向けのスキーマ言語として[Protocol Buffersが有用](https://qiita.com/yugui/items/160737021d25d761b353)という話や、そのために[protocプラグインを書く話](https://qiita.com/yugui/items/87d00d77dee159e74886)をしてきた。
ここで、少し話は変わってProtocol Buffersメッセージのテキスト形式の話をする必要がある。

protobufで定義されたメッセージは、実はバイナリ形式やJSONにシリアライズするほか、独自のテキスト形式にもシリアライズできる。
テキスト形式はバイナリ形式の効率性や[プロトコル後方互換性](https://qiita.com/yugui/items/160737021d25d761b353#comment-85fcad3aa52e4e517539)を欠いているが、いくつかよく使われるパターンがある。

* protobufスキーマにカスタムオプションを記述するとき
* protobufデータを処理中のログ出力
* protobufを積極活用しているプロダクト内での設定ファイル形式

別に読み書きが難しいフォーマットではないが、世の中にあまりドキュメントがないため書いておこうと思う。
なお本稿は、実験してみたりパーサーの実装を読んだり[昔自分で書いたパーサー](https://github.com/codekitchen/ruby-protocol-buffers/blob/master/lib/protocol_buffers/runtime/text_parser.ry)を読んで思い出したりした、リバースエンジニアリングの結果であり、包括的かつ正確とは限らない。

# 例
たとえば、次のようなメッセージ定義があったとする。

```protobuf:example.proto
syntax = "proto3";
package example.protobuf;

message SimpleMessage {
    message HeaderItem {
        string name = 1;
        string value = 2;
    }
    enum Type {
        START = 0;
        BLOB = 1;
        END = 2;
    }

    uint64 id = 1;
    Type message_type = 2;
    repeated HeaderItem header = 3;
    bytes blob = 4;
}
```

この定義に従った次のようなデータがあったとしよう。このデータはJSONで書いてある。

```json:example-data.json
{
  "id": 1,
  "messageType": "BLOB",
  "header": [
    {
      "name": "foo",
      "value": "abcde"
    },
    {
      "name": "bar",
      "value": "fghij"
    }
  ],
  "blob": "AQIDBA=="
}
```

これに対応するprotobufテキスト形式は次のようになる。

```protobuf:example-data.txt
id: 1
message_type: BLOB
header: <
  name: "foo"
  value: "abcde"
>
header: <
  name: "bar"
  value: "fghij"
>
blob: "\001\002\003\004"
```

なお、このテキスト形式のファイルに固有のデファクトスタンダード拡張子は存在しないと思われる。`.txt`や`.pb`あるいは`.pb.txt`などがよく使われるが、`.pb`はProtobufのバイナリシリアライゼーション形式にも使われることがあるので注意が必要である。

# 解説
## 全体の文法
* JSONと異なり、トップレベルでオブジェクトを囲う`{ ... }`は必要ない。
  * JSON stream的な用途で使われることはあまりないが、複数のメッセージを1ファイルに書きたい場合には2行空行を挟むことにより区切りを表現したりする。怖い。
* 各フィールドはフィールド名と値をコロン区切りで繋げるのが基本
* ネストしたデータ構造は例のように`< ... >`で囲うか、または`{ ... }`で囲う。これらの括弧の直前にある`:`は省略できる。
* `repeated`フィールドは「配列値を持つフィールド」ではなく、文字通り「フィールドが複数回出現する」形で表現するのがJSONやYAMLに比べると特徴的である。
* 各フィールドの間は空白文字で区切る
* `#`以降はコメントとして行末まで無視される

以上を合わせると、次のように書いても同じである。

```proto
id: 1 message_type: BLOB header <name: "foo" value: "abcde"> header {name: "bar" value: "fghij"} blob: "\001\002\003\004"
```

## oneof
ちなみに、もし`oneof`フィールドがある場合には単に`oneof`として列挙されているメンバーのうちのどれか1つだけを書けばよい。たとえば、上のスキーマを次のように変更するとしよう。
(`HeaderItem`と`header`は省略した)

```protobuf
message SimpleMessage {
    enum Type {
        START = 0;
        BLOB = 1;
        END = 2;
    }

    uint64 id = 1;
    Type message_type = 2;
    oneof data {
      bytes blob = 4;
      string plaintext = 5;
    }
}
```

この場合にも、`blob`がメッセージ直下にあったときと全く変わらずに次のように書ける。 (headerは省略されている)

```protobuf

id: 1
message_type: BLOB
blob: "\001\002\003\004"
```

あるいは、`blob`の代わりに`plaintext`のほうを指定する場合は

```protobuf:example-data.txt
id: 1
message_type: BLOB
plaintext: "abc"
```

## ブール値
* `true`, `True`, `t`、または正整数は真として評価される。
* `false`, `False`, `f`または0は偽として評価される。
* 正準表記は`true`および`false`

例:

```
a: true
b: false
```

## 整数値
Cとほぼ同じ。

* `0`で始まる値は8進数
* `0x`で始まる値は16進数
* それ以外は10進数

例:

```
a: 1
b: -0x2F
c: 0755
```

## 浮動小数点値
* 10進表記(`0.01234`)でも指数表記(`1.234e-2`)でも良い

例:

```
a: -0.01234
b: 7.2973525664e-3
```

## 文字列値
* ダブルクォート`"..."`またはシングルクォート`'...'`で囲む。両者の間に意味の違いはない。
* 連続する文字列値は結合される
   * つまり、`"aaa" 'bbb'`は`"aaabbb"`と同義。
* `"\xHH"`形式の16進バイト値、`"\ooo"`形式の8進バイト値、`\f, \r, \v, \t, \n, \\, \", \'`などのエスケープ表記を認識する。

例:

```
a: "foo"
b: "bar\n"
   'baz\n'
   'qux\x01\x02'
```

## 列挙値
* 列挙ラベルまたは対応する整数値で指定できる
* 他のパッケージ内で定義された列挙型を参照する際には、パッケージ名で修飾する必要がある

例:

```
message_type: BLOB
compression: my.another.package.COMPRESSION_GZIP
another_enum: 3
```

# 評価
好みの問題に帰着するとは思うのだが、個人的には設定ファイル記述言語としてJSONやYAMLより読み書きしやすいと思っている。
普及していないのでprotobufを使っていないプロダクトで利用するのは少しためらうものの。

良いと思っている点は、

* インデントが深くならない。
* 列挙値を使える