From 7df18f7d260b076f415a4e1da490e625d0313cfb Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Fri, 26 Sep 2025 00:20:53 -0400 Subject: [PATCH] tape: Assorted changes i forgor --- tape/dynamic.go | 63 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/tape/dynamic.go b/tape/dynamic.go index 9e9594a..15f3483 100644 --- a/tape/dynamic.go +++ b/tape/dynamic.go @@ -8,6 +8,17 @@ package tape // TODO: test all of these smaller functions individually +// For an explanation as to why this package always treats LBA/SBA as strings, +// refer to https://go.dev/blog/strings: +// +// It’s important to state right up front that a string holds arbitrary +// bytes. It is not required to hold Unicode text, UTF-8 text, or any other +// predefined format. As far as the content of a string is concerned, it is +// exactly equivalent to a slice of bytes. +// +// Arbitrary byte slices and blobs won't be as common of a use case as text +// data, and if you need that anyway you can just cast it to a byte slice. + import "fmt" import "reflect" @@ -142,7 +153,7 @@ func decodeAnyOrError(decoder *Decoder, destination reflect.Value, tag Tag) (n i switch tag.WithoutCN() { case SI: // SI: (none) - setInt(destination, uint64(tag.CN())) + setUint(destination, uint64(tag.CN()), 1) case LI: // LI: nn, err := decodeAndSetUint(decoder, destination, tag.CN() + 1) @@ -164,7 +175,7 @@ func decodeAnyOrError(decoder *Decoder, destination reflect.Value, tag Tag) (n i buffer := make([]byte, length) nn, err := decoder.Read(buffer) n += nn; if err != nil { return n, err } - setByteArray(destination, buffer) + setString(destination, string(buffer)) case LBA: // LBA: * length, nn, err := decoder.ReadUintN(tag.CN() + 1) @@ -175,7 +186,7 @@ func decodeAnyOrError(decoder *Decoder, destination reflect.Value, tag Tag) (n i buffer := make([]byte, length) nn, err = decoder.Read(buffer) n += nn; if err != nil { return n, err } - setByteArray(destination, buffer) + setString(destination, string(buffer)) case OTA: // OTA: * length, nn, err := decoder.ReadUintN(tag.CN() + 1) @@ -325,6 +336,10 @@ func encodeAnyMap(encoder *Encoder, value any, tag Tag) (n int, err error) { } func canSet(destination reflect.Type, tag Tag) error { + // anything can be assigned to `any` + if isTypeAny(destination) { + return nil + } switch tag.WithoutCN() { case SI, LI, LSI: switch destination.Kind() { @@ -362,17 +377,43 @@ func canSet(destination reflect.Type, tag Tag) error { } // setInt expects a settable destination. -func setInt[T int64 | uint64](destination reflect.Value, value T) { +func setInt(destination reflect.Value, value int64, bytes int) { switch { case destination.CanInt(): destination.Set(reflect.ValueOf(int64(value)).Convert(destination.Type())) case destination.CanUint(): destination.Set(reflect.ValueOf(value).Convert(destination.Type())) + case isTypeAny(destination.Type()): + switch { + case bytes > 4: destination.Set(reflect.ValueOf(int64(value))) + case bytes > 2: destination.Set(reflect.ValueOf(int32(value))) + case bytes > 1: destination.Set(reflect.ValueOf(int16(value))) + default: destination.Set(reflect.ValueOf(int8(value))) + } default: panic("setInt called on an unsupported type") } } +// setUint expects a settable destination. +func setUint(destination reflect.Value, value uint64, bytes int) { + switch { + case destination.CanInt(): + destination.Set(reflect.ValueOf(int64(value)).Convert(destination.Type())) + case destination.CanUint(): + destination.Set(reflect.ValueOf(value).Convert(destination.Type())) + case isTypeAny(destination.Type()): + switch { + case bytes > 4: destination.Set(reflect.ValueOf(uint64(value))) + case bytes > 2: destination.Set(reflect.ValueOf(uint32(value))) + case bytes > 1: destination.Set(reflect.ValueOf(uint16(value))) + default: destination.Set(reflect.ValueOf(uint8(value))) + } + default: + panic("setUint called on an unsupported type") + } +} + // setFloat expects a settable destination. func setFloat(destination reflect.Value, value float64) { destination.Set(reflect.ValueOf(value).Convert(destination.Type())) @@ -387,7 +428,7 @@ func setByteArray(destination reflect.Value, value []byte) { func decodeAndSetInt(decoder *Decoder, destination reflect.Value, bytes int) (n int, err error) { value, nn, err := decoder.ReadIntN(bytes) n += nn; if err != nil { return n, err } - setInt(destination, value) + setInt(destination, value, bytes) return n, nil } @@ -395,7 +436,7 @@ func decodeAndSetInt(decoder *Decoder, destination reflect.Value, bytes int) (n func decodeAndSetUint(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 } - setInt(destination, value) + setUint(destination, value, bytes) return n, nil } @@ -452,8 +493,8 @@ func typeOf(decoder *Decoder, tag Tag) (reflect.Type, error) { case 7: return reflect.TypeOf(float64(0)), nil } return nil, fmt.Errorf("unknown CN %d for FP", tag.CN()) - case SBA: return reflect.SliceOf(reflect.TypeOf(byte(0))), nil - case LBA: return reflect.SliceOf(reflect.TypeOf(byte(0))), nil + case SBA: return reflect.TypeOf(""), nil + case LBA: return reflect.TypeOf(""), nil case OTA: elemTag, dimension, err := peekSlice(decoder, tag) if err != nil { return nil, err } @@ -469,6 +510,12 @@ func typeOf(decoder *Decoder, tag Tag) (reflect.Type, error) { return nil, fmt.Errorf("unknown TN %d", tag.TN()) } +// isTypeAny returns whether the given reflect.Type is an interface with no +// methods. +func isTypeAny(typ reflect.Type) bool { + return typ.Kind() == reflect.Interface && typ.NumMethod() == 0 +} + // peekSlice returns the element tag and dimension count of the OTA currently // being decoded. It does not use up the decoder, it only peeks. func peekSlice(decoder *Decoder, tag Tag) (Tag, int, error) {