14 Commits

Author SHA1 Message Date
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
6 changed files with 178 additions and 60 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

@@ -99,6 +99,10 @@ func decodeAny(decoder *Decoder, destination reflect.Value, tag Tag) (n int, err
if err != nil { return n, err }
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:
@@ -168,30 +172,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 +214,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 +239,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 }
@@ -241,7 +255,7 @@ func encodeAnyMap(encoder *Encoder, value any, tag Tag) (n int, err error) {
}
// setInt expects a settable destination.
func setInt(destination reflect.Value, value uint64) error {
func setInt[T int64 | uint64](destination reflect.Value, value T) error {
switch {
case destination.CanInt():
destination.Set(reflect.ValueOf(int64(value)).Convert(destination.Type()))
@@ -277,6 +291,13 @@ func setByteArray(destination reflect.Value, value []byte) error {
// 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 }
return n, setInt(destination, value)
}
// 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)
@@ -319,6 +340,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,6 +56,20 @@ 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) }
}
@@ -60,6 +78,8 @@ 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 +174,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))

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"