tape: Clean slate

This commit is contained in:
Sasha Koshka 2025-06-01 23:06:28 -04:00
parent 38132dc58c
commit b85f3e7866
6 changed files with 0 additions and 1139 deletions

View File

@ -1,284 +0,0 @@
package tape
import "fmt"
import "iter"
import "slices"
// encoding and decoding functions must not make any allocations
// ArrayPushFunc adds an item to an array that is being encoded.
type ArrayPushFunc func(value []byte) (n int, err error)
// ArrayPullFunc gets the next item of an array that is being decoded.
type ArrayPullFunc func() (value []byte, n int, err error)
// DecodePASTA decodes a packed single-type array.
func DecodePASTA(data []byte, itemLength int) ArrayPullFunc {
n := 0
return func() (value []byte, n_ int, err error) {
if n > len(data) - itemLength {
return nil, 0, fmt.Errorf("decoding PASTA: %w", ErrWrongBufferLength)
}
value = data[n:n + itemLength]
n += itemLength
return value, itemLength, nil
}
}
// DecodePASTAIter decodes a packed single-type array and returns it as an
// iterator.
func DecodePASTAIter(data []byte, itemLength int) iter.Seq[[]byte] {
return slices.Chunk(data, itemLength)
}
// EncodePASTA encodes a packed single-type array.
func EncodePASTA(data []byte, itemLength int) ArrayPushFunc {
n := 0
return func(value []byte) (n_ int, err error) {
if n > len(data) - itemLength {
return 0, fmt.Errorf("encoding PASTA: %w", ErrWrongBufferLength)
}
copy(data[n:], value)
n += itemLength
return itemLength, nil
}
}
// PASTASize returns the size of a packed single-type array.
func PASTASize(length, itemLength int) int {
return length * itemLength
}
// DecodeVILA encodes a variable item length array.
func DecodeVILA(data []byte) ArrayPullFunc {
n := 0
return func() (value []byte, n_ int, err error) {
if n >= len(data) { return nil, n_, fmt.Errorf("decoding VILA: %w", ErrWrongBufferLength) }
length, nn, err := DecodeGBEU[uint](data[n:])
n += nn
n_ += nn
if err != nil { return nil, n_, err }
if n > len(data) - int(length) {
return nil, n_, fmt.Errorf("decoding VILA: %w", ErrWrongBufferLength)
}
value = data[n:n + int(length)]
n += int(length)
n_ += int(length)
return value, int(length), nil
}
}
// DecodeVILAIter decodes a variable item length array and returns it as an
// iterator.
func DecodeVILAIter(data []byte) iter.Seq[[]byte] {
return func(yield func([]byte) bool) {
pull := DecodeVILA(data)
for {
value, _, err := pull()
if err != nil { return }
if !yield(value) { return }
}
}
}
// EncodeVILA encodes a variable item length array.
func EncodeVILA(data []byte) ArrayPushFunc {
n := 0
return func(value []byte) (n_ int, err error) {
if n >= len(data) { return n_, fmt.Errorf("encoding VILA: %w", ErrWrongBufferLength) }
nn, err := EncodeGBEU(data[n:], uint(len(value)))
n += nn
n_ += nn
if err != nil { return n, err }
if n >= len(data) { return n_, fmt.Errorf("encoding VILA: %w", ErrWrongBufferLength) }
nn = copy(data[n:], value)
n += nn
n_ += nn
if nn != len(value) { return n_, fmt.Errorf("encoding VILA: %w", ErrWrongBufferLength) }
return n_, err
}
}
// VILASize returns the size of a variable item length array.
func VILASize(items ...[]byte) int {
size := 0
for _, item := range items {
size += GBEUSize[uint](uint(len(item)))
size += len(item)
}
return size
}
// DecodeStringArray decodes a VILA string array from the given data.
func DecodeStringArray[T String](data []byte) (result []T, n int, err error) {
pull := DecodeVILA(data)
for {
item, nn, err := pull()
n += nn
if err != nil { return nil, n, err }
result = append(result, T(item))
}
return result, n, nil
}
// EncodeStringArray encodes a VILA string array into the given buffer.
func EncodeStringArray[T String](buffer []byte, value []T) (n int, err error) {
push := EncodeVILA(buffer)
for _, item := range value {
nn, err := push([]byte(item))
n += nn
if err != nil { return n, err }
}
return n, nil
}
// StringArraySize returns the size of a VILA string array.
func StringArraySize[T String](value []T) int {
size := 0
for _, item := range value {
size += GBEUSize[uint](uint(len(item)))
size += len(item)
}
return size
}
// DecodeI8Array decodes a packed array of 8 bit integers from the given data.
func DecodeI8Array[T Int8](data []byte) (result []T, n int, err error) {
result = make([]T, len(data))
for index, item := range data {
result[index] = T(item)
}
return result, len(data), nil
}
// EncodeI8Array encodes a packed array of 8 bit integers into the given buffer.
func EncodeI8Array[T Int8](buffer []byte, value []T) (n int, err error) {
if len(buffer) != len(value) { return 0, fmt.Errorf("encoding []int8: %w", ErrWrongBufferLength) }
for index, item := range value {
buffer[index] = byte(item)
}
return len(buffer), nil
}
// I8ArraySize returns the size of a packed 8 bit integer array. Returns 0 and
// an error if the size is too large.
func I8ArraySize[T Int8](value []T) (int, error) {
total := len(value)
if total > dataMaxSize { return 0, ErrDataTooLarge }
return total, nil
}
// DecodeI16Array decodes a packed array of 16 bit integers from the given data.
func DecodeI16Array[T Int16](data []byte) (value []T, n int, err error) {
if len(data) % 2 != 0 { return nil, 0, fmt.Errorf("decoding []int16: %w", ErrWrongBufferLength) }
length := len(data) / 2
result := make([]T, length)
for index := range length {
offset := index * 2
result[index] = T(data[offset]) << 8 | T(data[offset + 1])
}
return result, len(data) / 2, nil
}
// EncodeI16Array encodes a packed array of 16 bit integers into the given buffer.
func EncodeI16Array[T Int16](buffer []byte, value []T) (n int, err error) {
if len(buffer) != len(value) * 2 { return 0, fmt.Errorf("encoding []int16: %w", ErrWrongBufferLength) }
for _, item := range value {
buffer[0] = byte(item >> 8)
buffer[1] = byte(item)
buffer = buffer[2:]
}
return len(value) * 2, nil
}
// I16ArraySize returns the size of a packed 16 bit integer array. Returns 0 and
// an error if the size is too large.
func I16ArraySize[T Int16](value []T) (int, error) {
total := len(value) * 2
if total > dataMaxSize { return 0, ErrDataTooLarge }
return total, nil
}
// DecodeI32Array decodes a packed array of 32 bit integers from the given data.
func DecodeI32Array[T Int32](data []byte) (value []T, n int, err error) {
if len(data) % 4 != 0 { return nil, 0, fmt.Errorf("decoding []int32: %w", ErrWrongBufferLength) }
length := len(data) / 4
result := make([]T, length)
for index := range length {
offset := index * 4
result[index] =
T(data[offset + 0]) << 24 |
T(data[offset + 1]) << 16 |
T(data[offset + 2]) << 8 |
T(data[offset + 3])
}
return result, len(data) / 4, nil
}
// EncodeI32Array encodes a packed array of 32 bit integers into the given buffer.
func EncodeI32Array[T Int32](buffer []byte, value []T) (n int, err error) {
if len(buffer) != len(value) * 4 { return 0, fmt.Errorf("encoding []int32: %w", ErrWrongBufferLength) }
for _, item := range value {
buffer[0] = byte(item >> 24)
buffer[1] = byte(item >> 16)
buffer[2] = byte(item >> 8)
buffer[3] = byte(item)
buffer = buffer[4:]
}
return len(value) * 4, nil
}
// I32ArraySize returns the size of a packed 32 bit integer array. Returns 0 and
// an error if the size is too large.
func I32ArraySize[T Int32](value []T) (int, error) {
total := len(value) * 4
if total > dataMaxSize { return 0, ErrDataTooLarge }
return total, nil
}
// DecodeI64Array decodes a packed array of 32 bit integers from the given data.
func DecodeI64Array[T Int64](data []byte) (value []T, n int, err error) {
if len(data) % 8 != 0 { return nil, 0, fmt.Errorf("decoding []int64: %w", ErrWrongBufferLength) }
length := len(data) / 8
result := make([]T, length)
for index := range length {
offset := index * 8
result[index] =
T(data[offset + 0]) << 56 |
T(data[offset + 1]) << 48 |
T(data[offset + 2]) << 40 |
T(data[offset + 3]) << 32 |
T(data[offset + 4]) << 24 |
T(data[offset + 5]) << 16 |
T(data[offset + 6]) << 8 |
T(data[offset + 7])
}
return result, len(data) / 8, nil
}
// EncodeI64Array encodes a packed array of 64 bit integers into the given buffer.
func EncodeI64Array[T Int64](buffer []byte, value []T) (n int, err error) {
if len(buffer) != len(value) * 8 { return 0, fmt.Errorf("encoding []int64: %w", ErrWrongBufferLength) }
for _, item := range value {
buffer[0] = byte(item >> 56)
buffer[1] = byte(item >> 48)
buffer[2] = byte(item >> 40)
buffer[3] = byte(item >> 32)
buffer[4] = byte(item >> 24)
buffer[5] = byte(item >> 16)
buffer[6] = byte(item >> 8)
buffer[7] = byte(item)
buffer = buffer[8:]
}
return len(value) * 8, nil
}
// I64ArraySize returns the size of a packed 64 bit integer array. Returns 0 and
// an error if the size is too large.
func I64ArraySize[T Int64](value []T) (int, error) {
total := len(value) * 8
if total > dataMaxSize { return 0, ErrDataTooLarge }
return total, nil
}

