tape: Implement encoding for "any" values

This commit is contained in:
Sasha Koshka 2025-06-20 18:39:16 -04:00
parent 285e83d995
commit c4407d9759

View File

@ -13,8 +13,43 @@ import "reflect"
// - string
// - []<supported type>
// - map[uint16]<supported type>
func EncodeAny(encoder *Encoder, value any) (Tag, error) {
// TODO
func EncodeAny(encoder *Encoder, value any, tag Tag) (n int, err error) {
// primitives
switch value := value.(type) {
case int: return encoder.WriteInt32(int32(value))
case uint: return encoder.WriteUint32(uint32(value))
case int8: return encoder.WriteInt8(value)
case uint8: return encoder.WriteUint8(value)
case int16: return encoder.WriteInt16(value)
case uint16: return encoder.WriteUint16(value)
case int32: return encoder.WriteInt32(value)
case uint32: return encoder.WriteUint32(value)
case int64: return encoder.WriteInt64(value)
case uint64: return encoder.WriteUint64(value)
case string: return EncodeAny(encoder, []byte(value), tag)
case []byte:
if tag.Is(LBA) {
nn, err := encoder.WriteUintN(uint64(len(value)), tag.CN() + 1)
n += nn; if err != nil { return n, err }
}
nn, err := encoder.Write(value)
n += nn; if err != nil { return n, err }
}
// aggregates
reflectType := reflect.TypeOf(value)
switch reflectType.Kind() {
case reflect.Slice:
return encodeAnySlice(encoder, value, tag)
case reflect.Array:
return encodeAnySlice(encoder, reflect.ValueOf(value).Slice(0, reflectType.Len()).Interface(), tag)
case reflect.Map:
if reflectType.Key() == reflect.TypeOf(uint16(0)) {
return encodeAnyMap(encoder, value, tag)
}
return 0, fmt.Errorf("cannot encode map key %T, key must be uint16", value)
}
return 0, fmt.Errorf("cannot encode type %T", value)
}
// TagAny returns the correct tag for an "any" value. Returns an error if the
@ -27,7 +62,7 @@ func TagAny(value any) (Tag, error) {
case int8, uint8: return LI.WithCN(0), nil
case int16, uint16: return LI.WithCN(1), nil
case int32, uint32: return LI.WithCN(3), nil
case int64, uint64: return LI.WithCN(8), nil
case int64, uint64: return LI.WithCN(7), nil
case string: return bufferLenTag(len(value)), nil
case []byte: return bufferLenTag(len(value)), nil
}
@ -43,5 +78,51 @@ func TagAny(value any) (Tag, error) {
}
return 0, fmt.Errorf("cannot encode map key %T, key must be uint16", value)
}
return 0, fmt.Errorf("cannot encode type %T", value)
return 0, fmt.Errorf("cannot get tag of type %T", value)
}
func encodeAnySlice(encoder *Encoder, value any, tag Tag) (n int, err error) {
// OTA: <length: UN> <elementTag: tape.Tag> <values>*
reflectValue := reflect.ValueOf(value)
nn, err := encoder.WriteUintN(uint64(reflectValue.Len()), tag.CN() + 1)
n += nn; if err != nil { return n, err }
reflectType := reflect.TypeOf(value)
oneTag, err := TagAny(reflect.Zero(reflectType.Elem()).Interface())
if err != nil { return n, err }
for index := 0; index <= reflectValue.Len(); index += 1 {
item := reflectValue.Index(index).Interface()
itemTag, err := TagAny(item)
if err != nil { return n, err }
if itemTag.CN() > oneTag.CN() { oneTag = itemTag }
}
if oneTag.Is(SBA) { oneTag += 1 << 5 }
nn, err = encoder.WriteUint8(uint8(oneTag))
n += nn; if err != nil { return n, err }
for index := 0; index <= reflectValue.Len(); index += 1 {
item := reflectValue.Index(index).Interface()
nn, err = EncodeAny(encoder, item, oneTag)
n += nn; if err != nil { return n, err }
}
return n, err
}
func encodeAnyMap(encoder *Encoder, value any, tag Tag) (n int, err error) {
// KTV: <length: UN> (<key: U16> <tag: Tag> <value>)*
reflectValue := reflect.ValueOf(value)
nn, err := encoder.WriteUintN(uint64(reflectValue.Len()), tag.CN() + 1)
n += nn; if err != nil { return n, err }
iter := reflectValue.MapRange()
for iter.Next() {
key := iter.Key().Interface().(uint16)
value := iter.Value().Interface()
nn, err = encoder.WriteUint16(key)
n += nn; if err != nil { return n, err }
itemTag, err := TagAny(value)
n += nn; if err != nil { return n, err }
nn, err = encoder.WriteUint8(uint8(itemTag))
n += nn; if err != nil { return n, err }
nn, err = EncodeAny(encoder, value, itemTag)
n += nn; if err != nil { return n, err }
}
return n, nil
}