17 Commits

Author SHA1 Message Date
a4da33536c tape: Utilize skimming in the dynamic decoder 2025-08-18 22:26:53 -04:00
2180d29615 tape: Implement Skim function 2025-08-12 12:29:50 -04:00
2209763666 tape: Write test for Skim function 2025-08-12 12:29:28 -04:00
96c8d7924f tape: Test that integers of a user-defined type can be encoded 2025-08-12 08:21:19 -04:00
fdf0aa89a4 tape: Use reflection when encoding integers 2025-08-12 08:16:34 -04:00
1bded9852d tape: Dynamic tests put out more information 2025-08-12 08:16:21 -04:00
8beb9de256 Merge pull request 'encode-signedness' (#13) from encode-signedness into message-size-increase
Reviewed-on: #13
2025-08-11 19:10:56 -06:00
dc72cc2010 generate: Support LSI tags 2025-08-11 20:59:20 -04:00
0e03f84b8a generate: Update tests with new TNs 2025-08-11 20:59:10 -04:00
02196edf61 design: Change tag for signed PDL integers 2025-08-11 20:10:03 -04:00
1058615f6f tape: Do something when receiving an LSI tag 2025-08-11 18:40:56 -04:00
024edfa922 tape: Actually test decoding lol 2025-08-11 18:39:38 -04:00
fe973af99c tape: Test dynamic encoding and decoding of signed integers 2025-08-10 23:28:43 -04:00
52f0d6932e tape: Add encoding and decoding of signed integers 2025-08-10 23:28:25 -04:00
8e14a2c3f1 tape: Add LSI to Tag constants 2025-08-07 21:14:40 -04:00
4fbb70081a generate: Finish test sub-case for MessageNestedArray 2025-08-06 22:15:22 -04:00
a108e53cb6 Merge pull request 'branched-generated-encoder' (#9) from branched-generated-encoder into message-size-increase
Reviewed-on: #9
2025-08-06 19:11:08 -06:00
8 changed files with 563 additions and 102 deletions

View File

@@ -7,12 +7,12 @@ PDL allows defining a protocol using HOPP and TAPE.
| Syntax | TN | CN | Description
| ---------- | ------- | -: | -----------
| I5 | SI | |
| I8 | LI | 0 |
| I16 | LI | 1 |
| I32 | LI | 3 |
| I64 | LI | 7 |
| I128[^2] | LI | 15 |
| I256[^2] | LI | 31 |
| I8 | LSI | 0 |
| I16 | LSI | 1 |
| I32 | LSI | 3 |
| I64 | LSI | 7 |
| I128[^2] | LSI | 15 |
| I256[^2] | LSI | 31 |
| U5 | SI | |
| U8 | LI | 0 |
| U16 | LI | 1 |

View File

@@ -307,8 +307,8 @@ func (this *Generator) generateMessage(method uint16, message Message) (n int, e
func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource string) (n int, err error) {
switch typ := typ.(type) {
case TypeInt:
// SI: (none)
// LI: <value: IntN>
// SI: (none)
// LI/LSI: <value: IntN>
if typ.Bits <= 5 {
// SI stores the value in the tag, so we write nothing here
break
@@ -478,8 +478,8 @@ func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource stri
func (this *Generator) generateDecodeValue(typ Type, typeName, valueSource, tagSource string) (n int, err error) {
switch typ := typ.(type) {
case TypeInt:
// SI: (none)
// LI: <value: IntN>
// SI: (none)
// LI/LSI: <value: IntN>
if typ.Bits <= 5 {
// SI stores the value in the tag
nn, err := this.iprintf("*%s = uint8(%s.CN())\n", valueSource, tagSource)
@@ -837,6 +837,9 @@ func (this *Generator) generateTag(typ Type, source string) (n int, err error) {
if typ.Bits <= 5 {
nn, err := this.printf("tape.SI.WithCN(int(%s))", source)
n += nn; if err != nil { return n, err }
} else if typ.Signed {
nn, err := this.printf("tape.LSI.WithCN(%d)", bitsToCN(typ.Bits))
n += nn; if err != nil { return n, err }
} else {
nn, err := this.printf("tape.LI.WithCN(%d)", bitsToCN(typ.Bits))
n += nn; if err != nil { return n, err }
@@ -881,6 +884,9 @@ func (this *Generator) generateTN(typ Type) (n int, err error) {
if typ.Bits <= 5 {
nn, err := this.printf("tape.SI")
n += nn; if err != nil { return n, err }
} else if typ.Signed {
nn, err := this.printf("tape.LSI")
n += nn; if err != nil { return n, err }
} else {
nn, err := this.printf("tape.LI")
n += nn; if err != nil { return n, err }

View File

@@ -230,6 +230,26 @@ func TestGenerateRun(test *testing.T) {
Name: "NestedArray",
Type: TypeArray { Element: TypeArray { Element: TypeInt { Bits: 8 } } },
}
protocol.Messages[0x0004] = Message {
Name: "Integers",
Type: TypeTableDefined {
Fields: map[uint16] Field {
0x0000: Field { Name: "U5", Type: TypeInt { Bits: 5 } },
0x0001: Field { Name: "U8", Type: TypeInt { Bits: 8 } },
0x0002: Field { Name: "U16", Type: TypeInt { Bits: 16 } },
0x0003: Field { Name: "U32", Type: TypeInt { Bits: 32 } },
0x0004: Field { Name: "U64", Type: TypeInt { Bits: 64 } },
0x0006: Field { Name: "I8", Type: TypeInt { Bits: 8, Signed: true } },
0x0007: Field { Name: "I16", Type: TypeInt { Bits: 16, Signed: true } },
0x0008: Field { Name: "I32", Type: TypeInt { Bits: 32, Signed: true } },
0x0009: Field { Name: "I64", Type: TypeInt { Bits: 64, Signed: true } },
0x000B: Field { Name: "NI8", Type: TypeInt { Bits: 8, Signed: true } },
0x000C: Field { Name: "NI16",Type: TypeInt { Bits: 16, Signed: true } },
0x000D: Field { Name: "NI32",Type: TypeInt { Bits: 32, Signed: true } },
0x000E: Field { Name: "NI64",Type: TypeInt { Bits: 64, Signed: true } },
},
},
}
protocol.Types["User"] = TypeTableDefined {
Fields: map[uint16] Field {
0x0000: Field { Name: "Name", Type: TypeString { } },
@@ -248,9 +268,9 @@ func TestGenerateRun(test *testing.T) {
}
testEncode(
&messageConnect,
tu.S(0xC1, 0x02).AddVar(
[]byte { 0x00, 0x00, 0x66, 'r', 'a', 'r', 'i', 't', 'y' },
[]byte { 0x00, 0x01, 0x64, 'g', 'e', 'm', 's' },
tu.S(0xE1, 0x02).AddVar(
[]byte { 0x00, 0x00, 0x86, 'r', 'a', 'r', 'i', 't', 'y' },
[]byte { 0x00, 0x01, 0x84, 'g', 'e', 'm', 's' },
))
log.Println("MessageUserList")
messageUserList := MessageUserList {
@@ -274,19 +294,19 @@ func TestGenerateRun(test *testing.T) {
}
testEncode(
&messageUserList,
tu.S(0xC1, 0x01, 0x00, 0x00,
0xA1, 0x03, 0xC1,
tu.S(0xE1, 0x01, 0x00, 0x00,
0xC1, 0x03, 0xE1,
).Add(0x03).AddVar(
[]byte { 0x00, 0x00, 0x66, 'r', 'a', 'r', 'i', 't', 'y' },
[]byte { 0x00, 0x01, 0x67, 'a', 's', 'd', 'j', 'a', 'd', 's' },
[]byte { 0x00, 0x00, 0x86, 'r', 'a', 'r', 'i', 't', 'y' },
[]byte { 0x00, 0x01, 0x87, 'a', 's', 'd', 'j', 'a', 'd', 's' },
[]byte { 0x00, 0x02, 0x23, 0x00, 0x00, 0x03, 0x24 },
).Add(0x03).AddVar(
[]byte { 0x00, 0x00, 0x69, 'd', 'e', 'e', 'z', ' ', 'n', 'u', 't', 's' },
[]byte { 0x00, 0x01, 0x64, 'l', 'o', 'g', 'y' },
[]byte { 0x00, 0x00, 0x89, 'd', 'e', 'e', 'z', ' ', 'n', 'u', 't', 's' },
[]byte { 0x00, 0x01, 0x84, 'l', 'o', 'g', 'y' },
[]byte { 0x00, 0x02, 0x23, 0x00, 0x00, 0x80, 0x00 },
).Add(0x03).AddVar(
[]byte { 0x00, 0x00, 0x69, 'c', 'r', 'e', 'e', 'k', 'f', 'l', 'o', 'w' },
[]byte { 0x00, 0x01, 0x6C, 'i', 'm', ' ', 'c', 'r', 'e', 'e', 'k', 'f',
[]byte { 0x00, 0x00, 0x89, 'c', 'r', 'e', 'e', 'k', 'f', 'l', 'o', 'w' },
[]byte { 0x00, 0x01, 0x8C, 'i', 'm', ' ', 'c', 'r', 'e', 'e', 'k', 'f',
'l', 'o', 'w' },
[]byte { 0x00, 0x02, 0x23, 0x00, 0x00, 0x38, 0x94 },
))
@@ -300,12 +320,12 @@ func TestGenerateRun(test *testing.T) {
}
testEncode(
&messagePulse,
tu.S(0xC1, 0x05).AddVar(
tu.S(0xE1, 0x05).AddVar(
[]byte { 0x00, 0x00, 0x09 },
[]byte { 0x00, 0x01, 0x21, 0xCA, 0xDF },
[]byte { 0x00, 0x02, 0x41, 0x51, 0xAC },
[]byte { 0x00, 0x03, 0x43, 0x43, 0x93, 0x0C, 0xCD },
[]byte { 0x00, 0x04, 0x47, 0x41, 0xB6, 0xEE, 0x81, 0x28, 0x3C, 0x21, 0xE2 },
[]byte { 0x00, 0x01, 0x41, 0xCA, 0xDF },
[]byte { 0x00, 0x02, 0x61, 0x51, 0xAC },
[]byte { 0x00, 0x03, 0x63, 0x43, 0x93, 0x0C, 0xCD },
[]byte { 0x00, 0x04, 0x67, 0x41, 0xB6, 0xEE, 0x81, 0x28, 0x3C, 0x21, 0xE2 },
))
log.Println("MessageNestedArray")
uint8s := func(n int) []uint8 {
@@ -321,7 +341,46 @@ func TestGenerateRun(test *testing.T) {
}
testEncode(
&messageNestedArray,
tu.S(0xA1, // TODO
tu.S(0xC1, 0x02, 0xC1,
0x06, 0x20, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
35, 0x20, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC,
0xFD, 0xFE, 0xFF, 0xF0, 0xF1, 0xF2,
0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8,
0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE,
0xFF, 0xF0, 0xF1, 0xF2, 0xF3))
log.Println("MessageIntegers")
messageIntegers := MessageIntegers {
U5: 0x13,
U8: 0xC9,
U16: 0x34C9,
U32: 0x10E134C9,
U64: 0x639109BC10E134C9,
I8: 0x35,
I16: 0x34C9,
I32: 0x10E134C9,
I64: 0x639109BC10E134C9,
NI8: -0x35,
NI16: -0x34C9,
NI32: -0x10E134C9,
NI64: -0x639109BC10E134C9,
}
testEncode(
&messageIntegers,
tu.S(0xE1, 13).AddVar(
[]byte { 0x00, 0x00, 0x13 },
[]byte { 0x00, 0x01, 0x20, 0xC9 },
[]byte { 0x00, 0x02, 0x21, 0x34, 0xC9 },
[]byte { 0x00, 0x03, 0x23, 0x10, 0xE1, 0x34, 0xC9 },
[]byte { 0x00, 0x04, 0x27, 0x63, 0x91, 0x09, 0xBC, 0x10, 0xE1, 0x34, 0xC9 },
[]byte { 0x00, 0x06, 0x40, 0x35 },
[]byte { 0x00, 0x07, 0x41, 0x34, 0xC9 },
[]byte { 0x00, 0x08, 0x43, 0x10, 0xE1, 0x34, 0xC9 },
[]byte { 0x00, 0x09, 0x47, 0x63, 0x91, 0x09, 0xBC, 0x10, 0xE1, 0x34, 0xC9 },
[]byte { 0x00, 0x0B, 0x40, 0xCB },
[]byte { 0x00, 0x0C, 0x41, 0xCB, 0x37 },
[]byte { 0x00, 0x0D, 0x43, 0xEF, 0x1E, 0xCB, 0x37 },
[]byte { 0x00, 0x0E, 0x47, 0x9C, 0x6E, 0xF6, 0x43, 0xEF, 0x1E, 0xCB, 0x37 },
))
`)
}

View File

@@ -14,6 +14,14 @@ import "reflect"
var dummyMap map[uint16] any
var dummyBuffer []byte
type errCantAssign string
func (err errCantAssign) Error() string {
return string(err)
}
func errCantAssignf(format string, v ...any) errCantAssign {
return errCantAssign(fmt.Sprintf(format, v...))
}
// EncodeAny encodes an "any" value. Returns an error if the underlying type is
// unsupported. Supported types are:
//
@@ -83,22 +91,40 @@ type unknownSlicePlaceholder struct { }
var unknownSlicePlaceholderType = reflect.TypeOf(unknownSlicePlaceholder { })
// decodeAny is internal to [DecodeAny]. It takes in an addressable
// [reflect.Value] as the destination.
// [reflect.Value] as the destination. If the decoded value cannot fit in the
// destination, it skims over the payload, leaves the destination empty, and
// returns without an error.
func decodeAny(decoder *Decoder, destination reflect.Value, tag Tag) (n int, err error) {
errWrongDestinationType := func(expected string) error {
panic(fmt.Errorf(
// return fmt.Errorf(
"expected %s destination, not %v",
expected, destination))
n, err = decodeAnyOrError(decoder, destination, tag)
if _, ok := err.(errCantAssign); ok {
if n > 0 { panic(fmt.Sprintf("decodeAnyOrError decoded more than it should: %d", n)) }
nn, err := Skim(decoder, tag)
n += nn; if err != nil { return n, err }
return n, nil
}
return n, err
}
// decodeAnyOrError is internal to [decodeAny]. It takes in an addressable
// [reflect.Value] as the destination. If the decoded value cannot fit in the
// destination, it decodes nothing and returns an error of type errCantAssign,
// except for the case of a mismatched OTA element tag, wherein it will skim
// over the rest of the payload, leave the destination empty, and return without
// an error.
func decodeAnyOrError(decoder *Decoder, destination reflect.Value, tag Tag) (n int, err error) {
err = canSet(destination.Type(), tag)
if err != nil { return n, err }
switch tag.WithoutCN() {
case SI:
// SI: (none)
err = setInt(destination, uint64(tag.CN()))
if err != nil { return n, err }
setInt(destination, uint64(tag.CN()))
case LI:
// LI: <value: IntN>
nn, err := decodeAndSetUint(decoder, destination, tag.CN() + 1)
n += nn; if err != nil { return n, err }
case LSI:
// LSI: <value: IntN>
nn, err := decodeAndSetInt(decoder, destination, tag.CN() + 1)
n += nn; if err != nil { return n, err }
case FP:
@@ -110,8 +136,7 @@ func decodeAny(decoder *Decoder, destination reflect.Value, tag Tag) (n int, err
buffer := make([]byte, tag.CN())
nn, err := decoder.Read(buffer)
n += nn; if err != nil { return n, err }
err = setByteArray(destination, buffer)
if err != nil { return n, err }
setByteArray(destination, buffer)
case LBA:
// LBA: <length: UN> <data: U8>*
length, nn, err := decoder.ReadUintN(tag.CN() + 1)
@@ -119,34 +144,42 @@ func decodeAny(decoder *Decoder, destination reflect.Value, tag Tag) (n int, err
buffer := make([]byte, length)
nn, err = decoder.Read(buffer)
n += nn; if err != nil { return n, err }
err = setByteArray(destination, buffer)
if err != nil { return n, err }
setByteArray(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 }
if destination.Kind() != reflect.Slice {
return n, errWrongDestinationType("slice")
}
if destination.Cap() < int(length) {
destination.Grow(int(length) - destination.Cap())
}
// skip the rest of the array if the one tag doesn't
// match up with the destination
err = canSet(destination.Type().Elem(), oneTag)
if _, ok := err.(errCantAssign); ok {
for _ = range length {
nn, err := Skim(decoder, oneTag)
n += nn; if err != nil { return n, err }
}
break
}
if err != nil { return n, err }
destination.SetLen(int(length))
for index := range length {
nn, err := decodeAny(decoder, destination.Index(int(index)), oneTag)
n += nn; if err != nil { return n, err }
n += nn
if _, ok := err.(errCantAssign); ok {
continue
} else if err != nil {
return n, err
}
}
case KTV:
// KTV: <length: UN> (<key: U16> <tag: Tag> <value>)*
table := destination
if table.Type() != reflect.TypeOf(dummyMap) {
return n, errWrongDestinationType("map[uint16] any")
}
length, nn, err := decoder.ReadUintN(tag.CN() + 1)
n += nn; if err != nil { return n, err }
table.Clear()
destination.Clear()
for _ = range length {
key, nn, err := decoder.ReadUint16()
n += nn; if err != nil { return n, err }
@@ -156,7 +189,7 @@ func decodeAny(decoder *Decoder, destination reflect.Value, tag Tag) (n int, err
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.Elem())
destination.SetMapIndex(reflect.ValueOf(key), value.Elem())
}
default:
return n, fmt.Errorf("unknown TN %d", tag.TN())
@@ -168,30 +201,40 @@ func decodeAny(decoder *Decoder, destination reflect.Value, tag Tag) (n int, err
// 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
return tagAny(reflect.ValueOf(value))
}
func tagAny(reflectValue reflect.Value) (Tag, error) {
// primitives
switch value := value.(type) {
case int, uint: return LI.WithCN(3), nil
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(7), nil
case string: return bufferLenTag(len(value)), nil
case []byte: return bufferLenTag(len(value)), nil
switch reflectValue.Kind() {
case reflect.Int: return LSI.WithCN(3), nil
case reflect.Int8: return LSI.WithCN(0), nil
case reflect.Int16: return LSI.WithCN(1), nil
case reflect.Int32: return LSI.WithCN(3), nil
case reflect.Int64: return LSI.WithCN(7), nil
case reflect.Uint: return LI.WithCN(3), nil
case reflect.Uint8: return LI.WithCN(0), nil
case reflect.Uint16: return LI.WithCN(1), nil
case reflect.Uint32: return LI.WithCN(3), nil
case reflect.Uint64: return LI.WithCN(7), nil
case reflect.String: return bufferLenTag(reflectValue.Len()), nil
}
if reflectValue.CanConvert(reflect.TypeOf(dummyBuffer)) {
return bufferLenTag(reflectValue.Len()), nil
}
// aggregates
reflectType := reflect.TypeOf(value)
reflectType := reflectValue.Type()
switch reflectType.Kind() {
case reflect.Slice: return OTA.WithCN(IntBytes(uint64(reflect.ValueOf(value).Len())) - 1), nil
case reflect.Slice: return OTA.WithCN(IntBytes(uint64(reflectValue.Len())) - 1), nil
case reflect.Array: return OTA.WithCN(reflectType.Len()), nil
case reflect.Map:
if reflectType.Key() == reflect.TypeOf(uint16(0)) {
return KTV.WithCN(IntBytes(uint64(reflect.ValueOf(value).Len())) - 1), nil
return KTV.WithCN(IntBytes(uint64(reflectValue.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 %v, key must be uint16", reflectType.Key())
}
return 0, fmt.Errorf("cannot get tag of type %T", value)
return 0, fmt.Errorf("cannot get tag of type %v", reflectType)
}
func encodeAnySlice(encoder *Encoder, value any, tag Tag) (n int, err error) {
@@ -200,11 +243,10 @@ func encodeAnySlice(encoder *Encoder, value any, tag Tag) (n int, err error) {
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())
oneTag, err := tagAny(reflect.Zero(reflectType.Elem()))
if err != nil { return n, err }
for index := 0; index < reflectValue.Len(); index += 1 {
item := reflectValue.Index(index).Interface()
itemTag, err := TagAny(item)
itemTag, err := tagAny(reflectValue.Index(index))
if err != nil { return n, err }
if itemTag.CN() > oneTag.CN() { oneTag = itemTag }
}
@@ -226,11 +268,12 @@ func encodeAnyMap(encoder *Encoder, value any, tag Tag) (n int, err error) {
n += nn; if err != nil { return n, err }
iter := reflectValue.MapRange()
for iter.Next() {
key := iter.Key().Interface().(uint16)
value := iter.Value().Interface()
reflectValue := iter.Value().Elem()
key := iter.Key().Interface().(uint16)
value := reflectValue.Interface()
nn, err = encoder.WriteUint16(key)
n += nn; if err != nil { return n, err }
itemTag, err := TagAny(value)
itemTag, err := tagAny(reflectValue)
if err != nil { return n, err }
nn, err = encoder.WriteUint8(uint8(itemTag))
n += nn; if err != nil { return n, err }
@@ -240,46 +283,79 @@ func encodeAnyMap(encoder *Encoder, value any, tag Tag) (n int, err error) {
return n, nil
}
func canSet(destination reflect.Type, tag Tag) error {
switch tag.WithoutCN() {
case SI, LI, LSI:
switch destination.Kind() {
case
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
default:
return errCantAssignf("cannot assign integer to %v", destination)
}
case FP:
switch destination.Kind() {
case reflect.Float32, reflect.Float64:
default:
return errCantAssignf("cannot assign float to %v", destination)
}
case SBA, LBA:
if destination.Kind() != reflect.Slice {
return errCantAssignf("cannot assign byte array to %v", destination)
}
if destination.Elem() != reflect.TypeOf(byte(0)) {
return errCantAssignf("cannot convert %v to *[]byte", destination)
}
case OTA:
if destination.Kind() != reflect.Slice {
return errCantAssignf("cannot assign array to %v", destination)
}
case KTV:
if destination != reflect.TypeOf(dummyMap) {
return errCantAssignf("cannot assign table to %v", destination)
}
default:
return fmt.Errorf("unknown TN %d", tag.TN())
}
return nil
}
// setInt expects a settable destination.
func setInt(destination reflect.Value, value uint64) error {
func setInt[T int64 | uint64](destination reflect.Value, value T) {
switch {
case destination.CanInt():
destination.Set(reflect.ValueOf(int64(value)).Convert(destination.Type()))
case destination.CanUint():
destination.Set(reflect.ValueOf(value).Convert(destination.Type()))
default:
return fmt.Errorf("cannot assign integer to %T", destination.Interface())
panic("setInt called on an unsupported type")
}
return nil
}
// setFloat expects a settable destination.
func setFloat(destination reflect.Value, value float64) error {
if !destination.CanFloat() {
return fmt.Errorf("cannot assign float to %T", destination.Interface())
}
func setFloat(destination reflect.Value, value float64) {
destination.Set(reflect.ValueOf(value).Convert(destination.Type()))
return nil
}
// setByteArrayexpects a settable destination.
func setByteArray(destination reflect.Value, value []byte) error {
typ := destination.Type()
if typ.Kind() != reflect.Slice {
return fmt.Errorf("cannot assign %T to ", value)
}
if typ.Elem() != reflect.TypeOf(byte(0)) {
return fmt.Errorf("cannot convert %T to *[]byte", value)
}
func setByteArray(destination reflect.Value, value []byte) {
destination.Set(reflect.ValueOf(value))
return nil
}
// decodeAndSetInt expects a settable destination.
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)
return n, nil
}
// decodeAndSetUint expects a settable destination.
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 }
return n, setInt(destination, value)
setInt(destination, value)
return n, nil
}
// decodeAndSetInt expects a settable destination.
@@ -288,13 +364,15 @@ func decodeAndSetFloat(decoder *Decoder, destination reflect.Value, bytes int) (
case 8:
value, nn, err := decoder.ReadFloat64()
n += nn; if err != nil { return n, err }
return n, setFloat(destination, float64(value))
setFloat(destination, float64(value))
return n, nil
case 4:
value, nn, err := decoder.ReadFloat32()
n += nn; if err != nil { return n, err }
return n, setFloat(destination, float64(value))
setFloat(destination, float64(value))
return n, nil
}
return n, fmt.Errorf("cannot decode float%d", bytes * 8)
return n, errCantAssignf("unsupported bit width float%d", bytes * 8)
}
// skeletonValue returns a pointer value. In order for it to be set, it must be
@@ -319,6 +397,14 @@ func typeOf(decoder *Decoder, tag Tag) (reflect.Type, error) {
case 7: return reflect.TypeOf(uint64(0)), nil
}
return nil, fmt.Errorf("unknown CN %d for LI", tag.CN())
case LSI:
switch tag.CN() {
case 0: return reflect.TypeOf(int8(0)), nil
case 1: return reflect.TypeOf(int16(0)), nil
case 3: return reflect.TypeOf(int32(0)), nil
case 7: return reflect.TypeOf(int64(0)), nil
}
return nil, fmt.Errorf("unknown CN %d for LSI", tag.CN())
case FP:
switch tag.CN() {
case 3: return reflect.TypeOf(float32(0)), nil

View File

@@ -6,10 +6,12 @@ import "testing"
import "reflect"
import tu "git.tebibyte.media/sashakoshka/hopp/internal/testutil"
type userDefinedInteger int16
func TestEncodeAnyInt(test *testing.T) {
err := testEncodeAny(test, uint8(0xCA), LI.WithCN(0), tu.S(0xCA))
if err != nil { test.Fatal(err) }
err = testEncodeAny(test, 400, LI.WithCN(3), tu.S(
err = testEncodeAny(test, 400, LSI.WithCN(3), tu.S(
0, 0, 0x1, 0x90,
))
if err != nil { test.Fatal(err) }
@@ -22,15 +24,17 @@ func TestEncodeAnyTable(test *testing.T) {
0x0000: "hi!",
0xFFFF: []uint16 { 0xBEE5, 0x7777 },
0x1234: [][]uint16 { []uint16 { 0x5 }, []uint16 { 0x17, 0xAAAA} },
}, KTV.WithCN(0), tu.S(5).AddVar(
0x2345: [][]int16 { []int16 { 0x5 }, []int16 { 0x17, -0xAAA } },
0x3456: userDefinedInteger(0x3921),
}, KTV.WithCN(0), tu.S(7).AddVar(
[]byte {
0xF3, 0xB9,
byte(LI.WithCN(3)),
byte(LSI.WithCN(3)),
0, 0, 0, 1,
},
[]byte {
0x01, 0x02,
byte(LI.WithCN(3)),
byte(LSI.WithCN(3)),
0, 0, 0, 2,
},
[]byte {
@@ -52,14 +56,125 @@ func TestEncodeAnyTable(test *testing.T) {
0, 0x17,
0xAA, 0xAA,
},
[]byte {
0x23, 0x45,
byte(OTA.WithCN(0)), 2, byte(OTA.WithCN(0)),
1, byte(LSI.WithCN(1)),
0, 0x5,
2, byte(LSI.WithCN(1)),
0, 0x17,
0xF5, 0x56,
},
[]byte {
0x34, 0x56,
byte(LSI.WithCN(1)),
0x39, 0x21,
},
))
if err != nil { test.Fatal(err) }
}
func TestDecodeWrongType(test *testing.T) {
datas := [][]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 },
/* 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, 0x23, byte(LI.WithCN(3)), 0x45, 0x67, 0x89, 0xAB,
},
}
for index, data := range datas {
test.Logf("data %2d %v [%s]", index, Tag(data[0]), tu.HexBytes(data[1:]))
// integers should only assign to other integers
if index > 8 {
cas := func(destination any) {
n, err := DecodeAny(NewDecoder(bytes.NewBuffer(data[1:])), destination, Tag(data[0]))
if err != nil { test.Fatalf("error: %v | n: %d", err, n) }
reflectValue := reflect.ValueOf(destination).Elem()
if reflectValue.CanInt() {
if reflectValue.Int() != 0 {
test.Fatalf("destination not zero: %v", reflectValue.Elem().Interface())
}
} else {
if reflectValue.Uint() != 0 {
test.Fatalf("destination not zero: %v", reflectValue.Elem().Interface())
}
}
if n != len(data) - 1 {
test.Fatalf("n not equal: %d != %d", n, len(data) - 1)
}
}
test.Log("- int8")
{ var dest int8; cas(&dest) }
test.Log("- int16")
{ var dest int16; cas(&dest) }
test.Log("- int32")
{ var dest int32; cas(&dest) }
test.Log("- int64")
{ var dest int64; cas(&dest) }
test.Log("- uint8")
{ var dest uint8; cas(&dest) }
test.Log("- uint16")
{ var dest uint16; cas(&dest) }
test.Log("- uint32")
{ var dest uint32; cas(&dest) }
test.Log("- uint64")
{ var dest uint64; cas(&dest) }
}
arrayCase := func(destination any) {
n, err := DecodeAny(NewDecoder(bytes.NewBuffer(data[1:])), destination, Tag(data[0]))
if err != nil { test.Fatalf("error: %v | n: %d", err, n) }
reflectDestination := reflect.ValueOf(destination)
reflectValue := reflectDestination.Elem()
if reflectValue.Len() != 0 {
test.Fatalf("len(destination) not zero: %v", reflectValue.Interface())
}
if n != len(data) - 1 {
test.Fatalf("n not equal: %d != %d", n, len(data) - 1)
}
}
// SBA/LBA types should only assign to other SBA/LBA types
if index != 9 && index != 10 {
test.Log("- string")
{ var dest string; arrayCase(&dest) }
test.Log("- []byte")
{ var dest []byte; arrayCase(&dest) }
}
// arrays should only assign to other arrays
if index != 11 {
test.Log("- []string")
{ var dest []string; arrayCase(&dest) }
}
// tables should only assign to other tables
if index != 12 {
test.Log("- map[uint16] any")
{ var dest = map[uint16] any { }; arrayCase(&dest) }
}
}
}
func TestEncodeDecodeAnyTable(test *testing.T) {
err := testEncodeDecodeAny(test, map[uint16] any {
0xF3B9: uint32(1),
0x0102: uint32(2),
0x0103: int64(23432),
0x0104: int64(-88777),
0x0000: []byte("hi!"),
0xFFFF: []uint16 { 0xBEE5, 0x7777 },
0x1234: [][]uint16 { []uint16 { 0x5 }, []uint16 { 0x17, 0xAAAA} },
@@ -154,10 +269,10 @@ func testEncodeAny(test *testing.T, value any, correctTag Tag, correctBytes tu.S
test.Log("got: ", tu.HexBytes(bytes))
test.Log("correct:", correctBytes)
if tag != correctTag {
return fmt.Errorf("tag not equal")
return fmt.Errorf("tag not equal: %v != %v", tag, correctTag)
}
if ok, n := correctBytes.Check(bytes); !ok {
return fmt.Errorf("bytes not equal: %d", n)
return fmt.Errorf("bytes not equal at index %d", n)
}
if n != len(bytes) {
return fmt.Errorf("n not equal: %d != %d", n, len(bytes))

54
tape/skim.go Normal file
View File

@@ -0,0 +1,54 @@
package tape
import "fmt"
// Skim uses up data from a decoder to "skim" over one value (and all else
// contained within it) without actually putting the data anywhere.
func Skim(decoder *Decoder, tag Tag) (n int, err error) {
switch tag.WithoutCN() {
case SI:
// SI: (none)
return n, nil
case LI, LSI, FP:
// LI: <value: IntN>
// LSI: <value: IntN>
// FP: <value: FloatN>
nn, err := decoder.Discard(tag.CN() + 1)
n += nn; if err != nil { return n, err }
case SBA:
// SBA: <data: U8>*
nn, err := decoder.Discard(tag.CN())
n += nn; if err != nil { return n, err }
case LBA:
// LBA: <length: UN> <data: U8>*
length, nn, err := decoder.ReadUintN(tag.CN() + 1)
n += nn; if err != nil { return n, err }
nn, err = decoder.Discard(int(length))
n += nn; if err != nil { return n, err }
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 }
for _ = range length {
nn, err := Skim(decoder, oneTag)
n += nn; if err != nil { return n, err }
}
case KTV:
// KTV: <length: UN> (<key: U16> <tag: Tag> <value>)*
length, nn, err := decoder.ReadUintN(tag.CN() + 1)
n += nn; if err != nil { return n, err }
for _ = range length {
nn, err := decoder.Discard(2)
n += nn; if err != nil { return n, err }
itemTag, nn, err := decoder.ReadTag()
n += nn; if err != nil { return n, err }
nn, err = Skim(decoder, itemTag)
n += nn; if err != nil { return n, err }
}
default:
return n, fmt.Errorf("unknown TN %d", tag.TN())
}
return n, nil
}

137
tape/skim_test.go Normal file
View File

@@ -0,0 +1,137 @@
package tape
import "bytes"
import "testing"
func TestSkimInteger(test *testing.T) {
data := []byte {
0x12, 0x45, 0x23, 0xF9,
}
mainDataLen := len(data)
// extra junk
data = append(data, 0x00, 0x01, 0x02, 0x03,)
n, err := Skim(NewDecoder(bytes.NewBuffer(data)), LI.WithCN(3))
if err != nil {
test.Fatal(err)
}
if got, correct := n, mainDataLen; got != correct {
test.Fatalf("n not equal: %d != %d", got, correct)
}
}
func TestSkimArray(test *testing.T) {
data := []byte {
2, byte(LI.WithCN(1)),
0xBE, 0xE5, 0x77, 0x77,
}
mainDataLen := len(data)
// extra junk
data = append(data, 0x00, 0x01, 0x02, 0x03,)
n, err := Skim(NewDecoder(bytes.NewBuffer(data)), OTA.WithCN(0))
if err != nil {
test.Fatal(err)
}
if got, correct := n, mainDataLen; got != correct {
test.Fatalf("n not equal: %d != %d", got, correct)
}
}
func TestSkimNestedArray(test *testing.T) {
data := []byte {
2, byte(OTA.WithCN(0)),
1, byte(LSI.WithCN(1)),
0, 0x5,
2, byte(LSI.WithCN(1)),
0, 0x17,
0xF5, 0x56,
}
mainDataLen := len(data)
// extra junk
data = append(data, 0x00, 0x01, 0x02, 0x03,)
n, err := Skim(NewDecoder(bytes.NewBuffer(data)), OTA.WithCN(0))
if err != nil {
test.Fatal(err)
}
if got, correct := n, mainDataLen; got != correct {
test.Fatalf("n not equal: %d != %d", got, correct)
}
}
func TestSkimTable(test *testing.T) {
data := []byte {
2,
0xF3, 0xB9,
byte(LSI.WithCN(3)),
0, 0, 0, 1,
0x01, 0x02,
byte(LSI.WithCN(3)),
0, 0, 0, 2,
}
mainDataLen := len(data)
// extra junk
data = append(data, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03)
n, err := Skim(NewDecoder(bytes.NewBuffer(data)), KTV.WithCN(0))
if got, correct := n, mainDataLen; got != correct {
test.Fatalf("n not equal: %d != %d ... (%d)", got, correct, len(data))
}
if err != nil {
test.Fatal(err)
}
}
func TestSkimTableComplex(test *testing.T) {
data := []byte {
7,
0xF3, 0xB9,
byte(LSI.WithCN(3)),
0, 0, 0, 1,
0x01, 0x02,
byte(LSI.WithCN(3)),
0, 0, 0, 2,
0, 0,
byte(SBA.WithCN(3)),
'h', 'i', '!',
0xFF, 0xFF,
byte(OTA.WithCN(0)), 2, byte(LI.WithCN(1)),
0xBE, 0xE5, 0x77, 0x77,
0x12, 0x34,
byte(OTA.WithCN(0)), 2, byte(OTA.WithCN(0)),
1, byte(LI.WithCN(1)),
0, 0x5,
2, byte(LI.WithCN(1)),
0, 0x17,
0xAA, 0xAA,
0x23, 0x45,
byte(OTA.WithCN(0)), 2, byte(OTA.WithCN(0)),
1, byte(LSI.WithCN(1)),
0, 0x5,
2, byte(LSI.WithCN(1)),
0, 0x17,
0xF5, 0x56,
0x34, 0x56,
byte(LSI.WithCN(1)),
0x39, 0x21,
}
mainDataLen := len(data)
// extra junk
data = append(data, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x02, 0x03)
n, err := Skim(NewDecoder(bytes.NewBuffer(data)), KTV.WithCN(0))
if got, correct := n, mainDataLen; got != correct {
test.Fatalf("n not equal: %d != %d ... (%d)", got, correct, len(data))
}
if err != nil {
test.Fatal(err)
}
}

View File

@@ -2,14 +2,17 @@ package tape
import "fmt"
// TODO: fix #7
type Tag byte; const (
SI Tag = 0 << 5 // Small integer
LI Tag = 1 << 5 // Large integer
FP Tag = 2 << 5 // Floating point
SBA Tag = 3 << 5 // Small byte array
LBA Tag = 4 << 5 // Large byte array
OTA Tag = 5 << 5 // One-tag array
KTV Tag = 6 << 5 // Key-tag-value table
LI Tag = 1 << 5 // Large unsigned integer
LSI Tag = 2 << 5 // Large signed integer
FP Tag = 3 << 5 // Floating point
SBA Tag = 4 << 5 // Small byte array
LBA Tag = 5 << 5 // Large byte array
OTA Tag = 6 << 5 // One-tag array
KTV Tag = 7 << 5 // Key-tag-value table
TNMask Tag = 0xE0 // The entire TN bitfield
CNMask Tag = 0x1F // The entire CN bitfield
CNLimit Tag = 32 // All valid CNs are < CNLimit
@@ -40,6 +43,7 @@ func (tag Tag) String() string {
switch tag.WithoutCN() {
case SI: tn = "SI"
case LI: tn = "LI"
case LSI: tn = "LSI"
case FP: tn = "FP"
case SBA: tn = "SBA"
case LBA: tn = "LBA"