Refactored binapi generator with message encoding
[govpp.git] / cmd / binapi-generator / generate.go
diff --git a/cmd/binapi-generator/generate.go b/cmd/binapi-generator/generate.go
deleted file mode 100644 (file)
index 715836d..0000000
+++ /dev/null
@@ -1,848 +0,0 @@
-// Copyright (c) 2017 Cisco and/or its affiliates.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at:
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package main
-
-import (
-       "bytes"
-       "fmt"
-       "io"
-       "os/exec"
-       "path"
-       "path/filepath"
-       "sort"
-       "strings"
-       "unicode"
-)
-
-// 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 = 1
-
-const (
-       inputFileExt  = ".api.json" // file extension of the VPP API files
-       outputFileExt = ".ba.go"    // file extension of the Go generated files
-
-       constModuleName = "ModuleName" // module name constant
-       constAPIVersion = "APIVersion" // API version constant
-       constVersionCrc = "VersionCrc" // version CRC constant
-
-       unionDataField = "XXX_UnionData" // name for the union data field
-
-       serviceApiName    = "RPCService"    // name for the RPC service interface
-       serviceImplName   = "serviceClient" // name for the RPC service implementation
-       serviceClientName = "ServiceClient" // name for the RPC service client
-)
-
-// context is a structure storing data for code generation
-type context struct {
-       inputFile  string // input file with VPP API in JSON
-       outputFile string // output file with generated Go package
-
-       importPrefix string // defines import path prefix for importing types
-
-       inputData []byte // contents of the input file
-
-       includeAPIVersion  bool // include constant with API version string
-       includeComments    bool // include parts of original source in comments
-       includeBinapiNames bool // include binary API names as struct tag
-       includeServices    bool // include service interface with client implementation
-
-       moduleName  string // name of the source VPP module
-       packageName string // name of the Go package being generated
-
-       packageData *Package // parsed package data
-}
-
-// newContext returns context details of the code generation task
-func newContext(inputFile, outputDir string) (*context, error) {
-       if !strings.HasSuffix(inputFile, inputFileExt) {
-               return nil, fmt.Errorf("invalid input file name: %q", inputFile)
-       }
-
-       ctx := &context{
-               inputFile: inputFile,
-       }
-
-       // package name
-       inputFileName := filepath.Base(inputFile)
-       ctx.moduleName = inputFileName[:strings.Index(inputFileName, ".")]
-
-       // alter package names for modules that are reserved keywords in Go
-       switch ctx.moduleName {
-       case "interface":
-               ctx.packageName = "interfaces"
-       case "map":
-               ctx.packageName = "maps"
-       default:
-               ctx.packageName = ctx.moduleName
-       }
-
-       // output file
-       packageDir := filepath.Join(outputDir, ctx.packageName)
-       outputFileName := ctx.packageName + outputFileExt
-       ctx.outputFile = filepath.Join(packageDir, outputFileName)
-
-       return ctx, nil
-}
-
-func generatePackage(ctx *context, w io.Writer) error {
-       logf("----------------------------")
-       logf("generating package %q", ctx.packageName)
-       logf("----------------------------")
-
-       fmt.Fprintln(w, "// Code generated by GoVPP's binapi-generator. DO NOT EDIT.")
-       fmt.Fprintf(w, "// source: %s\n", ctx.inputFile)
-       fmt.Fprintln(w)
-
-       generateHeader(ctx, w)
-       generateImports(ctx, w)
-
-       // 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.moduleName)
-
-       if ctx.includeAPIVersion {
-               if ctx.packageData.Version != "" {
-                       fmt.Fprintf(w, "\t// %s is the API version of this module.\n", constAPIVersion)
-                       fmt.Fprintf(w, "\t%s = \"%s\"\n", constAPIVersion, ctx.packageData.Version)
-               }
-               fmt.Fprintf(w, "\t// %s is the CRC of this module.\n", constVersionCrc)
-               fmt.Fprintf(w, "\t%s = %v\n", constVersionCrc, ctx.packageData.CRC)
-       }
-       fmt.Fprintln(w, ")")
-       fmt.Fprintln(w)
-
-       // generate enums
-       if len(ctx.packageData.Enums) > 0 {
-               for _, enum := range ctx.packageData.Enums {
-                       if imp, ok := ctx.packageData.Imports[enum.Name]; ok {
-                               generateImportedAlias(ctx, w, enum.Name, &imp)
-                               continue
-                       }
-                       generateEnum(ctx, w, &enum)
-               }
-       }
-
-       // generate aliases
-       if len(ctx.packageData.Aliases) > 0 {
-               for _, alias := range ctx.packageData.Aliases {
-                       if imp, ok := ctx.packageData.Imports[alias.Name]; ok {
-                               generateImportedAlias(ctx, w, alias.Name, &imp)
-                               continue
-                       }
-                       generateAlias(ctx, w, &alias)
-               }
-       }
-
-       // generate types
-       if len(ctx.packageData.Types) > 0 {
-               for _, typ := range ctx.packageData.Types {
-                       if imp, ok := ctx.packageData.Imports[typ.Name]; ok {
-                               generateImportedAlias(ctx, w, typ.Name, &imp)
-                               continue
-                       }
-                       generateType(ctx, w, &typ)
-               }
-       }
-
-       // generate unions
-       if len(ctx.packageData.Unions) > 0 {
-               for _, union := range ctx.packageData.Unions {
-                       if imp, ok := ctx.packageData.Imports[union.Name]; ok {
-                               generateImportedAlias(ctx, w, union.Name, &imp)
-                               continue
-                       }
-                       generateUnion(ctx, w, &union)
-               }
-       }
-
-       // generate messages
-       if len(ctx.packageData.Messages) > 0 {
-               for _, msg := range ctx.packageData.Messages {
-                       generateMessage(ctx, w, &msg)
-               }
-
-               // 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, "}")
-               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.packageData.Messages {
-                       name := camelCaseName(msg.Name)
-                       fmt.Fprintf(w, "\t(*%s)(nil),\n", name)
-               }
-               fmt.Fprintln(w, "}")
-               fmt.Fprintln(w, "}")
-       }
-
-       if ctx.includeServices {
-               // generate services
-               if len(ctx.packageData.Services) > 0 {
-                       generateServices(ctx, w, ctx.packageData.Services)
-               }
-       }
-
-       generateFooter(ctx, w)
-
-       return nil
-}
-
-func generateHeader(ctx *context, w io.Writer) {
-       fmt.Fprintln(w, "/*")
-       fmt.Fprintf(w, "Package %s is a generated VPP binary API for '%s' module.\n", ctx.packageName, ctx.moduleName)
-       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)
-               }
-       }
-       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("message", len(ctx.packageData.Messages))
-       printObjNum("service", len(ctx.packageData.Services))
-       fmt.Fprintln(w, "*/")
-       fmt.Fprintf(w, "package %s\n", ctx.packageName)
-       fmt.Fprintln(w)
-
-}
-
-func generateImports(ctx *context, w io.Writer) {
-       fmt.Fprintln(w, "import (")
-       fmt.Fprintln(w, `       "bytes"`)
-       fmt.Fprintln(w, `       "context"`)
-       fmt.Fprintln(w, `       "io"`)
-       fmt.Fprintln(w, `       "strconv"`)
-       fmt.Fprintln(w)
-       fmt.Fprintf(w, "\tapi \"%s\"\n", "git.fd.io/govpp.git/api")
-       fmt.Fprintf(w, "\tstruc \"%s\"\n", "github.com/lunixbochs/struc")
-       if len(ctx.packageData.Imports) > 0 {
-               fmt.Fprintln(w)
-               for _, imp := range getImports(ctx) {
-                       importPath := path.Join(ctx.importPrefix, imp)
-                       if importPath == "" {
-                               importPath = getImportPath(filepath.Dir(ctx.outputFile), imp)
-                       }
-                       fmt.Fprintf(w, "\t%s \"%s\"\n", imp, strings.TrimSpace(importPath))
-               }
-       }
-       fmt.Fprintln(w, ")")
-       fmt.Fprintln(w)
-}
-
-func getImportPath(outputDir string, pkg string) string {
-       absPath, _ := filepath.Abs(filepath.Join(outputDir, "..", pkg))
-       cmd := exec.Command("go", "list", absPath)
-       var errbuf, outbuf bytes.Buffer
-       cmd.Stdout = &outbuf
-       cmd.Stderr = &errbuf
-       if err := cmd.Run(); err != nil {
-               fmt.Printf("ERR: %v\n", errbuf.String())
-               panic(err)
-       }
-       return outbuf.String()
-}
-
-func getImports(ctx *context) (imports []string) {
-       impmap := map[string]struct{}{}
-       for _, imp := range ctx.packageData.Imports {
-               if _, ok := impmap[imp.Package]; !ok {
-                       imports = append(imports, imp.Package)
-                       impmap[imp.Package] = struct{}{}
-               }
-       }
-       sort.Strings(imports)
-       return imports
-}
-
-func generateFooter(ctx *context, w io.Writer) {
-       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)
-
-       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 _ = 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 _ = struc.Pack\n")
-}
-
-func generateComment(ctx *context, 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.moduleName)
-       } 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 := inputBuff.ReadString('\n')
-               if err != nil {
-                       break
-               }
-               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.Fprintln(w, "//")
-                       }
-               } 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.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent))
-       }
-
-       fmt.Fprintln(w, "//")
-}
-
-func generateEnum(ctx *context, w io.Writer, enum *Enum) {
-       name := camelCaseName(enum.Name)
-       typ := binapiTypes[enum.Type]
-
-       logf(" writing enum %q (%s) with %d entries", enum.Name, name, len(enum.Entries))
-
-       // 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)
-
-       // generate enum entries
-       fmt.Fprintln(w, "const (")
-       for _, entry := range enum.Entries {
-               fmt.Fprintf(w, "\t%s %s = %v\n", entry.Name, name, entry.Value)
-       }
-       fmt.Fprintln(w, ")")
-       fmt.Fprintln(w)
-
-       // generate enum conversion maps
-       fmt.Fprintf(w, "var %s_name = map[%s]string{\n", name, typ)
-       for _, entry := range enum.Entries {
-               fmt.Fprintf(w, "\t%v: \"%s\",\n", entry.Value, entry.Name)
-       }
-       fmt.Fprintln(w, "}")
-       fmt.Fprintln(w)
-
-       fmt.Fprintf(w, "var %s_value = map[string]%s{\n", name, typ)
-       for _, entry := range enum.Entries {
-               fmt.Fprintf(w, "\t\"%s\": %v,\n", entry.Name, entry.Value)
-       }
-       fmt.Fprintln(w, "}")
-       fmt.Fprintln(w)
-
-       fmt.Fprintf(w, "func (x %s) String() string {\n", name)
-       fmt.Fprintf(w, "\ts, ok := %s_name[%s(x)]\n", name, typ)
-       fmt.Fprintf(w, "\tif ok { return s }\n")
-       fmt.Fprintf(w, "\treturn strconv.Itoa(int(x))\n")
-       fmt.Fprintln(w, "}")
-       fmt.Fprintln(w)
-}
-
-func generateImportedAlias(ctx *context, w io.Writer, tName string, imp *Import) {
-       name := camelCaseName(tName)
-
-       fmt.Fprintf(w, "type %s = %s.%s\n", name, imp.Package, name)
-
-       fmt.Fprintln(w)
-}
-
-func generateAlias(ctx *context, w io.Writer, alias *Alias) {
-       name := camelCaseName(alias.Name)
-
-       logf(" writing type %q (%s), length: %d", alias.Name, name, alias.Length)
-
-       // generate struct comment
-       generateComment(ctx, w, name, alias.Name, "alias")
-
-       // generate struct definition
-       fmt.Fprintf(w, "type %s ", name)
-
-       if alias.Length > 0 {
-               fmt.Fprintf(w, "[%d]", alias.Length)
-       }
-
-       dataType := convertToGoType(ctx, alias.Type)
-       fmt.Fprintf(w, "%s\n", dataType)
-
-       fmt.Fprintln(w)
-}
-
-func generateUnion(ctx *context, w io.Writer, union *Union) {
-       name := camelCaseName(union.Name)
-
-       logf(" writing union %q (%s) with %d fields", union.Name, name, len(union.Fields))
-
-       // generate struct comment
-       generateComment(ctx, w, name, union.Name, "union")
-
-       // generate struct definition
-       fmt.Fprintln(w, "type", name, "struct {")
-
-       // maximum size for union
-       maxSize := getUnionSize(ctx, union)
-
-       // generate data field
-       fmt.Fprintf(w, "\t%s [%d]byte\n", unionDataField, maxSize)
-
-       // generate end of the struct
-       fmt.Fprintln(w, "}")
-
-       // generate name getter
-       generateTypeNameGetter(w, name, union.Name)
-
-       // generate CRC getter
-       if union.CRC != "" {
-               generateCrcGetter(w, name, union.CRC)
-       }
-
-       // generate getters for fields
-       for _, field := range union.Fields {
-               fieldName := camelCaseName(field.Name)
-               fieldType := convertToGoType(ctx, field.Type)
-               generateUnionGetterSetter(w, name, fieldName, fieldType)
-       }
-
-       // generate union methods
-       //generateUnionMethods(w, name)
-
-       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 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 {
-               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 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
-       if typ.CRC != "" {
-               generateCrcGetter(w, name, typ.CRC)
-       }
-
-       fmt.Fprintln(w)
-}
-
-func generateMessage(ctx *context, w io.Writer, msg *Message) {
-       name := camelCaseName(msg.Name)
-
-       logf(" writing message %q (%s) with %d fields", msg.Name, name, len(msg.Fields))
-
-       // generate struct comment
-       generateComment(ctx, w, name, msg.Name, "message")
-
-       // generate struct definition
-       fmt.Fprintf(w, "type %s struct {", name)
-
-       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 crcField, 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)
-
-       fmt.Fprintln(w)
-}
-
-func generateField(ctx *context, w io.Writer, fields []Field, i int) {
-       field := fields[i]
-
-       fieldName := strings.TrimPrefix(field.Name, "_")
-       fieldName = camelCaseName(fieldName)
-
-       dataType := convertToGoType(ctx, 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)
-       }
-
-       // check if it is array
-       if field.Length > 0 || field.SizeFrom != "" {
-               if dataType == "uint8" {
-                       dataType = "byte"
-               }
-               if dataType == "string" && field.SpecifiedLen {
-                       fieldType = "string"
-                       dataType = "byte"
-               } else {
-                       fieldType = "[]" + dataType
-               }
-       }
-       fmt.Fprintf(w, "\t%s %s", fieldName, fieldType)
-
-       fieldTags := map[string]string{}
-
-       if field.Length > 0 && field.SpecifiedLen {
-               // 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", sizeOfName)
-                       }
-               }
-       }
-
-       if ctx.includeBinapiNames {
-               fieldTags["binapi"] = field.Name
-       }
-       if field.Meta.Limit > 0 {
-               fieldTags["binapi"] = fmt.Sprintf("%s,limit=%d", fieldTags["binapi"], field.Meta.Limit)
-       }
-
-       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, "`")
-       }
-
-       fmt.Fprintln(w)
-}
-
-func generateMessageResetMethod(w io.Writer, structName string) {
-       fmt.Fprintf(w, "func (m *%[1]s) Reset() { *m = %[1]s{} }\n", structName)
-}
-
-func generateMessageNameGetter(w io.Writer, structName, msgName string) {
-       fmt.Fprintf(w, "func (*%s) GetMessageName() string {    return %q }\n", structName, msgName)
-}
-
-func generateTypeNameGetter(w io.Writer, structName, msgName string) {
-       fmt.Fprintf(w, "func (*%s) GetTypeName() string { return %q }\n", structName, msgName)
-}
-
-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 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)
-}
-
-func generateServices(ctx *context, w io.Writer, services []Service) {
-
-       // generate services comment
-       generateComment(ctx, w, serviceApiName, "services", "service")
-
-       // generate service api
-       fmt.Fprintf(w, "type %s interface {\n", serviceApiName)
-       for _, svc := range services {
-               generateServiceMethod(ctx, w, &svc)
-               fmt.Fprintln(w)
-       }
-       fmt.Fprintln(w, "}")
-       fmt.Fprintln(w)
-
-       // generate client implementation
-       fmt.Fprintf(w, "type %s struct {\n", serviceImplName)
-       fmt.Fprintf(w, "\tch api.Channel\n")
-       fmt.Fprintln(w, "}")
-       fmt.Fprintln(w)
-
-       // generate client constructor
-       fmt.Fprintf(w, "func New%s(ch api.Channel) %s {\n", serviceClientName, serviceApiName)
-       fmt.Fprintf(w, "\treturn &%s{ch}\n", serviceImplName)
-       fmt.Fprintln(w, "}")
-       fmt.Fprintln(w)
-
-       for _, svc := range services {
-               method := camelCaseName(svc.RequestType)
-               if m := strings.TrimSuffix(method, "Dump"); method != m {
-                       method = "Dump" + m
-               }
-
-               fmt.Fprintf(w, "func (c *%s) ", serviceImplName)
-               generateServiceMethod(ctx, w, &svc)
-               fmt.Fprintln(w, " {")
-               if svc.Stream {
-                       streamImpl := fmt.Sprintf("%s_%sClient", serviceImplName, method)
-                       fmt.Fprintf(w, "\tstream := c.ch.SendMultiRequest(in)\n")
-                       fmt.Fprintf(w, "\tx := &%s{stream}\n", streamImpl)
-                       fmt.Fprintf(w, "\treturn x, nil\n")
-               } else if replyTyp := camelCaseName(svc.ReplyType); replyTyp != "" {
-                       fmt.Fprintf(w, "\tout := new(%s)\n", replyTyp)
-                       fmt.Fprintf(w, "\terr:= c.ch.SendRequest(in).ReceiveReply(out)\n")
-                       fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
-                       fmt.Fprintf(w, "\treturn out, nil\n")
-               } else {
-                       fmt.Fprintf(w, "\tc.ch.SendRequest(in)\n")
-                       fmt.Fprintf(w, "\treturn nil\n")
-               }
-               fmt.Fprintln(w, "}")
-               fmt.Fprintln(w)
-
-               if svc.Stream {
-                       replyTyp := camelCaseName(svc.ReplyType)
-                       method := camelCaseName(svc.RequestType)
-                       if m := strings.TrimSuffix(method, "Dump"); method != m {
-                               method = "Dump" + m
-                       }
-                       streamApi := fmt.Sprintf("%s_%sClient", serviceApiName, method)
-
-                       fmt.Fprintf(w, "type %s interface {\n", streamApi)
-                       fmt.Fprintf(w, "\tRecv() (*%s, error)\n", replyTyp)
-                       fmt.Fprintln(w, "}")
-                       fmt.Fprintln(w)
-
-                       streamImpl := fmt.Sprintf("%s_%sClient", serviceImplName, method)
-                       fmt.Fprintf(w, "type %s struct {\n", streamImpl)
-                       fmt.Fprintf(w, "\tapi.MultiRequestCtx\n")
-                       fmt.Fprintln(w, "}")
-                       fmt.Fprintln(w)
-
-                       fmt.Fprintf(w, "func (c *%s) Recv() (*%s, error) {\n", streamImpl, replyTyp)
-                       fmt.Fprintf(w, "\tm := new(%s)\n", replyTyp)
-                       fmt.Fprintf(w, "\tstop, err := c.MultiRequestCtx.ReceiveReply(m)\n")
-                       fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
-                       fmt.Fprintf(w, "\tif stop { return nil, io.EOF }\n")
-                       fmt.Fprintf(w, "\treturn m, nil\n")
-                       fmt.Fprintln(w, "}")
-                       fmt.Fprintln(w)
-               }
-       }
-
-       fmt.Fprintln(w)
-}
-
-func generateServiceMethod(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("in *%s", reqTyp)
-       returns := "error"
-
-       if replyType := camelCaseName(svc.ReplyType); replyType != "" {
-               var replyTyp string
-               if svc.Stream {
-                       replyTyp = fmt.Sprintf("%s_%sClient", serviceApiName, method)
-               } else {
-                       replyTyp = fmt.Sprintf("*%s", replyType)
-               }
-               returns = fmt.Sprintf("(%s, error)", replyTyp)
-       }
-
-       fmt.Fprintf(w, "\t%s(ctx context.Context, %s) %s", method, params, returns)
-}