Merge pull request 'encode-signedness' (#13) from encode-signedness into message-size-increase

Reviewed-on: #13
This commit is contained in:
Sasha Koshka 2025-08-11 19:10:56 -06:00
commit 8beb9de256
6 changed files with 144 additions and 45 deletions

View File

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

View File

@ -308,7 +308,7 @@ func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource stri
switch typ := typ.(type) { switch typ := typ.(type) {
case TypeInt: case TypeInt:
// SI: (none) // SI: (none)
// LI: <value: IntN> // LI/LSI: <value: IntN>
if typ.Bits <= 5 { if typ.Bits <= 5 {
// SI stores the value in the tag, so we write nothing here // SI stores the value in the tag, so we write nothing here
break break
@ -479,7 +479,7 @@ func (this *Generator) generateDecodeValue(typ Type, typeName, valueSource, tagS
switch typ := typ.(type) { switch typ := typ.(type) {
case TypeInt: case TypeInt:
// SI: (none) // SI: (none)
// LI: <value: IntN> // LI/LSI: <value: IntN>
if typ.Bits <= 5 { if typ.Bits <= 5 {
// SI stores the value in the tag // SI stores the value in the tag
nn, err := this.iprintf("*%s = uint8(%s.CN())\n", valueSource, tagSource) 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 { if typ.Bits <= 5 {
nn, err := this.printf("tape.SI.WithCN(int(%s))", source) nn, err := this.printf("tape.SI.WithCN(int(%s))", source)
n += nn; if err != nil { return n, err } 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 { } else {
nn, err := this.printf("tape.LI.WithCN(%d)", bitsToCN(typ.Bits)) nn, err := this.printf("tape.LI.WithCN(%d)", bitsToCN(typ.Bits))
n += nn; if err != nil { return n, err } 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 { if typ.Bits <= 5 {
nn, err := this.printf("tape.SI") nn, err := this.printf("tape.SI")
n += nn; if err != nil { return n, err } 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 { } else {
nn, err := this.printf("tape.LI") nn, err := this.printf("tape.LI")
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }

View File

@ -230,6 +230,26 @@ func TestGenerateRun(test *testing.T) {
Name: "NestedArray", Name: "NestedArray",
Type: TypeArray { Element: TypeArray { Element: TypeInt { Bits: 8 } } }, 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 { protocol.Types["User"] = TypeTableDefined {
Fields: map[uint16] Field { Fields: map[uint16] Field {
0x0000: Field { Name: "Name", Type: TypeString { } }, 0x0000: Field { Name: "Name", Type: TypeString { } },
@ -248,9 +268,9 @@ func TestGenerateRun(test *testing.T) {
} }
testEncode( testEncode(
&messageConnect, &messageConnect,
tu.S(0xC1, 0x02).AddVar( tu.S(0xE1, 0x02).AddVar(
[]byte { 0x00, 0x00, 0x66, 'r', 'a', 'r', 'i', 't', 'y' }, []byte { 0x00, 0x00, 0x86, 'r', 'a', 'r', 'i', 't', 'y' },
[]byte { 0x00, 0x01, 0x64, 'g', 'e', 'm', 's' }, []byte { 0x00, 0x01, 0x84, 'g', 'e', 'm', 's' },
)) ))
log.Println("MessageUserList") log.Println("MessageUserList")
messageUserList := MessageUserList { messageUserList := MessageUserList {
@ -274,19 +294,19 @@ func TestGenerateRun(test *testing.T) {
} }
testEncode( testEncode(
&messageUserList, &messageUserList,
tu.S(0xC1, 0x01, 0x00, 0x00, tu.S(0xE1, 0x01, 0x00, 0x00,
0xA1, 0x03, 0xC1, 0xC1, 0x03, 0xE1,
).Add(0x03).AddVar( ).Add(0x03).AddVar(
[]byte { 0x00, 0x00, 0x66, 'r', 'a', 'r', 'i', 't', 'y' }, []byte { 0x00, 0x00, 0x86, 'r', 'a', 'r', 'i', 't', 'y' },
[]byte { 0x00, 0x01, 0x67, 'a', 's', 'd', 'j', 'a', 'd', 's' }, []byte { 0x00, 0x01, 0x87, 'a', 's', 'd', 'j', 'a', 'd', 's' },
[]byte { 0x00, 0x02, 0x23, 0x00, 0x00, 0x03, 0x24 }, []byte { 0x00, 0x02, 0x23, 0x00, 0x00, 0x03, 0x24 },
).Add(0x03).AddVar( ).Add(0x03).AddVar(
[]byte { 0x00, 0x00, 0x69, 'd', 'e', 'e', 'z', ' ', 'n', 'u', 't', 's' }, []byte { 0x00, 0x00, 0x89, 'd', 'e', 'e', 'z', ' ', 'n', 'u', 't', 's' },
[]byte { 0x00, 0x01, 0x64, 'l', 'o', 'g', 'y' }, []byte { 0x00, 0x01, 0x84, 'l', 'o', 'g', 'y' },
[]byte { 0x00, 0x02, 0x23, 0x00, 0x00, 0x80, 0x00 }, []byte { 0x00, 0x02, 0x23, 0x00, 0x00, 0x80, 0x00 },
).Add(0x03).AddVar( ).Add(0x03).AddVar(
[]byte { 0x00, 0x00, 0x69, 'c', 'r', 'e', 'e', 'k', 'f', 'l', 'o', 'w' }, []byte { 0x00, 0x00, 0x89, 'c', 'r', 'e', 'e', 'k', 'f', 'l', 'o', 'w' },
[]byte { 0x00, 0x01, 0x6C, 'i', 'm', ' ', 'c', 'r', 'e', 'e', 'k', 'f', []byte { 0x00, 0x01, 0x8C, 'i', 'm', ' ', 'c', 'r', 'e', 'e', 'k', 'f',
'l', 'o', 'w' }, 'l', 'o', 'w' },
[]byte { 0x00, 0x02, 0x23, 0x00, 0x00, 0x38, 0x94 }, []byte { 0x00, 0x02, 0x23, 0x00, 0x00, 0x38, 0x94 },
)) ))
@ -300,12 +320,12 @@ func TestGenerateRun(test *testing.T) {
} }
testEncode( testEncode(
&messagePulse, &messagePulse,
tu.S(0xC1, 0x05).AddVar( tu.S(0xE1, 0x05).AddVar(
[]byte { 0x00, 0x00, 0x09 }, []byte { 0x00, 0x00, 0x09 },
[]byte { 0x00, 0x01, 0x21, 0xCA, 0xDF }, []byte { 0x00, 0x01, 0x41, 0xCA, 0xDF },
[]byte { 0x00, 0x02, 0x41, 0x51, 0xAC }, []byte { 0x00, 0x02, 0x61, 0x51, 0xAC },
[]byte { 0x00, 0x03, 0x43, 0x43, 0x93, 0x0C, 0xCD }, []byte { 0x00, 0x03, 0x63, 0x43, 0x93, 0x0C, 0xCD },
[]byte { 0x00, 0x04, 0x47, 0x41, 0xB6, 0xEE, 0x81, 0x28, 0x3C, 0x21, 0xE2 }, []byte { 0x00, 0x04, 0x67, 0x41, 0xB6, 0xEE, 0x81, 0x28, 0x3C, 0x21, 0xE2 },
)) ))
log.Println("MessageNestedArray") log.Println("MessageNestedArray")
uint8s := func(n int) []uint8 { uint8s := func(n int) []uint8 {
@ -321,7 +341,7 @@ func TestGenerateRun(test *testing.T) {
} }
testEncode( testEncode(
&messageNestedArray, &messageNestedArray,
tu.S(0xA1, 0x02, 0xA1, tu.S(0xC1, 0x02, 0xC1,
0x06, 0x20, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0x06, 0x20, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
35, 0x20, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 35, 0x20, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC,
@ -329,5 +349,38 @@ func TestGenerateRun(test *testing.T) {
0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8,
0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE,
0xFF, 0xF0, 0xF1, 0xF2, 0xF3)) 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 } if err != nil { return n, err }
case LI: case LI:
// LI: <value: IntN> // 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) nn, err := decodeAndSetInt(decoder, destination, tag.CN() + 1)
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
case FP: case FP:
@ -171,11 +175,16 @@ func TagAny(value any) (Tag, error) {
// TODO use reflection for all of this to ignore type names // TODO use reflection for all of this to ignore type names
// primitives // primitives
switch value := value.(type) { switch value := value.(type) {
case int, uint: return LI.WithCN(3), nil case int: return LSI.WithCN(3), nil
case int8, uint8: return LI.WithCN(0), nil case int8: return LSI.WithCN(0), nil
case int16, uint16: return LI.WithCN(1), nil case int16: return LSI.WithCN(1), nil
case int32, uint32: return LI.WithCN(3), nil case int32: return LSI.WithCN(3), nil
case int64, uint64: return LI.WithCN(7), nil case int64: return LSI.WithCN(7), nil
case uint: return LI.WithCN(3), nil
case uint8: return LI.WithCN(0), nil
case uint16: return LI.WithCN(1), nil
case uint32: return LI.WithCN(3), nil
case uint64: return LI.WithCN(7), nil
case string: return bufferLenTag(len(value)), nil case string: return bufferLenTag(len(value)), nil
case []byte: return bufferLenTag(len(value)), nil case []byte: return bufferLenTag(len(value)), nil
} }
@ -241,7 +250,7 @@ func encodeAnyMap(encoder *Encoder, value any, tag Tag) (n int, err error) {
} }
// setInt expects a settable destination. // 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 { switch {
case destination.CanInt(): case destination.CanInt():
destination.Set(reflect.ValueOf(int64(value)).Convert(destination.Type())) destination.Set(reflect.ValueOf(int64(value)).Convert(destination.Type()))
@ -277,6 +286,13 @@ func setByteArray(destination reflect.Value, value []byte) error {
// decodeAndSetInt expects a settable destination. // decodeAndSetInt expects a settable destination.
func decodeAndSetInt(decoder *Decoder, destination reflect.Value, bytes int) (n int, err error) { 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) value, nn, err := decoder.ReadUintN(bytes)
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
return n, setInt(destination, value) return n, setInt(destination, value)
@ -319,6 +335,14 @@ func typeOf(decoder *Decoder, tag Tag) (reflect.Type, error) {
case 7: return reflect.TypeOf(uint64(0)), nil case 7: return reflect.TypeOf(uint64(0)), nil
} }
return nil, fmt.Errorf("unknown CN %d for LI", tag.CN()) 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: case FP:
switch tag.CN() { switch tag.CN() {
case 3: return reflect.TypeOf(float32(0)), nil case 3: return reflect.TypeOf(float32(0)), nil

View File

@ -9,7 +9,7 @@ import tu "git.tebibyte.media/sashakoshka/hopp/internal/testutil"
func TestEncodeAnyInt(test *testing.T) { func TestEncodeAnyInt(test *testing.T) {
err := testEncodeAny(test, uint8(0xCA), LI.WithCN(0), tu.S(0xCA)) err := testEncodeAny(test, uint8(0xCA), LI.WithCN(0), tu.S(0xCA))
if err != nil { test.Fatal(err) } 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, 0, 0, 0x1, 0x90,
)) ))
if err != nil { test.Fatal(err) } if err != nil { test.Fatal(err) }
@ -22,15 +22,16 @@ func TestEncodeAnyTable(test *testing.T) {
0x0000: "hi!", 0x0000: "hi!",
0xFFFF: []uint16 { 0xBEE5, 0x7777 }, 0xFFFF: []uint16 { 0xBEE5, 0x7777 },
0x1234: [][]uint16 { []uint16 { 0x5 }, []uint16 { 0x17, 0xAAAA} }, 0x1234: [][]uint16 { []uint16 { 0x5 }, []uint16 { 0x17, 0xAAAA} },
}, KTV.WithCN(0), tu.S(5).AddVar( 0x2345: [][]int16 { []int16 { 0x5 }, []int16 { 0x17, -0xAAA } },
}, KTV.WithCN(0), tu.S(6).AddVar(
[]byte { []byte {
0xF3, 0xB9, 0xF3, 0xB9,
byte(LI.WithCN(3)), byte(LSI.WithCN(3)),
0, 0, 0, 1, 0, 0, 0, 1,
}, },
[]byte { []byte {
0x01, 0x02, 0x01, 0x02,
byte(LI.WithCN(3)), byte(LSI.WithCN(3)),
0, 0, 0, 2, 0, 0, 0, 2,
}, },
[]byte { []byte {
@ -52,6 +53,15 @@ func TestEncodeAnyTable(test *testing.T) {
0, 0x17, 0, 0x17,
0xAA, 0xAA, 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,
},
)) ))
if err != nil { test.Fatal(err) } if err != nil { test.Fatal(err) }
} }
@ -60,6 +70,8 @@ func TestEncodeDecodeAnyTable(test *testing.T) {
err := testEncodeDecodeAny(test, map[uint16] any { err := testEncodeDecodeAny(test, map[uint16] any {
0xF3B9: uint32(1), 0xF3B9: uint32(1),
0x0102: uint32(2), 0x0102: uint32(2),
0x0103: int64(23432),
0x0104: int64(-88777),
0x0000: []byte("hi!"), 0x0000: []byte("hi!"),
0xFFFF: []uint16 { 0xBEE5, 0x7777 }, 0xFFFF: []uint16 { 0xBEE5, 0x7777 },
0x1234: [][]uint16 { []uint16 { 0x5 }, []uint16 { 0x17, 0xAAAA} }, 0x1234: [][]uint16 { []uint16 { 0x5 }, []uint16 { 0x17, 0xAAAA} },

View File

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