View File

@ -1,131 +0,0 @@
package tape
// import "fmt"
import "slices"
// import "errors"
import "testing"
import "math/rand"
func TestI8Array(test *testing.T) {
var buffer [64]byte
_, err := EncodeI8Array[uint8](buffer[:], []uint8 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int8: wrong buffer length") { test.Fatal(err) }
_, err = EncodeI8Array[uint8](buffer[:0], []uint8 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int8: wrong buffer length") { test.Fatal(err) }
_, _, err = DecodeI8Array[uint8](buffer[:])
if err != nil { test.Fatal(err) }
_, _, err = DecodeI8Array[uint8](buffer[:0])
if err != nil { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
array := randInts[uint8](rand.Intn(16))
length, _ := I8ArraySize(array)
if length != len(array) { test.Fatalf("%d != %d", length, len(array)) }
_, err := EncodeI8Array[uint8](buffer[:length], array)
if err != nil { test.Fatal(err) }
decoded, _, err := DecodeI8Array[uint8](buffer[:length])
if err != nil { test.Fatal(err) }
if !slices.Equal(decoded, array) {
test.Fatalf("%v != %v", decoded, array)
}
}
}
func TestI16Array(test *testing.T) {
var buffer [128]byte
_, err := EncodeI16Array[uint16](buffer[:], []uint16 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int16: wrong buffer length") { test.Fatal(err) }
_, err = EncodeI16Array[uint16](buffer[:0], []uint16 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int16: wrong buffer length") { test.Fatal(err) }
_, _, err = DecodeI16Array[uint16](buffer[:])
if err != nil { test.Fatal(err) }
_, _, err = DecodeI16Array[uint16](buffer[:0])
if err != nil { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
array := randInts[uint16](rand.Intn(16))
length, _ := I16ArraySize(array)
if length != 2 * len(array) { test.Fatalf("%d != %d", length, 2 * len(array)) }
_, err := EncodeI16Array[uint16](buffer[:length], array)
if err != nil { test.Fatal(err) }
decoded, _, err := DecodeI16Array[uint16](buffer[:length])
if err != nil { test.Fatal(err) }
if !slices.Equal(decoded, array) {
test.Fatalf("%v != %v", decoded, array)
}
}
}
func TestI32Array(test *testing.T) {
var buffer [256]byte
_, err := EncodeI32Array[uint32](buffer[:], []uint32 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int32: wrong buffer length") { test.Fatal(err) }
_, err = EncodeI32Array[uint32](buffer[:0], []uint32 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int32: wrong buffer length") { test.Fatal(err) }
_, _, err = DecodeI32Array[uint32](buffer[:])
if err != nil { test.Fatal(err) }
_, _, err = DecodeI32Array[uint32](buffer[:0])
if err != nil { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
array := randInts[uint32](rand.Intn(16))
length, _ := I32ArraySize(array)
if length != 4 * len(array) { test.Fatalf("%d != %d", length, 4 * len(array)) }
_, err := EncodeI32Array[uint32](buffer[:length], array)
if err != nil { test.Fatal(err) }
decoded, _, err := DecodeI32Array[uint32](buffer[:length])
if err != nil { test.Fatal(err) }
if !slices.Equal(decoded, array) {
test.Fatalf("%v != %v", decoded, array)
}
}
}
func TestI64Array(test *testing.T) {
var buffer [512]byte
_, err := EncodeI64Array[uint64](buffer[:], []uint64 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int64: wrong buffer length") { test.Fatal(err) }
_, err = EncodeI64Array[uint64](buffer[:0], []uint64 { 0, 4, 50, 19 })
if !errIs(err, ErrWrongBufferLength, "encoding []int64: wrong buffer length") { test.Fatal(err) }
_, _, err = DecodeI64Array[uint64](buffer[:])
if err != nil { test.Fatal(err) }
_, _, err = DecodeI64Array[uint64](buffer[:0])
if err != nil { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
array := randInts[uint64](rand.Intn(16))
length, _ := I64ArraySize(array)
if length != 8 * len(array) { test.Fatalf("%d != %d", length, 8 * len(array)) }
_, err := EncodeI64Array[uint64](buffer[:length], array)
if err != nil { test.Fatal(err) }
decoded, _, err := DecodeI64Array[uint64](buffer[:length])
if err != nil { test.Fatal(err) }
if !slices.Equal(decoded, array) {
test.Fatalf("%v != %v", decoded, array)
}
}
}
func TestStringArray(test *testing.T) {
var buffer [8192]byte
_, err := EncodeStringArray[string](buffer[:], []string { "0", "4", "50", "19" })
if !errIs(err, ErrWrongBufferLength, "encoding []string: wrong buffer length") { test.Fatal(err) }
_, err = EncodeStringArray[string](buffer[:0], []string { "0", "4", "50", "19" })
if !errIs(err, ErrWrongBufferLength, "encoding []string: wrong buffer length") { test.Fatal(err) }
_, _, err = DecodeStringArray[string](buffer[:0])
if err != nil { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
array := randStrings[string](rand.Intn(16), 16)
length, _ := StringArraySize(array)
// TODO test length
_, err := EncodeStringArray[string](buffer[:length], array)
if err != nil { test.Fatal(err) }
decoded, _, err := DecodeStringArray[string](buffer[:length])
if err != nil { test.Fatal(err) }
if !slices.Equal(decoded, array) {
test.Fatalf("%v != %v", decoded, array)
}
}
}

View File

@ -1,74 +0,0 @@
package tape
import "iter"
// encoding and decoding functions must not make any allocations
type TablePushFunc func(tag uint16, value []byte) (n int, err error)
type TablePullFunc func() (tag uint16, value []byte, n int, err error)
func DecodeTable(data []byte) TablePullFunc {
n := 0
return func() (tag uint16, value []byte, n_ int, err error) {
if n >= len(data) { return 0, nil, n, ErrWrongBufferLength }
tag, nn, err := DecodeI16[uint16](data[n:])
if err != nil { return 0, nil, n, err }
n += nn
if n >= len(data) { return 0, nil, n, ErrWrongBufferLength }
length, nn, err := DecodeGBEU[uint64](data[n:])
if err != nil { return 0, nil, n, err }
n += nn
end := n + int(length)
if end > len(data) { return 0, nil, n, ErrWrongBufferLength }
value = data[n:end]
n += int(length)
return tag, value, n, err
}
}
func DecodeTableIter(data []byte) iter.Seq2[uint16, []byte] {
return func(yield func(uint16, []byte) bool) {
pull := DecodeTable(data)
for {
tag, value, _, err := pull()
if err != nil { return }
if !yield(tag, value) { return }
}
}
}
func EncodeTable(data []byte) TablePushFunc {
return func(tag uint16, value []byte) (n int, err error) {
if n >= len(data) { return n, ErrWrongBufferLength }
nn, err := EncodeI16(data[n:], uint16(tag))
if err != nil { return n, err }
n += nn
if n >= len(data) { return n, ErrWrongBufferLength }
nn, err = EncodeGBEU(data[n:], uint(len(value)))
if err != nil { return n, err }
n += nn
if n >= len(data) { return n, ErrWrongBufferLength }
nn = copy(data[n:], value)
n += nn
if nn < len(value) {
return n, ErrWrongBufferLength
}
if n >= len(data) { return n, ErrWrongBufferLength }
data = data[n:]
return n, nil
}
}
func TableSize(itemLengths ...int) int {
sum := 0
for _, length := range itemLengths {
sum += GBEUSize(uint(length)) + length
}
return sum
}

View File

@ -1,144 +0,0 @@
package tape
import "fmt"
import "slices"
// import "errors"
import "testing"
// import "math/rand"
var longText =
`Curious, I started off in that direction, only for Prism to stop me. "Wrong way, Void. It's right over here." He trotted over to a door to the left of us. It was marked with the number '4004'. He took a key out of his saddlebags, unlocked it, and pushed it open. "You know, some say this suite is haunted. They call the ghost that lives here the 'Spirit of 4004'. Ooooh!" He made paddling motions in the air with his hooves.`
func TestTable(test *testing.T) {
item5 := []byte("hello")
item7 := []byte("world")
item0 := []byte(longText)
item3249 := []byte { 0x0, 0x1, 0x2, 0x3, 0xA0, 0x5 }
buffer := [512]byte { }
push := EncodeTable(buffer[:])
_, err := push(5, item5)
if err != nil { test.Fatal(err)}
_, err = push(7, item7)
if err != nil { test.Fatal(err)}
_, err = push(0, item0)
if err != nil { test.Fatal(err)}
_, err = push(3249, item3249)
if err != nil { test.Fatal(err)}
test.Logf("len of longText: %d 0x%X", len(longText), len(longText))
correct := []byte("\x00\x05\x05hello\x00\x07\x05world\x00\x00\x83\x28" + longText)
if got := buffer[:len(correct)]; !slices.Equal(got, correct) {
if !compareHexArray(test, got, correct) { test.Fatal("failed") }
}
pull := DecodeTable(buffer[:len(correct)])
tag, value, _, err := pull()
if err != nil { test.Fatal(err) }
if got, correct := tag, uint16(5); got != correct {
test.Fatal("not equal:", got)
}
if !compareHexArray(test, value, []byte("hello")) { test.Fatal("failed") }
tag, value, _, err = pull()
if err != nil { test.Fatal(err) }
if got, correct := tag, uint16(7); got != correct {
test.Fatal("not equal:", got)
}
if !compareHexArray(test, value, []byte("world")) { test.Fatal("failed") }
tag, value, _, err = pull()
if err != nil { test.Fatal(err) }
if got, correct := tag, uint16(0); got != correct {
test.Fatal("not equal:", got)
}
if !compareHexArray(test, value, []byte(longText)) { test.Fatal("failed") }
tag, value, _, err = pull()
if err != nil { test.Fatal(err) }
if got, correct := tag, uint16(3249); got != correct {
test.Fatal("not equal:", got)
}
if !compareHexArray(test, value, []byte { 0x0, 0x1, 0x2, 0x3, 0xA0, 0x5 }) { test.Fatal("failed") }
}
func TestTableSmall(test *testing.T) {
item2 := []byte("hello")
item3249 := []byte { 0x0, 0x1, 0x2, 0x3, 0xA0, 0x5 }
buffer := [64]byte { }
push := EncodeTable(buffer[:])
_, err := push(2, item2)
if err != nil { test.Fatal(err) }
_, err = push(3249, item3249)
if err != nil { test.Fatal(err) }
correct := []byte("\x00\x02\x05hello\x0C\xB1\x06\x00\x01\x02\x03\xA0\x05")
if got := buffer[:len(correct)]; !slices.Equal(got, correct) {
if !compareHexArray(test, got, correct) { test.Fatal("failed") }
}
pull := DecodeTable(buffer[:len(correct)])
tag, value, _, err := pull()
if err != nil { test.Fatal(err) }
if got, correct := tag, uint16(2); got != correct {
test.Fatal("not equal:", got)
}
if !compareHexArray(test, value, []byte("hello")) { test.Fatal("failed") }
tag, value, _, err = pull()
if err != nil { test.Fatal(err) }
if got, correct := tag, uint16(3249); got != correct {
test.Fatal("not equal:", got)
}
if !compareHexArray(test, value, []byte { 0x0, 0x1, 0x2, 0x3, 0xA0, 0x5 }) { test.Fatal("failed") }
}
func dumpHexArray(data []byte) (message string) {
for _, item := range data {
message = fmt.Sprintf("%s %02X", message, item)
}
return message
}
func compareHexArray(test *testing.T, got, correct []byte) bool {
index := 0
for {
if index >= len(correct) {
if index < len(got) {
test.Log("correct longer than got")
test.Log("got: ", dumpHexArray(got))
test.Log("correct:", dumpHexArray(correct))
return false
}
break
}
if index >= len(got) {
if index < len(correct) {
test.Log("got longer than correct")
test.Log("got: ", dumpHexArray(got))
test.Log("correct:", dumpHexArray(correct))
return false
}
break
}
if correct[index] != got[index] {
test.Log("not equal")
test.Log("got: ", dumpHexArray(got))
test.Log("correct:", dumpHexArray(correct))
partLow := index - 8
partHigh := index + 8
test.Log("got part ", dumpHexArray(safeSlice(got, partLow, partHigh)))
test.Log("correct part", dumpHexArray(safeSlice(correct, partLow, partHigh)))
return false
}
index ++
}
return true
}
func safeSlice[T any](slice []T, low, high int) []T {
return slice[max(low, 0):min(high, len(slice))]
}

View File

@ -1,180 +0,0 @@
// Package tape implements Table Pair Encoding.
package tape
import "fmt"
// encoding and decoding functions must not make any allocations
const dataMaxSize = 0xFFFF
const uint16Max = 0xFFFF
// Error enumerates common errors in this package.
type Error string; const (
ErrWrongBufferLength Error = "wrong buffer length"
ErrDataTooLarge Error = "data too large"
ErrGBEUNotTerminated Error = "GBEU not terminated"
)
// Error implements the error interface.
func (err Error) Error() string {
return string(err)
}
// Int8 is any 8-bit integer.
type Int8 interface { ~uint8 | ~int8 }
// Int16 is any 16-bit integer.
type Int16 interface { ~uint16 | ~int16 }
// Int32 is any 32-bit integer.
type Int32 interface { ~uint32 | ~int32 }
// Int64 is any 64-bit integer.
type Int64 interface { ~uint64 | ~int64 }
// Uint is any unsigned integer.
type Uint interface { ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 }
// String is any string.
type String interface { ~string | ~[]uint8 }
// DecodeI8 decodes an 8 bit integer from the given data.
func DecodeI8[T Int8](data []byte) (value T, n int, err error) {
if len(data) < 1 { return 0, 0, fmt.Errorf("decoding int8: %w", ErrWrongBufferLength) }
return T(data[0]), 1, nil
}
// EncodeI8 encodes an 8 bit integer into the given buffer.
func EncodeI8[T Int8](buffer []byte, value T) (n int, err error) {
if len(buffer) < 1 { return 0, fmt.Errorf("encoding int8: %w", ErrWrongBufferLength) }
buffer[0] = byte(value)
return 1, nil
}
// DecodeI16 decodes a 16 bit integer from the given data.
func DecodeI16[T Int16](data []byte) (value T, n int, err error) {
if len(data) < 2 { return 0, 0, fmt.Errorf("decoding int16: %w", ErrWrongBufferLength) }
return T(data[0]) << 8 | T(data[1]), 2, nil
}
// EncodeI16 encodes a 16 bit integer into the given buffer.
func EncodeI16[T Int16](buffer []byte, value T) (n int, err error) {
if len(buffer) < 2 { return 0, fmt.Errorf("encoding int16: %w", ErrWrongBufferLength) }
buffer[0] = byte(value >> 8)
buffer[1] = byte(value)
return 2, nil
}
// DecodeI32 decodes a 32 bit integer from the given data.
func DecodeI32[T Int32](data []byte) (value T, n int, err error) {
if len(data) < 4 { return 0, 0, fmt.Errorf("decoding int32: %w", ErrWrongBufferLength) }
return T(data[0]) << 24 |
T(data[1]) << 16 |
T(data[2]) << 8 |
T(data[3]), 4, nil
}
// EncodeI32 encodes a 32 bit integer into the given buffer.
func EncodeI32[T Int32](buffer []byte, value T) (n int, err error) {
if len(buffer) < 4 { return 0, fmt.Errorf("encoding int32: %w", ErrWrongBufferLength) }
buffer[0] = byte(value >> 24)
buffer[1] = byte(value >> 16)
buffer[2] = byte(value >> 8)
buffer[3] = byte(value)
return 4, nil
}
// DecodeI64 decodes a 64 bit integer from the given data.
func DecodeI64[T Int64](data []byte) (value T, n int, err error) {
if len(data) < 8 { return 0, 0, fmt.Errorf("decoding int64: %w", ErrWrongBufferLength) }
return T(data[0]) << 56 |
T(data[1]) << 48 |
T(data[2]) << 40 |
T(data[3]) << 32 |
T(data[4]) << 24 |
T(data[5]) << 16 |
T(data[6]) << 8 |
T(data[7]), 8, nil
}
// EncodeI64 encodes a 64 bit integer into the given buffer.
func EncodeI64[T Int64](buffer []byte, value T) (n int, err error) {
if len(buffer) < 8 { return 0, fmt.Errorf("encoding int64: %w", ErrWrongBufferLength) }
buffer[0] = byte(value >> 56)
buffer[1] = byte(value >> 48)
buffer[2] = byte(value >> 40)
buffer[3] = byte(value >> 32)
buffer[4] = byte(value >> 24)
buffer[5] = byte(value >> 16)
buffer[6] = byte(value >> 8)
buffer[7] = byte(value)
return 8, nil
}
// DecodeGBEU decodes an 8 to 64 bit growing integer into the given buffer.
func DecodeGBEU[T Uint](data []byte) (value T, n int, err error) {
var fullValue uint64
for _, chunk := range data {
fullValue *= 0x80
fullValue += uint64(chunk & 0x7F)
ccb := chunk >> 7
n += 1
if ccb == 0 {
return T(fullValue), n, nil
}
}
return 0, n, fmt.Errorf("decoding GBEU: %w", ErrGBEUNotTerminated)
}
// EncodeGBEU encodes an 8 to 64 bit growing integer into a given buffer.
func EncodeGBEU[T Uint] (buffer []byte, value T) (n int, err error) {
window := (GBEUSize(value) - 1) * 7
index := 0
for window >= 0 {
if index >= len(buffer) { return index, fmt.Errorf("encoding GBEU: %w", ErrWrongBufferLength) }
chunk := uint8(value >> window) & 0x7F
if window > 0 {
chunk |= 0x80
}
buffer[index] = chunk
index += 1
window -= 7
}
return index, nil
}
// GBEUSize returns the size (in octets) of a GBEU integer.
func GBEUSize[T Uint] (value T) int {
length := 0
for {
value >>= 7
length ++
if value == 0 { return length }
}
}
// DecodeString decodes a string from the given data.
func DecodeString[T String](data []byte) (value T, n int, err error) {
return T(data), len(data), nil
}
// EncodeString encodes a string into the given buffer.
func EncodeString[T String](data []byte, value T) (n int, err error) {
if len(data) != len(value) { return 0, fmt.Errorf("encoding string: %w", ErrWrongBufferLength) }
return copy(data, value), nil
}
// StringSize returns the size of a string. Returns 0 and an error if the size
// is too large.
func StringSize[T String](value T) (int, error) {
if len(value) > dataMaxSize { return 0, ErrDataTooLarge }
return len(value), nil
}
// U16CastSafe safely casts an integer to a uint16. If an overflow or underflow
// occurs, it will return (0, false).
func U16CastSafe(n int) (uint16, bool) {
if n < uint16Max && n >= 0 {
return uint16(n), true
} else {
return 0, false
}
}

View File

@ -1,326 +0,0 @@
package tape
import "fmt"
import "slices"
import "errors"
import "testing"
import "math/rand"
const largeNumberNTestRounds = 2048
const randStringBytes = "-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func TestI8(test *testing.T) {
var buffer [16]byte
_, err := EncodeI8[uint8](buffer[:], 5)
if err != nil { test.Fatal(err) }
_, err = EncodeI8[uint8](buffer[:0], 5)
if err.Error() != "encoding int8: wrong buffer length" { test.Fatal(err) }
_, _, err = DecodeI8[uint8](buffer[:])
if err != nil { test.Fatal(err) }
_, _, err = DecodeI8[uint8](buffer[:0])
if err.Error() != "decoding int8: wrong buffer length" { test.Fatal(err) }
for number := range uint8(255) {
n, err := EncodeI8[uint8](buffer[:1], number)
if err != nil { test.Fatal(err) }
if correct, got := 1, n; correct != got {
test.Fatal("not equal:", got)
}
decoded, n, err := DecodeI8[uint8](buffer[:1])
if err != nil { test.Fatal(err) }
if correct, got := 1, n; correct != got {
test.Fatal("not equal:", got)
}
if decoded != number {
test.Fatalf("%d != %d", decoded, number)
}
}
}
func TestI16(test *testing.T) {
var buffer [16]byte
_, err := EncodeI16[uint16](buffer[:], 5)
if err != nil { test.Fatal(err) }
_, err = EncodeI16[uint16](buffer[:0], 5)
if err.Error() != "encoding int16: wrong buffer length" { test.Fatal(err) }
_, _, err = DecodeI16[uint16](buffer[:])
if err != nil { test.Fatal(err) }
_, _, err = DecodeI16[uint16](buffer[:0])
if err.Error() != "decoding int16: wrong buffer length" { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
number := uint16(rand.Int())
n, err := EncodeI16[uint16](buffer[:2], number)
if err != nil { test.Fatal(err) }
if correct, got := 2, n; correct != got {
test.Fatal("not equal:", got)
}
decoded, n, err := DecodeI16[uint16](buffer[:2])
if err != nil { test.Fatal(err) }
if correct, got := 2, n; correct != got {
test.Fatal("not equal:", got)
}
if decoded != number {
test.Fatalf("%d != %d", decoded, number)
}
}
}
func TestI32(test *testing.T) {
var buffer [16]byte
_, err := EncodeI32[uint32](buffer[:], 5)
if err != nil { test.Fatal(err) }
_, err = EncodeI32[uint32](buffer[:0], 5)
if err.Error() != "encoding int32: wrong buffer length" { test.Fatal(err) }
_, _, err = DecodeI32[uint32](buffer[:])
if err != nil { test.Fatal(err) }
_, _, err = DecodeI32[uint32](buffer[:0])
if err.Error() != "decoding int32: wrong buffer length" { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
number := uint32(rand.Int())
n, err := EncodeI32[uint32](buffer[:4], number)
if err != nil { test.Fatal(err) }
if correct, got := 4, n; correct != got {
test.Fatal("not equal:", got)
}
decoded, n, err := DecodeI32[uint32](buffer[:4])
if err != nil { test.Fatal(err) }
if correct, got := 4, n; correct != got {
test.Fatal("not equal:", got)
}
if decoded != number {
test.Fatalf("%d != %d", decoded, number)
}
}
}
func TestI64(test *testing.T) {
var buffer [16]byte
_, err := EncodeI64[uint64](buffer[:], 5)
if err != nil { test.Fatal(err) }
_, err = EncodeI64[uint64](buffer[:0], 5)
if err.Error() != "encoding int64: wrong buffer length" { test.Fatal(err) }
_, _, err = DecodeI64[uint64](buffer[:])
if err != nil { test.Fatal(err) }
_, _, err = DecodeI64[uint64](buffer[:0])
if err.Error() != "decoding int64: wrong buffer length" { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
number := uint64(rand.Int())
n, err := EncodeI64[uint64](buffer[:8], number)
if err != nil { test.Fatal(err) }
if correct, got := 8, n; correct != got {
test.Fatal("not equal:", got)
}
decoded, n, err := DecodeI64[uint64](buffer[:8])
if err != nil { test.Fatal(err) }
if correct, got := 8, n; correct != got {
test.Fatal("not equal:", got)
}
if decoded != number {
test.Fatalf("%d != %d", decoded, number)
}
}
}
func TestGBEU(test *testing.T) {
var buffer = [16]byte {
255, 255, 255, 255,
255, 255, 255, 255,
255, 255, 255, 255,
255, 255, 255, 255,
}
_, err := EncodeGBEU[uint64](buffer[:0], 5)
if err == nil { test.Fatal("no error") }
if err.Error() != "encoding GBEU: wrong buffer length" { test.Fatal(err) }
_, err = EncodeGBEU[uint64](buffer[:2], 5555555)
if err == nil { test.Fatal("no error") }
if err.Error() != "encoding GBEU: wrong buffer length" { test.Fatal(err) }
_, _, err = DecodeGBEU[uint64](buffer[:0])
if err == nil { test.Fatal("no error") }
if err.Error() != "decoding GBEU: GBEU not terminated" { test.Fatal(err) }
n, err := EncodeGBEU[uint64](buffer[:], 0x97)
if err != nil { test.Fatal(err) }
if correct, got := 2, n; correct != got {
test.Fatal("not equal:", got)
}
if correct, got := []byte { 0x81, 0x17 }, buffer[:2]; !slices.Equal(correct, got) {
message := "not equal:"
for _, item := range got {
message = fmt.Sprintf("%s %x", message, item)
}
test.Fatal(message)
}
decoded, n, err := DecodeGBEU[uint64](buffer[:])
if err != nil { test.Fatal(err) }
if correct, got := 2, n; correct != got {
test.Fatal("not equal:", got)
}
if correct, got := uint64(0x97), decoded; correct != got {
test.Fatalf("not equal: %x", got)
}
n, err = EncodeGBEU[uint64](buffer[:], 0x123456)
if err != nil { test.Fatal(err) }
if correct, got := 3, n; correct != got {
test.Fatal("not equal:", got)
}
if correct, got := []byte { 0xc8, 0xe8, 0x56 }, buffer[:3]; !slices.Equal(correct, got) {
message := "not equal:"
for _, item := range got {
message = fmt.Sprintf("%s %x", message, item)
}
test.Fatal(message)
}
decoded, n, err = DecodeGBEU[uint64](buffer[:])
if err != nil { test.Fatal(err) }
if correct, got := 3, n; correct != got {
test.Fatal("not equal:", got)
}
if correct, got := uint64(0x123456), decoded; correct != got {
test.Fatalf("not equal: %x", got)
}
maxGBEU64 := []byte { 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F }
n, err = EncodeGBEU[uint64](buffer[:], 0xFFFFFFFFFFFFFFFF)
if err != nil { test.Fatal(err) }
if correct, got := 10, n; correct != got {
test.Fatal("not equal:", got)
}
if correct, got := maxGBEU64, buffer[:10]; !slices.Equal(correct, got) {
message := "not equal:"
for _, item := range got {
message = fmt.Sprintf("%s %x", message, item)
}
test.Fatal(message)
}
decoded, n, err = DecodeGBEU[uint64](buffer[:])
if err != nil { test.Fatal(err) }
if correct, got := 10, n; correct != got {
test.Fatal("not equal:", got)
}
if correct, got := uint64(0xFFFFFFFFFFFFFFFF), decoded; correct != got {
test.Fatalf("not equal: %x", got)
}
n, err = EncodeGBEU[uint64](buffer[:], 11)
if err != nil { test.Fatal(err) }
if correct, got := 1, n; correct != got {
test.Fatal("not equal:", got)
}
if correct, got := byte(0xb), buffer[0]; correct != got {
test.Fatal("not equal:", got)
}
decoded, n, err = DecodeGBEU[uint64](buffer[:])
if err != nil { test.Fatal(err) }
if correct, got := 1, n; correct != got {
test.Fatal("not equal:", got)
}
if correct, got := uint64(0xb), decoded; correct != got {
test.Fatalf("not equal: %x", got)
}
for _ = range largeNumberNTestRounds {
buffer = [16]byte { }
number := uint64(rand.Int())
_, err := EncodeGBEU[uint64](buffer[:], number)
if err != nil { test.Fatal(err) }
decoded, _, err := DecodeGBEU[uint64](buffer[:])
if err != nil { test.Fatal(err) }
if decoded != number {
test.Error("not equal:")
test.Errorf("%d != %d", decoded, number)
test.Errorf("%x != %x", decoded, number)
test.Fatal(buffer)
}
}
}
func TestGBEUSize(test *testing.T) {
if correct, got := 3, GBEUSize(uint(0x100000)); correct != got {
test.Fatal("not equal:", got)
}
if correct, got := 3, GBEUSize(uint(0x123456)); correct != got {
test.Fatal("not equal:", got)
}
if correct, got := 4, GBEUSize(uint(0x223456)); correct != got {
test.Fatal("not equal:", got)
}
if correct, got := 1, GBEUSize(uint(0)); correct != got {
test.Fatal("not equal:", got)
}
if correct, got := 1, GBEUSize(uint(127)); correct != got {
test.Fatal("not equal:", got)
}
}
func TestString(test *testing.T) {
var buffer [16]byte
_, err := EncodeString[string](buffer[:], "hello")
if !errIs(err, ErrWrongBufferLength, "encoding string: wrong buffer length") { test.Fatal(err) }
_, err = EncodeString[string](buffer[:0], "hello")
if !errIs(err, ErrWrongBufferLength, "encoding string: wrong buffer length") { test.Fatal(err) }
_, _, err = DecodeString[string](buffer[:])
if err != nil { test.Fatal(err) }
_, _, err = DecodeString[string](buffer[:0])
if err != nil { test.Fatal(err) }
for _ = range largeNumberNTestRounds {
length := rand.Intn(16)
str := randString(length)
_, err := EncodeString[string](buffer[:length], str)
if err != nil { test.Fatal(err) }
decoded, _, err := DecodeString[string](buffer[:length])
if err != nil { test.Fatal(err) }
if decoded != str {
test.Fatalf("%s != %s", decoded, str)
}
}
}
func TestU16CastSafe(test *testing.T) {
number, ok := U16CastSafe(90_000)
if ok { test.Fatalf("false positive: %v, %v", number, ok) }
number, ok = U16CastSafe(-478)
if ok { test.Fatalf("false positive: %v, %v", number, ok) }
number, ok = U16CastSafe(3870)
if !ok { test.Fatalf("false negative: %v, %v", number, ok) }
if got, correct := number, uint16(3870); got != correct {
test.Fatalf("not equal: %v %v", got, correct)
}
number, ok = U16CastSafe(0)
if !ok { test.Fatalf("false negative: %v, %v", number, ok) }
if got, correct := number, uint16(0); got != correct {
test.Fatalf("not equal: %v %v", got, correct)
}
}
func randString(length int) string {
buffer := make([]byte, length)
for index := range buffer {
buffer[index] = randStringBytes[rand.Intn(len(randStringBytes))]
}
return string(buffer)
}
func randInts[T interface { ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 }] (length int) []T {
buffer := make([]T, length)
for index := range buffer {
buffer[index] = T(rand.Int())
}
return buffer
}
func randStrings[T interface { ~string }] (length, maxItemLength int) []T {
buffer := make([]T, length)
for index := range buffer {
buffer[index] = T(randString(rand.Intn(maxItemLength)))
}
return buffer
}
func errIs(err error, wraps error, description string) bool {
return err != nil && (wraps == nil || errors.Is(err, wraps)) && err.Error() == description
}