From 72cd7738dfeec09e78ef52065630d37964b01ef3 Mon Sep 17 00:00:00 2001 From: Patrick Stephen Date: Sun, 24 Mar 2024 23:11:00 +0000 Subject: [PATCH] Replace comment tags with custom decorations, support new @decoration format --- bebop.go | 49 +-- equality_test.go | 82 ++-- gen.go | 28 +- gen_test.go | 4 - gen_union.go | 2 +- main/bebopc-go/main.go | 2 - parse.go | 390 ++++++++++--------- parse_test.go | 301 ++++++++------- testdata/base/decorations.bop | 36 ++ testdata/base/tags.bop | 19 - testdata/generated-always-pointers/tags.go | 417 --------------------- testdata/generated-private/tags.go | 417 --------------------- testdata/generated/tags.go | 417 --------------------- testdata/incompatible/README.md | 7 +- token.go | 2 + tokenize.go | 1 + tokenize_test.go | 2 + 17 files changed, 504 insertions(+), 1672 deletions(-) create mode 100644 testdata/base/decorations.bop delete mode 100644 testdata/base/tags.bop delete mode 100644 testdata/generated-always-pointers/tags.go delete mode 100644 testdata/generated-private/tags.go delete mode 100644 testdata/generated/tags.go diff --git a/bebop.go b/bebop.go index 8770df8..ef6732a 100644 --- a/bebop.go +++ b/bebop.go @@ -47,6 +47,7 @@ type Struct struct { // If ReadOnly is true, generated code for the struct will // provide field getters instead of exporting fields. ReadOnly bool + Decorations } // A Field is an individual, typed data component making up @@ -55,11 +56,7 @@ type Field struct { FieldType Name string Comment string - // Tags are not written by default, and must be enabled via a compiler flag. - Tags []Tag - // DeprecatedMessage is only provided if Deprecated is true. - DeprecatedMessage string - Deprecated bool + Decorations } // A Message is a record type where all fields are optional and keyed to indices. @@ -71,6 +68,7 @@ type Message struct { // Namespace is only provided for imported types, and only // used in code generation. Namespace string + Decorations } // A Union is like a message where explicitly one field will be provided. @@ -82,17 +80,14 @@ type Union struct { // Namespace is only provided for imported types, and only // used in code generation. Namespace string + Decorations } // A UnionField is either a Message, Struct, or Union, defined inline. type UnionField struct { Message *Message Struct *Struct - // Tags are not written by default, ard must be enabled via a compiler flag. - Tags []Tag - // DeprecatedMessage is only provided if Deprecated is true. - DeprecatedMessage string - Deprecated bool + Decorations } // An Enum is a definition that will generate typed enumerable options. @@ -107,19 +102,18 @@ type Enum struct { // otherwise specified, it defaults to uint32 SimpleType string Unsigned bool + Decorations } // An EnumOption is one possible value for a field typed as a specific Enum. type EnumOption struct { Name string Comment string - // DeprecatedMessage is only provided if Deprecated is true. - DeprecatedMessage string // Only one of Value or UintValue should e populated, dependant on // if the enum this option applies to is unsigned. - Value int64 - UintValue uint64 - Deprecated bool + Value int64 + UintValue uint64 + Decorations } // A FieldType is a union of three choices: Simple types, array types, and map types. @@ -146,12 +140,25 @@ type Const struct { Comment string Name string Value string + Decorations } -// A Tag is a Go struct field tag, e.g. `json:"userId,omitempty"` -type Tag struct { - Key string - Value string - // Boolean is set if Value is empty, in the form `key`, not `key:""`. - Boolean bool +type Decorations struct { + // A DeprecatedMessage is only provided if Deprecated is true. + DeprecatedMessage string + // The Deprecated boolean implies the field or top level structure + // this is associated with should not be used. Deprecated message + // fields will not be written to the wire format even if they are + // provided. + Deprecated bool + // Custom annotations are any not recognized by the parser. + // Recognized annotations are deprecated, opcode, flags; + // @decorator will result in an empty value for the key + // 'decorator', and is equivalent to @decorator(""). + // In Go, Custom decorations on fields will result in struct + // tags; empty values for custom decorations in this case + // correspond to boolean struct fields i.e. + // @boolean + @json("abc") = + // 'field Type `boolean json:"abc"`' + Custom map[string]string } diff --git a/equality_test.go b/equality_test.go index dd6b713..15ba7a0 100644 --- a/equality_test.go +++ b/equality_test.go @@ -42,7 +42,6 @@ func (f File) equals(f2 File) error { } for i, cons := range f.Consts { if err := cons.equals(f2.Consts[i]); err != nil { - fmt.Println(cons, f2.Consts[i]) return fmt.Errorf("const %d mismatched: %w", i, err) } } @@ -67,6 +66,34 @@ func (s Struct) equals(s2 Struct) (err error) { return fmt.Errorf("field %d mismatched: %v", i, err) } } + if err := s.Decorations.equals(s2.Decorations); err != nil { + return fmt.Errorf("decorations mismatched: %v", err) + } + return nil +} + +func (d Decorations) equals(d2 Decorations) (err error) { + if d.Deprecated != d2.Deprecated { + return fmt.Errorf("deprecation mismatch: %v vs %v", d.Deprecated, d2.Deprecated) + } + if d.DeprecatedMessage != d2.DeprecatedMessage { + return fmt.Errorf("deprecated message mismatch: %v vs %v", d.DeprecatedMessage, d2.DeprecatedMessage) + } + if len(d.Custom) != len(d2.Custom) { + return fmt.Errorf("custom decorations mismatch: %v vs %v", len(d.Custom), len(d2.Custom)) + } + for k, v := range d.Custom { + v2 := d2.Custom[k] + if v != v2 { + return fmt.Errorf("decoration %v mismatch: %v vs %v", k, v, v2) + } + } + for k, v := range d2.Custom { + v2 := d.Custom[k] + if v != v2 { + return fmt.Errorf("decoration %v mismatch: %v vs %v", k, v, v2) + } + } return nil } @@ -77,20 +104,8 @@ func (f Field) equals(f2 Field) error { if f.Comment != f2.Comment { return fmt.Errorf("comment mismatch: %q vs %q", f.Comment, f2.Comment) } - if f.DeprecatedMessage != f2.DeprecatedMessage { - return fmt.Errorf("deprecated message mismatch: %v vs %v", f.DeprecatedMessage, f2.DeprecatedMessage) - } - if f.Deprecated != f2.Deprecated { - return fmt.Errorf("deprecation mismatch: %v vs %v", f.Deprecated, f2.Deprecated) - } - if len(f.Tags) != len(f2.Tags) { - return fmt.Errorf("tag length mismatch: %v vs %v", len(f.Tags), len(f2.Tags)) - } - for i, tag := range f.Tags { - tag2 := f2.Tags[i] - if tag != tag2 { - return fmt.Errorf("tag %d mismatch: %v vs %v", i, tag, tag2) - } + if err := f.Decorations.equals(f2.Decorations); err != nil { + return fmt.Errorf("decorations mismatched: %v", err) } return f.FieldType.equals(f2.FieldType) } @@ -118,6 +133,9 @@ func (m Message) equals(m2 Message) error { return fmt.Errorf("field %d mismatched: %v", key, err) } } + if err := m.Decorations.equals(m2.Decorations); err != nil { + return fmt.Errorf("decorations mismatched: %v", err) + } return nil } @@ -142,6 +160,9 @@ func (e Enum) equals(e2 Enum) error { if e.Unsigned != e2.Unsigned { return fmt.Errorf("unsigned mismatch: %v vs %v", e.Unsigned, e2.Unsigned) } + if err := e.Decorations.equals(e2.Decorations); err != nil { + return fmt.Errorf("decorations mismatched: %v", err) + } return nil } @@ -152,15 +173,12 @@ func (eo EnumOption) equals(eo2 EnumOption) error { if eo.Comment != eo2.Comment { return fmt.Errorf("comment mismatch: %q vs %q", eo.Comment, eo2.Comment) } - if eo.DeprecatedMessage != eo2.DeprecatedMessage { - return fmt.Errorf("deprecated message mismatch: %v vs %v", eo.DeprecatedMessage, eo2.DeprecatedMessage) - } - if eo.Deprecated != eo2.Deprecated { - return fmt.Errorf("deprecated mismatch: %v vs %v", eo.Deprecated, eo2.Deprecated) - } if eo.Value != eo2.Value { return fmt.Errorf("value mismatch: %v vs %v", eo.Value, eo2.Value) } + if err := eo.Decorations.equals(eo2.Decorations); err != nil { + return fmt.Errorf("decorations mismatched: %v", err) + } return nil } @@ -220,15 +238,15 @@ func (u Union) equals(u2 Union) error { return fmt.Errorf("field %d mismatch: %w", key, err) } } + if err := u.Decorations.equals(u2.Decorations); err != nil { + return fmt.Errorf("decorations mismatched: %v", err) + } return nil } func (uf UnionField) equals(uf2 UnionField) error { - if uf.Deprecated != uf2.Deprecated { - return fmt.Errorf("deprecated mismatch: %v vs %v", uf.Deprecated, uf2.Deprecated) - } - if uf.DeprecatedMessage != uf2.DeprecatedMessage { - return fmt.Errorf("deprecated message mismatch: %v vs %v", uf.DeprecatedMessage, uf2.DeprecatedMessage) + if err := uf.Decorations.equals(uf2.Decorations); err != nil { + return fmt.Errorf("decorations mismatched: %v", err) } if (uf.Struct == nil) != (uf2.Struct == nil) { return fmt.Errorf("field is struct type mismatch: %v vs %v", uf.Struct != nil, uf2.Struct != nil) @@ -242,15 +260,6 @@ func (uf UnionField) equals(uf2 UnionField) error { if uf.Message != nil && uf2.Message != nil { return uf.Message.equals(*uf2.Message) } - if len(uf.Tags) != len(uf2.Tags) { - return fmt.Errorf("tag length mismatch: %v vs %v", len(uf.Tags), len(uf2.Tags)) - } - for i, tag := range uf.Tags { - tag2 := uf2.Tags[i] - if tag != tag2 { - return fmt.Errorf("tag %d mismatch: %v vs %v", i, tag, tag2) - } - } return nil } @@ -264,5 +273,8 @@ func (c Const) equals(c2 Const) error { if c.Value != c2.Value { return fmt.Errorf("value mismatch: %v vs %v", c.Value, c2.Value) } + if err := c.Decorations.equals(c2.Decorations); err != nil { + return fmt.Errorf("decorations mismatched: %v", err) + } return nil } diff --git a/gen.go b/gen.go index 56226c3..9dcaa41 100644 --- a/gen.go +++ b/gen.go @@ -7,6 +7,7 @@ import ( "os" "path" "path/filepath" + "sort" "strconv" "strings" @@ -36,7 +37,6 @@ type GenerateSettings struct { GenerateUnsafeMethods bool SharedMemoryStrings bool - GenerateFieldTags bool PrivateDefinitions bool AlwaysUsePointerReceivers bool } @@ -566,13 +566,22 @@ func writeFieldDefinition(fd Field, w *iohelp.ErrorWriter, readOnly bool, messag if message { typ = "*" + typ } - if settings.GenerateFieldTags && len(fd.Tags) != 0 { + if len(fd.Decorations.Custom) != 0 { formattedTags := []string{} - for _, tag := range fd.Tags { - if tag.Boolean { - formattedTags = append(formattedTags, tag.Key) + orderedTags := make([][2]string, len(fd.Decorations.Custom)) + i := 0 + for key, value := range fd.Decorations.Custom { + orderedTags[i] = [2]string{key, value} + i++ + } + sort.Slice(orderedTags, func(i, j int) bool { + return orderedTags[i][0] < orderedTags[j][0] + }) + for _, tag := range orderedTags { + if tag[1] == "" { + formattedTags = append(formattedTags, tag[0]) } else { - formattedTags = append(formattedTags, fmt.Sprintf("%s:%q", tag.Key, tag.Value)) + formattedTags = append(formattedTags, fmt.Sprintf("%s:%q", tag[0], tag[1])) } } writeLine(w, "\t%s %s `%s`", name, typ, strings.Join(formattedTags, " ")) @@ -859,13 +868,6 @@ func writeComment(w *iohelp.ErrorWriter, depth int, comment string, settings Gen commentLines := strings.Split(comment, "\n") for _, cm := range commentLines { - // If you have tag comments and are generating them as tags, - // you probably don't want them showing up in your code as comments too. - if settings.GenerateFieldTags { - if _, ok := parseCommentTag(cm); ok { - continue - } - } writeLine(w, tbs+"//%s", cm) } } diff --git a/gen_test.go b/gen_test.go index 0feeceb..cdba9f9 100644 --- a/gen_test.go +++ b/gen_test.go @@ -416,7 +416,6 @@ var genTestFiles = []string{ "union_field", "date", "message_1", - "tags", "typed_enums", } @@ -445,7 +444,6 @@ func TestGenerateToFile(t *testing.T) { PackageName: "generated", GenerateUnsafeMethods: true, SharedMemoryStrings: false, - GenerateFieldTags: true, }) if err != nil { t.Fatalf("generation failed: %v", err) @@ -483,7 +481,6 @@ func TestGenerateToFile_Private(t *testing.T) { PackageName: "generated", GenerateUnsafeMethods: true, SharedMemoryStrings: false, - GenerateFieldTags: true, PrivateDefinitions: true, }) if err != nil { @@ -522,7 +519,6 @@ func TestGenerateToFile_AlwaysPointers(t *testing.T) { PackageName: "generated", GenerateUnsafeMethods: true, SharedMemoryStrings: false, - GenerateFieldTags: true, AlwaysUsePointerReceivers: true, }) if err != nil { diff --git a/gen_union.go b/gen_union.go index 6656a2f..485db2d 100644 --- a/gen_union.go +++ b/gen_union.go @@ -169,7 +169,7 @@ func (u Union) Generate(w io.Writer, settings GenerateSettings) { fd.FieldType.Simple = ufd.Message.Name } fd.Name = fd.FieldType.Simple - fd.Tags = ufd.Tags + fd.Decorations = ufd.Decorations fields = append(fields, fieldWithNumber{ UnionField: ufd, Field: fd, diff --git a/main/bebopc-go/main.go b/main/bebopc-go/main.go index 81a16b4..e9bc2fc 100644 --- a/main/bebopc-go/main.go +++ b/main/bebopc-go/main.go @@ -18,7 +18,6 @@ var ( generateUnsafeMethods = flag.Bool("generate-unsafe", false, "whether unchecked additional methods should be generated") shareStringMemory = flag.Bool("share-string-memory", false, "whether strings read in unmarshalling should share memory with the original byte slice") combinedImports = flag.Bool("combined-imports", false, "whether imported files should be combined and generated as one, or to separate files") - generateTags = flag.Bool("generate-tags", false, "whether field tags found in comments should be parsed and generated") privateDefinitions = flag.Bool("private-definitions", false, "whether generated code should be private to the generated package") pointerReceivers = flag.Bool("force-pointer-receivers", false, "whether generated method receivers must be pointers") ) @@ -80,7 +79,6 @@ func run() error { GenerateUnsafeMethods: *generateUnsafeMethods, SharedMemoryStrings: *shareStringMemory, ImportGenerationMode: importMode, - GenerateFieldTags: *generateTags, PrivateDefinitions: *privateDefinitions, AlwaysUsePointerReceivers: *pointerReceivers, } diff --git a/parse.go b/parse.go index 0453ece..3242a26 100644 --- a/parse.go +++ b/parse.go @@ -25,10 +25,12 @@ func ReadFile(r io.Reader) (File, []string, error) { nextRecordOpCode := uint32(0) nextRecordReadOnly := false nextRecordBitFlags := false + nextDecorations := Decorations{Custom: map[string]string{}} warnings := []string{} for tr.Next() { tk := tr.Token() switch tk.kind { + // file headers case tokenKindImport: toks, err := expectNext(tr, tokenKindStringLiteral) if err != nil { @@ -38,6 +40,7 @@ func ReadFile(r io.Reader) (File, []string, error) { imported, _ := strconv.Unquote(string(toks[0].concrete)) f.Imports = append(f.Imports, imported) continue + // comments and whitespace case tokenKindNewline: nextCommentLines = []string{} continue @@ -47,25 +50,32 @@ func ReadFile(r io.Reader) (File, []string, error) { case tokenKindLineComment: nextCommentLines = append(nextCommentLines, sanitizeComment(tk)) continue - case tokenKindOpenSquare: - if err := expectAnyOfNext(tr, tokenKindOpCode, tokenKindFlags); err != nil { + // annotations + case tokenKindOpenSquare, tokenKindAtSign: + k, v, opcodeV, err := readDecoration(tr, tk.kind == tokenKindOpenSquare) + if err != nil { return f, warnings, err } - switch tr.Token().kind { - case tokenKindOpCode: - tr.UnNext() - var err error - nextRecordOpCode, err = readOpCode(tr) - if err != nil { - return f, warnings, err - } - case tokenKindFlags: + // TODO: are decorator keys case sensitive + // TODO: can you put multiple decorators in a row + // TODO: are semicolons required, allowed, disallowed after decorators + // TODO: if you put the same decorator twice, what errors + switch k { + case "opcode": + nextRecordOpCode = opcodeV + case "flags": nextRecordBitFlags = true - if err := expectAnyOfNext(tr, tokenKindCloseSquare); err != nil { - return f, warnings, err + case "deprecated": + if nextDecorations.Deprecated { + return f, warnings, readError(tk, "deprecated cannot be applied to the same target twice") } + nextDecorations.Deprecated = true + nextDecorations.DeprecatedMessage = v + default: + nextDecorations.Custom[k] = v } continue + // top level declarations / statements case tokenKindEnum: if nextRecordOpCode != 0 { return f, warnings, readError(tk, "enums may not have attached op codes") @@ -75,6 +85,9 @@ func ReadFile(r io.Reader) (File, []string, error) { return f, warnings, err } en.Comment = strings.Join(nextCommentLines, "\n") + en.Decorations = nextDecorations + nextDecorations = Decorations{Custom: map[string]string{}} + f.Enums = append(f.Enums, en) case tokenKindReadOnly: nextRecordReadOnly = true @@ -97,8 +110,12 @@ func ReadFile(r io.Reader) (File, []string, error) { st.Comment = strings.Join(nextCommentLines, "\n") st.OpCode = nextRecordOpCode st.ReadOnly = nextRecordReadOnly - f.Structs = append(f.Structs, st) nextRecordReadOnly = false + + st.Decorations = nextDecorations + nextDecorations = Decorations{Custom: map[string]string{}} + + f.Structs = append(f.Structs, st) case tokenKindMessage: if nextRecordBitFlags { return f, warnings, readError(tk, "messages may not use bitflags") @@ -109,6 +126,10 @@ func ReadFile(r io.Reader) (File, []string, error) { } msg.Comment = strings.Join(nextCommentLines, "\n") msg.OpCode = nextRecordOpCode + + msg.Decorations = nextDecorations + nextDecorations = Decorations{Custom: map[string]string{}} + f.Messages = append(f.Messages, msg) case tokenKindUnion: if nextRecordBitFlags { @@ -120,6 +141,10 @@ func ReadFile(r io.Reader) (File, []string, error) { } union.Comment = strings.Join(nextCommentLines, "\n") union.OpCode = nextRecordOpCode + + union.Decorations = nextDecorations + nextDecorations = Decorations{Custom: map[string]string{}} + f.Unions = append(f.Unions, union) case tokenKindConst: if nextRecordBitFlags { @@ -137,11 +162,18 @@ func ReadFile(r io.Reader) (File, []string, error) { if cons.Name == goPackage && cons.SimpleType == typeString { f.GoPackage, _ = strconv.Unquote(cons.Value) } + + cons.Decorations = nextDecorations + nextDecorations = Decorations{Custom: map[string]string{}} + f.Consts = append(f.Consts, cons) } nextCommentLines = []string{} nextRecordOpCode = 0 } + if nextDecorations.Deprecated || len(nextDecorations.Custom) != 0 || nextRecordOpCode != 0 { + return f, warnings, readError(tr.Token(), "file ended with unattached decoration") + } return f, warnings, nil } @@ -200,6 +232,18 @@ func optNewline(tr *tokenReader) { } } +func readUntil(tr *tokenReader, kind tokenKind) ([]token, error) { + toks := []token{} + for tr.Next() { + tk := tr.Token() + if tk.kind == kind { + return toks, nil + } + toks = append(toks, tk) + } + return nil, readError(tr.lastToken, "eof reading until %v", kind) +} + func readEnumOptionValue(tr *tokenReader, previousOptions []EnumOption, bitflags, uinttype bool, bitsize int) (int64, uint64, error) { if _, err := expectNext(tr, tokenKindEquals); err != nil { return 0, 0, err @@ -226,18 +270,6 @@ func readEnumOptionValue(tr *tokenReader, previousOptions []EnumOption, bitflags return readBitflagExpr(tr, previousOptions, uinttype, bitsize) } -func readUntil(tr *tokenReader, kind tokenKind) ([]token, error) { - toks := []token{} - for tr.Next() { - tk := tr.Token() - if tk.kind == kind { - return toks, nil - } - toks = append(toks, tk) - } - return nil, readError(tr.lastToken, "eof reading until %v", kind) -} - func readEnum(tr *tokenReader, bitflags bool) (Enum, error) { en := Enum{ SimpleType: "uint32", @@ -272,8 +304,8 @@ func readEnum(tr *tokenReader, bitflags bool) (Enum, error) { bitsize, uinttype := decodeIntegerType(en.SimpleType) en.Unsigned = uinttype nextCommentLines := []string{} - nextDeprecatedMessage := "" - nextIsDeprecated := false + nextDecorations := Decorations{Custom: map[string]string{}} + for tr.Token().kind != tokenKindCloseCurly { if !tr.Next() { return en, readError(tr.nextToken, "enum definition ended early") @@ -290,27 +322,34 @@ func readEnum(tr *tokenReader, bitflags bool) (Enum, error) { return en, err } en.Options = append(en.Options, EnumOption{ - Name: optName, - Value: signedValue, - UintValue: unsignedValue, - DeprecatedMessage: nextDeprecatedMessage, - Deprecated: nextIsDeprecated, - Comment: strings.Join(nextCommentLines, "\n"), + Name: optName, + Value: signedValue, + UintValue: unsignedValue, + Decorations: nextDecorations, + Comment: strings.Join(nextCommentLines, "\n"), }) - nextDeprecatedMessage = "" - nextIsDeprecated = false - nextCommentLines = []string{} + nextDecorations = Decorations{Custom: map[string]string{}} - case tokenKindOpenSquare: - if nextIsDeprecated { - return en, readError(tk, "expected enum option following deprecated annotation") - } - msg, err := readDeprecated(tr) + nextCommentLines = []string{} + case tokenKindOpenSquare, tokenKindAtSign: + k, v, _, err := readDecoration(tr, tk.kind == tokenKindOpenSquare) if err != nil { return en, err } - nextIsDeprecated = true - nextDeprecatedMessage = msg + switch k { + case "opcode": + return en, readError(tr.nextToken, "opcode annotation not allowed within enum") + case "flags": + return en, readError(tr.nextToken, "flags annotation not allowed within enum") + case "deprecated": + if nextDecorations.Deprecated { + return en, readError(tk, "deprecated cannot be applied to the same target twice") + } + nextDecorations.Deprecated = true + nextDecorations.DeprecatedMessage = v + default: + nextDecorations.Custom[k] = v + } case tokenKindBlockComment: nextCommentLines = append(nextCommentLines, readBlockComment(tr, tk)) case tokenKindLineComment: @@ -321,20 +360,6 @@ func readEnum(tr *tokenReader, bitflags bool) (Enum, error) { return en, nil } -func readDeprecated(tr *tokenReader) (string, error) { - // TODO: can deprecated / op code be followed by a semicolon? - toks, err := expectNext(tr, tokenKindDeprecated, tokenKindOpenParen, tokenKindStringLiteral, - tokenKindCloseParen, tokenKindCloseSquare) - if err != nil { - return "", err - } - // this cannot errors; token readers cannot parse strings - // with missing terminal quotes. - msg, _ := strconv.Unquote(string(toks[2].concrete)) - optNewline(tr) - return msg, nil -} - func skipEndOfLineComments(tr *tokenReader) { for tr.Next() { nextTk := tr.Token() @@ -361,9 +386,8 @@ func readStruct(tr *tokenReader) (Struct, error) { optNewline(tr) nextCommentLines := []string{} - nextCommentTags := []Tag{} - nextDeprecatedMessage := "" - nextIsDeprecated := false + nextDecorations := Decorations{Custom: map[string]string{}} + for tr.Token().kind != tokenKindCloseCurly { if !tr.Next() { return st, readError(tr.nextToken, "struct definition ended early") @@ -384,37 +408,39 @@ func readStruct(tr *tokenReader) (Struct, error) { } fdName := string(toks[0].concrete) st.Fields = append(st.Fields, Field{ - Name: fdName, - FieldType: fdType, - DeprecatedMessage: nextDeprecatedMessage, - Deprecated: nextIsDeprecated, - Comment: strings.Join(nextCommentLines, "\n"), - Tags: nextCommentTags, + Name: fdName, + FieldType: fdType, + Decorations: nextDecorations, + Comment: strings.Join(nextCommentLines, "\n"), }) - nextDeprecatedMessage = "" - nextIsDeprecated = false + nextDecorations = Decorations{Custom: map[string]string{}} + nextCommentLines = []string{} - nextCommentTags = []Tag{} skipEndOfLineComments(tr) - case tokenKindOpenSquare: - if nextIsDeprecated { - return st, readError(tk, "expected field following deprecated annotation") - } - msg, err := readDeprecated(tr) + case tokenKindOpenSquare, tokenKindAtSign: + k, v, _, err := readDecoration(tr, tk.kind == tokenKindOpenSquare) if err != nil { return st, err } - nextIsDeprecated = true - nextDeprecatedMessage = msg + switch k { + case "opcode": + return st, readError(tr.nextToken, "opcode annotation not allowed within struct") + case "flags": + return st, readError(tr.nextToken, "flags annotation not allowed within struct") + case "deprecated": + if nextDecorations.Deprecated { + return st, readError(tk, "deprecated cannot be applied to the same target twice") + } + nextDecorations.Deprecated = true + nextDecorations.DeprecatedMessage = v + default: + nextDecorations.Custom[k] = v + } case tokenKindBlockComment: nextCommentLines = append(nextCommentLines, readBlockComment(tr, tk)) case tokenKindLineComment: - cmt := sanitizeComment(tk) - if tag, ok := parseCommentTag(cmt); ok { - nextCommentTags = append(nextCommentTags, tag) - } - nextCommentLines = append(nextCommentLines, cmt) + nextCommentLines = append(nextCommentLines, sanitizeComment(tk)) } } @@ -506,9 +532,8 @@ func readMessage(tr *tokenReader) (Message, error) { optNewline(tr) nextCommentLines := []string{} - nextCommentTags := []Tag{} - nextDeprecatedMessage := "" - nextIsDeprecated := false + nextDecorations := Decorations{Custom: map[string]string{}} + for tr.Token().kind != tokenKindCloseCurly { if err := expectAnyOfNext(tr, tokenKindNewline, @@ -516,6 +541,7 @@ func readMessage(tr *tokenReader) (Message, error) { tokenKindOpenSquare, tokenKindBlockComment, tokenKindLineComment, + tokenKindAtSign, tokenKindCloseCurly); err != nil { return msg, err } @@ -547,37 +573,39 @@ func readMessage(tr *tokenReader) (Message, error) { fdName := string(toks[0].concrete) msg.Fields[uint8(fdInteger)] = Field{ - Name: fdName, - FieldType: fdType, - DeprecatedMessage: nextDeprecatedMessage, - Deprecated: nextIsDeprecated, - Comment: strings.Join(nextCommentLines, "\n"), - Tags: nextCommentTags, - } - nextDeprecatedMessage = "" - nextIsDeprecated = false + Name: fdName, + FieldType: fdType, + Decorations: nextDecorations, + Comment: strings.Join(nextCommentLines, "\n"), + } + nextDecorations = Decorations{Custom: map[string]string{}} + nextCommentLines = []string{} - nextCommentTags = []Tag{} skipEndOfLineComments(tr) - case tokenKindOpenSquare: - if nextIsDeprecated { - return msg, readError(tk, "expected field following deprecated annotation") - } - dpMsg, err := readDeprecated(tr) + case tokenKindOpenSquare, tokenKindAtSign: + k, v, _, err := readDecoration(tr, tk.kind == tokenKindOpenSquare) if err != nil { return msg, err } - nextIsDeprecated = true - nextDeprecatedMessage = dpMsg + switch k { + case "opcode": + return msg, readError(tr.nextToken, "opcode annotation not allowed within message") + case "flags": + return msg, readError(tr.nextToken, "flags annotation not allowed within message") + case "deprecated": + if nextDecorations.Deprecated { + return msg, readError(tk, "deprecated cannot be applied to the same target twice") + } + nextDecorations.Deprecated = true + nextDecorations.DeprecatedMessage = v + default: + nextDecorations.Custom[k] = v + } case tokenKindBlockComment: nextCommentLines = append(nextCommentLines, readBlockComment(tr, tk)) case tokenKindLineComment: - cmt := sanitizeComment(tk) - if tag, ok := parseCommentTag(cmt); ok { - nextCommentTags = append(nextCommentTags, tag) - } - nextCommentLines = append(nextCommentLines, cmt) + nextCommentLines = append(nextCommentLines, sanitizeComment(tk)) } } @@ -597,9 +625,8 @@ func readUnion(tr *tokenReader) (Union, error) { optNewline(tr) nextCommentLines := []string{} - nextCommentTags := []Tag{} - nextDeprecatedMessage := "" - nextIsDeprecated := false + nextDecorations := Decorations{Custom: map[string]string{}} + for tr.Token().kind != tokenKindCloseCurly { if !tr.Next() { return union, readError(tr.nextToken, "union definition ended early") @@ -641,43 +668,43 @@ func readUnion(tr *tokenReader) (Union, error) { unionFd.Struct = &st } - unionFd.Tags = nextCommentTags - unionFd.Deprecated = nextIsDeprecated - unionFd.DeprecatedMessage = nextDeprecatedMessage + unionFd.Decorations = nextDecorations union.Fields[uint8(fdInteger)] = unionFd - nextDeprecatedMessage = "" - nextIsDeprecated = false + nextDecorations = Decorations{Custom: map[string]string{}} + nextCommentLines = []string{} - nextCommentTags = []Tag{} // This is a close curly-- we must advance past it or the union // will read it and believe it is complete tr.Next() skipEndOfLineComments(tr) optNewline(tr) - - case tokenKindOpenSquare: - if nextIsDeprecated { - return union, readError(tk, "expected field following deprecated annotation") - } - dpMsg, err := readDeprecated(tr) + case tokenKindOpenSquare, tokenKindAtSign: + k, v, _, err := readDecoration(tr, tk.kind == tokenKindOpenSquare) if err != nil { return union, err } - nextIsDeprecated = true - nextDeprecatedMessage = dpMsg + switch k { + case "opcode": + return union, readError(tr.nextToken, "opcode annotation not allowed within union") + case "flags": + return union, readError(tr.nextToken, "flags annotation not allowed within union") + case "deprecated": + if nextDecorations.Deprecated { + return union, readError(tk, "deprecated cannot be applied to the same target twice") + } + nextDecorations.Deprecated = true + nextDecorations.DeprecatedMessage = v + default: + nextDecorations.Custom[k] = v + } case tokenKindBlockComment: nextCommentLines = append(nextCommentLines, readBlockComment(tr, tk)) case tokenKindLineComment: - cmt := sanitizeComment(tk) - if tag, ok := parseCommentTag(cmt); ok { - nextCommentTags = append(nextCommentTags, tag) - } - nextCommentLines = append(nextCommentLines, cmt) + nextCommentLines = append(nextCommentLines, sanitizeComment(tk)) } } - return union, nil } @@ -767,36 +794,63 @@ func readConst(tr *tokenReader) (Const, []string, error) { return cons, warnings, nil } -func readOpCode(tr *tokenReader) (uint32, error) { - if _, err := expectNext(tr, tokenKindOpCode, tokenKindOpenParen); err != nil { - return 0, err +func readDecoration(tr *tokenReader, leadingSquare bool) (k, v string, opcodeV uint32, err error) { + if err := expectAnyOfNext(tr, tokenKindOpCode, tokenKindDeprecated, tokenKindFlags, tokenKindIdent); err != nil { + return "", "", 0, err } - if err := expectAnyOfNext(tr, tokenKindIntegerLiteral, tokenKindStringLiteral); err != nil { - return 0, err - } - var opCode uint32 tk := tr.Token() - if tk.kind == tokenKindIntegerLiteral { - content := string(tk.concrete) - opc, err := strconv.ParseUint(content, 0, 32) - if err != nil { - return 0, readError(tk, err.Error()) + switch tk.kind { + case tokenKindOpCode: + if _, err := expectNext(tr, tokenKindOpenParen); err != nil { + return "", "", 0, err + } + if err := expectAnyOfNext(tr, tokenKindIntegerLiteral, tokenKindStringLiteral); err != nil { + return "", "", 0, err + } + tk := tr.Token() + if tk.kind == tokenKindIntegerLiteral { + content := string(tk.concrete) + opc, err := strconv.ParseUint(content, 0, 32) + if err != nil { + return "", "", 0, readError(tk, err.Error()) + } + opcodeV = uint32(opc) + } else if tk.kind == tokenKindStringLiteral { + tk.concrete = bytes.Trim(tk.concrete, "\"") + if len(tk.concrete) != 4 { + return "", "", 0, readError(tk, "opcode string %q not 4 ascii characters", string(tk.concrete)) + } + opcodeV = bytesToOpCode(*(*[4]byte)(tk.concrete)) } - opCode = uint32(opc) - } else if tk.kind == tokenKindStringLiteral { - tk.concrete = bytes.Trim(tk.concrete, "\"") - if len(tk.concrete) != 4 { - return 0, readError(tk, "opcode string %q not 4 ascii characters", string(tk.concrete)) + if _, err := expectNext(tr, tokenKindCloseParen); err != nil { + return "", "", 0, err } - opCode = bytesToOpCode(*(*[4]byte)(tk.concrete)) + k = "opcode" + case tokenKindDeprecated: + fallthrough + case tokenKindIdent: + if _, err := expectNext(tr, tokenKindOpenParen); err == nil { + tks, err := expectNext(tr, tokenKindStringLiteral, tokenKindCloseParen) + if err != nil { + return "", "", 0, err + } + // this cannot error; token readers cannot parse strings + // with missing terminal quotes. + v, _ = strconv.Unquote(string(tks[0].concrete)) + } + k = string(tk.concrete) + case tokenKindFlags: + k = "flags" } - if _, err := expectNext(tr, tokenKindCloseParen, tokenKindCloseSquare); err != nil { - return 0, err + if leadingSquare { + if _, err := expectNext(tr, tokenKindCloseSquare); err != nil { + return "", "", 0, err + } } + skipEndOfLineComments(tr) optNewline(tr) - - return opCode, nil + return } func readBlockComment(tr *tokenReader, tk token) string { @@ -829,35 +883,3 @@ func kindsStr(ks []tokenKind) string { } return strings.Join(kindsStrs, ", ") } - -func parseCommentTag(s string) (Tag, bool) { - // OK - //[tag(json:"example,omitempty")] - //[tag(json:"more colons::")] - //[tag(boolean)] - // Not OK - // [tag(db:unquotedstring)] - // [tag()] - - if !strings.HasPrefix(s, "[tag(") || !strings.HasSuffix(s, ")]") { - return Tag{}, false - } - s = strings.TrimPrefix(s, "[tag(") - s = strings.TrimSuffix(s, ")]") - key, value, split := strings.Cut(s, ":") - if !split { - return Tag{ - Key: key, - Boolean: true, - }, true - } - var err error - value, err = strconv.Unquote(value) - if err != nil { - return Tag{}, false - } - return Tag{ - Key: key, - Value: value, - }, true -} diff --git a/parse_test.go b/parse_test.go index 3765066..f4e9da7 100644 --- a/parse_test.go +++ b/parse_test.go @@ -13,6 +13,106 @@ func TestReadFile(t *testing.T) { expected File } tcs := []testCase{ + { + file: "decorations", + expected: File{ + Structs: []Struct{ + { + Name: "TaggedStruct2", + OpCode: 1111573057, + Fields: []Field{ + { + Name: "foo", + FieldType: FieldType{ + Simple: "string", + }, + Decorations: Decorations{ + Custom: map[string]string{ + "json": "foo,omitempty", + }, + }, + }, + }, + Decorations: Decorations{ + DeprecatedMessage: "yes", + Deprecated: true, + }, + }, + }, + Messages: []Message{ + { + Name: "TaggedMessage2", + OpCode: 32, + Fields: map[uint8]Field{ + 1: { + Name: "bar", + FieldType: FieldType{ + Simple: "uint8", + }, + Decorations: Decorations{ + Deprecated: true, + DeprecatedMessage: "bar", + Custom: map[string]string{ + "db": "bar", + "hello": "world", + }, + }, + }, + }, + Decorations: Decorations{ + Deprecated: true, + }, + }, + }, + Unions: []Union{ + { + Name: "TaggedUnion2", + Fields: map[uint8]UnionField{ + 1: { + Struct: &Struct{ + Name: "TaggedSubStruct", + Fields: []Field{ + { + Name: "biz", + FieldType: FieldType{ + Simple: "guid", + }, + Decorations: Decorations{ + Custom: map[string]string{ + "four": "four", + }, + }, + }, + }, + }, + Decorations: Decorations{ + Deprecated: true, + Custom: map[string]string{ + "one": "one", + "two": "two", + "boolean": "", + "other": "", + }, + }, + }, + }, + }, + }, + Enums: []Enum{ + { + Name: "TestFlags3", + SimpleType: "int64", + Options: []EnumOption{ + {Name: "None", Value: 0}, + {Name: "Read", Value: 1}, + {Name: "Write", Value: 2}, + {Name: "ReadWrite", Value: 3}, + {Name: "Complex", Value: 19}, + }, + }, + }, + }, + }, { file: "typed_enums", expected: File{ @@ -328,93 +428,6 @@ func TestReadFile(t *testing.T) { }, }, }, - { - file: "tags", - expected: File{ - Structs: []Struct{ - { - Name: "TaggedStruct", - Fields: []Field{ - { - Name: "foo", - FieldType: FieldType{ - Simple: "string", - }, - Comment: "[tag(json:\"foo,omitempty\")]", - Tags: []Tag{ - { - Key: "json", - Value: "foo,omitempty", - }, - }, - }, - }, - }, - }, - Messages: []Message{ - { - Name: "TaggedMessage", - Fields: map[uint8]Field{ - 1: { - Name: "bar", - Comment: "[tag(db:\"bar\")]", - FieldType: FieldType{ - Simple: "uint8", - }, - Tags: []Tag{ - { - Key: "db", - Value: "bar", - }, - }, - }, - }, - }, - }, - Unions: []Union{ - { - Name: "TaggedUnion", - Fields: map[uint8]UnionField{ - 1: { - Tags: []Tag{ - { - Key: "one", - Value: "one", - }, - { - Key: "two", - Value: "two", - }, - { - Key: "boolean", - Boolean: true, - }, - }, - Struct: &Struct{ - Name: "TaggedSubStruct", - Comment: "[tag(one:\"one\")]\n[tag(two:\"two\")]\n[tag(boolean)]", - Fields: []Field{ - { - Name: "biz", - FieldType: FieldType{ - Simple: "guid", - }, - Comment: "[tag(four:\"four\")]", - Tags: []Tag{ - { - Key: "four", - Value: "four", - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, { file: "import", expected: File{ @@ -737,9 +750,11 @@ func TestReadFile(t *testing.T) { Name: "DepM", Fields: map[uint8]Field{ 1: { - Name: "x", - Deprecated: true, - DeprecatedMessage: "x in DepM", + Name: "x", + Decorations: Decorations{ + Deprecated: true, + DeprecatedMessage: "x in DepM", + }, FieldType: FieldType{ Simple: typeInt32, }, @@ -757,18 +772,22 @@ func TestReadFile(t *testing.T) { }, }, 2: { - Name: "y", - Deprecated: true, - DeprecatedMessage: "y in DocM", + Name: "y", + Decorations: Decorations{ + Deprecated: true, + DeprecatedMessage: "y in DocM", + }, FieldType: FieldType{ Simple: typeInt32, }, }, 3: { - Name: "z", - Comment: " Deprecated, documented field ", - Deprecated: true, - DeprecatedMessage: "z in DocM", + Name: "z", + Comment: " Deprecated, documented field ", + Decorations: Decorations{ + Deprecated: true, + DeprecatedMessage: "z in DocM", + }, FieldType: FieldType{ Simple: typeInt32, }, @@ -798,10 +817,12 @@ func TestReadFile(t *testing.T) { Unsigned: true, Options: []EnumOption{ { - Name: "X", - UintValue: 1, - Deprecated: true, - DeprecatedMessage: "X in DepE", + Name: "X", + UintValue: 1, + Decorations: Decorations{ + Deprecated: true, + DeprecatedMessage: "X in DepE", + }, }, }, }, { @@ -815,16 +836,20 @@ func TestReadFile(t *testing.T) { UintValue: 1, Comment: " Documented constant ", }, { - Name: "Y", - UintValue: 2, - Deprecated: true, - DeprecatedMessage: "Y in DocE", + Name: "Y", + UintValue: 2, + Decorations: Decorations{ + Deprecated: true, + DeprecatedMessage: "Y in DocE", + }, }, { - Name: "Z", - UintValue: 3, - Comment: " Deprecated, documented constant ", - Deprecated: true, - DeprecatedMessage: "Z in DocE", + Name: "Z", + UintValue: 3, + Comment: " Deprecated, documented constant ", + Decorations: Decorations{ + Deprecated: true, + DeprecatedMessage: "Z in DocE", + }, }, }, }, @@ -850,10 +875,12 @@ func TestReadFile(t *testing.T) { Name: "Middle", UintValue: 3, }, { - Name: "Beginning", - UintValue: 4, - DeprecatedMessage: "who knows", - Deprecated: true, + Name: "Beginning", + UintValue: 4, + Decorations: Decorations{ + DeprecatedMessage: "who knows", + Deprecated: true, + }, }, }, }, @@ -881,10 +908,12 @@ func TestReadFile(t *testing.T) { Name: "Middle", UintValue: 3, }, { - Name: "Beginning", - UintValue: 4, - DeprecatedMessage: "who knows", - Deprecated: true, + Name: "Beginning", + UintValue: 4, + Decorations: Decorations{ + DeprecatedMessage: "who knows", + Deprecated: true, + }, }, }, }, @@ -1596,9 +1625,11 @@ func TestReadFile(t *testing.T) { }, }, 2: { - Name: "secretTunnel", - Deprecated: true, - DeprecatedMessage: "Nobody react to what I'm about to say...", + Name: "secretTunnel", + Decorations: Decorations{ + Deprecated: true, + DeprecatedMessage: "Nobody react to what I'm about to say...", + }, FieldType: FieldType{ Simple: typeString, }, @@ -1746,8 +1777,8 @@ func TestReadFileError(t *testing.T) { {file: "invalid_const_no_semi", errMessage: "[0:34] expected (Semicolon), got no token"}, {file: "invalid_const_float_no_semi", errMessage: "[0:36] expected (Semicolon), got no token"}, {file: "invalid_enum_with_op_code", errMessage: "[1:4] enums may not have attached op codes"}, - {file: "invalid_op_code_1", errMessage: "[0:2] expected (OpCode, Flags) got Close Square"}, - {file: "invalid_op_code_2", errMessage: "[0:6] expected (OpCode, Flags) got Ident"}, + {file: "invalid_op_code_1", errMessage: "[0:2] expected (OpCode, Deprecated, Flags, Ident) got Close Square"}, + {file: "invalid_op_code_2", errMessage: "[0:15] file ended with unattached decoration"}, {file: "invalid_op_code_3", errMessage: "[0:15] opcode string \"12345\" not 4 ascii characters"}, {file: "invalid_op_code_4", errMessage: "[0:8] expected (Open Paren) got Open Square"}, {file: "invalid_op_code_5", errMessage: "[0:81] strconv.ParseUint: parsing \"1111111111111111111111111111111111111111111111111111111111111111111111111\": value out of range"}, @@ -1756,7 +1787,7 @@ func TestReadFileError(t *testing.T) { {file: "invalid_op_code_8", errMessage: "[0:13] expected (Integer Literal, String Literal) got Ident"}, {file: "invalid_op_code_9", errMessage: "[0:13] opcode string \"123\" not 4 ascii characters"}, {file: "invalid_enum_bad_deprecated", errMessage: "[1:17] expected (String Literal) got Equals"}, - {file: "invalid_enum_double_deprecated", errMessage: "[2:5] expected enum option following deprecated annotation"}, + {file: "invalid_enum_double_deprecated", errMessage: "[2:5] deprecated cannot be applied to the same target twice"}, {file: "invalid_enum_no_close", errMessage: "[2:0] enum definition ended early"}, {file: "invalid_enum_no_curly", errMessage: "[1:0] expected (Colon, Open Curly) got Newline"}, {file: "invalid_enum_no_eq", errMessage: "[1:9] expected (Equals) got Integer Literal"}, @@ -1765,7 +1796,7 @@ func TestReadFileError(t *testing.T) { {file: "invalid_enum_no_semi", errMessage: "[2:0] expected (Semicolon) got Newline"}, {file: "invalid_struct_bad_deprecated", errMessage: "[1:20] expected (String Literal) got Ident"}, {file: "invalid_struct_bad_type", errMessage: "[1:9] expected (Ident, Array, Map) got Open Square"}, - {file: "invalid_struct_double_deprecated", errMessage: "[2:5] expected field following deprecated annotation"}, + {file: "invalid_struct_double_deprecated", errMessage: "[2:5] deprecated cannot be applied to the same target twice"}, {file: "invalid_struct_no_close", errMessage: "[1:14] struct definition ended early"}, {file: "invalid_struct_no_curly", errMessage: "[1:0] expected (Open Curly) got Newline"}, {file: "invalid_struct_no_field_name", errMessage: "[1:10] expected (Ident) got Semicolon"}, @@ -1773,10 +1804,10 @@ func TestReadFileError(t *testing.T) { {file: "invalid_struct_no_semi", errMessage: "[2:0] expected (Semicolon) got Newline"}, {file: "invalid_message_bad_deprecated", errMessage: "[1:18] expected (String Literal) got Arrow"}, {file: "invalid_message_bad_type", errMessage: "[1:14] expected (Ident, Array, Map) got Open Square"}, - {file: "invalid_message_double_deprecated", errMessage: "[2:5] expected field following deprecated annotation"}, + {file: "invalid_message_double_deprecated", errMessage: "[2:5] deprecated cannot be applied to the same target twice"}, {file: "invalid_message_hex_int", errMessage: "[1:7] strconv.ParseUint: parsing \"0x1\": invalid syntax"}, {file: "invalid_message_no_arrow", errMessage: "[1:11] expected (Arrow) got Ident"}, - {file: "invalid_message_no_close", errMessage: "[1:19] expected (Newline, Integer Literal, Open Square, Block Comment, Line Comment, Close Curly), got no token"}, + {file: "invalid_message_no_close", errMessage: "[1:19] expected (Newline, Integer Literal, Open Square, Block Comment, Line Comment, At Sign, Close Curly), got no token"}, {file: "invalid_message_no_curly", errMessage: "[1:0] expected (Open Curly) got Newline"}, {file: "invalid_message_no_field_name", errMessage: "[1:15] expected (Ident) got Semicolon"}, {file: "invalid_message_no_name", errMessage: "[0:9] expected (Ident) got Open Curly"}, @@ -1789,7 +1820,7 @@ func TestReadFileError(t *testing.T) { {file: "invalid_readonly_message", errMessage: "[0:16] expected (Struct) got (Message)"}, {file: "invalid_readonly_comment", errMessage: "[0:19] expected (Struct) got (Block Comment)"}, {file: "invalid_nested_union", errMessage: "[1:14] union fields must be messages or structs"}, - {file: "invalid_union_double_deprecated", errMessage: "[2:5] expected field following deprecated annotation"}, + {file: "invalid_union_double_deprecated", errMessage: "[2:5] deprecated cannot be applied to the same target twice"}, {file: "invalid_union_invalid_deprecated", errMessage: "[1:17] unexpected token '!', expected number, letter, or control sequence"}, {file: "invalid_union_invalid_message", errMessage: "[2:10] strconv.ParseUint: parsing \"-1\": invalid syntax"}, {file: "invalid_union_invalid_struct", errMessage: "[2:19] expected (Ident) got Semicolon"}, @@ -1820,7 +1851,7 @@ func TestReadFileError(t *testing.T) { {file: "invalid_array_bad_key", errMessage: "[1:15] expected (Ident, Array, Map) got Close Square"}, {file: "invalid_array_no_close_square", errMessage: "[1:19] expected (Close Square) got Ident"}, {file: "invalid_array_suffix__no_close_square", errMessage: "[1:13] expected (Close Square) got Ident"}, - {file: "invalid_union_no_message_int", errMessage: "[5:14] expected (Newline, Integer Literal, Open Square, Block Comment, Line Comment, Close Curly) got Ident"}, + {file: "invalid_union_no_message_int", errMessage: "[5:14] expected (Newline, Integer Literal, Open Square, Block Comment, Line Comment, At Sign, Close Curly) got Ident"}, {file: "invalid_bitflags_unknown_name_uint", errMessage: "[2:9] enum option B undefined"}, {file: "invalid_bitflags_unknown_name", errMessage: "[2:9] enum option B undefined"}, {file: "invalid_bitflags_unparseable_int", errMessage: "strconv.ParseInt: parsing \"1111111111111111111111111111111111111111111111111111111111111111111111\": value out of range"}, @@ -1858,15 +1889,3 @@ func TestReadFileError(t *testing.T) { }) } } - -func Test_parseCommentTag(t *testing.T) { - t.Parallel() - t.Run("un-unquoteable", func(t *testing.T) { - t.Parallel() - s := "[tag(k:\"foo)]" - _, ok := parseCommentTag(s) - if ok { - t.Fatalf("parseCommentTag should have failed") - } - }) -} diff --git a/testdata/base/decorations.bop b/testdata/base/decorations.bop new file mode 100644 index 0000000..2fa2e22 --- /dev/null +++ b/testdata/base/decorations.bop @@ -0,0 +1,36 @@ +@deprecated("yes") +@opcode("ABAB") +struct TaggedStruct2 { + @json("foo,omitempty") + string foo; +} + +@deprecated +@opcode(32) +message TaggedMessage2 { + @deprecated("bar") + @db("bar") + @hello("world") + 1 -> uint8 bar; +} + +union TaggedUnion2 { + @one("one") + @two("two") + @boolean + @deprecated + @other + 1 -> struct TaggedSubStruct { + @four("four") + guid biz; + } +} + +@flags +enum TestFlags3 : int64 { + None = 0; + Read = 0x0001; + Write = 1 << 1; + ReadWrite = Read | Write; + Complex = (Read | Write) | 0xF0 & 0x1F; +} \ No newline at end of file diff --git a/testdata/base/tags.bop b/testdata/base/tags.bop deleted file mode 100644 index e0116c1..0000000 --- a/testdata/base/tags.bop +++ /dev/null @@ -1,19 +0,0 @@ -struct TaggedStruct { - //[tag(json:"foo,omitempty")] - string foo; -} - -message TaggedMessage { - //[tag(db:"bar")] - 1 -> uint8 bar; -} - -union TaggedUnion { - //[tag(one:"one")] - //[tag(two:"two")] - //[tag(boolean)] - 1 -> struct TaggedSubStruct { - //[tag(four:"four")] - guid biz; - } -} diff --git a/testdata/generated-always-pointers/tags.go b/testdata/generated-always-pointers/tags.go deleted file mode 100644 index 9f648bb..0000000 --- a/testdata/generated-always-pointers/tags.go +++ /dev/null @@ -1,417 +0,0 @@ -// Code generated by bebopc-go; DO NOT EDIT. - -package generated - -import ( - "github.com/200sc/bebop" - "github.com/200sc/bebop/iohelp" - "io" -) - -var _ bebop.Record = &TaggedStruct{} - -type TaggedStruct struct { - Foo string `json:"foo,omitempty"` -} - -func (bbp *TaggedStruct) MarshalBebopTo(buf []byte) int { - at := 0 - iohelp.WriteUint32Bytes(buf[at:], uint32(len(bbp.Foo))) - copy(buf[at+4:at+4+len(bbp.Foo)], []byte(bbp.Foo)) - at += 4 + len(bbp.Foo) - return at -} - -func (bbp *TaggedStruct) UnmarshalBebop(buf []byte) (err error) { - at := 0 - bbp.Foo, err = iohelp.ReadStringBytes(buf[at:]) - if err != nil { - return err - } - at += 4 + len(bbp.Foo) - return nil -} - -func (bbp *TaggedStruct) MustUnmarshalBebop(buf []byte) { - at := 0 - bbp.Foo = iohelp.MustReadStringBytes(buf[at:]) - at += 4 + len(bbp.Foo) -} - -func (bbp *TaggedStruct) EncodeBebop(iow io.Writer) (err error) { - w := iohelp.NewErrorWriter(iow) - iohelp.WriteUint32(w, uint32(len(bbp.Foo))) - w.Write([]byte(bbp.Foo)) - return w.Err -} - -func (bbp *TaggedStruct) DecodeBebop(ior io.Reader) (err error) { - r := iohelp.NewErrorReader(ior) - bbp.Foo = iohelp.ReadString(r) - return r.Err -} - -func (bbp *TaggedStruct) Size() int { - bodyLen := 0 - bodyLen += 4 + len(bbp.Foo) - return bodyLen -} - -func (bbp *TaggedStruct) MarshalBebop() []byte { - buf := make([]byte, bbp.Size()) - bbp.MarshalBebopTo(buf) - return buf -} - -func MakeTaggedStruct(r *iohelp.ErrorReader) (TaggedStruct, error) { - v := TaggedStruct{} - err := v.DecodeBebop(r) - return v, err -} - -func MakeTaggedStructFromBytes(buf []byte) (TaggedStruct, error) { - v := TaggedStruct{} - err := v.UnmarshalBebop(buf) - return v, err -} - -func MustMakeTaggedStructFromBytes(buf []byte) TaggedStruct { - v := TaggedStruct{} - v.MustUnmarshalBebop(buf) - return v -} - -var _ bebop.Record = &TaggedMessage{} - -type TaggedMessage struct { - Bar *uint8 `db:"bar"` -} - -func (bbp *TaggedMessage) MarshalBebopTo(buf []byte) int { - at := 0 - iohelp.WriteUint32Bytes(buf[at:], uint32(bbp.Size()-4)) - at += 4 - if bbp.Bar != nil { - buf[at] = 1 - at++ - iohelp.WriteUint8Bytes(buf[at:], *bbp.Bar) - at += 1 - } - return at -} - -func (bbp *TaggedMessage) UnmarshalBebop(buf []byte) (err error) { - at := 0 - _ = iohelp.ReadUint32Bytes(buf[at:]) - buf = buf[4:] - for { - switch buf[at] { - case 1: - at += 1 - bbp.Bar = new(uint8) - if len(buf[at:]) < 1 { - return io.ErrUnexpectedEOF - } - (*bbp.Bar) = iohelp.ReadUint8Bytes(buf[at:]) - at += 1 - default: - return nil - } - } -} - -func (bbp *TaggedMessage) MustUnmarshalBebop(buf []byte) { - at := 0 - _ = iohelp.ReadUint32Bytes(buf[at:]) - buf = buf[4:] - for { - switch buf[at] { - case 1: - at += 1 - bbp.Bar = new(uint8) - (*bbp.Bar) = iohelp.ReadUint8Bytes(buf[at:]) - at += 1 - default: - return - } - } -} - -func (bbp *TaggedMessage) EncodeBebop(iow io.Writer) (err error) { - w := iohelp.NewErrorWriter(iow) - iohelp.WriteUint32(w, uint32(bbp.Size()-4)) - if bbp.Bar != nil { - w.Write([]byte{1}) - iohelp.WriteUint8(w, *bbp.Bar) - } - w.Write([]byte{0}) - return w.Err -} - -func (bbp *TaggedMessage) DecodeBebop(ior io.Reader) (err error) { - r := iohelp.NewErrorReader(ior) - bodyLen := iohelp.ReadUint32(r) - r.Reader = &io.LimitedReader{R:r.Reader, N:int64(bodyLen)} - for { - switch iohelp.ReadByte(r) { - case 1: - bbp.Bar = new(uint8) - *bbp.Bar = iohelp.ReadUint8(r) - default: - r.Drain() - return r.Err - } - } -} - -func (bbp *TaggedMessage) Size() int { - bodyLen := 5 - if bbp.Bar != nil { - bodyLen += 1 - bodyLen += 1 - } - return bodyLen -} - -func (bbp *TaggedMessage) MarshalBebop() []byte { - buf := make([]byte, bbp.Size()) - bbp.MarshalBebopTo(buf) - return buf -} - -func MakeTaggedMessage(r *iohelp.ErrorReader) (TaggedMessage, error) { - v := TaggedMessage{} - err := v.DecodeBebop(r) - return v, err -} - -func MakeTaggedMessageFromBytes(buf []byte) (TaggedMessage, error) { - v := TaggedMessage{} - err := v.UnmarshalBebop(buf) - return v, err -} - -func MustMakeTaggedMessageFromBytes(buf []byte) TaggedMessage { - v := TaggedMessage{} - v.MustUnmarshalBebop(buf) - return v -} - -var _ bebop.Record = &TaggedSubStruct{} - -type TaggedSubStruct struct { - Biz [16]byte `four:"four"` -} - -func (bbp *TaggedSubStruct) MarshalBebopTo(buf []byte) int { - at := 0 - iohelp.WriteGUIDBytes(buf[at:], bbp.Biz) - at += 16 - return at -} - -func (bbp *TaggedSubStruct) UnmarshalBebop(buf []byte) (err error) { - at := 0 - if len(buf[at:]) < 16 { - return io.ErrUnexpectedEOF - } - bbp.Biz = iohelp.ReadGUIDBytes(buf[at:]) - at += 16 - return nil -} - -func (bbp *TaggedSubStruct) MustUnmarshalBebop(buf []byte) { - at := 0 - bbp.Biz = iohelp.ReadGUIDBytes(buf[at:]) - at += 16 -} - -func (bbp *TaggedSubStruct) EncodeBebop(iow io.Writer) (err error) { - w := iohelp.NewErrorWriter(iow) - iohelp.WriteGUID(w, bbp.Biz) - return w.Err -} - -func (bbp *TaggedSubStruct) DecodeBebop(ior io.Reader) (err error) { - r := iohelp.NewErrorReader(ior) - bbp.Biz = iohelp.ReadGUID(r) - return r.Err -} - -func (bbp *TaggedSubStruct) Size() int { - bodyLen := 0 - bodyLen += 16 - return bodyLen -} - -func (bbp *TaggedSubStruct) MarshalBebop() []byte { - buf := make([]byte, bbp.Size()) - bbp.MarshalBebopTo(buf) - return buf -} - -func MakeTaggedSubStruct(r *iohelp.ErrorReader) (TaggedSubStruct, error) { - v := TaggedSubStruct{} - err := v.DecodeBebop(r) - return v, err -} - -func MakeTaggedSubStructFromBytes(buf []byte) (TaggedSubStruct, error) { - v := TaggedSubStruct{} - err := v.UnmarshalBebop(buf) - return v, err -} - -func MustMakeTaggedSubStructFromBytes(buf []byte) TaggedSubStruct { - v := TaggedSubStruct{} - v.MustUnmarshalBebop(buf) - return v -} - -var _ bebop.Record = &TaggedUnion{} - -type TaggedUnion struct { - TaggedSubStruct *TaggedSubStruct `one:"one" two:"two" boolean` -} - -func (bbp *TaggedUnion) MarshalBebopTo(buf []byte) int { - at := 0 - iohelp.WriteUint32Bytes(buf[at:], uint32(bbp.Size()-5)) - at += 4 - if bbp.TaggedSubStruct != nil { - buf[at] = 1 - at++ - (*bbp.TaggedSubStruct).MarshalBebopTo(buf[at:]) - { - tmp := (*bbp.TaggedSubStruct) - at += tmp.Size() - } - - return at - } - return at -} - -func (bbp *TaggedUnion) UnmarshalBebop(buf []byte) (err error) { - at := 0 - _ = iohelp.ReadUint32Bytes(buf[at:]) - buf = buf[4:] - if len(buf) == 0 { - return iohelp.ErrUnpopulatedUnion - } - for { - switch buf[at] { - case 1: - at += 1 - bbp.TaggedSubStruct = new(TaggedSubStruct) - (*bbp.TaggedSubStruct), err = MakeTaggedSubStructFromBytes(buf[at:]) - if err != nil { - return err - } - { - tmp := ((*bbp.TaggedSubStruct)) - at += tmp.Size() - } - - return nil - default: - return nil - } - } -} - -func (bbp *TaggedUnion) MustUnmarshalBebop(buf []byte) { - at := 0 - _ = iohelp.ReadUint32Bytes(buf[at:]) - buf = buf[4:] - for { - switch buf[at] { - case 1: - at += 1 - bbp.TaggedSubStruct = new(TaggedSubStruct) - (*bbp.TaggedSubStruct) = MustMakeTaggedSubStructFromBytes(buf[at:]) - { - tmp := ((*bbp.TaggedSubStruct)) - at += tmp.Size() - } - - return - default: - return - } - } -} - -func (bbp *TaggedUnion) EncodeBebop(iow io.Writer) (err error) { - w := iohelp.NewErrorWriter(iow) - iohelp.WriteUint32(w, uint32(bbp.Size()-5)) - if bbp.TaggedSubStruct != nil { - w.Write([]byte{1}) - err = (*bbp.TaggedSubStruct).EncodeBebop(w) - if err != nil { - return err - } - return w.Err - } - return w.Err -} - -func (bbp *TaggedUnion) DecodeBebop(ior io.Reader) (err error) { - r := iohelp.NewErrorReader(ior) - bodyLen := iohelp.ReadUint32(r) - r.Reader = &io.LimitedReader{R: r.Reader, N: int64(bodyLen) + 1} - for { - switch iohelp.ReadByte(r) { - case 1: - bbp.TaggedSubStruct = new(TaggedSubStruct) - (*bbp.TaggedSubStruct), err = MakeTaggedSubStruct(r) - if err != nil { - return err - } - r.Drain() - return r.Err - default: - r.Drain() - return r.Err - } - } -} - -func (bbp *TaggedUnion) Size() int { - bodyLen := 4 - if bbp.TaggedSubStruct != nil { - bodyLen += 1 - { - tmp := (*bbp.TaggedSubStruct) - bodyLen += tmp.Size() - } - - return bodyLen - } - return bodyLen -} - -func (bbp *TaggedUnion) MarshalBebop() []byte { - buf := make([]byte, bbp.Size()) - bbp.MarshalBebopTo(buf) - return buf -} - -func MakeTaggedUnion(r *iohelp.ErrorReader) (TaggedUnion, error) { - v := TaggedUnion{} - err := v.DecodeBebop(r) - return v, err -} - -func MakeTaggedUnionFromBytes(buf []byte) (TaggedUnion, error) { - v := TaggedUnion{} - err := v.UnmarshalBebop(buf) - return v, err -} - -func MustMakeTaggedUnionFromBytes(buf []byte) TaggedUnion { - v := TaggedUnion{} - v.MustUnmarshalBebop(buf) - return v -} - diff --git a/testdata/generated-private/tags.go b/testdata/generated-private/tags.go deleted file mode 100644 index 19cc109..0000000 --- a/testdata/generated-private/tags.go +++ /dev/null @@ -1,417 +0,0 @@ -// Code generated by bebopc-go; DO NOT EDIT. - -package generated - -import ( - "github.com/200sc/bebop" - "github.com/200sc/bebop/iohelp" - "io" -) - -var _ bebop.Record = &taggedStruct{} - -type taggedStruct struct { - foo string `json:"foo,omitempty"` -} - -func (bbp taggedStruct) MarshalBebopTo(buf []byte) int { - at := 0 - iohelp.WriteUint32Bytes(buf[at:], uint32(len(bbp.foo))) - copy(buf[at+4:at+4+len(bbp.foo)], []byte(bbp.foo)) - at += 4 + len(bbp.foo) - return at -} - -func (bbp *taggedStruct) UnmarshalBebop(buf []byte) (err error) { - at := 0 - bbp.foo, err = iohelp.ReadStringBytes(buf[at:]) - if err != nil { - return err - } - at += 4 + len(bbp.foo) - return nil -} - -func (bbp *taggedStruct) MustUnmarshalBebop(buf []byte) { - at := 0 - bbp.foo = iohelp.MustReadStringBytes(buf[at:]) - at += 4 + len(bbp.foo) -} - -func (bbp taggedStruct) EncodeBebop(iow io.Writer) (err error) { - w := iohelp.NewErrorWriter(iow) - iohelp.WriteUint32(w, uint32(len(bbp.foo))) - w.Write([]byte(bbp.foo)) - return w.Err -} - -func (bbp *taggedStruct) DecodeBebop(ior io.Reader) (err error) { - r := iohelp.NewErrorReader(ior) - bbp.foo = iohelp.ReadString(r) - return r.Err -} - -func (bbp taggedStruct) Size() int { - bodyLen := 0 - bodyLen += 4 + len(bbp.foo) - return bodyLen -} - -func (bbp taggedStruct) MarshalBebop() []byte { - buf := make([]byte, bbp.Size()) - bbp.MarshalBebopTo(buf) - return buf -} - -func maketaggedStruct(r *iohelp.ErrorReader) (taggedStruct, error) { - v := taggedStruct{} - err := v.DecodeBebop(r) - return v, err -} - -func maketaggedStructFromBytes(buf []byte) (taggedStruct, error) { - v := taggedStruct{} - err := v.UnmarshalBebop(buf) - return v, err -} - -func mustMaketaggedStructFromBytes(buf []byte) taggedStruct { - v := taggedStruct{} - v.MustUnmarshalBebop(buf) - return v -} - -var _ bebop.Record = &taggedMessage{} - -type taggedMessage struct { - bar *uint8 `db:"bar"` -} - -func (bbp taggedMessage) MarshalBebopTo(buf []byte) int { - at := 0 - iohelp.WriteUint32Bytes(buf[at:], uint32(bbp.Size()-4)) - at += 4 - if bbp.bar != nil { - buf[at] = 1 - at++ - iohelp.WriteUint8Bytes(buf[at:], *bbp.bar) - at += 1 - } - return at -} - -func (bbp *taggedMessage) UnmarshalBebop(buf []byte) (err error) { - at := 0 - _ = iohelp.ReadUint32Bytes(buf[at:]) - buf = buf[4:] - for { - switch buf[at] { - case 1: - at += 1 - bbp.bar = new(uint8) - if len(buf[at:]) < 1 { - return io.ErrUnexpectedEOF - } - (*bbp.bar) = iohelp.ReadUint8Bytes(buf[at:]) - at += 1 - default: - return nil - } - } -} - -func (bbp *taggedMessage) MustUnmarshalBebop(buf []byte) { - at := 0 - _ = iohelp.ReadUint32Bytes(buf[at:]) - buf = buf[4:] - for { - switch buf[at] { - case 1: - at += 1 - bbp.bar = new(uint8) - (*bbp.bar) = iohelp.ReadUint8Bytes(buf[at:]) - at += 1 - default: - return - } - } -} - -func (bbp taggedMessage) EncodeBebop(iow io.Writer) (err error) { - w := iohelp.NewErrorWriter(iow) - iohelp.WriteUint32(w, uint32(bbp.Size()-4)) - if bbp.bar != nil { - w.Write([]byte{1}) - iohelp.WriteUint8(w, *bbp.bar) - } - w.Write([]byte{0}) - return w.Err -} - -func (bbp *taggedMessage) DecodeBebop(ior io.Reader) (err error) { - r := iohelp.NewErrorReader(ior) - bodyLen := iohelp.ReadUint32(r) - r.Reader = &io.LimitedReader{R:r.Reader, N:int64(bodyLen)} - for { - switch iohelp.ReadByte(r) { - case 1: - bbp.bar = new(uint8) - *bbp.bar = iohelp.ReadUint8(r) - default: - r.Drain() - return r.Err - } - } -} - -func (bbp taggedMessage) Size() int { - bodyLen := 5 - if bbp.bar != nil { - bodyLen += 1 - bodyLen += 1 - } - return bodyLen -} - -func (bbp taggedMessage) MarshalBebop() []byte { - buf := make([]byte, bbp.Size()) - bbp.MarshalBebopTo(buf) - return buf -} - -func maketaggedMessage(r *iohelp.ErrorReader) (taggedMessage, error) { - v := taggedMessage{} - err := v.DecodeBebop(r) - return v, err -} - -func maketaggedMessageFromBytes(buf []byte) (taggedMessage, error) { - v := taggedMessage{} - err := v.UnmarshalBebop(buf) - return v, err -} - -func mustMaketaggedMessageFromBytes(buf []byte) taggedMessage { - v := taggedMessage{} - v.MustUnmarshalBebop(buf) - return v -} - -var _ bebop.Record = &taggedSubStruct{} - -type taggedSubStruct struct { - biz [16]byte `four:"four"` -} - -func (bbp taggedSubStruct) MarshalBebopTo(buf []byte) int { - at := 0 - iohelp.WriteGUIDBytes(buf[at:], bbp.biz) - at += 16 - return at -} - -func (bbp *taggedSubStruct) UnmarshalBebop(buf []byte) (err error) { - at := 0 - if len(buf[at:]) < 16 { - return io.ErrUnexpectedEOF - } - bbp.biz = iohelp.ReadGUIDBytes(buf[at:]) - at += 16 - return nil -} - -func (bbp *taggedSubStruct) MustUnmarshalBebop(buf []byte) { - at := 0 - bbp.biz = iohelp.ReadGUIDBytes(buf[at:]) - at += 16 -} - -func (bbp taggedSubStruct) EncodeBebop(iow io.Writer) (err error) { - w := iohelp.NewErrorWriter(iow) - iohelp.WriteGUID(w, bbp.biz) - return w.Err -} - -func (bbp *taggedSubStruct) DecodeBebop(ior io.Reader) (err error) { - r := iohelp.NewErrorReader(ior) - bbp.biz = iohelp.ReadGUID(r) - return r.Err -} - -func (bbp taggedSubStruct) Size() int { - bodyLen := 0 - bodyLen += 16 - return bodyLen -} - -func (bbp taggedSubStruct) MarshalBebop() []byte { - buf := make([]byte, bbp.Size()) - bbp.MarshalBebopTo(buf) - return buf -} - -func maketaggedSubStruct(r *iohelp.ErrorReader) (taggedSubStruct, error) { - v := taggedSubStruct{} - err := v.DecodeBebop(r) - return v, err -} - -func maketaggedSubStructFromBytes(buf []byte) (taggedSubStruct, error) { - v := taggedSubStruct{} - err := v.UnmarshalBebop(buf) - return v, err -} - -func mustMaketaggedSubStructFromBytes(buf []byte) taggedSubStruct { - v := taggedSubStruct{} - v.MustUnmarshalBebop(buf) - return v -} - -var _ bebop.Record = &taggedUnion{} - -type taggedUnion struct { - taggedSubStruct *taggedSubStruct `one:"one" two:"two" boolean` -} - -func (bbp taggedUnion) MarshalBebopTo(buf []byte) int { - at := 0 - iohelp.WriteUint32Bytes(buf[at:], uint32(bbp.Size()-5)) - at += 4 - if bbp.taggedSubStruct != nil { - buf[at] = 1 - at++ - (*bbp.taggedSubStruct).MarshalBebopTo(buf[at:]) - { - tmp := (*bbp.taggedSubStruct) - at += tmp.Size() - } - - return at - } - return at -} - -func (bbp *taggedUnion) UnmarshalBebop(buf []byte) (err error) { - at := 0 - _ = iohelp.ReadUint32Bytes(buf[at:]) - buf = buf[4:] - if len(buf) == 0 { - return iohelp.ErrUnpopulatedUnion - } - for { - switch buf[at] { - case 1: - at += 1 - bbp.taggedSubStruct = new(taggedSubStruct) - (*bbp.taggedSubStruct), err = maketaggedSubStructFromBytes(buf[at:]) - if err != nil { - return err - } - { - tmp := ((*bbp.taggedSubStruct)) - at += tmp.Size() - } - - return nil - default: - return nil - } - } -} - -func (bbp *taggedUnion) MustUnmarshalBebop(buf []byte) { - at := 0 - _ = iohelp.ReadUint32Bytes(buf[at:]) - buf = buf[4:] - for { - switch buf[at] { - case 1: - at += 1 - bbp.taggedSubStruct = new(taggedSubStruct) - (*bbp.taggedSubStruct) = mustMaketaggedSubStructFromBytes(buf[at:]) - { - tmp := ((*bbp.taggedSubStruct)) - at += tmp.Size() - } - - return - default: - return - } - } -} - -func (bbp taggedUnion) EncodeBebop(iow io.Writer) (err error) { - w := iohelp.NewErrorWriter(iow) - iohelp.WriteUint32(w, uint32(bbp.Size()-5)) - if bbp.taggedSubStruct != nil { - w.Write([]byte{1}) - err = (*bbp.taggedSubStruct).EncodeBebop(w) - if err != nil { - return err - } - return w.Err - } - return w.Err -} - -func (bbp *taggedUnion) DecodeBebop(ior io.Reader) (err error) { - r := iohelp.NewErrorReader(ior) - bodyLen := iohelp.ReadUint32(r) - r.Reader = &io.LimitedReader{R: r.Reader, N: int64(bodyLen) + 1} - for { - switch iohelp.ReadByte(r) { - case 1: - bbp.taggedSubStruct = new(taggedSubStruct) - (*bbp.taggedSubStruct), err = maketaggedSubStruct(r) - if err != nil { - return err - } - r.Drain() - return r.Err - default: - r.Drain() - return r.Err - } - } -} - -func (bbp taggedUnion) Size() int { - bodyLen := 4 - if bbp.taggedSubStruct != nil { - bodyLen += 1 - { - tmp := (*bbp.taggedSubStruct) - bodyLen += tmp.Size() - } - - return bodyLen - } - return bodyLen -} - -func (bbp taggedUnion) MarshalBebop() []byte { - buf := make([]byte, bbp.Size()) - bbp.MarshalBebopTo(buf) - return buf -} - -func maketaggedUnion(r *iohelp.ErrorReader) (taggedUnion, error) { - v := taggedUnion{} - err := v.DecodeBebop(r) - return v, err -} - -func maketaggedUnionFromBytes(buf []byte) (taggedUnion, error) { - v := taggedUnion{} - err := v.UnmarshalBebop(buf) - return v, err -} - -func mustMaketaggedUnionFromBytes(buf []byte) taggedUnion { - v := taggedUnion{} - v.MustUnmarshalBebop(buf) - return v -} - diff --git a/testdata/generated/tags.go b/testdata/generated/tags.go deleted file mode 100644 index 21f3453..0000000 --- a/testdata/generated/tags.go +++ /dev/null @@ -1,417 +0,0 @@ -// Code generated by bebopc-go; DO NOT EDIT. - -package generated - -import ( - "github.com/200sc/bebop" - "github.com/200sc/bebop/iohelp" - "io" -) - -var _ bebop.Record = &TaggedStruct{} - -type TaggedStruct struct { - Foo string `json:"foo,omitempty"` -} - -func (bbp TaggedStruct) MarshalBebopTo(buf []byte) int { - at := 0 - iohelp.WriteUint32Bytes(buf[at:], uint32(len(bbp.Foo))) - copy(buf[at+4:at+4+len(bbp.Foo)], []byte(bbp.Foo)) - at += 4 + len(bbp.Foo) - return at -} - -func (bbp *TaggedStruct) UnmarshalBebop(buf []byte) (err error) { - at := 0 - bbp.Foo, err = iohelp.ReadStringBytes(buf[at:]) - if err != nil { - return err - } - at += 4 + len(bbp.Foo) - return nil -} - -func (bbp *TaggedStruct) MustUnmarshalBebop(buf []byte) { - at := 0 - bbp.Foo = iohelp.MustReadStringBytes(buf[at:]) - at += 4 + len(bbp.Foo) -} - -func (bbp TaggedStruct) EncodeBebop(iow io.Writer) (err error) { - w := iohelp.NewErrorWriter(iow) - iohelp.WriteUint32(w, uint32(len(bbp.Foo))) - w.Write([]byte(bbp.Foo)) - return w.Err -} - -func (bbp *TaggedStruct) DecodeBebop(ior io.Reader) (err error) { - r := iohelp.NewErrorReader(ior) - bbp.Foo = iohelp.ReadString(r) - return r.Err -} - -func (bbp TaggedStruct) Size() int { - bodyLen := 0 - bodyLen += 4 + len(bbp.Foo) - return bodyLen -} - -func (bbp TaggedStruct) MarshalBebop() []byte { - buf := make([]byte, bbp.Size()) - bbp.MarshalBebopTo(buf) - return buf -} - -func MakeTaggedStruct(r *iohelp.ErrorReader) (TaggedStruct, error) { - v := TaggedStruct{} - err := v.DecodeBebop(r) - return v, err -} - -func MakeTaggedStructFromBytes(buf []byte) (TaggedStruct, error) { - v := TaggedStruct{} - err := v.UnmarshalBebop(buf) - return v, err -} - -func MustMakeTaggedStructFromBytes(buf []byte) TaggedStruct { - v := TaggedStruct{} - v.MustUnmarshalBebop(buf) - return v -} - -var _ bebop.Record = &TaggedMessage{} - -type TaggedMessage struct { - Bar *uint8 `db:"bar"` -} - -func (bbp TaggedMessage) MarshalBebopTo(buf []byte) int { - at := 0 - iohelp.WriteUint32Bytes(buf[at:], uint32(bbp.Size()-4)) - at += 4 - if bbp.Bar != nil { - buf[at] = 1 - at++ - iohelp.WriteUint8Bytes(buf[at:], *bbp.Bar) - at += 1 - } - return at -} - -func (bbp *TaggedMessage) UnmarshalBebop(buf []byte) (err error) { - at := 0 - _ = iohelp.ReadUint32Bytes(buf[at:]) - buf = buf[4:] - for { - switch buf[at] { - case 1: - at += 1 - bbp.Bar = new(uint8) - if len(buf[at:]) < 1 { - return io.ErrUnexpectedEOF - } - (*bbp.Bar) = iohelp.ReadUint8Bytes(buf[at:]) - at += 1 - default: - return nil - } - } -} - -func (bbp *TaggedMessage) MustUnmarshalBebop(buf []byte) { - at := 0 - _ = iohelp.ReadUint32Bytes(buf[at:]) - buf = buf[4:] - for { - switch buf[at] { - case 1: - at += 1 - bbp.Bar = new(uint8) - (*bbp.Bar) = iohelp.ReadUint8Bytes(buf[at:]) - at += 1 - default: - return - } - } -} - -func (bbp TaggedMessage) EncodeBebop(iow io.Writer) (err error) { - w := iohelp.NewErrorWriter(iow) - iohelp.WriteUint32(w, uint32(bbp.Size()-4)) - if bbp.Bar != nil { - w.Write([]byte{1}) - iohelp.WriteUint8(w, *bbp.Bar) - } - w.Write([]byte{0}) - return w.Err -} - -func (bbp *TaggedMessage) DecodeBebop(ior io.Reader) (err error) { - r := iohelp.NewErrorReader(ior) - bodyLen := iohelp.ReadUint32(r) - r.Reader = &io.LimitedReader{R:r.Reader, N:int64(bodyLen)} - for { - switch iohelp.ReadByte(r) { - case 1: - bbp.Bar = new(uint8) - *bbp.Bar = iohelp.ReadUint8(r) - default: - r.Drain() - return r.Err - } - } -} - -func (bbp TaggedMessage) Size() int { - bodyLen := 5 - if bbp.Bar != nil { - bodyLen += 1 - bodyLen += 1 - } - return bodyLen -} - -func (bbp TaggedMessage) MarshalBebop() []byte { - buf := make([]byte, bbp.Size()) - bbp.MarshalBebopTo(buf) - return buf -} - -func MakeTaggedMessage(r *iohelp.ErrorReader) (TaggedMessage, error) { - v := TaggedMessage{} - err := v.DecodeBebop(r) - return v, err -} - -func MakeTaggedMessageFromBytes(buf []byte) (TaggedMessage, error) { - v := TaggedMessage{} - err := v.UnmarshalBebop(buf) - return v, err -} - -func MustMakeTaggedMessageFromBytes(buf []byte) TaggedMessage { - v := TaggedMessage{} - v.MustUnmarshalBebop(buf) - return v -} - -var _ bebop.Record = &TaggedSubStruct{} - -type TaggedSubStruct struct { - Biz [16]byte `four:"four"` -} - -func (bbp TaggedSubStruct) MarshalBebopTo(buf []byte) int { - at := 0 - iohelp.WriteGUIDBytes(buf[at:], bbp.Biz) - at += 16 - return at -} - -func (bbp *TaggedSubStruct) UnmarshalBebop(buf []byte) (err error) { - at := 0 - if len(buf[at:]) < 16 { - return io.ErrUnexpectedEOF - } - bbp.Biz = iohelp.ReadGUIDBytes(buf[at:]) - at += 16 - return nil -} - -func (bbp *TaggedSubStruct) MustUnmarshalBebop(buf []byte) { - at := 0 - bbp.Biz = iohelp.ReadGUIDBytes(buf[at:]) - at += 16 -} - -func (bbp TaggedSubStruct) EncodeBebop(iow io.Writer) (err error) { - w := iohelp.NewErrorWriter(iow) - iohelp.WriteGUID(w, bbp.Biz) - return w.Err -} - -func (bbp *TaggedSubStruct) DecodeBebop(ior io.Reader) (err error) { - r := iohelp.NewErrorReader(ior) - bbp.Biz = iohelp.ReadGUID(r) - return r.Err -} - -func (bbp TaggedSubStruct) Size() int { - bodyLen := 0 - bodyLen += 16 - return bodyLen -} - -func (bbp TaggedSubStruct) MarshalBebop() []byte { - buf := make([]byte, bbp.Size()) - bbp.MarshalBebopTo(buf) - return buf -} - -func MakeTaggedSubStruct(r *iohelp.ErrorReader) (TaggedSubStruct, error) { - v := TaggedSubStruct{} - err := v.DecodeBebop(r) - return v, err -} - -func MakeTaggedSubStructFromBytes(buf []byte) (TaggedSubStruct, error) { - v := TaggedSubStruct{} - err := v.UnmarshalBebop(buf) - return v, err -} - -func MustMakeTaggedSubStructFromBytes(buf []byte) TaggedSubStruct { - v := TaggedSubStruct{} - v.MustUnmarshalBebop(buf) - return v -} - -var _ bebop.Record = &TaggedUnion{} - -type TaggedUnion struct { - TaggedSubStruct *TaggedSubStruct `one:"one" two:"two" boolean` -} - -func (bbp TaggedUnion) MarshalBebopTo(buf []byte) int { - at := 0 - iohelp.WriteUint32Bytes(buf[at:], uint32(bbp.Size()-5)) - at += 4 - if bbp.TaggedSubStruct != nil { - buf[at] = 1 - at++ - (*bbp.TaggedSubStruct).MarshalBebopTo(buf[at:]) - { - tmp := (*bbp.TaggedSubStruct) - at += tmp.Size() - } - - return at - } - return at -} - -func (bbp *TaggedUnion) UnmarshalBebop(buf []byte) (err error) { - at := 0 - _ = iohelp.ReadUint32Bytes(buf[at:]) - buf = buf[4:] - if len(buf) == 0 { - return iohelp.ErrUnpopulatedUnion - } - for { - switch buf[at] { - case 1: - at += 1 - bbp.TaggedSubStruct = new(TaggedSubStruct) - (*bbp.TaggedSubStruct), err = MakeTaggedSubStructFromBytes(buf[at:]) - if err != nil { - return err - } - { - tmp := ((*bbp.TaggedSubStruct)) - at += tmp.Size() - } - - return nil - default: - return nil - } - } -} - -func (bbp *TaggedUnion) MustUnmarshalBebop(buf []byte) { - at := 0 - _ = iohelp.ReadUint32Bytes(buf[at:]) - buf = buf[4:] - for { - switch buf[at] { - case 1: - at += 1 - bbp.TaggedSubStruct = new(TaggedSubStruct) - (*bbp.TaggedSubStruct) = MustMakeTaggedSubStructFromBytes(buf[at:]) - { - tmp := ((*bbp.TaggedSubStruct)) - at += tmp.Size() - } - - return - default: - return - } - } -} - -func (bbp TaggedUnion) EncodeBebop(iow io.Writer) (err error) { - w := iohelp.NewErrorWriter(iow) - iohelp.WriteUint32(w, uint32(bbp.Size()-5)) - if bbp.TaggedSubStruct != nil { - w.Write([]byte{1}) - err = (*bbp.TaggedSubStruct).EncodeBebop(w) - if err != nil { - return err - } - return w.Err - } - return w.Err -} - -func (bbp *TaggedUnion) DecodeBebop(ior io.Reader) (err error) { - r := iohelp.NewErrorReader(ior) - bodyLen := iohelp.ReadUint32(r) - r.Reader = &io.LimitedReader{R: r.Reader, N: int64(bodyLen) + 1} - for { - switch iohelp.ReadByte(r) { - case 1: - bbp.TaggedSubStruct = new(TaggedSubStruct) - (*bbp.TaggedSubStruct), err = MakeTaggedSubStruct(r) - if err != nil { - return err - } - r.Drain() - return r.Err - default: - r.Drain() - return r.Err - } - } -} - -func (bbp TaggedUnion) Size() int { - bodyLen := 4 - if bbp.TaggedSubStruct != nil { - bodyLen += 1 - { - tmp := (*bbp.TaggedSubStruct) - bodyLen += tmp.Size() - } - - return bodyLen - } - return bodyLen -} - -func (bbp TaggedUnion) MarshalBebop() []byte { - buf := make([]byte, bbp.Size()) - bbp.MarshalBebopTo(buf) - return buf -} - -func MakeTaggedUnion(r *iohelp.ErrorReader) (TaggedUnion, error) { - v := TaggedUnion{} - err := v.DecodeBebop(r) - return v, err -} - -func MakeTaggedUnionFromBytes(buf []byte) (TaggedUnion, error) { - v := TaggedUnion{} - err := v.UnmarshalBebop(buf) - return v, err -} - -func MustMakeTaggedUnionFromBytes(buf []byte) TaggedUnion { - v := TaggedUnion{} - v.MustUnmarshalBebop(buf) - return v -} - diff --git a/testdata/incompatible/README.md b/testdata/incompatible/README.md index e197682..fd45c32 100644 --- a/testdata/incompatible/README.md +++ b/testdata/incompatible/README.md @@ -10,4 +10,9 @@ Rainway allows overwriting primitive types with message, struct, or enum definit We do not allow recursive struct definitions, as they will never terminate when encoded or parsed. Rainway does not have a check for this. -See `compatibility_test.go` for which files specifically fail for which compilers. \ No newline at end of file +See `compatibility_test.go` for which files specifically fail for which compilers. + +## 2 to 3 compatibility + +bebopc-go supports both bebop 2 and bebop 3. The changes from 2 to 3 are not backwards compatible, but the grammar changes can be supported at the same time without adding ambiguity. +TODO: a flag can be provided to require bebop 2 or bebop 3 grammars are required. \ No newline at end of file diff --git a/token.go b/token.go index df965c8..5ac46b8 100644 --- a/token.go +++ b/token.go @@ -51,6 +51,7 @@ const ( tokenKindDoubleCaretLeft tokenKindDoubleCaretRight tokenKindColon + tokenKindAtSign tokenKindNewline @@ -98,6 +99,7 @@ var tokenStrings = map[tokenKind]string{ tokenKindDoubleCaretLeft: "Double Caret Left", tokenKindDoubleCaretRight: "Double Caret Right", tokenKindColon: "Colon", + tokenKindAtSign: "At Sign", } func (tk tokenKind) String() string { diff --git a/tokenize.go b/tokenize.go index 503ff71..e347ff3 100644 --- a/tokenize.go +++ b/tokenize.go @@ -393,6 +393,7 @@ func newTokenTree() *tokenTree { tt.add([]byte{'|'}, simpleToken(tokenKindVerticalBar)) tt.add([]byte{'&'}, simpleToken(tokenKindAmpersand)) tt.add([]byte{':'}, simpleToken(tokenKindColon)) + tt.add([]byte{'@'}, simpleToken(tokenKindAtSign)) tt.add([]byte{'-', '>'}, simpleToken(tokenKindArrow)) tt.add([]byte{'>', '>'}, simpleToken(tokenKindDoubleCaretRight)) tt.add([]byte{'<', '<'}, simpleToken(tokenKindDoubleCaretLeft)) diff --git a/tokenize_test.go b/tokenize_test.go index e67a3e0..0e23001 100644 --- a/tokenize_test.go +++ b/tokenize_test.go @@ -27,6 +27,7 @@ var testTokenizeFiles = []string{ "union", "bitflags", "typed_enums", + "decorations", } func TestTokenize(t *testing.T) { @@ -110,6 +111,7 @@ func TestTokenizeNoSemis(t *testing.T) { "import_b": "import semis cannot be added", "import": "import semis cannot be added", "msgpack_comparison": "we are naively removing semis from within comments", + "decorations": "TODO", } for _, filename := range testTokenizeFiles { filename := filename