From d1f24d37bd447b64e402298bb8eb2479681facf9 Mon Sep 17 00:00:00 2001 From: Ondrej Fabry Date: Fri, 17 Jul 2020 10:36:28 +0200 Subject: Improve binapi generator - simplified Size/Marshal/Unmarshal methods - replace struc in unions with custom marshal/unmarshal - fix imports in generated files - fix mock adapter - generate rpc service using low-level stream API (dumps generate control ping or stream msg..) - move examples/binapi to binapi and generate all API for latest release - add binapigen.Plugin for developing custom generator plugins - optionally generate HTTP handlers (REST API) for RPC services - add govpp program for browsing VPP API Change-Id: I092e9ed2b0c17972b3476463c3d4b14dd76ed42b Signed-off-by: Ondrej Fabry --- binapigen/generate.go | 1518 +++++++++++-------------------------------------- 1 file changed, 324 insertions(+), 1194 deletions(-) (limited to 'binapigen/generate.go') diff --git a/binapigen/generate.go b/binapigen/generate.go index d35427f..689463e 100644 --- a/binapigen/generate.go +++ b/binapigen/generate.go @@ -16,1338 +16,468 @@ package binapigen import ( "fmt" - "io" + "path" "sort" + "strconv" "strings" - "git.fd.io/govpp.git/version" - "github.com/sirupsen/logrus" + "git.fd.io/govpp.git/internal/version" ) -// generatedCodeVersion indicates a version of the generated code. -// It is incremented whenever an incompatibility between the generated code and -// GoVPP api package is introduced; the generated code references -// a constant, api.GoVppAPIPackageIsVersionN (where N is generatedCodeVersion). -const generatedCodeVersion = 2 - -// common message fields +// library dependencies const ( - msgIdField = "_vl_msg_id" - clientIndexField = "client_index" - contextField = "context" - retvalField = "retval" -) + strconvPkg = GoImportPath("strconv") -// global API info -const ( - constModuleName = "ModuleName" // module name constant - constAPIVersion = "APIVersion" // API version constant - constVersionCrc = "VersionCrc" // version CRC constant + govppApiPkg = GoImportPath("git.fd.io/govpp.git/api") + govppCodecPkg = GoImportPath("git.fd.io/govpp.git/codec") ) -// generated fiels +// generated names const ( - unionDataField = "XXX_UnionData" // name for the union data field -) - -// MessageType represents the type of a VPP message -type MessageType int + apiName = "APIFile" // API file name + apiVersion = "APIVersion" // API version number + apiCrc = "VersionCrc" // version checksum -const ( - requestMessage MessageType = iota // VPP request message - replyMessage // VPP reply message - eventMessage // VPP event message - otherMessage // other VPP message + fieldUnionData = "XXX_UnionData" // name for the union data field ) -func generateFileBinapi(ctx *GenFile, w io.Writer) { +func GenerateAPI(gen *Generator, file *File) *GenFile { logf("----------------------------") - logf("generating BINAPI file package: %q", ctx.file.PackageName) + logf(" Generate API - %s", file.Desc.Name) logf("----------------------------") - // generate file header - fmt.Fprintln(w, "// Code generated by GoVPP's binapi-generator. DO NOT EDIT.") - fmt.Fprintln(w, "// versions:") - fmt.Fprintf(w, "// binapi-generator: %s\n", version.Version()) - if ctx.IncludeVppVersion { - fmt.Fprintf(w, "// VPP: %s\n", ctx.VPPVersion) + filename := path.Join(file.FilenamePrefix, file.Desc.Name+".ba.go") + g := gen.NewGenFile(filename, file.GoImportPath) + g.file = file + + g.P("// Code generated by GoVPP's binapi-generator. DO NOT EDIT.") + if !gen.opts.NoVersionInfo { + g.P("// versions:") + g.P("// binapi-generator: ", version.Version()) + g.P("// VPP: ", g.gen.vppVersion) + g.P("// source: ", g.file.Desc.Path) } - fmt.Fprintf(w, "// source: %s\n", ctx.file.Path) - fmt.Fprintln(w) + g.P() - generatePackageHeader(ctx, w) - generateImports(ctx, w) + genPackageComment(g) + g.P("package ", file.PackageName) + g.P() - generateApiInfo(ctx, w) - generateTypes(ctx, w) - generateMessages(ctx, w) + for _, imp := range g.file.Imports { + genImport(g, imp) + } - generateImportRefs(ctx, w) -} + // generate version assertion + g.P("// This is a compile-time assertion to ensure that this generated file") + g.P("// is compatible with the GoVPP api package it is being compiled against.") + g.P("// A compilation error at this line likely means your copy of the") + g.P("// GoVPP api package needs to be updated.") + g.P("const _ = ", govppApiPkg.Ident("GoVppAPIPackageIsVersion"), generatedCodeVersion) + g.P() -func generatePackageHeader(ctx *GenFile, w io.Writer) { - fmt.Fprintln(w, "/*") - fmt.Fprintf(w, "Package %s contains generated code for VPP API file %s.api (%s).\n", - ctx.file.PackageName, ctx.file.Name, ctx.file.Version()) - fmt.Fprintln(w) - fmt.Fprintln(w, "It consists of:") - printObjNum := func(obj string, num int) { - if num > 0 { - if num > 1 { - if strings.HasSuffix(obj, "s") { - obj += "es" - } else { - obj += "s" - } - } - fmt.Fprintf(w, "\t%3d %s\n", num, obj) - } + if !file.isTypesFile() { + g.P("const (") + g.P(apiName, " = ", strconv.Quote(g.file.Desc.Name)) + g.P(apiVersion, " = ", strconv.Quote(g.file.Version)) + g.P(apiCrc, " = ", g.file.Desc.CRC) + g.P(")") + g.P() } - printObjNum("alias", len(ctx.file.Aliases)) - printObjNum("enum", len(ctx.file.Enums)) - printObjNum("message", len(ctx.file.Messages)) - printObjNum("type", len(ctx.file.Structs)) - printObjNum("union", len(ctx.file.Unions)) - fmt.Fprintln(w, "*/") - fmt.Fprintf(w, "package %s\n", ctx.file.PackageName) - fmt.Fprintln(w) -} -func generateImports(ctx *GenFile, w io.Writer) { - fmt.Fprintln(w, "import (") - fmt.Fprintln(w, ` "bytes"`) - fmt.Fprintln(w, ` "context"`) - fmt.Fprintln(w, ` "encoding/binary"`) - fmt.Fprintln(w, ` "fmt"`) - fmt.Fprintln(w, ` "io"`) - fmt.Fprintln(w, ` "math"`) - fmt.Fprintln(w, ` "net"`) - fmt.Fprintln(w, ` "strconv"`) - fmt.Fprintln(w, ` "strings"`) - fmt.Fprintln(w) - fmt.Fprintf(w, "\tapi \"%s\"\n", "git.fd.io/govpp.git/api") - fmt.Fprintf(w, "\tcodec \"%s\"\n", "git.fd.io/govpp.git/codec") - fmt.Fprintf(w, "\tstruc \"%s\"\n", "github.com/lunixbochs/struc") - imports := listImports(ctx) - if len(imports) > 0 { - fmt.Fprintln(w) - for imp, importPath := range imports { - fmt.Fprintf(w, "\t%s \"%s\"\n", imp, importPath) - } + for _, enum := range g.file.Enums { + genEnum(g, enum) } - fmt.Fprintln(w, ")") - fmt.Fprintln(w) - - fmt.Fprintln(w, "// This is a compile-time assertion to ensure that this generated file") - fmt.Fprintln(w, "// is compatible with the GoVPP api package it is being compiled against.") - fmt.Fprintln(w, "// A compilation error at this line likely means your copy of the") - fmt.Fprintln(w, "// GoVPP api package needs to be updated.") - fmt.Fprintf(w, "const _ = api.GoVppAPIPackageIsVersion%d // please upgrade the GoVPP api package\n", generatedCodeVersion) - fmt.Fprintln(w) -} - -func generateApiInfo(ctx *GenFile, w io.Writer) { - // generate module desc - fmt.Fprintln(w, "const (") - fmt.Fprintf(w, "\t// %s is the name of this module.\n", constModuleName) - fmt.Fprintf(w, "\t%s = \"%s\"\n", constModuleName, ctx.file.Name) - - if ctx.IncludeAPIVersion { - fmt.Fprintf(w, "\t// %s is the API version of this module.\n", constAPIVersion) - fmt.Fprintf(w, "\t%s = \"%s\"\n", constAPIVersion, ctx.file.Version()) - fmt.Fprintf(w, "\t// %s is the CRC of this module.\n", constVersionCrc) - fmt.Fprintf(w, "\t%s = %v\n", constVersionCrc, ctx.file.CRC) + for _, alias := range g.file.Aliases { + genAlias(g, alias) } - fmt.Fprintln(w, ")") - fmt.Fprintln(w) -} - -func generateTypes(ctx *GenFile, w io.Writer) { - // generate enums - if len(ctx.file.Enums) > 0 { - for _, enum := range ctx.file.Enums { - if imp, ok := ctx.file.imports[enum.Name]; ok { - if strings.HasSuffix(ctx.file.Name, "_types") { - generateImportedAlias(ctx, w, enum.GoName, imp) - } - continue - } - generateEnum(ctx, w, enum) - } + for _, typ := range g.file.Structs { + genStruct(g, typ) } - - // generate aliases - if len(ctx.file.Aliases) > 0 { - for _, alias := range ctx.file.Aliases { - if imp, ok := ctx.file.imports[alias.Name]; ok { - if strings.HasSuffix(ctx.file.Name, "_types") { - generateImportedAlias(ctx, w, alias.GoName, imp) - } - continue - } - generateAlias(ctx, w, alias) - } + for _, union := range g.file.Unions { + genUnion(g, union) } + genMessages(g) - // generate types - if len(ctx.file.Structs) > 0 { - for _, typ := range ctx.file.Structs { - if imp, ok := ctx.file.imports[typ.Name]; ok { - if strings.HasSuffix(ctx.file.Name, "_types") { - generateImportedAlias(ctx, w, typ.GoName, imp) - } - continue - } - generateStruct(ctx, w, typ) - } - } + return g +} - // generate unions - if len(ctx.file.Unions) > 0 { - for _, union := range ctx.file.Unions { - if imp, ok := ctx.file.imports[union.Name]; ok { - if strings.HasSuffix(ctx.file.Name, "_types") { - generateImportedAlias(ctx, w, union.GoName, imp) +func genPackageComment(g *GenFile) { + apifile := g.file.Desc.Name + ".api" + g.P("// Package ", g.file.PackageName, " contains generated bindings for API file ", apifile, ".") + g.P("//") + g.P("// Contents:") + printObjNum := func(obj string, num int) { + if num > 0 { + if num > 1 { + if strings.HasSuffix(obj, "s") { + obj += "es" + } else { + obj += "s" } - continue } - generateUnion(ctx, w, union) + g.P("// ", fmt.Sprintf("%3d", num), " ", obj) } } + printObjNum("alias", len(g.file.Aliases)) + printObjNum("enum", len(g.file.Enums)) + printObjNum("struct", len(g.file.Structs)) + printObjNum("union", len(g.file.Unions)) + printObjNum("message", len(g.file.Messages)) + g.P("//") } -func generateMessages(ctx *GenFile, w io.Writer) { - if len(ctx.file.Messages) == 0 { +func genImport(g *GenFile, imp string) { + impFile, ok := g.gen.FilesByName[imp] + if !ok { return } - - for _, msg := range ctx.file.Messages { - generateMessage(ctx, w, msg) - } - - // generate message registrations - initFnName := fmt.Sprintf("file_%s_binapi_init", ctx.file.PackageName) - - fmt.Fprintf(w, "func init() { %s() }\n", initFnName) - fmt.Fprintf(w, "func %s() {\n", initFnName) - for _, msg := range ctx.file.Messages { - fmt.Fprintf(w, "\tapi.RegisterMessage((*%s)(nil), \"%s\")\n", - msg.GoName, ctx.file.Name+"."+msg.GoName) - } - fmt.Fprintln(w, "}") - fmt.Fprintln(w) - - // generate list of messages - fmt.Fprintf(w, "// Messages returns list of all messages in this module.\n") - fmt.Fprintln(w, "func AllMessages() []api.Message {") - fmt.Fprintln(w, "\treturn []api.Message{") - for _, msg := range ctx.file.Messages { - fmt.Fprintf(w, "\t(*%s)(nil),\n", msg.GoName) + if impFile.GoImportPath == g.file.GoImportPath { + // Skip generating imports for types in the same package + return } - fmt.Fprintln(w, "}") - fmt.Fprintln(w, "}") + // Generate imports for all dependencies, even if not used + g.Import(impFile.GoImportPath) } -func generateImportRefs(ctx *GenFile, w io.Writer) { - fmt.Fprintf(w, "// Reference imports to suppress errors if they are not otherwise used.\n") - fmt.Fprintf(w, "var _ = api.RegisterMessage\n") - fmt.Fprintf(w, "var _ = codec.DecodeString\n") - fmt.Fprintf(w, "var _ = bytes.NewBuffer\n") - fmt.Fprintf(w, "var _ = context.Background\n") - fmt.Fprintf(w, "var _ = io.Copy\n") - fmt.Fprintf(w, "var _ = strconv.Itoa\n") - fmt.Fprintf(w, "var _ = strings.Contains\n") - fmt.Fprintf(w, "var _ = struc.Pack\n") - fmt.Fprintf(w, "var _ = binary.BigEndian\n") - fmt.Fprintf(w, "var _ = math.Float32bits\n") - fmt.Fprintf(w, "var _ = net.ParseIP\n") - fmt.Fprintf(w, "var _ = fmt.Errorf\n") +func genTypeComment(g *GenFile, goName string, vppName string, objKind string) { + g.P("// ", goName, " defines ", objKind, " '", vppName, "'.") } -func generateComment(ctx *GenFile, w io.Writer, goName string, vppName string, objKind string) { - if objKind == "service" { - fmt.Fprintf(w, "// %s represents RPC service API for %s module.\n", goName, ctx.file.Name) - } else { - fmt.Fprintf(w, "// %s represents VPP binary API %s '%s'.\n", goName, objKind, vppName) - } -} +func genEnum(g *GenFile, enum *Enum) { + logf("gen ENUM %s (%s) - %d entries", enum.GoName, enum.Name, len(enum.Entries)) -func generateEnum(ctx *GenFile, w io.Writer, enum *Enum) { - name := enum.GoName - typ := binapiTypes[enum.Type] + genTypeComment(g, enum.GoName, enum.Name, "enum") - logf(" writing ENUM %q (%s) with %d entries", enum.Name, name, len(enum.Entries)) + gotype := BaseTypesGo[enum.Type] - // generate enum comment - generateComment(ctx, w, name, enum.Name, "enum") - - // generate enum definition - fmt.Fprintf(w, "type %s %s\n", name, typ) - fmt.Fprintln(w) + g.P("type ", enum.GoName, " ", gotype) + g.P() // generate enum entries - fmt.Fprintln(w, "const (") + g.P("const (") for _, entry := range enum.Entries { - fmt.Fprintf(w, "\t%s %s = %v\n", entry.Name, name, entry.Value) + g.P(entry.Name, " ", enum.GoName, " = ", entry.Value) } - fmt.Fprintln(w, ")") - fmt.Fprintln(w) + g.P(")") + g.P() // generate enum conversion maps - fmt.Fprintln(w, "var (") - fmt.Fprintf(w, "%s_name = map[%s]string{\n", name, typ) + g.P("var (") + g.P(enum.GoName, "_name = map[", gotype, "]string{") for _, entry := range enum.Entries { - fmt.Fprintf(w, "\t%v: \"%s\",\n", entry.Value, entry.Name) + g.P(entry.Value, ": ", strconv.Quote(entry.Name), ",") } - fmt.Fprintln(w, "}") - fmt.Fprintf(w, "%s_value = map[string]%s{\n", name, typ) + g.P("}") + g.P(enum.GoName, "_value = map[string]", gotype, "{") for _, entry := range enum.Entries { - fmt.Fprintf(w, "\t\"%s\": %v,\n", entry.Name, entry.Value) + g.P(strconv.Quote(entry.Name), ": ", entry.Value, ",") + } + g.P("}") + g.P(")") + g.P() + + if isEnumFlag(enum) { + size := BaseTypeSizes[enum.Type] * 8 + g.P("func (x ", enum.GoName, ") String() string {") + g.P(" s, ok := ", enum.GoName, "_name[", gotype, "(x)]") + g.P(" if ok { return s }") + g.P(" str := func(n ", gotype, ") string {") + g.P(" s, ok := ", enum.GoName, "_name[", gotype, "(n)]") + g.P(" if ok {") + g.P(" return s") + g.P(" }") + g.P(" return \"", enum.GoName, "(\" + ", strconvPkg.Ident("Itoa"), "(int(n)) + \")\"") + g.P(" }") + g.P(" for i := ", gotype, "(0); i <= ", size, "; i++ {") + g.P(" val := ", gotype, "(x)") + g.P(" if val&(1< 0 { - fmt.Fprintf(w, "[%d]", alias.Length) + gotype = fmt.Sprintf("[%d]%s", alias.Length, gotype) } - dataType := convertToGoType(ctx.file, alias.Type) - fmt.Fprintf(w, "%s\n", dataType) + g.P("type ", alias.GoName, " ", gotype) + g.P() // generate alias-specific methods switch alias.Name { + case "ip4_address": + generateIPConversion(g, alias.GoName, 4) + case "ip6_address": + generateIPConversion(g, alias.GoName, 16) + case "address_with_prefix": + generateAddressWithPrefixConversion(g, alias.GoName) case "mac_address": - fmt.Fprintln(w) - generateMacAddressConversion(w, name) + generateMacAddressConversion(g, alias.GoName) } - - fmt.Fprintln(w) } -func generateStruct(ctx *GenFile, w io.Writer, typ *Struct) { - name := typ.GoName - - logf(" writing STRUCT %q (%s) with %d fields", typ.Name, name, len(typ.Fields)) +func genStruct(g *GenFile, typ *Struct) { + logf("gen STRUCT %s (%s) - %d fields", typ.GoName, typ.Name, len(typ.Fields)) - // generate struct comment - generateComment(ctx, w, name, typ.Name, "type") + genTypeComment(g, typ.GoName, typ.Name, "type") - // generate struct definition - fmt.Fprintf(w, "type %s struct {\n", name) - - // generate struct fields - for i := range typ.Fields { - // skip internal fields - switch strings.ToLower(typ.Name) { - case msgIdField: - continue + if len(typ.Fields) == 0 { + g.P("type ", typ.GoName, " struct {}") + } else { + g.P("type ", typ.GoName, " struct {") + for i := range typ.Fields { + generateField(g, typ.Fields, i) } - - generateField(ctx, w, typ.Fields, i) + g.P("}") } - - // generate end of the struct - fmt.Fprintln(w, "}") - - // generate name getter - generateTypeNameGetter(w, name, typ.Name) + g.P() // generate type-specific methods switch typ.Name { case "address": - fmt.Fprintln(w) - generateIPAddressConversion(w, name) + generateAddressConversion(g, typ.GoName) case "prefix": - fmt.Fprintln(w) - generatePrefixConversion(w, name) + generatePrefixConversion(g, typ.GoName) + case "ip4_prefix": + generateIPPrefixConversion(g, typ.GoName, 4) + case "ip6_prefix": + generateIPPrefixConversion(g, typ.GoName, 6) } - - fmt.Fprintln(w) } -// generateUnionMethods generates methods that implement struc.Custom -// interface to allow having XXX_uniondata field unexported -// TODO: do more testing when unions are actually used in some messages -/*func generateUnionMethods(w io.Writer, structName string) { - // generate struc.Custom implementation for union - fmt.Fprintf(w, ` -func (u *%[1]s) Pack(p []byte, opt *struc.Options) (int, error) { - var b = new(bytes.Buffer) - if err := struc.PackWithOptions(b, u.union_data, opt); err != nil { - return 0, err - } - copy(p, b.Bytes()) - return b.Len(), nil -} -func (u *%[1]s) Unpack(r io.Reader, length int, opt *struc.Options) error { - return struc.UnpackWithOptions(r, u.union_data[:], opt) -} -func (u *%[1]s) Size(opt *struc.Options) int { - return len(u.union_data) -} -func (u *%[1]s) String() string { - return string(u.union_data[:]) -} -`, structName) -}*/ - -/*func generateUnionGetterSetterNew(w io.Writer, structName string, getterField, getterStruct string) { - fmt.Fprintf(w, ` -func %[1]s%[2]s(a %[3]s) (u %[1]s) { - u.Set%[2]s(a) - return -} -func (u *%[1]s) Set%[2]s(a %[3]s) { - copy(u.%[4]s[:], a[:]) -} -func (u *%[1]s) Get%[2]s() (a %[3]s) { - copy(a[:], u.%[4]s[:]) - return -} -`, structName, getterField, getterStruct, unionDataField) -}*/ - -func generateUnion(ctx *GenFile, w io.Writer, union *Union) { - name := union.GoName +func genUnion(g *GenFile, union *Union) { + logf("gen UNION %s (%s) - %d fields", union.GoName, union.Name, len(union.Fields)) - logf(" writing UNION %q (%s) with %d fields", union.Name, name, len(union.Fields)) + genTypeComment(g, union.GoName, union.Name, "union") - // generate struct comment - generateComment(ctx, w, name, union.Name, "union") + g.P("type ", union.GoName, " struct {") - // generate struct definition - fmt.Fprintln(w, "type", name, "struct {") - - // maximum size for union - maxSize := getUnionSize(ctx.file, union) + for _, field := range union.Fields { + g.P("// ", field.GoName, " *", getFieldType(g, field)) + } // generate data field - fmt.Fprintf(w, "\t%s [%d]byte\n", unionDataField, maxSize) + maxSize := getUnionSize(union) + g.P(fieldUnionData, " [", maxSize, "]byte") // generate end of the struct - fmt.Fprintln(w, "}") + g.P("}") + g.P() - // generate name getter - generateTypeNameGetter(w, name, union.Name) - - // generate getters for fields + // generate methods for fields for _, field := range union.Fields { - fieldType := convertToGoType(ctx.file, field.Type) - generateUnionGetterSetter(w, name, field.GoName, fieldType) + genUnionFieldMethods(g, union.GoName, field) } - - // generate union methods - //generateUnionMethods(w, name) - - fmt.Fprintln(w) -} - -func generateUnionGetterSetter(w io.Writer, structName string, getterField, getterStruct string) { - fmt.Fprintf(w, ` -func %[1]s%[2]s(a %[3]s) (u %[1]s) { - u.Set%[2]s(a) - return + g.P() } -func (u *%[1]s) Set%[2]s(a %[3]s) { - var b = new(bytes.Buffer) - if err := struc.Pack(b, &a); err != nil { - return - } - copy(u.%[4]s[:], b.Bytes()) -} -func (u *%[1]s) Get%[2]s() (a %[3]s) { - var b = bytes.NewReader(u.%[4]s[:]) - struc.Unpack(b, &a) - return -} -`, structName, getterField, getterStruct, unionDataField) -} - -func generateMessage(ctx *GenFile, w io.Writer, msg *Message) { - name := msg.GoName - logf(" writing MESSAGE %q (%s) with %d fields", msg.Name, name, len(msg.Fields)) +func genUnionFieldMethods(g *GenFile, structName string, field *Field) { + getterStruct := fieldGoType(g, field) - // generate struct comment - generateComment(ctx, w, name, msg.Name, "message") + // Constructor + g.P("func ", structName, field.GoName, "(a ", getterStruct, ") (u ", structName, ") {") + g.P(" u.Set", field.GoName, "(a)") + g.P(" return") + g.P("}") - // generate struct definition - fmt.Fprintf(w, "type %s struct {", name) + // Setter + g.P("func (u *", structName, ") Set", field.GoName, "(a ", getterStruct, ") {") + g.P(" var buf = ", govppCodecPkg.Ident("NewBuffer"), "(u.", fieldUnionData, "[:])") + encodeField(g, field, "a", func(name string) string { + return "a." + name + }, 0) + g.P("}") - msgType := otherMessage - wasClientIndex := false - - // generate struct fields - n := 0 - for i, field := range msg.Fields { - if i == 1 { - if field.Name == clientIndexField { - // "client_index" as the second member, - // this might be an event message or a request - msgType = eventMessage - wasClientIndex = true - } else if field.Name == contextField { - // reply needs "context" as the second member - msgType = replyMessage - } - } else if i == 2 { - if wasClientIndex && field.Name == contextField { - // request needs "client_index" as the second member - // and "context" as the third member - msgType = requestMessage - } - } - - // skip internal fields - switch strings.ToLower(field.Name) { - case msgIdField: - continue - case clientIndexField, contextField: - if n == 0 { - continue - } - } - n++ - if n == 1 { - fmt.Fprintln(w) - } - - generateField(ctx, w, msg.Fields, i) - } - - // generate end of the struct - fmt.Fprintln(w, "}") - - // generate message methods - generateMessageResetMethod(w, name) - generateMessageNameGetter(w, name, msg.Name) - generateCrcGetter(w, name, msg.CRC) - generateMessageTypeGetter(w, name, msgType) - generateMessageSize(ctx, w, name, msg.Fields) - generateMessageMarshal(ctx, w, name, msg.Fields) - generateMessageUnmarshal(ctx, w, name, msg.Fields) - - fmt.Fprintln(w) + // Getter + g.P("func (u *", structName, ") Get", field.GoName, "() (a ", getterStruct, ") {") + g.P(" var buf = ", govppCodecPkg.Ident("NewBuffer"), "(u.", fieldUnionData, "[:])") + decodeField(g, field, "a", func(name string) string { + return "a." + name + }, 0) + g.P(" return") + g.P("}") + g.P() } -func generateMessageSize(ctx *GenFile, w io.Writer, name string, fields []*Field) { - fmt.Fprintf(w, "func (m *%[1]s) Size() int {\n", name) - - fmt.Fprintf(w, "\tif m == nil { return 0 }\n") - fmt.Fprintf(w, "\tvar size int\n") - - encodeBaseType := func(typ, name string, length int, sizefrom string) bool { - t, ok := BaseTypeNames[typ] - if !ok { - return false - } +func generateField(g *GenFile, fields []*Field, i int) { + field := fields[i] - var s = BaseTypeSizes[t] - switch t { - case STRING: - if length > 0 { - s = length - fmt.Fprintf(w, "\tsize += %d\n", s) - } else { - s = 4 - fmt.Fprintf(w, "\tsize += %d + len(%s)\n", s, name) - } - default: - if sizefrom != "" { - //fmt.Fprintf(w, "\tsize += %d * int(%s)\n", s, sizefrom) - fmt.Fprintf(w, "\tsize += %d * len(%s)\n", s, name) - } else { - if length > 0 { - s = BaseTypeSizes[t] * length - } - fmt.Fprintf(w, "\tsize += %d\n", s) - } - } + logf(" gen FIELD[%d] %s (%s) - type: %q (array: %v/%v)", i, field.GoName, field.Name, field.Type, field.Array, field.Length) - return true + gotype := getFieldType(g, field) + tags := structTags{ + "binapi": fieldTagJSON(field), + "json": fieldTagBinapi(field), } - lvl := 0 - var sizeFields func(fields []*Field, parentName string) - sizeFields = func(fields []*Field, parentName string) { - lvl++ - defer func() { lvl-- }() - - n := 0 - for _, field := range fields { - if field.ParentMessage != nil { - // skip internal fields - switch strings.ToLower(field.Name) { - case msgIdField: - continue - case clientIndexField, contextField: - if n == 0 { - continue - } - } - } - n++ - - fieldName := field.GoName //camelCaseName(strings.TrimPrefix(field.Name, "_")) - name := fmt.Sprintf("%s.%s", parentName, fieldName) - sizeFrom := camelCaseName(strings.TrimPrefix(field.SizeFrom, "_")) - var sizeFromName string - if sizeFrom != "" { - sizeFromName = fmt.Sprintf("%s.%s", parentName, sizeFrom) - } - - fmt.Fprintf(w, "\t// field[%d] %s\n", lvl, name) - - if encodeBaseType(field.Type, name, field.Length, sizeFromName) { - continue - } - - char := fmt.Sprintf("s%d", lvl) - index := fmt.Sprintf("j%d", lvl) - - if field.Array { - if field.Length > 0 { - fmt.Fprintf(w, "\tfor %[2]s := 0; %[2]s < %[1]d; %[2]s ++ {\n", field.Length, index) - } else if field.SizeFrom != "" { - //fmt.Fprintf(w, "\tfor %[1]s := 0; %[1]s < int(%[2]s.%[3]s); %[1]s++ {\n", index, parentName, sizeFrom) - fmt.Fprintf(w, "\tfor %[1]s := 0; %[1]s < len(%[2]s); %[1]s++ {\n", index, name) - } - - fmt.Fprintf(w, "\tvar %[1]s %[2]s\n_ = %[1]s\n", char, convertToGoType(ctx.file, field.Type)) - fmt.Fprintf(w, "\tif %[1]s < len(%[2]s) { %[3]s = %[2]s[%[1]s] }\n", index, name, char) - name = char - } - - if enum := getEnumByRef(ctx.file, field.Type); enum != nil { - if encodeBaseType(enum.Type, name, 0, "") { - } else { - fmt.Fprintf(w, "\t// ??? ENUM %s %s\n", name, enum.Type) - } - } else if alias := getAliasByRef(ctx.file, field.Type); alias != nil { - if encodeBaseType(alias.Type, name, alias.Length, "") { - } else if typ := getTypeByRef(ctx.file, alias.Type); typ != nil { - sizeFields(typ.Fields, name) - } else { - fmt.Fprintf(w, "\t// ??? ALIAS %s %s\n", name, alias.Type) - } - } else if typ := getTypeByRef(ctx.file, field.Type); typ != nil { - sizeFields(typ.Fields, name) - } else if union := getUnionByRef(ctx.file, field.Type); union != nil { - maxSize := getUnionSize(ctx.file, union) - fmt.Fprintf(w, "\tsize += %d\n", maxSize) - } else { - fmt.Fprintf(w, "\t// ??? buf[pos] = (%s)\n", name) - } + g.P(field.GoName, " ", gotype, tags) +} - if field.Array { - fmt.Fprintf(w, "\t}\n") - } - } +func fieldTagBinapi(field *Field) string { + if field.FieldSizeOf != nil { + return "-" } - - sizeFields(fields, "m") - - fmt.Fprintf(w, "return size\n") - - fmt.Fprintf(w, "}\n") + return fmt.Sprintf("%s,omitempty", field.Name) } -func generateMessageMarshal(ctx *GenFile, w io.Writer, name string, fields []*Field) { - fmt.Fprintf(w, "func (m *%[1]s) Marshal(b []byte) ([]byte, error) {\n", name) - - fmt.Fprintf(w, "\to := binary.BigEndian\n") - fmt.Fprintf(w, "\t_ = o\n") - fmt.Fprintf(w, "\tpos := 0\n") - fmt.Fprintf(w, "\t_ = pos\n") - - var buf = new(strings.Builder) - - encodeBaseType := func(typ, name string, length int, sizefrom string) bool { - t, ok := BaseTypeNames[typ] - if !ok { - return false - } - - isArray := length > 0 || sizefrom != "" - - switch t { - case I8, U8, I16, U16, I32, U32, I64, U64, F64: - if isArray { - if length != 0 { - fmt.Fprintf(buf, "\tfor i := 0; i < %d; i++ {\n", length) - } else if sizefrom != "" { - //fmt.Fprintf(buf, "\tfor i := 0; i < int(%s); i++ {\n", sizefrom) - fmt.Fprintf(buf, "\tfor i := 0; i < len(%s); i++ {\n", name) - } - } - } - - switch t { - case I8, U8: - if isArray { - fmt.Fprintf(buf, "\tvar x uint8\n") - fmt.Fprintf(buf, "\tif i < len(%s) { x = uint8(%s[i]) }\n", name, name) - name = "x" - } - fmt.Fprintf(buf, "\tbuf[pos] = uint8(%s)\n", name) - fmt.Fprintf(buf, "\tpos += 1\n") - if isArray { - fmt.Fprintf(buf, "\t}\n") - } - case I16, U16: - if isArray { - fmt.Fprintf(buf, "\tvar x uint16\n") - fmt.Fprintf(buf, "\tif i < len(%s) { x = uint16(%s[i]) }\n", name, name) - name = "x" - } - fmt.Fprintf(buf, "\to.PutUint16(buf[pos:pos+2], uint16(%s))\n", name) - fmt.Fprintf(buf, "\tpos += 2\n") - if isArray { - fmt.Fprintf(buf, "\t}\n") - } - case I32, U32: - if isArray { - fmt.Fprintf(buf, "\tvar x uint32\n") - fmt.Fprintf(buf, "\tif i < len(%s) { x = uint32(%s[i]) }\n", name, name) - name = "x" - } - fmt.Fprintf(buf, "\to.PutUint32(buf[pos:pos+4], uint32(%s))\n", name) - fmt.Fprintf(buf, "\tpos += 4\n") - if isArray { - fmt.Fprintf(buf, "\t}\n") - } - case I64, U64: - if isArray { - fmt.Fprintf(buf, "\tvar x uint64\n") - fmt.Fprintf(buf, "\tif i < len(%s) { x = uint64(%s[i]) }\n", name, name) - name = "x" - } - fmt.Fprintf(buf, "\to.PutUint64(buf[pos:pos+8], uint64(%s))\n", name) - fmt.Fprintf(buf, "\tpos += 8\n") - if isArray { - fmt.Fprintf(buf, "\t}\n") - } - case F64: - if isArray { - fmt.Fprintf(buf, "\tvar x float64\n") - fmt.Fprintf(buf, "\tif i < len(%s) { x = float64(%s[i]) }\n", name, name) - name = "x" - } - fmt.Fprintf(buf, "\to.PutUint64(buf[pos:pos+8], math.Float64bits(float64(%s)))\n", name) - fmt.Fprintf(buf, "\tpos += 8\n") - if isArray { - fmt.Fprintf(buf, "\t}\n") - } - case BOOL: - fmt.Fprintf(buf, "\tif %s { buf[pos] = 1 }\n", name) - fmt.Fprintf(buf, "\tpos += 1\n") - case STRING: - if length != 0 { - fmt.Fprintf(buf, "\tcopy(buf[pos:pos+%d], %s)\n", length, name) - fmt.Fprintf(buf, "\tpos += %d\n", length) - } else { - fmt.Fprintf(buf, "\to.PutUint32(buf[pos:pos+4], uint32(len(%s)))\n", name) - fmt.Fprintf(buf, "\tpos += 4\n") - fmt.Fprintf(buf, "\tcopy(buf[pos:pos+len(%s)], %s[:])\n", name, name) - fmt.Fprintf(buf, "\tpos += len(%s)\n", name) - } - default: - fmt.Fprintf(buf, "\t// ??? %s %s\n", name, typ) - return false +func fieldTagJSON(field *Field) string { + typ := fromApiType(field.Type) + if field.Array { + if field.Length > 0 { + typ = fmt.Sprintf("%s[%d]", typ, field.Length) + } else if field.SizeFrom != "" { + typ = fmt.Sprintf("%s[%s]", typ, field.SizeFrom) + } else { + typ = fmt.Sprintf("%s[]", typ) } - return true } - - lvl := 0 - var encodeFields func(fields []*Field, parentName string) - encodeFields = func(fields []*Field, parentName string) { - lvl++ - defer func() { lvl-- }() - - n := 0 - for _, field := range fields { - if field.ParentMessage != nil { - // skip internal fields - switch strings.ToLower(field.Name) { - case msgIdField: - continue - case clientIndexField, contextField: - if n == 0 { - continue - } - } - } - n++ - - getFieldName := func(name string) string { - fieldName := camelCaseName(strings.TrimPrefix(name, "_")) - return fmt.Sprintf("%s.%s", parentName, fieldName) - } - - fieldName := camelCaseName(strings.TrimPrefix(field.Name, "_")) - name := fmt.Sprintf("%s.%s", parentName, fieldName) - sizeFrom := camelCaseName(strings.TrimPrefix(field.SizeFrom, "_")) - var sizeFromName string - if sizeFrom != "" { - sizeFromName = fmt.Sprintf("%s.%s", parentName, sizeFrom) - } - - fmt.Fprintf(buf, "\t// field[%d] %s\n", lvl, name) - - getSizeOfField := func() *Field { - for _, f := range fields { - if f.SizeFrom == field.Name { - return f - } - } - return nil - } - if f := getSizeOfField(); f != nil { - if encodeBaseType(field.Type, fmt.Sprintf("len(%s)", getFieldName(f.Name)), field.Length, "") { - continue - } - panic(fmt.Sprintf("failed to encode base type of sizefrom field: %s", field.Name)) - } - - if encodeBaseType(field.Type, name, field.Length, sizeFromName) { - continue - } - - char := fmt.Sprintf("v%d", lvl) - index := fmt.Sprintf("j%d", lvl) - - if field.Array { - if field.Length > 0 { - fmt.Fprintf(buf, "\tfor %[2]s := 0; %[2]s < %[1]d; %[2]s ++ {\n", field.Length, index) - } else if field.SizeFrom != "" { - //fmt.Fprintf(buf, "\tfor %[1]s := 0; %[1]s < int(%[2]s.%[3]s); %[1]s++ {\n", index, parentName, sizeFrom) - fmt.Fprintf(buf, "\tfor %[1]s := 0; %[1]s < len(%[2]s); %[1]s++ {\n", index, name) - } - - fmt.Fprintf(buf, "\tvar %s %s\n", char, convertToGoType(ctx.file, field.Type)) - fmt.Fprintf(buf, "\tif %[1]s < len(%[2]s) { %[3]s = %[2]s[%[1]s] }\n", index, name, char) - name = char - } - - if enum := getEnumByRef(ctx.file, field.Type); enum != nil { - if encodeBaseType(enum.Type, name, 0, "") { - } else { - fmt.Fprintf(buf, "\t// ??? ENUM %s %s\n", name, enum.Type) - } - } else if alias := getAliasByRef(ctx.file, field.Type); alias != nil { - if encodeBaseType(alias.Type, name, alias.Length, "") { - } else if typ := getTypeByRef(ctx.file, alias.Type); typ != nil { - encodeFields(typ.Fields, name) - } else { - fmt.Fprintf(buf, "\t// ??? ALIAS %s %s\n", name, alias.Type) - } - } else if typ := getTypeByRef(ctx.file, field.Type); typ != nil { - encodeFields(typ.Fields, name) - } else if union := getUnionByRef(ctx.file, field.Type); union != nil { - maxSize := getUnionSize(ctx.file, union) - fmt.Fprintf(buf, "\tcopy(buf[pos:pos+%d], %s.%s[:])\n", maxSize, name, unionDataField) - fmt.Fprintf(buf, "\tpos += %d\n", maxSize) - } else { - fmt.Fprintf(buf, "\t// ??? buf[pos] = (%s)\n", name) - } - - if field.Array { - fmt.Fprintf(buf, "\t}\n") - } - } + tag := []string{ + typ, + fmt.Sprintf("name=%s", field.Name), } - - encodeFields(fields, "m") - - fmt.Fprintf(w, "\tvar buf []byte\n") - fmt.Fprintf(w, "\tif b == nil {\n") - fmt.Fprintf(w, "\tbuf = make([]byte, m.Size())\n") - fmt.Fprintf(w, "\t} else {\n") - fmt.Fprintf(w, "\tbuf = b\n") - fmt.Fprintf(w, "\t}\n") - fmt.Fprint(w, buf.String()) - - fmt.Fprintf(w, "return buf, nil\n") - - fmt.Fprintf(w, "}\n") -} - -func generateMessageUnmarshal(ctx *GenFile, w io.Writer, name string, fields []*Field) { - fmt.Fprintf(w, "func (m *%[1]s) Unmarshal(tmp []byte) error {\n", name) - - fmt.Fprintf(w, "\to := binary.BigEndian\n") - fmt.Fprintf(w, "\t_ = o\n") - fmt.Fprintf(w, "\tpos := 0\n") - fmt.Fprintf(w, "\t_ = pos\n") - - decodeBaseType := func(typ, orig, name string, length int, sizefrom string, alloc bool) bool { - t, ok := BaseTypeNames[typ] - if !ok { - return false - } - - isArray := length > 0 || sizefrom != "" - - switch t { - case I8, U8, I16, U16, I32, U32, I64, U64, F64: - if isArray { - if alloc { - if length != 0 { - fmt.Fprintf(w, "\t%s = make([]%s, %d)\n", name, orig, length) - } else if sizefrom != "" { - fmt.Fprintf(w, "\t%s = make([]%s, %s)\n", name, orig, sizefrom) - } - } - fmt.Fprintf(w, "\tfor i := 0; i < len(%s); i++ {\n", name) - } - } - - switch t { - case I8, U8: - if isArray { - fmt.Fprintf(w, "\t%s[i] = %s(tmp[pos])\n", name, convertToGoType(ctx.file, typ)) - } else { - fmt.Fprintf(w, "\t%s = %s(tmp[pos])\n", name, orig) - } - fmt.Fprintf(w, "\tpos += 1\n") - if isArray { - fmt.Fprintf(w, "\t}\n") - } - case I16, U16: - if isArray { - fmt.Fprintf(w, "\t%s[i] = %s(o.Uint16(tmp[pos:pos+2]))\n", name, orig) - } else { - fmt.Fprintf(w, "\t%s = %s(o.Uint16(tmp[pos:pos+2]))\n", name, orig) - } - fmt.Fprintf(w, "\tpos += 2\n") - if isArray { - fmt.Fprintf(w, "\t}\n") - } - case I32, U32: - if isArray { - fmt.Fprintf(w, "\t%s[i] = %s(o.Uint32(tmp[pos:pos+4]))\n", name, orig) - } else { - fmt.Fprintf(w, "\t%s = %s(o.Uint32(tmp[pos:pos+4]))\n", name, orig) - } - fmt.Fprintf(w, "\tpos += 4\n") - if isArray { - fmt.Fprintf(w, "\t}\n") - } - case I64, U64: - if isArray { - fmt.Fprintf(w, "\t%s[i] = %s(o.Uint64(tmp[pos:pos+8]))\n", name, orig) - } else { - fmt.Fprintf(w, "\t%s = %s(o.Uint64(tmp[pos:pos+8]))\n", name, orig) - } - fmt.Fprintf(w, "\tpos += 8\n") - if isArray { - fmt.Fprintf(w, "\t}\n") - } - case F64: - if isArray { - fmt.Fprintf(w, "\t%s[i] = %s(math.Float64frombits(o.Uint64(tmp[pos:pos+8])))\n", name, orig) - } else { - fmt.Fprintf(w, "\t%s = %s(math.Float64frombits(o.Uint64(tmp[pos:pos+8])))\n", name, orig) - } - fmt.Fprintf(w, "\tpos += 8\n") - if isArray { - fmt.Fprintf(w, "\t}\n") - } - case BOOL: - fmt.Fprintf(w, "\t%s = tmp[pos] != 0\n", name) - fmt.Fprintf(w, "\tpos += 1\n") - case STRING: - if length != 0 { - fmt.Fprintf(w, "\t{\n") - fmt.Fprintf(w, "\tnul := bytes.Index(tmp[pos:pos+%d], []byte{0x00})\n", length) - fmt.Fprintf(w, "\t%[1]s = codec.DecodeString(tmp[pos:pos+nul])\n", name) - fmt.Fprintf(w, "\tpos += %d\n", length) - fmt.Fprintf(w, "\t}\n") - } else { - fmt.Fprintf(w, "\t{\n") - fmt.Fprintf(w, "\tsiz := o.Uint32(tmp[pos:pos+4])\n") - fmt.Fprintf(w, "\tpos += 4\n") - fmt.Fprintf(w, "\t%[1]s = codec.DecodeString(tmp[pos:pos+int(siz)])\n", name) - fmt.Fprintf(w, "\tpos += len(%s)\n", name) - fmt.Fprintf(w, "\t}\n") - } - default: - fmt.Fprintf(w, "\t// ??? %s %s\n", name, typ) - return false - } - return true + if limit, ok := field.Meta["limit"]; ok && limit.(int) > 0 { + tag = append(tag, fmt.Sprintf("limit=%s", limit)) } - - lvl := 0 - var decodeFields func(fields []*Field, parentName string) - decodeFields = func(fields []*Field, parentName string) { - lvl++ - defer func() { lvl-- }() - - n := 0 - for _, field := range fields { - if field.ParentMessage != nil { - // skip internal fields - switch strings.ToLower(field.Name) { - case msgIdField: - continue - case clientIndexField, contextField: - if n == 0 { - continue - } - } - } - n++ - - fieldName := camelCaseName(strings.TrimPrefix(field.Name, "_")) - name := fmt.Sprintf("%s.%s", parentName, fieldName) - sizeFrom := camelCaseName(strings.TrimPrefix(field.SizeFrom, "_")) - var sizeFromName string - if sizeFrom != "" { - sizeFromName = fmt.Sprintf("%s.%s", parentName, sizeFrom) - } - - fmt.Fprintf(w, "\t// field[%d] %s\n", lvl, name) - - if decodeBaseType(field.Type, convertToGoType(ctx.file, field.Type), name, field.Length, sizeFromName, true) { - continue - } - - //char := fmt.Sprintf("v%d", lvl) - index := fmt.Sprintf("j%d", lvl) - - if field.Array { - if field.Length > 0 { - fmt.Fprintf(w, "\tfor %[2]s := 0; %[2]s < %[1]d; %[2]s ++ {\n", field.Length, index) - } else if field.SizeFrom != "" { - fieldType := getFieldType(ctx, field) - if strings.HasPrefix(fieldType, "[]") { - fmt.Fprintf(w, "\t%s = make(%s, int(%s.%s))\n", name, fieldType, parentName, sizeFrom) - } - fmt.Fprintf(w, "\tfor %[1]s := 0; %[1]s < int(%[2]s.%[3]s); %[1]s++ {\n", index, parentName, sizeFrom) - } - - /*fmt.Fprintf(w, "\tvar %s %s\n", char, convertToGoType(ctx, field.Type)) - fmt.Fprintf(w, "\tif %[1]s < len(%[2]s) { %[3]s = %[2]s[%[1]s] }\n", index, name, char) - name = char*/ - name = fmt.Sprintf("%s[%s]", name, index) - } - - if enum := getEnumByRef(ctx.file, field.Type); enum != nil { - if decodeBaseType(enum.Type, convertToGoType(ctx.file, field.Type), name, 0, "", false) { - } else { - fmt.Fprintf(w, "\t// ??? ENUM %s %s\n", name, enum.Type) - } - } else if alias := getAliasByRef(ctx.file, field.Type); alias != nil { - if decodeBaseType(alias.Type, convertToGoType(ctx.file, field.Type), name, alias.Length, "", false) { - } else if typ := getTypeByRef(ctx.file, alias.Type); typ != nil { - decodeFields(typ.Fields, name) - } else { - fmt.Fprintf(w, "\t// ??? ALIAS %s %s\n", name, alias.Type) - } - } else if typ := getTypeByRef(ctx.file, field.Type); typ != nil { - decodeFields(typ.Fields, name) - } else if union := getUnionByRef(ctx.file, field.Type); union != nil { - maxSize := getUnionSize(ctx.file, union) - fmt.Fprintf(w, "\tcopy(%s.%s[:], tmp[pos:pos+%d])\n", name, unionDataField, maxSize) - fmt.Fprintf(w, "\tpos += %d\n", maxSize) - } else { - fmt.Fprintf(w, "\t// ??? buf[pos] = (%s)\n", name) - } - - if field.Array { - fmt.Fprintf(w, "\t}\n") + if def, ok := field.Meta["default"]; ok && def != nil { + actual := fieldActualType(field) + if t, ok := BaseTypesGo[actual]; ok { + switch t { + case I8, I16, I32, I64: + def = int(def.(float64)) + case U8, U16, U32, U64: + def = uint(def.(float64)) + case F64: + def = def.(float64) } } + tag = append(tag, fmt.Sprintf("default=%s", def)) } - - decodeFields(fields, "m") - - fmt.Fprintf(w, "return nil\n") - - fmt.Fprintf(w, "}\n") + return strings.Join(tag, ",") } -func getFieldType(ctx *GenFile, field *Field) string { - //fieldName := strings.TrimPrefix(field.Name, "_") - //fieldName = camelCaseName(fieldName) - //fieldName := field.GoName +type structTags map[string]string - dataType := convertToGoType(ctx.file, field.Type) - fieldType := dataType - - // check if it is array - if field.Length > 0 || field.SizeFrom != "" { - if dataType == "uint8" { - dataType = "byte" - } - if dataType == "string" && field.Array { - fieldType = "string" - dataType = "byte" - } else if _, ok := BaseTypeNames[field.Type]; !ok && field.SizeFrom == "" { - fieldType = fmt.Sprintf("[%d]%s", field.Length, dataType) - } else { - fieldType = "[]" + dataType - } +func (tags structTags) String() string { + if len(tags) == 0 { + return "" } - - return fieldType -} - -func generateField(ctx *GenFile, w io.Writer, fields []*Field, i int) { - field := fields[i] - - //fieldName := strings.TrimPrefix(field.Name, "_") - //fieldName = camelCaseName(fieldName) - fieldName := field.GoName - - dataType := convertToGoType(ctx.file, field.Type) - fieldType := dataType - - // generate length field for strings - if field.Type == "string" && field.Length == 0 { - fmt.Fprintf(w, "\tXXX_%sLen uint32 `struc:\"sizeof=%s\"`\n", fieldName, fieldName) + var keys []string + for k := range tags { + keys = append(keys, k) } - - // check if it is array - if field.Length > 0 || field.SizeFrom != "" { - if dataType == "uint8" { - dataType = "byte" - } - if dataType == "string" && field.Array { - fieldType = "string" - dataType = "byte" - } else if _, ok := BaseTypeNames[field.Type]; !ok && field.SizeFrom == "" { - fieldType = fmt.Sprintf("[%d]%s", field.Length, dataType) - } else { - fieldType = "[]" + dataType - } + sort.Strings(keys) + var ss []string + for _, key := range keys { + tag := tags[key] + ss = append(ss, fmt.Sprintf(`%s:%s`, key, strconv.Quote(tag))) } - fmt.Fprintf(w, "\t%s %s", fieldName, fieldType) - - fieldTags := map[string]string{} + return "`" + strings.Join(ss, " ") + "`" +} - if field.Length > 0 && field.Array { - // fixed size array - fieldTags["struc"] = fmt.Sprintf("[%d]%s", field.Length, dataType) - } else { - for _, f := range fields { - if f.SizeFrom == field.Name { - // variable sized array - //sizeOfName := camelCaseName(f.Name) - fieldTags["struc"] = fmt.Sprintf("sizeof=%s", f.GoName) - } - } +func genMessages(g *GenFile) { + if len(g.file.Messages) == 0 { + return } - if ctx.IncludeBinapiNames { - typ := fromApiType(field.Type) - if field.Array { - if field.Length > 0 { - fieldTags["binapi"] = fmt.Sprintf("%s[%d],name=%s", typ, field.Length, field.Name) - } else if field.SizeFrom != "" { - fieldTags["binapi"] = fmt.Sprintf("%s[%s],name=%s", typ, field.SizeFrom, field.Name) - } - } else { - fieldTags["binapi"] = fmt.Sprintf("%s,name=%s", typ, field.Name) - } - } - if limit, ok := field.Meta["limit"]; ok && limit.(int) > 0 { - fieldTags["binapi"] = fmt.Sprintf("%s,limit=%d", fieldTags["binapi"], limit) - } - if def, ok := field.Meta["default"]; ok && def != nil { - actual := getActualType(ctx.file, fieldType) - if t, ok := binapiTypes[actual]; ok && t != "float64" { - defnum := int(def.(float64)) - fieldTags["binapi"] = fmt.Sprintf("%s,default=%d", fieldTags["binapi"], defnum) - } else { - fieldTags["binapi"] = fmt.Sprintf("%s,default=%v", fieldTags["binapi"], def) - } + for _, msg := range g.file.Messages { + genMessage(g, msg) } - fieldTags["json"] = fmt.Sprintf("%s,omitempty", field.Name) + // generate registrations + initFnName := fmt.Sprintf("file_%s_binapi_init", g.file.PackageName) - if len(fieldTags) > 0 { - fmt.Fprintf(w, "\t`") - var keys []string - for k := range fieldTags { - keys = append(keys, k) - } - sort.Strings(keys) - var n int - for _, tt := range keys { - t, ok := fieldTags[tt] - if !ok { - continue - } - if n > 0 { - fmt.Fprintf(w, " ") - } - n++ - fmt.Fprintf(w, `%s:"%s"`, tt, t) - } - fmt.Fprintf(w, "`") + g.P("func init() { ", initFnName, "() }") + g.P("func ", initFnName, "() {") + for _, msg := range g.file.Messages { + id := fmt.Sprintf("%s_%s", msg.Name, msg.CRC) + g.P(govppApiPkg.Ident("RegisterMessage"), "((*", msg.GoIdent, ")(nil), ", strconv.Quote(id), ")") } + g.P("}") + g.P() - fmt.Fprintln(w) + // generate list of messages + g.P("// Messages returns list of all messages in this module.") + g.P("func AllMessages() []", govppApiPkg.Ident("Message"), " {") + g.P("return []", govppApiPkg.Ident("Message"), "{") + for _, msg := range g.file.Messages { + g.P("(*", msg.GoIdent, ")(nil),") + } + g.P("}") + g.P("}") } -func generateMessageResetMethod(w io.Writer, structName string) { - fmt.Fprintf(w, "func (m *%[1]s) Reset() { *m = %[1]s{} }\n", structName) -} +func genMessage(g *GenFile, msg *Message) { + logf("gen MESSAGE %s (%s) - %d fields", msg.GoName, msg.Name, len(msg.Fields)) -func generateMessageNameGetter(w io.Writer, structName, msgName string) { - fmt.Fprintf(w, "func (*%s) GetMessageName() string { return %q }\n", structName, msgName) -} + genTypeComment(g, msg.GoIdent.GoName, msg.Name, "message") -func generateTypeNameGetter(w io.Writer, structName, msgName string) { - fmt.Fprintf(w, "func (*%s) GetTypeName() string { return %q }\n", structName, msgName) -} - -func generateIPAddressConversion(w io.Writer, structName string) { - f1 := func(ipVer, ipVerExt int) string { - return fmt.Sprintf(`address.Af = ADDRESS_IP%[1]d - var ip%[1]daddr IP%[1]dAddress - copy(ip%[1]daddr[:], netIP.To%[2]d()) - address.Un.SetIP%[1]d(ip%[1]daddr)`, ipVer, ipVerExt) - } - f2 := func(ipVer, ipVerExt int) string { - return fmt.Sprintf("ip%[1]dAddress := a.Un.GetIP%[1]d()\nip = net.IP(ip%[1]dAddress[:]).To%[2]d().String()", - ipVer, ipVerExt) - } - // IP to Address - fmt.Fprintf(w, `func ParseAddress(ip string) (%[1]s, error) { - var address %[1]s - netIP := net.ParseIP(ip) - if netIP == nil { - return address, fmt.Errorf("invalid address: %[2]s", ip) - } - if ip4 := netIP.To4(); ip4 == nil { - %[3]s + // generate message definition + if len(msg.Fields) == 0 { + g.P("type ", msg.GoIdent, " struct {}") } else { - %[4]s - } - return address, nil -} -`, structName, "%s", f1(6, 16), f1(4, 4)) - fmt.Fprintln(w) - - // Address to IP - fmt.Fprintln(w) - fmt.Fprintf(w, `func (a *%[1]s) ToString() string { - var ip string - if a.Af == ADDRESS_IP6 { - %[2]s - } else { - %[3]s + g.P("type ", msg.GoIdent, " struct {") + for i := range msg.Fields { + generateField(g, msg.Fields, i) + } + g.P("}") } - return ip -}`, structName, f2(6, 16), f2(4, 4)) -} + g.P() -func generatePrefixConversion(w io.Writer, structName string) { - fErr := func() string { - return fmt.Sprintf(`if err != nil { - return Prefix{}, fmt.Errorf("invalid IP %s: %s", ip, err) - }`, "%s", "%v") - } + generateMessageMethods(g, msg) - // IP to Prefix - fmt.Fprintf(w, `func ParsePrefix(ip string) (prefix %[1]s, err error) { - hasPrefix := strings.Contains(ip, "/") - if hasPrefix { - netIP, network, err := net.ParseCIDR(ip) - %[2]s - maskSize, _ := network.Mask.Size() - prefix.Len = byte(maskSize) - prefix.Address, err = ParseAddress(netIP.String()) - %[2]s - } else { - netIP := net.ParseIP(ip) - defaultMaskSize, _ := net.CIDRMask(32, 32).Size() - if netIP.To4() == nil { - defaultMaskSize, _ = net.CIDRMask(128, 128).Size() - } - prefix.Len = byte(defaultMaskSize) - prefix.Address, err = ParseAddress(netIP.String()) - %[2]s - } - return prefix, nil -}`, structName, fErr(), nil) - fmt.Fprintln(w) - - // Prefix to IP - fmt.Fprintln(w) - fmt.Fprintf(w, `func (p *%[1]s) ToString() string { - ip := p.Address.ToString() - return ip + "/" + strconv.Itoa(int(p.Len)) - }`, structName) -} + // encoding methods + generateMessageSize(g, msg.GoIdent.GoName, msg.Fields) + generateMessageMarshal(g, msg.GoIdent.GoName, msg.Fields) + generateMessageUnmarshal(g, msg.GoIdent.GoName, msg.Fields) -func generateMacAddressConversion(w io.Writer, structName string) { - // string to MAC - fmt.Fprintf(w, `func ParseMAC(mac string) (parsed %[1]s, err error) { - var hw net.HardwareAddr - if hw, err = net.ParseMAC(mac); err != nil { - return - } - copy(parsed[:], hw[:]) - return -}`, structName) - fmt.Fprintln(w) - - // MAC to string - fmt.Fprintln(w) - fmt.Fprintf(w, `func (m *%[1]s) ToString() string { - return net.HardwareAddr(m[:]).String() - }`, structName) + g.P() } -func generateCrcGetter(w io.Writer, structName, crc string) { - crc = strings.TrimPrefix(crc, "0x") - fmt.Fprintf(w, "func (*%s) GetCrcString() string { return %q }\n", structName, crc) -} +func generateMessageMethods(g *GenFile, msg *Message) { + // Reset method + g.P("func (m *", msg.GoIdent.GoName, ") Reset() { *m = ", msg.GoIdent.GoName, "{} }") -func generateMessageTypeGetter(w io.Writer, structName string, msgType MessageType) { - fmt.Fprintf(w, "func (*"+structName+") GetMessageType() api.MessageType {") - if msgType == requestMessage { - fmt.Fprintf(w, "\treturn api.RequestMessage") - } else if msgType == replyMessage { - fmt.Fprintf(w, "\treturn api.ReplyMessage") - } else if msgType == eventMessage { - fmt.Fprintf(w, "\treturn api.EventMessage") - } else { - fmt.Fprintf(w, "\treturn api.OtherMessage") - } - fmt.Fprintln(w, "}") - fmt.Fprintln(w) -} + // GetMessageName method + g.P("func (*", msg.GoIdent.GoName, ") GetMessageName() string { return ", strconv.Quote(msg.Name), " }") + + // GetCrcString method + g.P("func (*", msg.GoIdent.GoName, ") GetCrcString() string { return ", strconv.Quote(msg.CRC), " }") + + // GetMessageType method + g.P("func (*", msg.GoIdent.GoName, ") GetMessageType() api.MessageType {") + g.P(" return ", apiMsgType(msg.msgType)) + g.P("}") -func logf(f string, v ...interface{}) { - logrus.Debugf(f, v...) + g.P() } -- cgit 1.2.3-korg