Add support for using multiple generated versions
[govpp.git] / cmd / binapi-generator / generate.go
index 73bcd2a..f22a035 100644 (file)
@@ -25,9 +25,11 @@ import (
 )
 
 const (
+       inputFileExt  = ".api.json" // file extension of the VPP API files
+       outputFileExt = ".ba.go"    // file extension of the Go generated files
+
        govppApiImportPath = "git.fd.io/govpp.git/api" // import path of the govpp API package
-       inputFileExt       = ".api.json"               // file extension of the VPP binary API files
-       outputFileExt      = ".ba.go"                  // file extension of the Go generated files
+       constAPIVersionCrc = "APIVersionCrc"           // name for the API version CRC constant
 )
 
 // context is a structure storing data for code generation
@@ -35,9 +37,10 @@ type context struct {
        inputFile  string // input file with VPP API in JSON
        outputFile string // output file with generated Go package
 
-       inputData []byte        // contents of the input file
-       inputBuff *bytes.Buffer // contents of the input file currently being read
-       inputLine int           // currently processed line in the input file
+       inputData []byte // contents of the input file
+
+       includeAPIVersionCrc bool // include constant with API version CRC string
+       includeComments      bool // include parts of original source in comments
 
        moduleName  string // name of the source VPP module
        packageName string // name of the Go package being generated
@@ -85,30 +88,41 @@ func generatePackage(ctx *context, w *bufio.Writer) error {
        generateHeader(ctx, w)
        generateImports(ctx, w)
 
-       if *includeAPIVer {
-               const APIVerConstName = "VlAPIVersion"
-               fmt.Fprintf(w, "// %s represents version of the API.\n", APIVerConstName)
-               fmt.Fprintf(w, "const %s = %v\n", APIVerConstName, ctx.packageData.APIVersion)
+       if ctx.includeAPIVersionCrc {
+               fmt.Fprintf(w, "// %s defines API version CRC of the VPP binary API module.\n", constAPIVersionCrc)
+               fmt.Fprintf(w, "const %s = %v\n", constAPIVersionCrc, ctx.packageData.APIVersion)
                fmt.Fprintln(w)
        }
 
+       // generate services
+       if len(ctx.packageData.Services) > 0 {
+               generateServices(ctx, w, ctx.packageData.Services)
+
+               // TODO: generate implementation for Services interface
+       }
+
        // generate enums
        if len(ctx.packageData.Enums) > 0 {
                fmt.Fprintf(w, "/* Enums */\n\n")
 
-               ctx.inputBuff = bytes.NewBuffer(ctx.inputData)
-               ctx.inputLine = 0
                for _, enum := range ctx.packageData.Enums {
                        generateEnum(ctx, w, &enum)
                }
        }
 
+       // generate aliases
+       if len(ctx.packageData.Aliases) > 0 {
+               fmt.Fprintf(w, "/* Aliases */\n\n")
+
+               for _, alias := range ctx.packageData.Aliases {
+                       generateAlias(ctx, w, &alias)
+               }
+       }
+
        // generate types
        if len(ctx.packageData.Types) > 0 {
                fmt.Fprintf(w, "/* Types */\n\n")
 
-               ctx.inputBuff = bytes.NewBuffer(ctx.inputData)
-               ctx.inputLine = 0
                for _, typ := range ctx.packageData.Types {
                        generateType(ctx, w, &typ)
                }
@@ -118,8 +132,6 @@ func generatePackage(ctx *context, w *bufio.Writer) error {
        if len(ctx.packageData.Unions) > 0 {
                fmt.Fprintf(w, "/* Unions */\n\n")
 
-               ctx.inputBuff = bytes.NewBuffer(ctx.inputData)
-               ctx.inputLine = 0
                for _, union := range ctx.packageData.Unions {
                        generateUnion(ctx, w, &union)
                }
@@ -129,36 +141,26 @@ func generatePackage(ctx *context, w *bufio.Writer) error {
        if len(ctx.packageData.Messages) > 0 {
                fmt.Fprintf(w, "/* Messages */\n\n")
 
-               ctx.inputBuff = bytes.NewBuffer(ctx.inputData)
-               ctx.inputLine = 0
                for _, msg := range ctx.packageData.Messages {
                        generateMessage(ctx, w, &msg)
                }
-       }
-
-       // generate services
-       if len(ctx.packageData.Services) > 0 {
-               fmt.Fprintf(w, "/* Services */\n\n")
 
-               ctx.inputBuff = bytes.NewBuffer(ctx.inputData)
-               ctx.inputLine = 0
-               fmt.Fprintf(w, "type %s interface {\n", "Services")
-               for _, svc := range ctx.packageData.Services {
-                       generateService(ctx, w, &svc)
+               // generate message registrations
+               fmt.Fprintln(w, "func init() {")
+               for _, msg := range ctx.packageData.Messages {
+                       name := camelCaseName(msg.Name)
+                       fmt.Fprintf(w, "\tapi.RegisterMessage((*%s)(nil), \"%s\")\n", name, ctx.moduleName+"."+name)
                }
                fmt.Fprintln(w, "}")
-       }
-
-       // TODO: generate implementation for Services interface
+               fmt.Fprintln(w)
 
-       // generate message registrations
-       fmt.Fprintln(w)
-       fmt.Fprintln(w, "func init() {")
-       for _, msg := range ctx.packageData.Messages {
-               name := camelCaseName(msg.Name)
-               fmt.Fprintf(w, "\tapi.RegisterMessage((*%s)(nil), \"%s\")\n", name, ctx.moduleName+"."+name)
+               fmt.Fprintln(w, "var Messages = []api.Message{")
+               for _, msg := range ctx.packageData.Messages {
+                       name := camelCaseName(msg.Name)
+                       fmt.Fprintf(w, "\t(*%s)(nil),\n", name)
+               }
+               fmt.Fprintln(w, "}")
        }
-       fmt.Fprintln(w, "}")
 
        // flush the data:
        if err := w.Flush(); err != nil {
@@ -171,31 +173,32 @@ func generatePackage(ctx *context, w *bufio.Writer) error {
 // generateHeader writes generated package header into w
 func generateHeader(ctx *context, w io.Writer) {
        fmt.Fprintln(w, "// Code generated by GoVPP binapi-generator. DO NOT EDIT.")
-       fmt.Fprintf(w, "// source: %s\n", ctx.inputFile)
+       fmt.Fprintf(w, "//  source: %s\n", ctx.inputFile)
        fmt.Fprintln(w)
 
        fmt.Fprintln(w, "/*")
-       fmt.Fprintf(w, "Package %s is a generated VPP binary API of the '%s' VPP module.\n", ctx.packageName, ctx.moduleName)
+       fmt.Fprintf(w, " Package %s is a generated from VPP binary API module '%s'.\n", ctx.packageName, ctx.moduleName)
        fmt.Fprintln(w)
-       fmt.Fprintln(w, "It is generated from this file:")
-       fmt.Fprintf(w, "\t%s\n", filepath.Base(ctx.inputFile))
-       fmt.Fprintln(w)
-       fmt.Fprintln(w, "It contains these VPP binary API objects:")
-
+       fmt.Fprintln(w, " It contains following objects:")
        var printObjNum = func(obj string, num int) {
                if num > 0 {
                        if num > 1 {
-                               obj += "s"
+                               if strings.HasSuffix(obj, "s") {
+                                       obj += "es"
+                               } else {
+                                       obj += "s"
+                               }
                        }
-                       fmt.Fprintf(w, "\t%d %s\n", num, obj)
+                       fmt.Fprintf(w, "\t%3d %s\n", num, obj)
                }
        }
-       printObjNum("message", len(ctx.packageData.Messages))
-       printObjNum("type", len(ctx.packageData.Types))
+
+       printObjNum("service", len(ctx.packageData.Services))
        printObjNum("enum", len(ctx.packageData.Enums))
+       printObjNum("alias", len(ctx.packageData.Aliases))
+       printObjNum("type", len(ctx.packageData.Types))
        printObjNum("union", len(ctx.packageData.Unions))
-       printObjNum("service", len(ctx.packageData.Services))
-
+       printObjNum("message", len(ctx.packageData.Messages))
        fmt.Fprintln(w, "*/")
        fmt.Fprintf(w, "package %s\n", ctx.packageName)
        fmt.Fprintln(w)
@@ -203,9 +206,9 @@ func generateHeader(ctx *context, w io.Writer) {
 
 // generateImports writes generated package imports into w
 func generateImports(ctx *context, w io.Writer) {
-       fmt.Fprintf(w, "import \"%s\"\n", govppApiImportPath)
-       fmt.Fprintf(w, "import \"%s\"\n", "github.com/lunixbochs/struc")
-       fmt.Fprintf(w, "import \"%s\"\n", "bytes")
+       fmt.Fprintf(w, "import api \"%s\"\n", govppApiImportPath)
+       fmt.Fprintf(w, "import struc \"%s\"\n", "github.com/lunixbochs/struc")
+       fmt.Fprintf(w, "import bytes \"%s\"\n", "bytes")
        fmt.Fprintln(w)
 
        fmt.Fprintf(w, "// Reference imports to suppress errors if they are not otherwise used.\n")
@@ -213,49 +216,118 @@ func generateImports(ctx *context, w io.Writer) {
        fmt.Fprintf(w, "var _ = struc.Pack\n")
        fmt.Fprintf(w, "var _ = bytes.NewBuffer\n")
        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.Fprintln(w, "const _ = api.GoVppAPIPackageIsVersion1 // please upgrade the GoVPP api package")
+       fmt.Fprintln(w)*/
 }
 
 // generateComment writes generated comment for the object into w
 func generateComment(ctx *context, w io.Writer, goName string, vppName string, objKind string) {
-       fmt.Fprintf(w, "// %s represents the VPP binary API %s '%s'.\n", goName, objKind, vppName)
+       if objKind == "service" {
+               fmt.Fprintf(w, "// %s represents VPP binary API services:\n", goName)
+       } else {
+               fmt.Fprintf(w, "// %s represents VPP binary API %s '%s':\n", goName, objKind, vppName)
+       }
+
+       if !ctx.includeComments {
+               return
+       }
 
        var isNotSpace = func(r rune) bool {
                return !unicode.IsSpace(r)
        }
 
        // print out the source of the generated object
+       mapType := false
        objFound := false
        objTitle := fmt.Sprintf(`"%s",`, vppName)
+       switch objKind {
+       case "alias", "service":
+               objTitle = fmt.Sprintf(`"%s": {`, vppName)
+               mapType = true
+       }
+
+       inputBuff := bytes.NewBuffer(ctx.inputData)
+       inputLine := 0
+
+       var trimIndent string
        var indent int
        for {
-               line, err := ctx.inputBuff.ReadString('\n')
+               line, err := inputBuff.ReadString('\n')
                if err != nil {
                        break
                }
-               ctx.inputLine++
+               inputLine++
 
+               noSpaceAt := strings.IndexFunc(line, isNotSpace)
                if !objFound {
                        indent = strings.Index(line, objTitle)
                        if indent == -1 {
                                continue
                        }
+                       trimIndent = line[:indent]
                        // If no other non-whitespace character then we are at the message header.
                        if trimmed := strings.TrimSpace(line); trimmed == objTitle {
                                objFound = true
-                               fmt.Fprintf(w, "// Generated from '%s', line %d:\n", filepath.Base(ctx.inputFile), ctx.inputLine)
                                fmt.Fprintln(w, "//")
                        }
-               } else {
-                       if strings.IndexFunc(line, isNotSpace) < indent {
-                               break // end of the object definition in JSON
-                       }
+               } else if noSpaceAt < indent {
+                       break // end of the definition in JSON for array types
+               } else if objFound && mapType && noSpaceAt <= indent {
+                       fmt.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent))
+                       break // end of the definition in JSON for map types (aliases, services)
                }
-               fmt.Fprint(w, "//", line)
+               fmt.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent))
        }
 
        fmt.Fprintln(w, "//")
 }
 
+// generateServices writes generated code for the Services interface into w
+func generateServices(ctx *context, w *bufio.Writer, services []Service) {
+       // generate services comment
+       generateComment(ctx, w, "Services", "services", "service")
+
+       // generate interface
+       fmt.Fprintf(w, "type %s interface {\n", "Services")
+       for _, svc := range services {
+               generateService(ctx, w, &svc)
+       }
+       fmt.Fprintln(w, "}")
+
+       fmt.Fprintln(w)
+}
+
+// generateService writes generated code for the service into w
+func generateService(ctx *context, w io.Writer, svc *Service) {
+       reqTyp := camelCaseName(svc.RequestType)
+
+       // method name is same as parameter type name by default
+       method := reqTyp
+       if svc.Stream {
+               // use Dump as prefix instead of suffix for stream services
+               if m := strings.TrimSuffix(method, "Dump"); method != m {
+                       method = "Dump" + m
+               }
+       }
+
+       params := fmt.Sprintf("*%s", reqTyp)
+       returns := "error"
+       if replyType := camelCaseName(svc.ReplyType); replyType != "" {
+               repTyp := fmt.Sprintf("*%s", replyType)
+               if svc.Stream {
+                       repTyp = fmt.Sprintf("[]%s", repTyp)
+               }
+               returns = fmt.Sprintf("(%s, error)", repTyp)
+       }
+
+       fmt.Fprintf(w, "\t%s(%s) %s\n", method, params, returns)
+}
+
 // generateEnum writes generated code for the enum into w
 func generateEnum(ctx *context, w io.Writer, enum *Enum) {
        name := camelCaseName(enum.Name)
@@ -282,37 +354,24 @@ func generateEnum(ctx *context, w io.Writer, enum *Enum) {
        fmt.Fprintln(w)
 }
 
-// generateType writes generated code for the type into w
-func generateType(ctx *context, w io.Writer, typ *Type) {
-       name := camelCaseName(typ.Name)
+// generateAlias writes generated code for the alias into w
+func generateAlias(ctx *context, w io.Writer, alias *Alias) {
+       name := camelCaseName(alias.Name)
 
-       logf(" writing type %q (%s) with %d fields", typ.Name, name, len(typ.Fields))
+       logf(" writing type %q (%s), length: %d", alias.Name, name, alias.Length)
 
        // generate struct comment
-       generateComment(ctx, w, name, typ.Name, "type")
+       generateComment(ctx, w, name, alias.Name, "alias")
 
        // generate struct definition
-       fmt.Fprintf(w, "type %s struct {\n", name)
+       fmt.Fprintf(w, "type %s ", name)
 
-       // generate struct fields
-       for i, field := range typ.Fields {
-               // skip internal fields
-               switch strings.ToLower(field.Name) {
-               case "crc", "_vl_msg_id":
-                       continue
-               }
-
-               generateField(ctx, w, typ.Fields, i)
+       if alias.Length > 0 {
+               fmt.Fprintf(w, "[%d]", alias.Length)
        }
 
-       // generate end of the struct
-       fmt.Fprintln(w, "}")
-
-       // generate name getter
-       generateTypeNameGetter(w, name, typ.Name)
-
-       // generate CRC getter
-       generateCrcGetter(w, name, typ.CRC)
+       dataType := convertToGoType(ctx, alias.Type)
+       fmt.Fprintf(w, "%s\n", dataType)
 
        fmt.Fprintln(w)
 }
@@ -386,6 +445,10 @@ func (u *%[1]s) String() string {
 
 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
+}
 func (u *%[1]s) Set%[2]s(a %[3]s) {
        var b = new(bytes.Buffer)
        if err := struc.Pack(b, &a); err != nil {
@@ -401,6 +464,41 @@ func (u *%[1]s) Get%[2]s() (a %[3]s) {
 `, structName, getterField, getterStruct)
 }
 
+// generateType writes generated code for the type into w
+func generateType(ctx *context, w io.Writer, typ *Type) {
+       name := camelCaseName(typ.Name)
+
+       logf(" writing type %q (%s) with %d fields", typ.Name, name, len(typ.Fields))
+
+       // generate struct comment
+       generateComment(ctx, w, name, typ.Name, "type")
+
+       // generate struct definition
+       fmt.Fprintf(w, "type %s struct {\n", name)
+
+       // generate struct fields
+       for i, field := range typ.Fields {
+               // skip internal fields
+               switch strings.ToLower(field.Name) {
+               case crcField, msgIdField:
+                       continue
+               }
+
+               generateField(ctx, w, typ.Fields, i)
+       }
+
+       // generate end of the struct
+       fmt.Fprintln(w, "}")
+
+       // generate name getter
+       generateTypeNameGetter(w, name, typ.Name)
+
+       // generate CRC getter
+       generateCrcGetter(w, name, typ.CRC)
+
+       fmt.Fprintln(w)
+}
+
 // generateMessage writes generated code for the message into w
 func generateMessage(ctx *context, w io.Writer, msg *Message) {
        name := camelCaseName(msg.Name)
@@ -420,26 +518,28 @@ func generateMessage(ctx *context, w io.Writer, msg *Message) {
        n := 0
        for i, field := range msg.Fields {
                if i == 1 {
-                       if field.Name == "client_index" {
-                               // "client_index" as the second member, this might be an event message or a request
+                       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 == "context" {
+                       } else if field.Name == contextField {
                                // reply needs "context" as the second member
                                msgType = replyMessage
                        }
                } else if i == 2 {
-                       if wasClientIndex && field.Name == "context" {
-                               // request needs "client_index" as the second member and "context" as the third member
+                       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 "crc", "_vl_msg_id":
+               case crcField, msgIdField:
                        continue
-               case "client_index", "context":
+               case clientIndexField, contextField:
                        if n == 0 {
                                continue
                        }
@@ -464,8 +564,7 @@ func generateMessage(ctx *context, w io.Writer, msg *Message) {
        // generate message type getter method
        generateMessageTypeGetter(w, name, msgType)
 
-       // generate message factory
-       generateMessageFactory(w, name)
+       fmt.Fprintln(w)
 }
 
 // generateField writes generated code for the field into w
@@ -475,10 +574,16 @@ func generateField(ctx *context, w io.Writer, fields []Field, i int) {
        fieldName := strings.TrimPrefix(field.Name, "_")
        fieldName = camelCaseName(fieldName)
 
-       dataType := convertToGoType(ctx, field.Type)
+       // generate length field for strings
+       if field.Type == "string" {
+               fmt.Fprintf(w, "\tXXX_%sLen uint32 `struc:\"sizeof=%s\"`\n", fieldName, fieldName)
+       }
 
+       dataType := convertToGoType(ctx, field.Type)
        fieldType := dataType
-       if field.IsArray() {
+
+       // check if it is array
+       if field.Length > 0 || field.SizeFrom != "" {
                if dataType == "uint8" {
                        dataType = "byte"
                }
@@ -502,21 +607,6 @@ func generateField(ctx *context, w io.Writer, fields []Field, i int) {
        fmt.Fprintln(w)
 }
 
-// generateService writes generated code for the service into w
-func generateService(ctx *context, w io.Writer, svc *Service) {
-       reqTyp := camelCaseName(svc.RequestType)
-
-       // method name is same as parameter type name by default
-       method := svc.MethodName()
-       params := fmt.Sprintf("*%s", reqTyp)
-       returns := "error"
-       if replyTyp := camelCaseName(svc.ReplyType); replyTyp != "" {
-               returns = fmt.Sprintf("(*%s, error)", replyTyp)
-       }
-
-       fmt.Fprintf(w, "\t%s(%s) %s\n", method, params, returns)
-}
-
 // generateMessageNameGetter generates getter for original VPP message name into the provider writer
 func generateMessageNameGetter(w io.Writer, structName, msgName string) {
        fmt.Fprintf(w, `func (*%s) GetMessageName() string {
@@ -556,10 +646,3 @@ func generateMessageTypeGetter(w io.Writer, structName string, msgType MessageTy
        }
        fmt.Fprintln(w, "}")
 }
-
-// generateMessageFactory generates message factory for the generated message into the provider writer
-func generateMessageFactory(w io.Writer, structName string) {
-       fmt.Fprintln(w, "func New"+structName+"() api.Message {")
-       fmt.Fprintln(w, "\treturn &"+structName+"{}")
-       fmt.Fprintln(w, "}")
-}