From 3d8a0124777b79cc9f39f4ba3ed7550a9b5aabed Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sun, 18 May 2025 15:50:24 -0400 Subject: [PATCH] tape: Add table encoding/decoding functions --- tape/table.go | 59 ++++++++++++++++++++++++++ tape/table_test.go | 100 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 tape/table.go create mode 100644 tape/table_test.go diff --git a/tape/table.go b/tape/table.go new file mode 100644 index 0000000..2128dc2 --- /dev/null +++ b/tape/table.go @@ -0,0 +1,59 @@ +package tape + +import "iter" + +// encoding and decoding functions must not make any allocations + +type TablePushFunc func(tag uint16, value []byte) (n int, err error) + +func DecodeTable(data []byte) iter.Seq2[uint16, []byte] { + return func(yield func(tag uint16, value []byte) bool) { + n := 0 + for { + tag, nn, err := DecodeI16[uint16](data[n:]) + if err != nil { return } + n += nn + + length, nn, err := DecodeGBEU[uint64](data[n:]) + if err != nil { return } + n += nn + + value := data[n:n + int(length)] + yield(tag, value) + n += int(length) + } + } +} + +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 +} diff --git a/tape/table_test.go b/tape/table_test.go new file mode 100644 index 0000000..6dc64dc --- /dev/null +++ b/tape/table_test.go @@ -0,0 +1,100 @@ +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, correct, got) { + test.FailNow() + } + } +} + +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, correct, got) { + test.FailNow() + } + } +} + +func dumpHexArray(data []byte) (message string) { + for _, item := range data { + message = fmt.Sprintf("%s %02X", message, item) + } + return message +} + +func compareHexArray(test *testing.T, correct, got []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 + } + } + 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 + } + } + 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))] +}