tape: Implement option type in the dynamic system
This commit is contained in:
parent
7bebc8c5eb
commit
77a4d7893f
@ -6,6 +6,10 @@ package tape
|
||||
// TODO: add support for struct tags: `tape:"0000"`, tape:"0001"` so they can get
|
||||
// transformed into tables with a defined schema
|
||||
|
||||
// TODO: support special behavior for options in structs: don't just write a
|
||||
// zero value if the option is void, write no field at all. also consider doing
|
||||
// this for maps, and maybe slices.
|
||||
|
||||
// TODO: test all of these smaller functions individually
|
||||
|
||||
// For an explanation as to why this package always treats LBA/SBA as strings,
|
||||
@ -79,6 +83,12 @@ func EncodeAny(encoder *Encoder, value any, tag Tag) (n int, err error) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// option
|
||||
if isTypeOption(reflectValue.Type()) {
|
||||
elemValue, _ := optionValue(reflectValue) // zero value for free
|
||||
return EncodeAny(encoder, elemValue, tag)
|
||||
}
|
||||
|
||||
// aggregates
|
||||
reflectType := reflect.TypeOf(value)
|
||||
switch reflectType.Kind() {
|
||||
@ -310,6 +320,12 @@ func tagAny(reflectValue reflect.Value) (Tag, error) {
|
||||
if reflectValue.CanConvert(reflect.TypeOf(dummyBuffer)) {
|
||||
return bufferLenTag(reflectValue.Len()), nil
|
||||
}
|
||||
|
||||
// option
|
||||
if isTypeOption(reflectValue.Type()) {
|
||||
elem, _ := optionValue(reflectValue) // zero value for free
|
||||
return tagAny(elem)
|
||||
}
|
||||
|
||||
// aggregates
|
||||
reflectType := reflectValue.Type()
|
||||
@ -336,9 +352,14 @@ func encodeAnySlice(encoder *Encoder, value any, tag Tag) (n int, err error) {
|
||||
for index := 0; index < reflectValue.Len(); index += 1 {
|
||||
itemTag, err := tagAny(reflectValue.Index(index))
|
||||
if err != nil { return n, err }
|
||||
if itemTag.Is(SBA) {
|
||||
// SBA data in an LBA will always have the tag LBA:0,
|
||||
// because 32 <= 256
|
||||
continue
|
||||
}
|
||||
if itemTag.CN() > oneTag.CN() { oneTag = itemTag }
|
||||
}
|
||||
if oneTag.Is(SBA) { oneTag += 1 << 5 }
|
||||
if oneTag.Is(SBA) { oneTag = LBA.WithCN(oneTag.CN()) }
|
||||
nn, err = encoder.WriteUint8(uint8(oneTag))
|
||||
n += nn; if err != nil { return n, err }
|
||||
for index := 0; index < reflectValue.Len(); index += 1 {
|
||||
@ -576,6 +597,21 @@ func isTypeAny(typ reflect.Type) bool {
|
||||
return typ.Kind() == reflect.Interface && typ.NumMethod() == 0
|
||||
}
|
||||
|
||||
// isTypeOption returns whether the given reflect.Type is a ucontainer.Option,
|
||||
// and returns the element type if true.
|
||||
func isTypeOption(typ reflect.Type) bool {
|
||||
// TODO: change when needed
|
||||
goutilPath := "git.tebibyte.media/sashakoshka/go-util"
|
||||
return typ.Name() == "Option" && typ.PkgPath() == goutilPath + "/container"
|
||||
}
|
||||
|
||||
// optionValue returns the value of an option. The value MUST be an option, or
|
||||
// this function will panic.
|
||||
func optionValue(value reflect.Value) (elem reflect.Value, ok bool) {
|
||||
result := value.MethodByName("Value").Call([]reflect.Value { })
|
||||
return result[0], result[1].Bool()
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
@ -3,39 +3,10 @@ package tape
|
||||
import "bytes"
|
||||
import "testing"
|
||||
import "reflect"
|
||||
import "git.tebibyte.media/sashakoshka/go-util/container"
|
||||
import tu "git.tebibyte.media/sashakoshka/hopp/internal/testutil"
|
||||
|
||||
var samplePayloads = [][]byte {
|
||||
/* int8 */ []byte { byte(LSI.WithCN(0)), 0x45 },
|
||||
/* int16 */ []byte { byte(LSI.WithCN(1)), 0x45, 0x67 },
|
||||
/* int32 */ []byte { byte(LSI.WithCN(3)), 0x45, 0x67, 0x89, 0xAB },
|
||||
/* int64 */ []byte { byte(LSI.WithCN(7)), 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23 },
|
||||
/* uint5 */ []byte { byte(SI.WithCN(12)) },
|
||||
/* uint8 */ []byte { byte(LI.WithCN(0)), 0x45 },
|
||||
/* uint16 */ []byte { byte(LI.WithCN(1)), 0x45, 0x67 },
|
||||
/* uint32 */ []byte { byte(LI.WithCN(3)), 0x45, 0x67, 0x89, 0xAB },
|
||||
/* uint64 */ []byte { byte(LI.WithCN(7)), 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23 },
|
||||
/* bool */ []byte { byte(SI.WithCN(0)) },
|
||||
/* bool */ []byte { byte(SI.WithCN(1)) },
|
||||
/* string */ []byte { byte(SBA.WithCN(7)), 'p', 'u', 'p', 'e', 'v', 'e', 'r' },
|
||||
/* []byte */ []byte { byte(SBA.WithCN(5)), 'b', 'l', 'a', 'r', 'g' },
|
||||
/* []string */ []byte {
|
||||
byte(OTA.WithCN(0)), 2, byte(LBA.WithCN(0)),
|
||||
0x08, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23,
|
||||
0x05, 0x11, 0x11, 0x11, 0x11, 0x11,
|
||||
},
|
||||
/* map[uint16] any */ []byte {
|
||||
byte(KTV.WithCN(0)), 2,
|
||||
0x02, 0x23, byte(LSI.WithCN(1)), 0x45, 0x67,
|
||||
0x02, 0x24, byte(LI.WithCN(3)), 0x45, 0x67, 0x89, 0xAB,
|
||||
},
|
||||
/* map[uint16] any */ []byte {
|
||||
byte(KTV.WithCN(0)), 3,
|
||||
0x00, 0x01, 0x63, 0x43, 0xF4, 0xC0, 0x00,
|
||||
0x00, 0x02, 0x82, 'h', 'i',
|
||||
0x00, 0x03, 0x21, 0x39, 0x92,
|
||||
},
|
||||
}
|
||||
var samplePayloads [][]byte
|
||||
|
||||
var sampleValues = []any {
|
||||
/* int8 */ int8(0x45),
|
||||
@ -64,6 +35,64 @@ var sampleValues = []any {
|
||||
0x0002: "hi",
|
||||
0x0003: uint16(0x3992),
|
||||
},
|
||||
// IMPORTANT: ONLY ADD AT THE END!!!! DO NOT MOVE WHAT IS ALREADY HERE!
|
||||
// IMPORTANT: ONLY ADD AT THE END!!!! DO NOT MOVE WHAT IS ALREADY HERE!
|
||||
}
|
||||
|
||||
type sample struct {
|
||||
t Tag
|
||||
v any
|
||||
s tu.Snake
|
||||
}
|
||||
|
||||
var samples = []sample {
|
||||
/* int8 */ sample { t: LSI.WithCN(0), s: tu.S(0x45) },
|
||||
/* int16 */ sample { t: LSI.WithCN(1), s: tu.S(0x45, 0x67) },
|
||||
/* int32 */ sample { t: LSI.WithCN(3), s: tu.S(0x45, 0x67, 0x89, 0xAB) },
|
||||
/* int64 */ sample { t: LSI.WithCN(7), s: tu.S(0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23) },
|
||||
/* uint5 */ sample { t: SI.WithCN(12), s: tu.S() },
|
||||
/* uint8 */ sample { t: LI.WithCN(0), s: tu.S(0x45) },
|
||||
/* uint16 */ sample { t: LI.WithCN(1), s: tu.S(0x45, 0x67) },
|
||||
/* uint32 */ sample { t: LI.WithCN(3), s: tu.S(0x45, 0x67, 0x89, 0xAB) },
|
||||
/* uint64 */ sample { t: LI.WithCN(7), s: tu.S(0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23) },
|
||||
/* bool */ sample { t: SI.WithCN(0), s: tu.S() },
|
||||
/* bool */ sample { t: SI.WithCN(1), s: tu.S() },
|
||||
/* string */ sample { t: SBA.WithCN(7), s: tu.S('p', 'u', 'p', 'e', 'v', 'e', 'r') },
|
||||
/* []byte */ sample { t: SBA.WithCN(5), s: tu.S('b', 'l', 'a', 'r', 'g') },
|
||||
/* []string */ sample {
|
||||
t: OTA.WithCN(0),
|
||||
s: tu.S(2, byte(LBA.WithCN(0)),
|
||||
0x08, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23,
|
||||
0x05, 0x11, 0x11, 0x11, 0x11, 0x11),
|
||||
},
|
||||
/* map[uint16] any */ sample {
|
||||
t: KTV.WithCN(0),
|
||||
s: tu.S(2).AddVar(
|
||||
[]byte { 0x02, 0x23, byte(LSI.WithCN(1)), 0x45, 0x67 },
|
||||
[]byte { 0x02, 0x24, byte(LI.WithCN(3)), 0x45, 0x67, 0x89, 0xAB }),
|
||||
},
|
||||
/* map[uint16] any */ sample {
|
||||
t: KTV.WithCN(0),
|
||||
s: tu.S(3).AddVar(
|
||||
[]byte { 0x00, 0x01, 0x63, 0x43, 0xF4, 0xC0, 0x00 },
|
||||
[]byte { 0x00, 0x02, 0x82, 'h', 'i' },
|
||||
[]byte { 0x00, 0x03, 0x21, 0x39, 0x92 }),
|
||||
},
|
||||
}
|
||||
|
||||
var sampleOptionValues []any
|
||||
|
||||
func init() {
|
||||
sampleOptionValues = make([]any, len(sampleValues))
|
||||
for index, value := range sampleValues {
|
||||
sampleOptionValues[index] = ucontainer.O(value)
|
||||
samples[index].v = value
|
||||
}
|
||||
samplePayloads = make([][]byte, len(samples))
|
||||
for index, sample := range samples {
|
||||
item := append([]byte { byte(sample.t) }, sample.s.Flatten()...)
|
||||
samplePayloads[index] = item
|
||||
}
|
||||
}
|
||||
|
||||
type userDefinedInteger int16
|
||||
@ -271,6 +300,20 @@ func TestEncodeDecodeAnyDestination(test *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeOption(test *testing.T) {
|
||||
for _, sample := range samples {
|
||||
snake := sample.s
|
||||
tag := sample.t
|
||||
value := sample.v
|
||||
if _, ok := value.(bool); tag.Is(SI) && !ok {
|
||||
// we will never encode an SI unless its a bool
|
||||
continue
|
||||
}
|
||||
err := testEncodeAny(test, value, tag, snake)
|
||||
if err != nil { test.Fatal(err) }
|
||||
}
|
||||
}
|
||||
|
||||
func TestPeekSlice(test *testing.T) {
|
||||
buffer := bytes.NewBuffer([]byte {
|
||||
2, byte(OTA.WithCN(3)),
|
||||
|
||||
@ -32,7 +32,9 @@ func testEncodeAny(test *testing.T, value any, correctTag Tag, correctBytes tu.S
|
||||
test.Log("n: ", n)
|
||||
test.Log("tag: ", tag)
|
||||
test.Log("got: ", tu.HexBytes(bytes))
|
||||
test.Log(" : ", tu.HexChars(bytes))
|
||||
test.Log("correct:", correctBytes)
|
||||
test.Log(" :", correctBytes.CharsString())
|
||||
if tag != correctTag {
|
||||
return fmt.Errorf("tag not equal: %v != %v", tag, correctTag)
|
||||
}
|
||||
@ -56,6 +58,7 @@ func testEncodeDecodeAny(test *testing.T, value, correctValue any) error {
|
||||
test.Log("n: ", n)
|
||||
test.Log("tag:", tag)
|
||||
test.Log("got:", tu.HexBytes(bytes))
|
||||
test.Log(" :", tu.HexChars(bytes))
|
||||
test.Log("decoding...", tag)
|
||||
if n != len(bytes) {
|
||||
return fmt.Errorf("n not equal: %d != %d", n, len(bytes))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user