diff --git a/generate/generate_test.go b/generate/generate_test.go new file mode 100644 index 0000000..ceb2ac9 --- /dev/null +++ b/generate/generate_test.go @@ -0,0 +1,236 @@ +package generate + +// import "fmt" +import "strings" +import "testing" +import "git.tebibyte.media/sashakoshka/goparse" + +var testGenerateCorrect = +`package protocol + +/* # Do not edit this package by hand! + * + * This file was automatically generated by the Holanet PDL compiler. The + * source file is located at input.pdl + * Please edit that file instead, and re-compile it to this location. + * + * HOPP, TAPE, METADAPT, PDL/0 (c) 2025 holanet.xyz + */ + +import "git.tebibyte.media/sashakoshka/hopp/tape" + +// Table is a KTV table with an undefined schema. +type Table map[uint16] any + +// Message is any message that can be sent along this protocol. +type Message interface { + tape.Encodable + tape.Decodable + + // Method returns the method code of the message. + Method() uint16 +} + +// User represents the protocol data type User. +type User struct { + Name string + Bio string + Followers uint32 +} + +// EncodeValue encodes the value of this type without the tag. The value is +// encoded according to the parameters specified by the tag, if possible. +func (this *User) EncodeValue(encoder *tape.Encoder) (n int, err error) { + nn, err := tape.WriteTableHeader(2) + n += nn; if err != nil { return n, err } + nn, err := encoder.WriteUint16(0x0000) + n += nn; if err != nil { return n, err } + nn, err := tape.WriteString(encoder, this.Name) + n += nn; if err != nil { return n, err } + nn, err := encoder.WriteUint16(0x0001) + n += nn; if err != nil { return n, err } + nn, err := tape.WriteString(encoder, this.Bio) + n += nn; if err != nil { return n, err } + return n, nil +} + +// Decode replaces the data in this User with information from the decoder. +func (this *User) Decode(decoder *tape.Decoder) (n int, err error) { + pull, nn, err := tape.ReadTableHeader(decoder) + n += nn; if err != nil { return n, err } + + for { + key, tag, end, nn, err := pull() + n += nn; if err != nil { return n, err } + if end { break } + + switch key { + case 0x0000: + value, nn, err := tape.ReadString(decoder) + n += nn; if err != nil { return n, err } + this.Name = value + case 0x0001: + value, nn, err := tape.ReadString(decoder) + n += nn; if err != nil { return n, err } + this.Bio = value + } + } + + return n, nil +} + +// MessageConnect represents the protocol message M0000 Connect. +type MessageConnect struct { + Name string + Password string +} + +// Method returns the method code, M0000. +func (this *MessageConnect) Method() uint16 { + return 0x0000 +} + +// Encode encodes the message to the encoder. +func (this *MessageConnect) Encode(encoder *tape.Encoder) (n int, err error) { + nn, err := tape.WriteTableHeader(2) + n += nn; if err != nil { return n, err } + nn, err := encoder.WriteUint16(0x0000) + n += nn; if err != nil { return n, err } + nn, err := tape.WriteString(encoder, this.Name) + n += nn; if err != nil { return n, err } + nn, err := encoder.WriteUint16(0x0001) + n += nn; if err != nil { return n, err } + nn, err := tape.WriteString(encoder, this.Password) + n += nn; if err != nil { return n, err } + return n, nil +} + +// Decode replaces the data in this message with information from the decoder. +func (this *MessageConnect) Decode(decoder *tape.Decoder) (n int, err error) { + pull, nn, err := tape.ReadTableHeader(decoder) + n += nn; if err != nil { return n, err } + + for { + key, tag, end, nn, err := pull() + n += nn; if err != nil { return n, err } + if end { break } + + switch key { + case 0x0000: + value, nn, err := tape.ReadString(decoder) + n += nn; if err != nil { return n, err } + this.Name = value + case 0x0001: + value, nn, err := tape.ReadString(decoder) + n += nn; if err != nil { return n, err } + this.Password = value + } + } + + return n, nil +} + +// MessageUserList represents the protocol message M0001 UserList. +type MessageUserList struct { + Users []User +} + +// Method returns the method code, M0001. +func (this *MessageUserList) Method() uint16 { + return 0x0001 +} + +// TODO methods +` + +func TestGenerate(test *testing.T) { + protocol := defaultProtocol() + protocol.Messages[0x0000] = Message { + Name: "Connect", + Type: TypeTableDefined { + Fields: map[uint16] Field { + 0x0000: Field { Name: "Name", Type: TypeNamed { Name: "String" } }, + 0x0001: Field { Name: "Password", Type: TypeNamed { Name: "String" } }, + }, + }, + } + protocol.Messages[0x0001] = Message { + Name: "UserList", + Type: TypeTableDefined { + Fields: map[uint16] Field { + 0x0000: Field { Name: "Users", Type: TypeArray { Element: TypeNamed { Name: "User" } } }, + }, + }, + } + protocol.Types["User"] = TypeTableDefined { + Fields: map[uint16] Field { + 0x0000: Field { Name: "Name", Type: TypeNamed { Name: "String" } }, + 0x0001: Field { Name: "Bio", Type: TypeNamed { Name: "String" } }, + 0x0002: Field { Name: "Followers", Type: TypeNamed { Name: "U32" } }, + }, + } + + correct := testGenerateCorrect + + builder := strings.Builder { } + generator := Generator { Output: &builder } + /* TODO test n: */ _, err := generator.Generate(&protocol) + if err != nil { test.Fatal(parse.Format(err)) } + got := builder.String() + + test.Log("CORRECT:") + test.Log(correct) + test.Log("GOT:") + test.Log(got) + + if correct != got { + test.Error("not equal") + for index := range min(len(correct), len(got)) { + if correct[index] == got[index] { continue } + test.Log("C:", correct[max(0, index - 8):min(len(correct), index + 8)]) + test.Log("G:", got[max(0, index - 8):min(len(got), index + 8)]) + break + } + test.FailNow() + } +} + +func TestGenerateRun(test *testing.T) { + protocol := defaultProtocol() + protocol.Messages[0x0000] = Message { + Name: "Connect", + Type: TypeTableDefined { + Fields: map[uint16] Field { + 0x0000: Field { Name: "Name", Type: TypeNamed { Name: "String" } }, + 0x0001: Field { Name: "Password", Type: TypeNamed { Name: "String" } }, + }, + }, + } + protocol.Messages[0x0001] = Message { + Name: "UserList", + Type: TypeTableDefined { + Fields: map[uint16] Field { + 0x0000: Field { Name: "Users", Type: TypeArray { Element: TypeNamed { Name: "User" } } }, + }, + }, + } + protocol.Types["User"] = TypeTableDefined { + Fields: map[uint16] Field { + 0x0000: Field { Name: "Name", Type: TypeNamed { Name: "String" } }, + 0x0001: Field { Name: "Bio", Type: TypeNamed { Name: "String" } }, + 0x0002: Field { Name: "Followers", Type: TypeNamed { Name: "U32" } }, + }, + } + testGenerateRun(test, &protocol, ` + // imports + `, ` + // test case + messageConnect := MessageConnect { + Name: "rarity", + Password: "gems", + } + testEncode( + messageConnect, + 0x0) // TODO + `) +}