Field tags

primitive VarintField fun string(): String val => "VarintField"
primitive Fixed32Field fun string(): String val => "Fixed32Field"
primitive Fixed64Field fun string(): String val => "Fixed64Field"
primitive DelimitedField fun string(): String val => "DelimitedField"
type TagKind is (VarintField | Fixed32Field | Fixed64Field | DelimitedField)

primitive _TagUtil
  fun to_num(t: TagKind): U64 =>
    match t
    | VarintField => 0
    | Fixed64Field => 1
    | DelimitedField => 2
    | Fixed32Field => 5
    end

  fun from_num(n: U64): TagKind ? =>
    match n
    | 0 => VarintField
    | 1 => Fixed64Field
    | 2 => DelimitedField
    | 5 => Fixed32Field
    else
      error
    end

primitive FieldSize
  fun varint
    [T: (I32 | I64 | U32 | U64 | Bool)]
    (field: U64, n: T)
    : U32
  =>
    let field_size =
      iftype T <: Bool then
        raw_varint(if (n and true) then 1 else 0 end)
      elseif T <: I32 then
        raw_varint(U64.from[I32](n))
      elseif T <: I64 then
        raw_varint(U64.from[I64](n))
      elseif T <: U32 then
        raw_varint(U64.from[U32](n))
      elseif T <: U64 then
        raw_varint(n)
      else
        0 // Can't happen, but Pony doesn't know
      end

    _tag_size(field) + field_size

  fun varint_zigzag
    [T: (I32 | I64)]
    (field: U64, n: T)
    : U32
  =>
    let field_size =
      iftype T <: I32 then
        raw_varint(ZigZag.encode_32(n).u64())
      elseif T <: I64 then
        raw_varint(ZigZag.encode_64(n))
      else
        0 // Can't happen, but Pony doesn't know
      end
    _tag_size(field) + field_size

  fun enum(field: U64, enum_field: ProtoEnumValue): U32 =>
    _tag_size(field) + raw_varint(enum_field.as_i32().u64())

  fun fixed32(field: U64): U32 => _tag_size(field) + 4

  fun fixed64(field: U64): U32 => _tag_size(field) + 8

  fun delimited(field: U64, bytes: (String box | Array[U8] box)): U32 =>
    _tag_size(field) + raw_varint(bytes.size().u64()) + bytes.size().u32()

  fun inner_message(field: U64, n: ProtoMessage box): U32 =>
    let len = n.compute_size()
    _tag_size(field) + raw_varint(len.u64()) + len

  fun packed_varint
    [T: (I32 | I64 | U32 | U64 | Bool)]
    (field: U64, arg: Array[T] box)
    : U32
  =>
    if arg.size() == 0 then
      0
    else
      var data_size: U32 = 0
      for v in arg.values() do
        iftype T <: Bool then
          data_size = data_size + raw_varint(if (v and true) then 1 else 0 end)
        elseif T <: I32 then
          data_size = data_size + raw_varint(v.u64())
        elseif T <: I64 then
          data_size = data_size + raw_varint(v.u64())
        elseif T <: U32 then
          data_size = data_size + raw_varint(v.u64())
        elseif T <: U64 then
          data_size = data_size + raw_varint(v)
        end
      end
      _tag_size(field) + raw_varint(data_size.u64()) + data_size
    end

  fun packed_varint_zigzag
    [T: (I32 | I64)]
    (field: U64, arg: Array[T] box)
    : U32
  =>
    if arg.size() == 0 then
      0
    else
      var data_size: U32 = 0
      for v in arg.values() do
        iftype T <: I32 then
          data_size = data_size + raw_varint(ZigZag.encode_32(v).u64())
        elseif T <: I64 then
          data_size = data_size + raw_varint(ZigZag.encode_64(v))
        end
      end
      _tag_size(field) + raw_varint(data_size.u64()) + data_size
    end

  fun packed_fixed32
    [T: (I32 | U32 | F32)]
    (field: U64, arg: Array[T] box)
    : U32
  =>
    let array_size = arg.size()
    if array_size == 0 then
      0
    else
      let data_size: USize = 4 * array_size
      _tag_size(field) + raw_varint(data_size.u64()) + data_size.u32()
    end

  fun packed_fixed64
    [T: (I64 | U64 | F64)]
    (field: U64, arg: Array[T] box)
    : U32
  =>
    let array_size = arg.size()
    if array_size == 0 then
      0
    else
      let data_size: USize = 8 * array_size
      _tag_size(field) + raw_varint(data_size.u64()) + data_size.u32()
    end

  fun packed_enum
    [T: ProtoEnumValue val]
    (field: U64, arg: Array[T] box)
    : U32
  =>
    if arg.size() == 0 then
      0
    else
      var data_size: U32 = 0
      for v in arg.values() do
        data_size = data_size + raw_varint(v.as_i32().u64())
      end
      _tag_size(field) + raw_varint(data_size.u64()) + data_size
    end

  fun _tag_size(field: U64): U32 => raw_varint((field << 3))

  // From
  // https://github.com/stepancheg/rust-protobuf/blob/bbe35a98e196c4dea67dd23ac93c0a66ca11b903/protobuf/src/rt.rs#L39
  fun raw_varint(v: U64): U32 =>
    let cnt: U64 = 0xffffffffffffffff
    if (v and (cnt << 7)) == 0 then
      return 1
    end
    if (v and (cnt << 14)) == 0 then
      return 2
    end
    if (v and (cnt << 21)) == 0 then
      return 3
    end
    if (v and (cnt << 28)) == 0 then
      return 4
    end
    if (v and (cnt << 35)) == 0 then
      return 5
    end
    if (v and (cnt << 42)) == 0 then
      return 6
    end
    if (v and (cnt << 49)) == 0 then
      return 7
    end
    if (v and (cnt << 56)) == 0 then
      return 8
    end
    if (v and (cnt << 63)) == 0 then
      return 9
    end
    10