tape: Implement dynamic decoding (untested)

This commit is contained in:
Sasha Koshka 2025-06-24 14:39:16 -04:00
parent 1bc0788ff2
commit 9932abd6c4

View File

@ -1,5 +1,11 @@
package tape package tape
// dont smoke reflection, kids!!!!!!!!!
// totally reflectric, reflectrified, etc. this is probably souper slow but
// certainly no slower than the built in json encoder i'd imagine.
// TODO: add support for struct tags: `tape:"0000"`, tape:"0001"` so they can get
// transformed into tables with a defined schema
import "fmt" import "fmt"
import "reflect" import "reflect"
@ -14,6 +20,7 @@ import "reflect"
// - []<supported type> // - []<supported type>
// - map[uint16]<supported type> // - map[uint16]<supported type>
func EncodeAny(encoder *Encoder, value any, tag Tag) (n int, err error) { func EncodeAny(encoder *Encoder, value any, tag Tag) (n int, err error) {
// TODO use reflection for all of this to ignore type names
// primitives // primitives
switch value := value.(type) { switch value := value.(type) {
case int: return encoder.WriteInt32(int32(value)) case int: return encoder.WriteInt32(int32(value))
@ -41,21 +48,128 @@ func EncodeAny(encoder *Encoder, value any, tag Tag) (n int, err error) {
switch reflectType.Kind() { switch reflectType.Kind() {
case reflect.Slice: case reflect.Slice:
return encodeAnySlice(encoder, value, tag) return encodeAnySlice(encoder, value, tag)
case reflect.Array: // case reflect.Array:
return encodeAnySlice(encoder, reflect.ValueOf(value).Slice(0, reflectType.Len()).Interface(), tag) // return encodeAnySlice(encoder, reflect.ValueOf(value).Slice(0, reflectType.Len()).Interface(), tag)
case reflect.Map: case reflect.Map:
if reflectType.Key() == reflect.TypeOf(uint16(0)) { if reflectType.Key() == reflect.TypeOf(uint16(0)) {
return encodeAnyMap(encoder, value, tag) return encodeAnyMap(encoder, value, tag)
} }
return 0, fmt.Errorf("cannot encode map key %T, key must be uint16", value) return n, fmt.Errorf("cannot encode map key %T, key must be uint16", value)
} }
return 0, fmt.Errorf("cannot encode type %T", value) return n, fmt.Errorf("cannot encode type %T", value)
}
// DecodeAny decodes data and places it into destination, which must be a
// pointer to a supported type. See [EncodeAny] for a list of supported types.
func DecodeAny(decoder *Decoder, destination any, tag Tag) (n int, err error) {
return decodeAny(decoder, reflect.ValueOf(destination), tag)
}
func decodeAny(decoder *Decoder, destination reflect.Value, tag Tag) (n int, err error) {
errWrongDestinationType := func(expected string) error {
return fmt.Errorf(
"expected %s destination, not %v",
expected, destination)
}
if destination.Kind() != reflect.Pointer {
return n, errWrongDestinationType("pointer")
}
switch tag.WithoutCN() {
case SI:
// SI: (none)
err = setIntPtr(destination, uint64(tag.CN()))
if err != nil { return n, err }
case LI:
// LI: <value: IntN>
nn, err := decodeAndSetIntPtr(decoder, destination, tag.CN() - 1)
n += nn; if err != nil { return n, err }
case FP:
// FP: <value: FloatN>
nn, err := decodeAndSetFloatPtr(decoder, destination, tag.CN() - 1)
n += nn; if err != nil { return n, err }
case SBA:
// SBA: <data: U8>*
destination, err := asByteArrayPtr(destination)
if err != nil { return n, err }
buffer := make([]byte, tag.CN())
nn, err := decoder.Read(buffer)
n += nn; if err != nil { return n, err }
*destination = buffer
case LBA:
// LBA: <length: UN> <data: U8>*
destination, err := asByteArrayPtr(destination)
if err != nil { return n, err }
length, nn, err := decoder.ReadUintN(tag.CN() - 1)
n += nn; if err != nil { return n, err }
buffer := make([]byte, length)
nn, err = decoder.Read(buffer)
n += nn; if err != nil { return n, err }
*destination = buffer
case OTA:
// OTA: <length: UN> <elementTag: tape.Tag> <values>*
length, nn, err := decoder.ReadUintN(tag.CN() - 1)
n += nn; if err != nil { return n, err }
oneTag, nn, err := decoder.ReadTag()
n += nn; if err != nil { return n, err }
var slice reflect.Value
needSet := false
elem := destination.Elem()
if elem.Kind() == reflect.Struct && elem.Type().Name() == "unknownSlicePlaceholder" {
needSet = true
slice, err = skeletonValueSlice(oneTag, int(length))
if err != nil { return n, err }
} else {
slice = elem
if slice.Kind() != reflect.Slice {
return n, errWrongDestinationType("slice")
}
slice.SetLen(int(length))
}
for index := range length {
nn, err := decodeAny(decoder, slice.Index(int(index)), oneTag)
n += nn; if err != nil { return n, err }
}
if needSet {
destination.Elem().Set(slice)
}
case KTV:
// KTV: <length: UN> (<key: U16> <tag: Tag> <value>)*
table := destination.Elem()
if table.Kind() != reflect.Map {
return n, errWrongDestinationType("map")
}
typ := table.Type()
if typ.Key().Kind() != reflect.Uint16 {
return n, errWrongDestinationType("map[uint16]")
}
if typ.Elem() != reflect.TypeOf(any(nil)) {
return n, errWrongDestinationType("map[uint16] any")
}
length, nn, err := decoder.ReadUintN(tag.CN() - 1)
n += nn; if err != nil { return n, err }
table.Clear()
for _ = range length {
key, nn, err := decoder.ReadUint16()
n += nn; if err != nil { return n, err }
itemTag, nn, err := decoder.ReadTag()
n += nn; if err != nil { return n, err }
value, err := skeletonValue(itemTag)
if err != nil { return n, err }
nn, err = decodeAny(decoder, value.Elem(), itemTag)
n += nn; if err != nil { return n, err }
table.SetMapIndex(reflect.ValueOf(key), value)
}
}
return n, fmt.Errorf("unknown TN %d", tag.TN())
} }
// TagAny returns the correct tag for an "any" value. Returns an error if the // TagAny returns the correct tag for an "any" value. Returns an error if the
// underlying type is unsupported. See [EncodeAny] for a list of supported // underlying type is unsupported. See [EncodeAny] for a list of supported
// types. // types.
func TagAny(value any) (Tag, error) { func TagAny(value any) (Tag, error) {
// TODO use reflection for all of this to ignore type names
// primitives // primitives
switch value := value.(type) { switch value := value.(type) {
case int, uint: return LI.WithCN(3), nil case int, uint: return LI.WithCN(3), nil
@ -70,11 +184,11 @@ func TagAny(value any) (Tag, error) {
// aggregates // aggregates
reflectType := reflect.TypeOf(value) reflectType := reflect.TypeOf(value)
switch reflectType.Kind() { switch reflectType.Kind() {
case reflect.Slice: return OTA.WithCN(reflect.ValueOf(value).Len()), nil case reflect.Slice: return OTA.WithCN(reflect.ValueOf(value).Len() - 1), nil
case reflect.Array: return OTA.WithCN(reflectType.Len()), nil case reflect.Array: return OTA.WithCN(reflectType.Len()), nil
case reflect.Map: case reflect.Map:
if reflectType.Key() == reflect.TypeOf(uint16(0)) { if reflectType.Key() == reflect.TypeOf(uint16(0)) {
return OTA.WithCN(reflect.ValueOf(value).Len()), nil return KTV.WithCN(IntBytes(uint64(reflect.ValueOf(value).Len())) - 1), nil
} }
return 0, fmt.Errorf("cannot encode map key %T, key must be uint16", value) return 0, fmt.Errorf("cannot encode map key %T, key must be uint16", value)
} }
@ -126,3 +240,114 @@ func encodeAnyMap(encoder *Encoder, value any, tag Tag) (n int, err error) {
} }
return n, nil return n, nil
} }
func setIntPtr(destination reflect.Value, value uint64) error {
elem := destination.Elem()
if !elem.CanInt() {
return fmt.Errorf("cannot assign integer to %T", elem.Interface())
}
elem.Set(reflect.ValueOf(value).Convert(elem.Type()))
return nil
}
func setFloatPtr(destination reflect.Value, value float64) error {
elem := destination.Elem()
if !elem.CanFloat() {
return fmt.Errorf("cannot assign float to %T", elem.Interface())
}
elem.Set(reflect.ValueOf(value).Convert(elem.Type()))
return nil
}
func decodeAndSetIntPtr(decoder *Decoder, destination reflect.Value, bytes int) (n int, err error) {
value, nn, err := decoder.ReadUintN(bytes)
n += nn; if err != nil { return n, err }
return n, setIntPtr(destination, value)
}
func decodeAndSetFloatPtr(decoder *Decoder, destination reflect.Value, bytes int) (n int, err error) {
switch bytes {
case 8:
value, nn, err := decoder.ReadFloat64()
n += nn; if err != nil { return n, err }
return n, setFloatPtr(destination, float64(value))
case 4:
value, nn, err := decoder.ReadFloat32()
n += nn; if err != nil { return n, err }
return n, setFloatPtr(destination, float64(value))
}
return n, fmt.Errorf("cannot decode float%d", bytes * 8)
}
func asByteArrayPtr(value reflect.Value) (*[]byte, error) {
typ := value.Type()
if typ.Kind() != reflect.Pointer {
return nil, fmt.Errorf("cannot convert %T to pointer", value)
}
if typ.Elem().Kind() != reflect.Slice {
return nil, fmt.Errorf("cannot convert %T to slice pointer", value)
}
if typ.Elem().Elem() != reflect.TypeOf(byte(0)) {
return nil, fmt.Errorf("cannot convert %T to *[]byte", value)
}
return value.Convert(reflect.PtrTo(reflect.SliceOf(reflect.TypeOf(byte(0))))).Interface().(*[]byte), nil
}
func skeletonValue(tag Tag) (reflect.Value, error) {
switch tag.WithoutCN() {
case SI:
value := uint8(0)
return reflect.ValueOf(&value), nil
case LI:
switch tag.CN() {
case 0: value := uint8(0); return reflect.ValueOf(&value), nil
case 1: value := uint16(0); return reflect.ValueOf(&value), nil
case 3: value := uint32(0); return reflect.ValueOf(&value), nil
case 7: value := uint64(0); return reflect.ValueOf(&value), nil
}
return reflect.Value { }, fmt.Errorf("unknown CN %d for LI", tag.CN())
case FP:
switch tag.CN() {
case 3: value := float32(0); return reflect.ValueOf(&value), nil
case 7: value := float64(0); return reflect.ValueOf(&value), nil
}
return reflect.Value { }, fmt.Errorf("unknown CN %d for FP", tag.CN())
case SBA: value := []byte { }; return reflect.ValueOf(&value), nil
case LBA: value := []byte { }; return reflect.ValueOf(&value), nil
case OTA: value := unknownSlicePlaceholder { }; return reflect.ValueOf(&value), nil
case KTV: value := map[uint16] any { }; return reflect.ValueOf(&value), nil
}
return reflect.Value { }, fmt.Errorf("unknown TN %d", tag.TN())
}
func skeletonValueSlice(tag Tag, length int) (reflect.Value, error) {
switch tag.WithoutCN() {
case SI:
value := make([]uint8, length)
return reflect.ValueOf(&value), nil
case LI:
switch tag.CN() {
case 0: value := make([]uint8, length); return reflect.ValueOf(&value), nil
case 1: value := make([]uint16, length); return reflect.ValueOf(&value), nil
case 3: value := make([]uint32, length); return reflect.ValueOf(&value), nil
case 7: value := make([]uint64, length); return reflect.ValueOf(&value), nil
}
return reflect.Value { }, fmt.Errorf("unknown CN %d for LI OTA", tag.CN())
case FP:
switch tag.CN() {
case 3: value := make([]float32, length); return reflect.ValueOf(&value), nil
case 7: value := make([]float64, length); return reflect.ValueOf(&value), nil
}
return reflect.Value { }, fmt.Errorf("unknown CN %d for FP OTA", tag.CN())
case SBA: value := make([][]byte, length); return reflect.ValueOf(&value), nil
case LBA: value := make([][]byte, length); return reflect.ValueOf(&value), nil
case OTA: value := make([]any, length); return reflect.ValueOf(&value), nil
case KTV: value := make([]map[uint16] any, length); return reflect.ValueOf(&value), nil
}
return reflect.Value { }, fmt.Errorf("unknown TN %d", tag.TN())
}
// unknownSlicePlaceholder is inserted by skeletonValue and informs the program
// that the destination for the slice needs to be generated based on the item
// tag in the OTA.
type unknownSlicePlaceholder struct { }