diff --git a/tape/dynamic.go b/tape/dynamic.go index 6dc0380..67d1a88 100644 --- a/tape/dynamic.go +++ b/tape/dynamic.go @@ -1,5 +1,11 @@ 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 "reflect" @@ -14,6 +20,7 @@ import "reflect" // - [] // - map[uint16] func EncodeAny(encoder *Encoder, value any, tag Tag) (n int, err error) { + // TODO use reflection for all of this to ignore type names // primitives switch value := value.(type) { 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() { 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.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 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: + nn, err := decodeAndSetIntPtr(decoder, destination, tag.CN() - 1) + n += nn; if err != nil { return n, err } + case FP: + // FP: + nn, err := decodeAndSetFloatPtr(decoder, destination, tag.CN() - 1) + n += nn; if err != nil { return n, err } + case SBA: + // SBA: * + 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: * + 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, 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: ( )* + 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 // underlying type is unsupported. See [EncodeAny] for a list of supported // types. func TagAny(value any) (Tag, error) { + // TODO use reflection for all of this to ignore type names // primitives switch value := value.(type) { case int, uint: return LI.WithCN(3), nil @@ -70,11 +184,11 @@ func TagAny(value any) (Tag, error) { // aggregates reflectType := reflect.TypeOf(value) 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.Map: 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) } @@ -126,3 +240,114 @@ func encodeAnyMap(encoder *Encoder, value any, tag Tag) (n int, err error) { } 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 { }