12 Commits

10 changed files with 235 additions and 55 deletions

View File

@@ -6,7 +6,7 @@ PDL allows defining a protocol using HOPP and TAPE.
| Syntax | TN | CN | Description | Syntax | TN | CN | Description
| ---------- | ------- | -: | ----------- | ---------- | ------- | -: | -----------
| I5 | SI | | | I5 | SI | |
| I8 | LSI | 0 | | I8 | LSI | 0 |
| I16 | LSI | 1 | | I16 | LSI | 1 |
| I32 | LSI | 3 | | I32 | LSI | 3 |
@@ -25,6 +25,7 @@ PDL allows defining a protocol using HOPP and TAPE.
| F64 | FP | 7 | | F64 | FP | 7 |
| F128[^2] | FP | 15 | | F128[^2] | FP | 15 |
| F256[^2] | FP | 31 | | F256[^2] | FP | 31 |
| Bool | SI | |
| String | SBA/LBA | * | UTF-8 string | String | SBA/LBA | * | UTF-8 string
| Buffer | SBA/LBA | * | Byte array | Buffer | SBA/LBA | * | Byte array
| []\<TYPE\> | OTA | * | Array of any type[^1] | []\<TYPE\> | OTA | * | Array of any type[^1]

View File

@@ -47,6 +47,15 @@ func canAssign(destination, source tape.Tag) bool {
} }
return false return false
} }
// boolInt converts a bool to an integer.
func boolInt(input bool) int {
if input {
return 1
} else {
return 0
}
}
` `
// Generator converts protocols into Go code. // Generator converts protocols into Go code.
@@ -112,13 +121,20 @@ func (this *Generator) Generate(protocol *Protocol) (n int, err error) {
return n, nil return n, nil
} }
func (this *Generator) generateTypedef(name string, typ Type) (n int, err error) { func (this *Generator) generateTypedef(name string, typedef Typedef) (n int, err error) {
typ := typedef.Type
// type definition // type definition
nn, err := this.iprintf( if typedef.Doc == "" {
"\n// %s represents the protocol data type %s.\n", nn, err := this.iprintf(
name, name) "\n// %s represents the protocol data type %s.\n",
n += nn; if err != nil { return n, err } name, name)
nn, err = this.iprintf("type %s ", name) n += nn; if err != nil { return n, err }
} else {
nn, err := this.iprintf("\n%s\n", this.formatComment(typedef.Doc))
n += nn; if err != nil { return n, err }
}
nn, err := this.iprintf("type %s ", name)
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
nn, err = this.generateType(typ) nn, err = this.generateType(typ)
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
@@ -208,10 +224,16 @@ func (this *Generator) generateTypedef(name string, typ Type) (n int, err error)
// generateMessage generates the structure, as well as encoding decoding // generateMessage generates the structure, as well as encoding decoding
// functions for the given message. // functions for the given message.
func (this *Generator) generateMessage(method uint16, message Message) (n int, err error) { func (this *Generator) generateMessage(method uint16, message Message) (n int, err error) {
nn, err := this.iprintf( if message.Doc == "" {
"\n// %s represents the protocol message M%04X %s.\n", nn, err := this.iprintf(
message.Name, method, message.Name) "\n// %s represents the protocol message M%04X %s.\n",
nn, err = this.iprintf("type %s ", this.resolveMessageName(message.Name)) message.Name, method, message.Name)
n += nn; if err != nil { return n, err }
} else {
nn, err := this.iprintf("\n%s\n", this.formatComment(message.Doc))
n += nn; if err != nil { return n, err }
}
nn, err := this.iprintf("type %s ", this.resolveMessageName(message.Name))
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
nn, err = this.generateType(message.Type) nn, err = this.generateType(message.Type)
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
@@ -302,6 +324,9 @@ func (this *Generator) generateMessage(method uint16, message Message) (n int, e
// - nn int // - nn int
func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource string) (n int, err error) { func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource string) (n int, err error) {
switch typ := typ.(type) { switch typ := typ.(type) {
case TypeBool:
// SI: (none)
// SI stores the value in the tag, so we write nothing here
case TypeInt: case TypeInt:
// SI: (none) // SI: (none)
// LI/LSI: <value: IntN> // LI/LSI: <value: IntN>
@@ -505,6 +530,11 @@ func (this *Generator) generateEncodeValue(typ Type, valueSource, tagSource stri
// for [Generator.generateDecodeBranch]. // for [Generator.generateDecodeBranch].
func (this *Generator) generateDecodeValue(typ Type, typeName, valueSource, tagSource string) (n int, err error) { func (this *Generator) generateDecodeValue(typ Type, typeName, valueSource, tagSource string) (n int, err error) {
switch typ := typ.(type) { switch typ := typ.(type) {
case TypeBool:
// SI: (none)
// SI stores the value in the tag
nn, err := this.iprintf("*%s = %s.CN() > 0\n", valueSource, tagSource)
n += nn; if err != nil { return n, err }
case TypeInt: case TypeInt:
// SI: (none) // SI: (none)
// LI/LSI: <value: IntN> // LI/LSI: <value: IntN>
@@ -931,6 +961,9 @@ func (this *Generator) generateBareErrorCheck() (n int, err error) {
func (this *Generator) generateTag(typ Type, source string) (tagVar string, n int, err error) { func (this *Generator) generateTag(typ Type, source string) (tagVar string, n int, err error) {
tagVar = this.newTemporaryVar("tag") tagVar = this.newTemporaryVar("tag")
switch typ := typ.(type) { switch typ := typ.(type) {
case TypeBool:
nn, err := this.iprintf("%s := tape.SI.WithCN(boolInt(bool(%s)))\n", tagVar, source)
n += nn; if err != nil { return tagVar, n, err }
case TypeInt: case TypeInt:
if typ.Bits <= 5 { if typ.Bits <= 5 {
nn, err := this.iprintf("%s := tape.SI.WithCN(int(%s))\n", tagVar, source) nn, err := this.iprintf("%s := tape.SI.WithCN(int(%s))\n", tagVar, source)
@@ -984,6 +1017,9 @@ func (this *Generator) generateTag(typ Type, source string) (tagVar string, n in
// information is chosen. // information is chosen.
func (this *Generator) generateTN(typ Type) (n int, err error) { func (this *Generator) generateTN(typ Type) (n int, err error) {
switch typ := typ.(type) { switch typ := typ.(type) {
case TypeBool:
nn, err := this.printf("tape.SI")
n += nn; if err != nil { return n, err }
case TypeInt: case TypeInt:
if typ.Bits <= 5 { if typ.Bits <= 5 {
nn, err := this.printf("tape.SI") nn, err := this.printf("tape.SI")
@@ -1027,6 +1063,9 @@ func (this *Generator) generateTN(typ Type) (n int, err error) {
func (this *Generator) generateType(typ Type) (n int, err error) { func (this *Generator) generateType(typ Type) (n int, err error) {
switch typ := typ.(type) { switch typ := typ.(type) {
case TypeBool:
nn, err := this.printf("bool")
n += nn; if err != nil { return n, err }
case TypeInt: case TypeInt:
if err := this.validateIntBitSize(typ.Bits); err != nil { if err := this.validateIntBitSize(typ.Bits); err != nil {
return n, err return n, err
@@ -1090,7 +1129,9 @@ func (this *Generator) generateTypeTableDefined(typ TypeTableDefined) (n int, er
for _, key := range slices.Sorted(maps.Keys(typ.Fields)) { for _, key := range slices.Sorted(maps.Keys(typ.Fields)) {
field := typ.Fields[key] field := typ.Fields[key]
nn, err := this.iprintf("%s ", field.Name) nn, err := this.iprintf("%s\n", this.formatComment(field.Doc))
n += nn; if err != nil { return n, err }
nn, err = this.iprintf("%s ", field.Name)
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
nn, err = this.generateType(field.Type) nn, err = this.generateType(field.Type)
n += nn; if err != nil { return n, err } n += nn; if err != nil { return n, err }
@@ -1175,17 +1216,21 @@ func (this *Generator) iprintf(format string, args ...any) (n int, err error) {
return fmt.Fprintf(this.Output, this.indent() + format, args...) return fmt.Fprintf(this.Output, this.indent() + format, args...)
} }
func (this *Generator) formatComment(comment string) string {
return "// " + strings.ReplaceAll(comment, "\n", "\n" + this.indent() + "// ")
}
func (this *Generator) resolveMessageName(message string) string { func (this *Generator) resolveMessageName(message string) string {
return "Message" + message return "Message" + message
} }
func (this *Generator) resolveTypeName(name string) (Type, error) { func (this *Generator) resolveTypeName(name string) (Type, error) {
if typ, ok := this.protocol.Types[name]; ok { if typedef, ok := this.protocol.Types[name]; ok {
if typ, ok := typ.(TypeNamed); ok { if typ, ok := typedef.Type.(TypeNamed); ok {
return this.resolveTypeName(typ.Name) return this.resolveTypeName(typ.Name)
} }
return typ, nil return typedef.Type, nil
} }
return nil, fmt.Errorf("no type exists called %s", name) return nil, fmt.Errorf("no type exists called %s", name)
} }

View File

@@ -59,6 +59,7 @@ func init() {
0x000C: Field { Name: "NI16",Type: TypeInt { Bits: 16, Signed: true } }, 0x000C: Field { Name: "NI16",Type: TypeInt { Bits: 16, Signed: true } },
0x000D: Field { Name: "NI32",Type: TypeInt { Bits: 32, Signed: true } }, 0x000D: Field { Name: "NI32",Type: TypeInt { Bits: 32, Signed: true } },
0x000E: Field { Name: "NI64",Type: TypeInt { Bits: 64, Signed: true } }, 0x000E: Field { Name: "NI64",Type: TypeInt { Bits: 64, Signed: true } },
0x000F: Field { Name: "Bool",Type: TypeBool { } },
}, },
}, },
} }
@@ -83,11 +84,13 @@ func init() {
}, },
}, },
} }
exampleProtocol.Types["User"] = TypeTableDefined { exampleProtocol.Types["User"] = Typedef {
Fields: map[uint16] Field { Type: TypeTableDefined {
0x0000: Field { Name: "Name", Type: TypeString { } }, Fields: map[uint16] Field {
0x0001: Field { Name: "Bio", Type: TypeString { } }, 0x0000: Field { Name: "Name", Type: TypeString { } },
0x0002: Field { Name: "Followers", Type: TypeInt { Bits: 32 } }, 0x0001: Field { Name: "Bio", Type: TypeString { } },
0x0002: Field { Name: "Followers", Type: TypeInt { Bits: 32 } },
},
}, },
} }
} }
@@ -199,10 +202,11 @@ func TestGenerateRunEncodeDecode(test *testing.T) {
NI16: -0x34C9, NI16: -0x34C9,
NI32: -0x10E134C9, NI32: -0x10E134C9,
NI64: -0x639109BC10E134C9, NI64: -0x639109BC10E134C9,
Bool: true,
} }
testEncodeDecode( testEncodeDecode(
&messageIntegers, &messageIntegers,
tu.S(0xE0, 13).AddVar( tu.S(0xE0, 14).AddVar(
[]byte { 0x00, 0x00, 0x13 }, []byte { 0x00, 0x00, 0x13 },
[]byte { 0x00, 0x01, 0x20, 0xC9 }, []byte { 0x00, 0x01, 0x20, 0xC9 },
[]byte { 0x00, 0x02, 0x21, 0x34, 0xC9 }, []byte { 0x00, 0x02, 0x21, 0x34, 0xC9 },
@@ -216,6 +220,7 @@ func TestGenerateRunEncodeDecode(test *testing.T) {
[]byte { 0x00, 0x0C, 0x41, 0xCB, 0x37 }, []byte { 0x00, 0x0C, 0x41, 0xCB, 0x37 },
[]byte { 0x00, 0x0D, 0x43, 0xEF, 0x1E, 0xCB, 0x37 }, []byte { 0x00, 0x0D, 0x43, 0xEF, 0x1E, 0xCB, 0x37 },
[]byte { 0x00, 0x0E, 0x47, 0x9C, 0x6E, 0xF6, 0x43, 0xEF, 0x1E, 0xCB, 0x37 }, []byte { 0x00, 0x0E, 0x47, 0x9C, 0x6E, 0xF6, 0x43, 0xEF, 0x1E, 0xCB, 0x37 },
[]byte { 0x00, 0x0F, 0x01 },
)) ))
log.Println("MessageDynamic") log.Println("MessageDynamic")
messageDynamic := MessageDynamic { messageDynamic := MessageDynamic {

View File

@@ -15,6 +15,7 @@ const (
TokenRBrace TokenRBrace
TokenLBracket TokenLBracket
TokenRBracket TokenRBracket
TokenComment
) )
var tokenNames = map[parse.TokenKind] string { var tokenNames = map[parse.TokenKind] string {
@@ -26,6 +27,7 @@ var tokenNames = map[parse.TokenKind] string {
TokenRBrace: "RBrace", TokenRBrace: "RBrace",
TokenLBracket: "LBracket", TokenLBracket: "LBracket",
TokenRBracket: "RBracket", TokenRBracket: "RBracket",
TokenComment: "Comment",
} }
func Lex(fileName string, reader io.Reader) (parse.Lexer, error) { func Lex(fileName string, reader io.Reader) (parse.Lexer, error) {
@@ -81,6 +83,18 @@ func (this *lexer) nextInternal() (token parse.Token, err error) {
} }
} }
unexpected := func() error {
if unicode.IsPrint(this.rune) {
return parse.Errorf (
this.pos(), "unexpected rune '%c'",
this.rune)
} else {
return parse.Errorf (
this.pos(), "unexpected rune %U",
this.rune)
}
}
defer func () { defer func () {
newPos := this.pos() newPos := this.pos()
newPos.End -- // TODO figure out why tf we have to do this newPos.End -- // TODO figure out why tf we have to do this
@@ -133,14 +147,21 @@ func (this *lexer) nextInternal() (token parse.Token, err error) {
token.Kind = TokenRBracket token.Kind = TokenRBracket
appendRune() appendRune()
if this.eof { err = nil; return } if this.eof { err = nil; return }
case unicode.IsPrint(this.rune): // Comment
err = parse.Errorf ( case this.rune == '/':
this.pos(), "unexpected rune '%c'", token.Kind = TokenComment
this.rune) appendRune()
if this.eof { return }
if this.rune != '/' {
err = unexpected()
return
}
for this.rune != '\n' {
appendRune()
if this.eof { err = nil; return }
}
default: default:
err = parse.Errorf ( err = unexpected()
this.pos(), "unexpected rune %U",
this.rune)
} }
return return

View File

@@ -6,14 +6,21 @@ import "git.tebibyte.media/sashakoshka/goparse"
func TestLex(test *testing.T) { func TestLex(test *testing.T) {
lexer, err := Lex("test.pdl", strings.NewReader(` lexer, err := Lex("test.pdl", strings.NewReader(`
// User holds profile information about a single user.
M0001 User { M0001 User {
0000 Name String, 0000 Name String,
// dog water comment
// Users is asdkjsagkj why
//
// wow
0001 Users []User, 0001 Users []User,
0002 Followers U32, 0002 Followers U32,
}`)) }`))
if err != nil { test.Fatal(parse.Format(err)) } if err != nil { test.Fatal(parse.Format(err)) }
correctTokens := []parse.Token { correctTokens := []parse.Token {
tok(TokenComment, "// User holds profile information about a single user."),
tok(TokenMethod, "0001"), tok(TokenMethod, "0001"),
tok(TokenIdent, "User"), tok(TokenIdent, "User"),
tok(TokenLBrace, "{"), tok(TokenLBrace, "{"),
@@ -21,6 +28,10 @@ func TestLex(test *testing.T) {
tok(TokenIdent, "Name"), tok(TokenIdent, "Name"),
tok(TokenIdent, "String"), tok(TokenIdent, "String"),
tok(TokenComma, ","), tok(TokenComma, ","),
tok(TokenComment, "// dog water comment"),
tok(TokenComment, "// Users is asdkjsagkj why"),
tok(TokenComment, "// "),
tok(TokenComment, "// wow"),
tok(TokenKey, "0001"), tok(TokenKey, "0001"),
tok(TokenIdent, "Users"), tok(TokenIdent, "Users"),
tok(TokenLBracket, "["), tok(TokenLBracket, "["),

View File

@@ -1,6 +1,7 @@
package generate package generate
import "io" import "io"
import "strings"
import "strconv" import "strconv"
import "git.tebibyte.media/sashakoshka/goparse" import "git.tebibyte.media/sashakoshka/goparse"
@@ -21,7 +22,7 @@ func Parse(lx parse.Lexer) (*Protocol, error) {
func defaultProtocol() Protocol { func defaultProtocol() Protocol {
return Protocol { return Protocol {
Messages: make(map[uint16] Message), Messages: make(map[uint16] Message),
Types: map[string] Type { }, Types: map[string] Typedef { },
} }
} }
@@ -47,18 +48,28 @@ func (this *parser) parse() error {
} }
func (this *parser) parseTopLevel() error { func (this *parser) parseTopLevel() error {
err := this.ExpectDesc("message or typedef", TokenMethod, TokenIdent) doc := ""
if err != nil { return err } for {
if this.EOF() { return nil } err := this.ExpectDesc("message or typedef", TokenMethod, TokenIdent, TokenComment)
if err != nil { return err }
if this.EOF() { return nil }
if this.Kind() == TokenComment {
if doc != "" { doc += "\n" }
doc += this.parseComment(this.Value())
this.Next()
} else {
break
}
}
switch this.Kind() { switch this.Kind() {
case TokenMethod: return this.parseMessage() case TokenMethod: return this.parseMessage(doc)
case TokenIdent: return this.parseTypedef() case TokenIdent: return this.parseTypedef(doc)
} }
panic("bug") panic("bug")
} }
func (this *parser) parseMessage() error { func (this *parser) parseMessage(doc string) error {
err := this.Expect(TokenMethod) err := this.Expect(TokenMethod)
if err != nil { return err } if err != nil { return err }
method, err := this.parseHexNumber(this.Value(), 0xFFFF) method, err := this.parseHexNumber(this.Value(), 0xFFFF)
@@ -72,12 +83,13 @@ func (this *parser) parseMessage() error {
if err != nil { return err } if err != nil { return err }
this.protocol.Messages[uint16(method)] = Message { this.protocol.Messages[uint16(method)] = Message {
Name: name, Name: name,
Doc: doc,
Type: typ, Type: typ,
} }
return nil return nil
} }
func (this *parser) parseTypedef() error { func (this *parser) parseTypedef(doc string) error {
err := this.Expect(TokenIdent) err := this.Expect(TokenIdent)
if err != nil { return err } if err != nil { return err }
name := this.Value() name := this.Value()
@@ -85,7 +97,10 @@ func (this *parser) parseTypedef() error {
if err != nil { return err } if err != nil { return err }
typ, err := this.parseType() typ, err := this.parseType()
if err != nil { return err } if err != nil { return err }
this.protocol.Types[name] = typ this.protocol.Types[name] = Typedef {
Doc: doc,
Type: typ,
}
return nil return nil
} }
@@ -117,6 +132,7 @@ func (this *parser) parseType() (Type, error) {
case "Buffer": return TypeBuffer { }, this.Next() case "Buffer": return TypeBuffer { }, this.Next()
case "Table": return TypeTable { }, this.Next() case "Table": return TypeTable { }, this.Next()
case "Any": return TypeAny { }, this.Next() case "Any": return TypeAny { }, this.Next()
case "Bool": return TypeBool { }, this.Next()
} }
return this.parseTypeNamed() return this.parseTypeNamed()
case TokenLBracket: case TokenLBracket:
@@ -157,12 +173,22 @@ func (this *parser) parseTypeTable() (TypeTableDefined, error) {
Fields: make(map[uint16] Field), Fields: make(map[uint16] Field),
} }
for { for {
err := this.ExpectDesc("table field", TokenKey, TokenRBrace) doc := ""
if err != nil { return TypeTableDefined { }, err } for {
err := this.ExpectDesc("table field", TokenKey, TokenRBrace, TokenComment)
if err != nil { return TypeTableDefined { }, err }
if this.Kind() == TokenComment {
if doc != "" { doc += "\n" }
doc += this.parseComment(this.Value())
this.Next()
} else {
break
}
}
if this.Is(TokenRBrace) { if this.Is(TokenRBrace) {
break break
} }
key, field, err := this.parseField() key, field, err := this.parseField(doc)
if err != nil { return TypeTableDefined { }, err } if err != nil { return TypeTableDefined { }, err }
typ.Fields[key] = field typ.Fields[key] = field
err = this.Expect(TokenComma, TokenRBrace) err = this.Expect(TokenComma, TokenRBrace)
@@ -178,7 +204,7 @@ func (this *parser) parseTypeTable() (TypeTableDefined, error) {
return typ, nil return typ, nil
} }
func (this *parser) parseField() (uint16, Field, error) { func (this *parser) parseField(doc string) (uint16, Field, error) {
err := this.Expect(TokenKey) err := this.Expect(TokenKey)
if err != nil { return 0, Field { }, err } if err != nil { return 0, Field { }, err }
key, err := this.parseHexNumber(this.Value(), 0xFFFF) key, err := this.parseHexNumber(this.Value(), 0xFFFF)
@@ -192,6 +218,7 @@ func (this *parser) parseField() (uint16, Field, error) {
if err != nil { return 0, Field { }, err } if err != nil { return 0, Field { }, err }
return uint16(key), Field { return uint16(key), Field {
Name: name, Name: name,
Doc: doc,
Type: typ, Type: typ,
}, nil }, nil
} }
@@ -206,3 +233,7 @@ func (this *parser) parseHexNumber(input string, maxValue int64) (int64, error)
} }
return number, nil return number, nil
} }
func (this *parser) parseComment(input string) string {
return strings.TrimPrefix(strings.TrimPrefix(input, "//"), " ")
}

View File

@@ -9,6 +9,7 @@ func TestParse(test *testing.T) {
correct := defaultProtocol() correct := defaultProtocol()
correct.Messages[0x0000] = Message { correct.Messages[0x0000] = Message {
Name: "Connect", Name: "Connect",
Doc: "Connect is sent from the client to the server as the first message of an\nauthenticated transaction.",
Type: TypeTableDefined { Type: TypeTableDefined {
Fields: map[uint16] Field { Fields: map[uint16] Field {
0x0000: Field { Name: "Name", Type: TypeString { } }, 0x0000: Field { Name: "Name", Type: TypeString { } },
@@ -18,36 +19,50 @@ func TestParse(test *testing.T) {
} }
correct.Messages[0x0001] = Message { correct.Messages[0x0001] = Message {
Name: "UserList", Name: "UserList",
Doc: "UserList is sent from the server to the client in response to a Connect\nmessage.",
Type: TypeTableDefined { Type: TypeTableDefined {
Fields: map[uint16] Field { Fields: map[uint16] Field {
0x0000: Field { Name: "Users", Type: TypeArray { Element: TypeNamed { Name: "User" } } }, 0x0000: Field { Name: "Users", Type: TypeArray { Element: TypeNamed { Name: "User" } } },
}, },
}, },
} }
correct.Types["User"] = TypeTableDefined { correct.Types["User"] = Typedef {
Fields: map[uint16] Field { Doc: "User holds profile information about a single user.",
0x0000: Field { Name: "Name", Type: TypeString { } }, Type: TypeTableDefined {
0x0001: Field { Name: "Bio", Type: TypeString { } }, Fields: map[uint16] Field {
0x0002: Field { Name: "Followers", Type: TypeInt { Bits: 32 } }, 0x0000: Field { Name: "Name", Type: TypeString { } },
0x0001: Field { Name: "Bio", Type: TypeString { } },
0x0002: Field { Name: "Followers", Type: TypeInt { Bits: 32 } },
0x0003: Field { Name: "Bouncy", Type: TypeBool { } },
},
}, },
} }
correct.Types["Anything"] = TypeAny { } correct.Types["Anything"] = Typedef {
Type: TypeAny { },
}
test.Log("CORRECT:", &correct) test.Log("CORRECT:", &correct)
got, err := ParseReader("test.pdl", strings.NewReader(` got, err := ParseReader("test.pdl", strings.NewReader(`
// Connect is sent from the client to the server as the first message of an
// authenticated transaction.
M0000 Connect { M0000 Connect {
0000 Name String, 0000 Name String,
// Password is where you put your secrets, your shameful secrets
0001 Password String, 0001 Password String,
} }
// UserList is sent from the server to the client in response to a Connect
// message.
M0001 UserList { M0001 UserList {
0000 Users []User, 0000 Users []User,
} }
// User holds profile information about a single user.
User { User {
0000 Name String, 0000 Name String,
0001 Bio String, 0001 Bio String,
0002 Followers U32, 0002 Followers U32,
0003 Bouncy Bool,
} }
Anything Any Anything Any

View File

@@ -7,11 +7,17 @@ import "crypto/md5"
type Protocol struct { type Protocol struct {
Messages map[uint16] Message Messages map[uint16] Message
Types map[string] Type Types map[string] Typedef
} }
type Message struct { type Message struct {
Name string Name string
Doc string
Type Type
}
type Typedef struct {
Doc string
Type Type Type Type
} }
@@ -43,6 +49,12 @@ func (typ TypeFloat) String() string {
return fmt.Sprintf("F%d", typ.Bits) return fmt.Sprintf("F%d", typ.Bits)
} }
type TypeBool struct { }
func (TypeBool) String() string {
return "Bool"
}
type TypeString struct { } type TypeString struct { }
func (TypeString) String() string { func (TypeString) String() string {
@@ -84,6 +96,7 @@ func (typ TypeTableDefined) String() string {
type Field struct { type Field struct {
Name string Name string
Doc string
Type Type Type Type
} }

View File

@@ -59,6 +59,7 @@ func EncodeAny(encoder *Encoder, value any, tag Tag) (n int, err error) {
case reflect.Uint64: return encoder.WriteUint64(uint64(reflectValue.Uint())) case reflect.Uint64: return encoder.WriteUint64(uint64(reflectValue.Uint()))
case reflect.Float32: return encoder.WriteFloat32(float32(reflectValue.Float())) case reflect.Float32: return encoder.WriteFloat32(float32(reflectValue.Float()))
case reflect.Float64: return encoder.WriteFloat64(float64(reflectValue.Float())) case reflect.Float64: return encoder.WriteFloat64(float64(reflectValue.Float()))
case reflect.Bool: return // SI has no payload
case reflect.String: case reflect.String:
if reflectValue.Len() > MaxStructureLength { if reflectValue.Len() > MaxStructureLength {
return 0, ErrTooLong return 0, ErrTooLong
@@ -298,6 +299,12 @@ func tagAny(reflectValue reflect.Value) (Tag, error) {
case reflect.Uint64: return LI.WithCN(7), nil case reflect.Uint64: return LI.WithCN(7), nil
case reflect.Float32: return FP.WithCN(3), nil case reflect.Float32: return FP.WithCN(3), nil
case reflect.Float64: return FP.WithCN(7), nil case reflect.Float64: return FP.WithCN(7), nil
case reflect.Bool:
if reflectValue.Bool() {
return SI.WithCN(1), nil
} else {
return SI.WithCN(0), nil
}
case reflect.String: return bufferLenTag(reflectValue.Len()), nil case reflect.String: return bufferLenTag(reflectValue.Len()), nil
} }
if reflectValue.CanConvert(reflect.TypeOf(dummyBuffer)) { if reflectValue.CanConvert(reflect.TypeOf(dummyBuffer)) {
@@ -374,7 +381,8 @@ func canSet(destination reflect.Type, tag Tag) error {
switch destination.Kind() { switch destination.Kind() {
case case
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Bool:
default: default:
return errCantAssignf("cannot assign integer to %v", destination) return errCantAssignf("cannot assign integer to %v", destination)
} }
@@ -432,6 +440,8 @@ func setInt(destination reflect.Value, value int64, bytes int) {
// setUint expects a settable destination. // setUint expects a settable destination.
func setUint(destination reflect.Value, value uint64, bytes int) { func setUint(destination reflect.Value, value uint64, bytes int) {
switch { switch {
case destination.Kind() == reflect.Bool:
destination.Set(reflect.ValueOf(value > 0))
case destination.CanInt(): case destination.CanInt():
destination.Set(reflect.ValueOf(int64(value)).Convert(destination.Type())) destination.Set(reflect.ValueOf(int64(value)).Convert(destination.Type()))
case destination.CanUint(): case destination.CanUint():

View File

@@ -15,6 +15,8 @@ var samplePayloads = [][]byte {
/* uint16 */ []byte { byte(LI.WithCN(1)), 0x45, 0x67 }, /* uint16 */ []byte { byte(LI.WithCN(1)), 0x45, 0x67 },
/* uint32 */ []byte { byte(LI.WithCN(3)), 0x45, 0x67, 0x89, 0xAB }, /* uint32 */ []byte { byte(LI.WithCN(3)), 0x45, 0x67, 0x89, 0xAB },
/* uint64 */ []byte { byte(LI.WithCN(7)), 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23 }, /* uint64 */ []byte { byte(LI.WithCN(7)), 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23 },
/* bool */ []byte { byte(SI.WithCN(0)) },
/* bool */ []byte { byte(SI.WithCN(1)) },
/* string */ []byte { byte(SBA.WithCN(7)), 'p', 'u', 'p', 'e', 'v', 'e', 'r' }, /* string */ []byte { byte(SBA.WithCN(7)), 'p', 'u', 'p', 'e', 'v', 'e', 'r' },
/* []byte */ []byte { byte(SBA.WithCN(5)), 'b', 'l', 'a', 'r', 'g' }, /* []byte */ []byte { byte(SBA.WithCN(5)), 'b', 'l', 'a', 'r', 'g' },
/* []string */ []byte { /* []string */ []byte {
@@ -45,6 +47,8 @@ var sampleValues = []any {
/* uint16 */ uint16(0x4567), /* uint16 */ uint16(0x4567),
/* uint32 */ uint32(0x456789AB), /* uint32 */ uint32(0x456789AB),
/* uint64 */ uint64(0x456789ABCDEF0123), /* uint64 */ uint64(0x456789ABCDEF0123),
/* bool */ false,
/* bool */ true,
/* string */ "pupever", /* string */ "pupever",
/* []byte */ "blarg", /* []byte */ "blarg",
/* []string */ []string { /* []string */ []string {
@@ -84,7 +88,9 @@ func TestEncodeAnyTable(test *testing.T) {
0x3456: userDefinedInteger(0x3921), 0x3456: userDefinedInteger(0x3921),
0x1F1F: float32(67.26), 0x1F1F: float32(67.26),
0x0F0F: float64(5.3), 0x0F0F: float64(5.3),
}, KTV.WithCN(0), tu.S(9).AddVar( 0xAAAA: false,
0xBBBB: true,
}, KTV.WithCN(0), tu.S(11).AddVar(
[]byte { []byte {
0xF3, 0xB9, 0xF3, 0xB9,
byte(LSI.WithCN(3)), byte(LSI.WithCN(3)),
@@ -138,6 +144,14 @@ func TestEncodeAnyTable(test *testing.T) {
byte(FP.WithCN(7)), byte(FP.WithCN(7)),
0x40, 0x15, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x40, 0x15, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33,
}, },
[]byte {
0xAA, 0xAA,
byte(SI.WithCN(0)),
},
[]byte {
0xBB, 0xBB,
byte(SI.WithCN(1)),
},
)) ))
if err != nil { test.Fatal(err) } if err != nil { test.Fatal(err) }
} }
@@ -146,7 +160,7 @@ func TestDecodeWrongType(test *testing.T) {
for index, data := range samplePayloads { for index, data := range samplePayloads {
test.Logf("data %2d %v [%s]", index, Tag(data[0]), tu.HexBytes(data[1:])) test.Logf("data %2d %v [%s]", index, Tag(data[0]), tu.HexBytes(data[1:]))
// integers should only assign to other integers // integers should only assign to other integers
if index > 8 { if index > 10 {
cas := func(destination any) { cas := func(destination any) {
n, err := DecodeAnyInto(NewDecoder(bytes.NewBuffer(data[1:])), destination, Tag(data[0])) n, err := DecodeAnyInto(NewDecoder(bytes.NewBuffer(data[1:])), destination, Tag(data[0]))
if err != nil { test.Fatalf("error: %v | n: %d", err, n) } if err != nil { test.Fatalf("error: %v | n: %d", err, n) }
@@ -155,7 +169,7 @@ func TestDecodeWrongType(test *testing.T) {
if reflectValue.Int() != 0 { if reflectValue.Int() != 0 {
test.Fatalf("destination not zero: %v", reflectValue.Elem().Interface()) test.Fatalf("destination not zero: %v", reflectValue.Elem().Interface())
} }
} else { } else if reflectValue.Kind() != reflect.Bool {
if reflectValue.Uint() != 0 { if reflectValue.Uint() != 0 {
test.Fatalf("destination not zero: %v", reflectValue.Elem().Interface()) test.Fatalf("destination not zero: %v", reflectValue.Elem().Interface())
} }
@@ -180,6 +194,8 @@ func TestDecodeWrongType(test *testing.T) {
{ var dest uint32; cas(&dest) } { var dest uint32; cas(&dest) }
test.Log("- uint64") test.Log("- uint64")
{ var dest uint64; cas(&dest) } { var dest uint64; cas(&dest) }
test.Log("- bool")
{ var dest bool; cas(&dest) }
} }
arrayCase := func(destination any) { arrayCase := func(destination any) {
n, err := DecodeAnyInto(NewDecoder(bytes.NewBuffer(data[1:])), destination, Tag(data[0])) n, err := DecodeAnyInto(NewDecoder(bytes.NewBuffer(data[1:])), destination, Tag(data[0]))
@@ -194,19 +210,19 @@ func TestDecodeWrongType(test *testing.T) {
} }
} }
// SBA/LBA types should only assign to other SBA/LBA types // SBA/LBA types should only assign to other SBA/LBA types
if index != 9 && index != 10 { if index != 11 && index != 12 {
test.Log("- string") test.Log("- string")
{ var dest string; arrayCase(&dest) } { var dest string; arrayCase(&dest) }
test.Log("- []byte") test.Log("- []byte")
{ var dest []byte; arrayCase(&dest) } { var dest []byte; arrayCase(&dest) }
} }
// arrays should only assign to other arrays // arrays should only assign to other arrays
if index != 11 { if index != 13 {
test.Log("- []string") test.Log("- []string")
{ var dest []string; arrayCase(&dest) } { var dest []string; arrayCase(&dest) }
} }
// tables should only assign to other tables // tables should only assign to other tables
if index != 12 && index != 13 { if index != 14 && index != 15 {
test.Log("- map[uint16] any") test.Log("- map[uint16] any")
{ var dest = map[uint16] any { }; arrayCase(&dest) } { var dest = map[uint16] any { }; arrayCase(&dest) }
} }
@@ -231,6 +247,12 @@ func TestEncodeDecodeAnyTable(test *testing.T) {
func TestEncodeDecodeAnyDestination(test *testing.T) { func TestEncodeDecodeAnyDestination(test *testing.T) {
var destination any var destination any
for index, data := range samplePayloads { for index, data := range samplePayloads {
if _, isBool := sampleValues[index].(bool); isBool {
// test is invalid for bools because they are never
// created as a skeleton value
continue
}
tag := Tag(data[0]) tag := Tag(data[0])
payload := data[1:] payload := data[1:]
test.Logf("data %2d %v [%s]", index, tag, tu.HexBytes(payload)) test.Logf("data %2d %v [%s]", index, tag, tu.HexBytes(payload))
@@ -349,6 +371,12 @@ func TestTagAny(test *testing.T) {
func TestDecodeAny(test *testing.T) { func TestDecodeAny(test *testing.T) {
for index, payload := range samplePayloads { for index, payload := range samplePayloads {
if _, isBool := sampleValues[index].(bool); isBool {
// test is invalid for bools because they are never
// created as a skeleton value
continue
}
correctValue := sampleValues[index] correctValue := sampleValues[index]
data := payload[1:] data := payload[1:]
decoder := NewDecoder(bytes.NewBuffer(data)) decoder := NewDecoder(bytes.NewBuffer(data))