Optimize socketclient adapter and add various code improvements
[govpp.git] / cmd / binapi-generator / generate.go
1 // Copyright (c) 2017 Cisco and/or its affiliates.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at:
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package main
16
17 import (
18         "bytes"
19         "fmt"
20         "io"
21         "os/exec"
22         "path"
23         "path/filepath"
24         "sort"
25         "strings"
26         "unicode"
27 )
28
29 // generatedCodeVersion indicates a version of the generated code.
30 // It is incremented whenever an incompatibility between the generated code and
31 // GoVPP api package is introduced; the generated code references
32 // a constant, api.GoVppAPIPackageIsVersionN (where N is generatedCodeVersion).
33 const generatedCodeVersion = 1
34
35 const (
36         inputFileExt  = ".api.json" // file extension of the VPP API files
37         outputFileExt = ".ba.go"    // file extension of the Go generated files
38
39         constModuleName = "ModuleName" // module name constant
40         constAPIVersion = "APIVersion" // API version constant
41         constVersionCrc = "VersionCrc" // version CRC constant
42
43         unionDataField = "XXX_UnionData" // name for the union data field
44
45         serviceApiName    = "RPCService"    // name for the RPC service interface
46         serviceImplName   = "serviceClient" // name for the RPC service implementation
47         serviceClientName = "ServiceClient" // name for the RPC service client
48 )
49
50 // context is a structure storing data for code generation
51 type context struct {
52         inputFile  string // input file with VPP API in JSON
53         outputFile string // output file with generated Go package
54
55         importPrefix string // defines import path prefix for importing types
56
57         inputData []byte // contents of the input file
58
59         includeAPIVersion  bool // include constant with API version string
60         includeComments    bool // include parts of original source in comments
61         includeBinapiNames bool // include binary API names as struct tag
62         includeServices    bool // include service interface with client implementation
63
64         moduleName  string // name of the source VPP module
65         packageName string // name of the Go package being generated
66
67         packageData *Package // parsed package data
68 }
69
70 // newContext returns context details of the code generation task
71 func newContext(inputFile, outputDir string) (*context, error) {
72         if !strings.HasSuffix(inputFile, inputFileExt) {
73                 return nil, fmt.Errorf("invalid input file name: %q", inputFile)
74         }
75
76         ctx := &context{
77                 inputFile: inputFile,
78         }
79
80         // package name
81         inputFileName := filepath.Base(inputFile)
82         ctx.moduleName = inputFileName[:strings.Index(inputFileName, ".")]
83
84         // alter package names for modules that are reserved keywords in Go
85         switch ctx.moduleName {
86         case "interface":
87                 ctx.packageName = "interfaces"
88         case "map":
89                 ctx.packageName = "maps"
90         default:
91                 ctx.packageName = ctx.moduleName
92         }
93
94         // output file
95         packageDir := filepath.Join(outputDir, ctx.packageName)
96         outputFileName := ctx.packageName + outputFileExt
97         ctx.outputFile = filepath.Join(packageDir, outputFileName)
98
99         return ctx, nil
100 }
101
102 func generatePackage(ctx *context, w io.Writer) error {
103         logf("----------------------------")
104         logf("generating package %q", ctx.packageName)
105         logf("----------------------------")
106
107         fmt.Fprintln(w, "// Code generated by GoVPP's binapi-generator. DO NOT EDIT.")
108         fmt.Fprintf(w, "// source: %s\n", ctx.inputFile)
109         fmt.Fprintln(w)
110
111         generateHeader(ctx, w)
112         generateImports(ctx, w)
113
114         // generate module desc
115         fmt.Fprintln(w, "const (")
116         fmt.Fprintf(w, "\t// %s is the name of this module.\n", constModuleName)
117         fmt.Fprintf(w, "\t%s = \"%s\"\n", constModuleName, ctx.moduleName)
118
119         if ctx.includeAPIVersion {
120                 if ctx.packageData.Version != "" {
121                         fmt.Fprintf(w, "\t// %s is the API version of this module.\n", constAPIVersion)
122                         fmt.Fprintf(w, "\t%s = \"%s\"\n", constAPIVersion, ctx.packageData.Version)
123                 }
124                 fmt.Fprintf(w, "\t// %s is the CRC of this module.\n", constVersionCrc)
125                 fmt.Fprintf(w, "\t%s = %v\n", constVersionCrc, ctx.packageData.CRC)
126         }
127         fmt.Fprintln(w, ")")
128         fmt.Fprintln(w)
129
130         // generate enums
131         if len(ctx.packageData.Enums) > 0 {
132                 for _, enum := range ctx.packageData.Enums {
133                         if imp, ok := ctx.packageData.Imports[enum.Name]; ok {
134                                 generateImportedAlias(ctx, w, enum.Name, &imp)
135                                 continue
136                         }
137                         generateEnum(ctx, w, &enum)
138                 }
139         }
140
141         // generate aliases
142         if len(ctx.packageData.Aliases) > 0 {
143                 for _, alias := range ctx.packageData.Aliases {
144                         if imp, ok := ctx.packageData.Imports[alias.Name]; ok {
145                                 generateImportedAlias(ctx, w, alias.Name, &imp)
146                                 continue
147                         }
148                         generateAlias(ctx, w, &alias)
149                 }
150         }
151
152         // generate types
153         if len(ctx.packageData.Types) > 0 {
154                 for _, typ := range ctx.packageData.Types {
155                         if imp, ok := ctx.packageData.Imports[typ.Name]; ok {
156                                 generateImportedAlias(ctx, w, typ.Name, &imp)
157                                 continue
158                         }
159                         generateType(ctx, w, &typ)
160                 }
161         }
162
163         // generate unions
164         if len(ctx.packageData.Unions) > 0 {
165                 for _, union := range ctx.packageData.Unions {
166                         if imp, ok := ctx.packageData.Imports[union.Name]; ok {
167                                 generateImportedAlias(ctx, w, union.Name, &imp)
168                                 continue
169                         }
170                         generateUnion(ctx, w, &union)
171                 }
172         }
173
174         // generate messages
175         if len(ctx.packageData.Messages) > 0 {
176                 for _, msg := range ctx.packageData.Messages {
177                         generateMessage(ctx, w, &msg)
178                 }
179
180                 // generate message registrations
181                 fmt.Fprintln(w, "func init() {")
182                 for _, msg := range ctx.packageData.Messages {
183                         name := camelCaseName(msg.Name)
184                         fmt.Fprintf(w, "\tapi.RegisterMessage((*%s)(nil), \"%s\")\n", name, ctx.moduleName+"."+name)
185                 }
186                 fmt.Fprintln(w, "}")
187                 fmt.Fprintln(w)
188
189                 // generate list of messages
190                 fmt.Fprintf(w, "// Messages returns list of all messages in this module.\n")
191                 fmt.Fprintln(w, "func AllMessages() []api.Message {")
192                 fmt.Fprintln(w, "\treturn []api.Message{")
193                 for _, msg := range ctx.packageData.Messages {
194                         name := camelCaseName(msg.Name)
195                         fmt.Fprintf(w, "\t(*%s)(nil),\n", name)
196                 }
197                 fmt.Fprintln(w, "}")
198                 fmt.Fprintln(w, "}")
199         }
200
201         if ctx.includeServices {
202                 // generate services
203                 if len(ctx.packageData.Services) > 0 {
204                         generateServices(ctx, w, ctx.packageData.Services)
205                 }
206         }
207
208         generateFooter(ctx, w)
209
210         return nil
211 }
212
213 func generateHeader(ctx *context, w io.Writer) {
214         fmt.Fprintln(w, "/*")
215         fmt.Fprintf(w, "Package %s is a generated VPP binary API for '%s' module.\n", ctx.packageName, ctx.moduleName)
216         fmt.Fprintln(w)
217         fmt.Fprintln(w, "It consists of:")
218         printObjNum := func(obj string, num int) {
219                 if num > 0 {
220                         if num > 1 {
221                                 if strings.HasSuffix(obj, "s") {
222
223                                         obj += "es"
224                                 } else {
225                                         obj += "s"
226                                 }
227                         }
228                         fmt.Fprintf(w, "\t%3d %s\n", num, obj)
229                 }
230         }
231         printObjNum("enum", len(ctx.packageData.Enums))
232         printObjNum("alias", len(ctx.packageData.Aliases))
233         printObjNum("type", len(ctx.packageData.Types))
234         printObjNum("union", len(ctx.packageData.Unions))
235         printObjNum("message", len(ctx.packageData.Messages))
236         printObjNum("service", len(ctx.packageData.Services))
237         fmt.Fprintln(w, "*/")
238         fmt.Fprintf(w, "package %s\n", ctx.packageName)
239         fmt.Fprintln(w)
240
241 }
242
243 func generateImports(ctx *context, w io.Writer) {
244         fmt.Fprintln(w, "import (")
245         fmt.Fprintln(w, `       "bytes"`)
246         fmt.Fprintln(w, `       "context"`)
247         fmt.Fprintln(w, `       "io"`)
248         fmt.Fprintln(w, `       "strconv"`)
249         fmt.Fprintln(w)
250         fmt.Fprintf(w, "\tapi \"%s\"\n", "git.fd.io/govpp.git/api")
251         fmt.Fprintf(w, "\tstruc \"%s\"\n", "github.com/lunixbochs/struc")
252         if len(ctx.packageData.Imports) > 0 {
253                 fmt.Fprintln(w)
254                 for _, imp := range getImports(ctx) {
255                         importPath := path.Join(ctx.importPrefix, imp)
256                         if importPath == "" {
257                                 importPath = getImportPath(filepath.Dir(ctx.outputFile), imp)
258                         }
259                         fmt.Fprintf(w, "\t%s \"%s\"\n", imp, strings.TrimSpace(importPath))
260                 }
261         }
262         fmt.Fprintln(w, ")")
263         fmt.Fprintln(w)
264 }
265
266 func getImportPath(outputDir string, pkg string) string {
267         absPath, _ := filepath.Abs(filepath.Join(outputDir, "..", pkg))
268         cmd := exec.Command("go", "list", absPath)
269         var errbuf, outbuf bytes.Buffer
270         cmd.Stdout = &outbuf
271         cmd.Stderr = &errbuf
272         if err := cmd.Run(); err != nil {
273                 fmt.Printf("ERR: %v\n", errbuf.String())
274                 panic(err)
275         }
276         return outbuf.String()
277 }
278
279 func getImports(ctx *context) (imports []string) {
280         impmap := map[string]struct{}{}
281         for _, imp := range ctx.packageData.Imports {
282                 if _, ok := impmap[imp.Package]; !ok {
283                         imports = append(imports, imp.Package)
284                         impmap[imp.Package] = struct{}{}
285                 }
286         }
287         sort.Strings(imports)
288         return imports
289 }
290
291 func generateFooter(ctx *context, w io.Writer) {
292         fmt.Fprintln(w, "// This is a compile-time assertion to ensure that this generated file")
293         fmt.Fprintln(w, "// is compatible with the GoVPP api package it is being compiled against.")
294         fmt.Fprintln(w, "// A compilation error at this line likely means your copy of the")
295         fmt.Fprintln(w, "// GoVPP api package needs to be updated.")
296         fmt.Fprintf(w, "const _ = api.GoVppAPIPackageIsVersion%d // please upgrade the GoVPP api package\n", generatedCodeVersion)
297         fmt.Fprintln(w)
298
299         fmt.Fprintf(w, "// Reference imports to suppress errors if they are not otherwise used.\n")
300         fmt.Fprintf(w, "var _ = api.RegisterMessage\n")
301         fmt.Fprintf(w, "var _ = bytes.NewBuffer\n")
302         fmt.Fprintf(w, "var _ = context.Background\n")
303         fmt.Fprintf(w, "var _ = io.Copy\n")
304         fmt.Fprintf(w, "var _ = strconv.Itoa\n")
305         fmt.Fprintf(w, "var _ = struc.Pack\n")
306 }
307
308 func generateComment(ctx *context, w io.Writer, goName string, vppName string, objKind string) {
309         if objKind == "service" {
310                 fmt.Fprintf(w, "// %s represents RPC service API for %s module.\n", goName, ctx.moduleName)
311         } else {
312                 fmt.Fprintf(w, "// %s represents VPP binary API %s '%s'.\n", goName, objKind, vppName)
313         }
314
315         if !ctx.includeComments {
316                 return
317         }
318
319         var isNotSpace = func(r rune) bool {
320                 return !unicode.IsSpace(r)
321         }
322
323         // print out the source of the generated object
324         mapType := false
325         objFound := false
326         objTitle := fmt.Sprintf(`"%s",`, vppName)
327         switch objKind {
328         case "alias", "service":
329                 objTitle = fmt.Sprintf(`"%s": {`, vppName)
330                 mapType = true
331         }
332
333         inputBuff := bytes.NewBuffer(ctx.inputData)
334         inputLine := 0
335
336         var trimIndent string
337         var indent int
338         for {
339                 line, err := inputBuff.ReadString('\n')
340                 if err != nil {
341                         break
342                 }
343                 inputLine++
344
345                 noSpaceAt := strings.IndexFunc(line, isNotSpace)
346                 if !objFound {
347                         indent = strings.Index(line, objTitle)
348                         if indent == -1 {
349                                 continue
350                         }
351                         trimIndent = line[:indent]
352                         // If no other non-whitespace character then we are at the message header.
353                         if trimmed := strings.TrimSpace(line); trimmed == objTitle {
354                                 objFound = true
355                                 fmt.Fprintln(w, "//")
356                         }
357                 } else if noSpaceAt < indent {
358                         break // end of the definition in JSON for array types
359                 } else if objFound && mapType && noSpaceAt <= indent {
360                         fmt.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent))
361                         break // end of the definition in JSON for map types (aliases, services)
362                 }
363                 fmt.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent))
364         }
365
366         fmt.Fprintln(w, "//")
367 }
368
369 func generateEnum(ctx *context, w io.Writer, enum *Enum) {
370         name := camelCaseName(enum.Name)
371         typ := binapiTypes[enum.Type]
372
373         logf(" writing enum %q (%s) with %d entries", enum.Name, name, len(enum.Entries))
374
375         // generate enum comment
376         generateComment(ctx, w, name, enum.Name, "enum")
377
378         // generate enum definition
379         fmt.Fprintf(w, "type %s %s\n", name, typ)
380         fmt.Fprintln(w)
381
382         // generate enum entries
383         fmt.Fprintln(w, "const (")
384         for _, entry := range enum.Entries {
385                 fmt.Fprintf(w, "\t%s %s = %v\n", entry.Name, name, entry.Value)
386         }
387         fmt.Fprintln(w, ")")
388         fmt.Fprintln(w)
389
390         // generate enum conversion maps
391         fmt.Fprintf(w, "var %s_name = map[%s]string{\n", name, typ)
392         for _, entry := range enum.Entries {
393                 fmt.Fprintf(w, "\t%v: \"%s\",\n", entry.Value, entry.Name)
394         }
395         fmt.Fprintln(w, "}")
396         fmt.Fprintln(w)
397
398         fmt.Fprintf(w, "var %s_value = map[string]%s{\n", name, typ)
399         for _, entry := range enum.Entries {
400                 fmt.Fprintf(w, "\t\"%s\": %v,\n", entry.Name, entry.Value)
401         }
402         fmt.Fprintln(w, "}")
403         fmt.Fprintln(w)
404
405         fmt.Fprintf(w, "func (x %s) String() string {\n", name)
406         fmt.Fprintf(w, "\ts, ok := %s_name[%s(x)]\n", name, typ)
407         fmt.Fprintf(w, "\tif ok { return s }\n")
408         fmt.Fprintf(w, "\treturn strconv.Itoa(int(x))\n")
409         fmt.Fprintln(w, "}")
410         fmt.Fprintln(w)
411 }
412
413 func generateImportedAlias(ctx *context, w io.Writer, tName string, imp *Import) {
414         name := camelCaseName(tName)
415
416         fmt.Fprintf(w, "type %s = %s.%s\n", name, imp.Package, name)
417
418         fmt.Fprintln(w)
419 }
420
421 func generateAlias(ctx *context, w io.Writer, alias *Alias) {
422         name := camelCaseName(alias.Name)
423
424         logf(" writing type %q (%s), length: %d", alias.Name, name, alias.Length)
425
426         // generate struct comment
427         generateComment(ctx, w, name, alias.Name, "alias")
428
429         // generate struct definition
430         fmt.Fprintf(w, "type %s ", name)
431
432         if alias.Length > 0 {
433                 fmt.Fprintf(w, "[%d]", alias.Length)
434         }
435
436         dataType := convertToGoType(ctx, alias.Type)
437         fmt.Fprintf(w, "%s\n", dataType)
438
439         fmt.Fprintln(w)
440 }
441
442 func generateUnion(ctx *context, w io.Writer, union *Union) {
443         name := camelCaseName(union.Name)
444
445         logf(" writing union %q (%s) with %d fields", union.Name, name, len(union.Fields))
446
447         // generate struct comment
448         generateComment(ctx, w, name, union.Name, "union")
449
450         // generate struct definition
451         fmt.Fprintln(w, "type", name, "struct {")
452
453         // maximum size for union
454         maxSize := getUnionSize(ctx, union)
455
456         // generate data field
457         fmt.Fprintf(w, "\t%s [%d]byte\n", unionDataField, maxSize)
458
459         // generate end of the struct
460         fmt.Fprintln(w, "}")
461
462         // generate name getter
463         generateTypeNameGetter(w, name, union.Name)
464
465         // generate CRC getter
466         if union.CRC != "" {
467                 generateCrcGetter(w, name, union.CRC)
468         }
469
470         // generate getters for fields
471         for _, field := range union.Fields {
472                 fieldName := camelCaseName(field.Name)
473                 fieldType := convertToGoType(ctx, field.Type)
474                 generateUnionGetterSetter(w, name, fieldName, fieldType)
475         }
476
477         // generate union methods
478         //generateUnionMethods(w, name)
479
480         fmt.Fprintln(w)
481 }
482
483 // generateUnionMethods generates methods that implement struc.Custom
484 // interface to allow having XXX_uniondata field unexported
485 // TODO: do more testing when unions are actually used in some messages
486 /*func generateUnionMethods(w io.Writer, structName string) {
487         // generate struc.Custom implementation for union
488         fmt.Fprintf(w, `
489 func (u *%[1]s) Pack(p []byte, opt *struc.Options) (int, error) {
490         var b = new(bytes.Buffer)
491         if err := struc.PackWithOptions(b, u.union_data, opt); err != nil {
492                 return 0, err
493         }
494         copy(p, b.Bytes())
495         return b.Len(), nil
496 }
497 func (u *%[1]s) Unpack(r io.Reader, length int, opt *struc.Options) error {
498         return struc.UnpackWithOptions(r, u.union_data[:], opt)
499 }
500 func (u *%[1]s) Size(opt *struc.Options) int {
501         return len(u.union_data)
502 }
503 func (u *%[1]s) String() string {
504         return string(u.union_data[:])
505 }
506 `, structName)
507 }*/
508
509 func generateUnionGetterSetter(w io.Writer, structName string, getterField, getterStruct string) {
510         fmt.Fprintf(w, `
511 func %[1]s%[2]s(a %[3]s) (u %[1]s) {
512         u.Set%[2]s(a)
513         return
514 }
515 func (u *%[1]s) Set%[2]s(a %[3]s) {
516         var b = new(bytes.Buffer)
517         if err := struc.Pack(b, &a); err != nil {
518                 return
519         }
520         copy(u.%[4]s[:], b.Bytes())
521 }
522 func (u *%[1]s) Get%[2]s() (a %[3]s) {
523         var b = bytes.NewReader(u.%[4]s[:])
524         struc.Unpack(b, &a)
525         return
526 }
527 `, structName, getterField, getterStruct, unionDataField)
528 }
529
530 func generateType(ctx *context, w io.Writer, typ *Type) {
531         name := camelCaseName(typ.Name)
532
533         logf(" writing type %q (%s) with %d fields", typ.Name, name, len(typ.Fields))
534
535         // generate struct comment
536         generateComment(ctx, w, name, typ.Name, "type")
537
538         // generate struct definition
539         fmt.Fprintf(w, "type %s struct {\n", name)
540
541         // generate struct fields
542         for i, field := range typ.Fields {
543                 // skip internal fields
544                 switch strings.ToLower(field.Name) {
545                 case crcField, msgIdField:
546                         continue
547                 }
548
549                 generateField(ctx, w, typ.Fields, i)
550         }
551
552         // generate end of the struct
553         fmt.Fprintln(w, "}")
554
555         // generate name getter
556         generateTypeNameGetter(w, name, typ.Name)
557
558         // generate CRC getter
559         if typ.CRC != "" {
560                 generateCrcGetter(w, name, typ.CRC)
561         }
562
563         fmt.Fprintln(w)
564 }
565
566 func generateMessage(ctx *context, w io.Writer, msg *Message) {
567         name := camelCaseName(msg.Name)
568
569         logf(" writing message %q (%s) with %d fields", msg.Name, name, len(msg.Fields))
570
571         // generate struct comment
572         generateComment(ctx, w, name, msg.Name, "message")
573
574         // generate struct definition
575         fmt.Fprintf(w, "type %s struct {", name)
576
577         msgType := otherMessage
578         wasClientIndex := false
579
580         // generate struct fields
581         n := 0
582         for i, field := range msg.Fields {
583                 if i == 1 {
584                         if field.Name == clientIndexField {
585                                 // "client_index" as the second member,
586                                 // this might be an event message or a request
587                                 msgType = eventMessage
588                                 wasClientIndex = true
589                         } else if field.Name == contextField {
590                                 // reply needs "context" as the second member
591                                 msgType = replyMessage
592                         }
593                 } else if i == 2 {
594                         if wasClientIndex && field.Name == contextField {
595                                 // request needs "client_index" as the second member
596                                 // and "context" as the third member
597                                 msgType = requestMessage
598                         }
599                 }
600
601                 // skip internal fields
602                 switch strings.ToLower(field.Name) {
603                 case crcField, msgIdField:
604                         continue
605                 case clientIndexField, contextField:
606                         if n == 0 {
607                                 continue
608                         }
609                 }
610                 n++
611                 if n == 1 {
612                         fmt.Fprintln(w)
613                 }
614
615                 generateField(ctx, w, msg.Fields, i)
616         }
617
618         // generate end of the struct
619         fmt.Fprintln(w, "}")
620
621         // generate message methods
622         generateMessageResetMethod(w, name)
623         generateMessageNameGetter(w, name, msg.Name)
624         generateCrcGetter(w, name, msg.CRC)
625         generateMessageTypeGetter(w, name, msgType)
626
627         fmt.Fprintln(w)
628 }
629
630 func generateField(ctx *context, w io.Writer, fields []Field, i int) {
631         field := fields[i]
632
633         fieldName := strings.TrimPrefix(field.Name, "_")
634         fieldName = camelCaseName(fieldName)
635
636         dataType := convertToGoType(ctx, field.Type)
637         fieldType := dataType
638
639         // generate length field for strings
640         if field.Type == "string" && field.Length == 0 {
641                 fmt.Fprintf(w, "\tXXX_%sLen uint32 `struc:\"sizeof=%s\"`\n", fieldName, fieldName)
642         }
643
644         // check if it is array
645         if field.Length > 0 || field.SizeFrom != "" {
646                 if dataType == "uint8" {
647                         dataType = "byte"
648                 }
649                 if dataType == "string" && field.SpecifiedLen {
650                         fieldType = "string"
651                         dataType = "byte"
652                 } else {
653                         fieldType = "[]" + dataType
654                 }
655         }
656         fmt.Fprintf(w, "\t%s %s", fieldName, fieldType)
657
658         fieldTags := map[string]string{}
659
660         if field.Length > 0 && field.SpecifiedLen {
661                 // fixed size array
662                 fieldTags["struc"] = fmt.Sprintf("[%d]%s", field.Length, dataType)
663         } else {
664                 for _, f := range fields {
665                         if f.SizeFrom == field.Name {
666                                 // variable sized array
667                                 sizeOfName := camelCaseName(f.Name)
668                                 fieldTags["struc"] = fmt.Sprintf("sizeof=%s", sizeOfName)
669                         }
670                 }
671         }
672
673         if ctx.includeBinapiNames {
674                 fieldTags["binapi"] = field.Name
675         }
676         if field.Meta.Limit > 0 {
677                 fieldTags["binapi"] = fmt.Sprintf("%s,limit=%d", fieldTags["binapi"], field.Meta.Limit)
678         }
679
680         if len(fieldTags) > 0 {
681                 fmt.Fprintf(w, "\t`")
682                 var keys []string
683                 for k := range fieldTags {
684                         keys = append(keys, k)
685                 }
686                 sort.Strings(keys)
687                 var n int
688                 for _, tt := range keys {
689                         t, ok := fieldTags[tt]
690                         if !ok {
691                                 continue
692                         }
693                         if n > 0 {
694                                 fmt.Fprintf(w, " ")
695                         }
696                         n++
697                         fmt.Fprintf(w, `%s:"%s"`, tt, t)
698                 }
699                 fmt.Fprintf(w, "`")
700         }
701
702         fmt.Fprintln(w)
703 }
704
705 func generateMessageResetMethod(w io.Writer, structName string) {
706         fmt.Fprintf(w, "func (m *%[1]s) Reset() { *m = %[1]s{} }\n", structName)
707 }
708
709 func generateMessageNameGetter(w io.Writer, structName, msgName string) {
710         fmt.Fprintf(w, "func (*%s) GetMessageName() string {    return %q }\n", structName, msgName)
711 }
712
713 func generateTypeNameGetter(w io.Writer, structName, msgName string) {
714         fmt.Fprintf(w, "func (*%s) GetTypeName() string { return %q }\n", structName, msgName)
715 }
716
717 func generateCrcGetter(w io.Writer, structName, crc string) {
718         crc = strings.TrimPrefix(crc, "0x")
719         fmt.Fprintf(w, "func (*%s) GetCrcString() string { return %q }\n", structName, crc)
720 }
721
722 func generateMessageTypeGetter(w io.Writer, structName string, msgType MessageType) {
723         fmt.Fprintf(w, "func (*"+structName+") GetMessageType() api.MessageType {")
724         if msgType == requestMessage {
725                 fmt.Fprintf(w, "\treturn api.RequestMessage")
726         } else if msgType == replyMessage {
727                 fmt.Fprintf(w, "\treturn api.ReplyMessage")
728         } else if msgType == eventMessage {
729                 fmt.Fprintf(w, "\treturn api.EventMessage")
730         } else {
731                 fmt.Fprintf(w, "\treturn api.OtherMessage")
732         }
733         fmt.Fprintln(w, "}")
734         fmt.Fprintln(w)
735 }
736
737 func generateServices(ctx *context, w io.Writer, services []Service) {
738
739         // generate services comment
740         generateComment(ctx, w, serviceApiName, "services", "service")
741
742         // generate service api
743         fmt.Fprintf(w, "type %s interface {\n", serviceApiName)
744         for _, svc := range services {
745                 generateServiceMethod(ctx, w, &svc)
746                 fmt.Fprintln(w)
747         }
748         fmt.Fprintln(w, "}")
749         fmt.Fprintln(w)
750
751         // generate client implementation
752         fmt.Fprintf(w, "type %s struct {\n", serviceImplName)
753         fmt.Fprintf(w, "\tch api.Channel\n")
754         fmt.Fprintln(w, "}")
755         fmt.Fprintln(w)
756
757         // generate client constructor
758         fmt.Fprintf(w, "func New%s(ch api.Channel) %s {\n", serviceClientName, serviceApiName)
759         fmt.Fprintf(w, "\treturn &%s{ch}\n", serviceImplName)
760         fmt.Fprintln(w, "}")
761         fmt.Fprintln(w)
762
763         for _, svc := range services {
764                 method := camelCaseName(svc.RequestType)
765                 if m := strings.TrimSuffix(method, "Dump"); method != m {
766                         method = "Dump" + m
767                 }
768
769                 fmt.Fprintf(w, "func (c *%s) ", serviceImplName)
770                 generateServiceMethod(ctx, w, &svc)
771                 fmt.Fprintln(w, " {")
772                 if svc.Stream {
773                         streamImpl := fmt.Sprintf("%s_%sClient", serviceImplName, method)
774                         fmt.Fprintf(w, "\tstream := c.ch.SendMultiRequest(in)\n")
775                         fmt.Fprintf(w, "\tx := &%s{stream}\n", streamImpl)
776                         fmt.Fprintf(w, "\treturn x, nil\n")
777                 } else if replyTyp := camelCaseName(svc.ReplyType); replyTyp != "" {
778                         fmt.Fprintf(w, "\tout := new(%s)\n", replyTyp)
779                         fmt.Fprintf(w, "\terr:= c.ch.SendRequest(in).ReceiveReply(out)\n")
780                         fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
781                         fmt.Fprintf(w, "\treturn out, nil\n")
782                 } else {
783                         fmt.Fprintf(w, "\tc.ch.SendRequest(in)\n")
784                         fmt.Fprintf(w, "\treturn nil\n")
785                 }
786                 fmt.Fprintln(w, "}")
787                 fmt.Fprintln(w)
788
789                 if svc.Stream {
790                         replyTyp := camelCaseName(svc.ReplyType)
791                         method := camelCaseName(svc.RequestType)
792                         if m := strings.TrimSuffix(method, "Dump"); method != m {
793                                 method = "Dump" + m
794                         }
795                         streamApi := fmt.Sprintf("%s_%sClient", serviceApiName, method)
796
797                         fmt.Fprintf(w, "type %s interface {\n", streamApi)
798                         fmt.Fprintf(w, "\tRecv() (*%s, error)\n", replyTyp)
799                         fmt.Fprintln(w, "}")
800                         fmt.Fprintln(w)
801
802                         streamImpl := fmt.Sprintf("%s_%sClient", serviceImplName, method)
803                         fmt.Fprintf(w, "type %s struct {\n", streamImpl)
804                         fmt.Fprintf(w, "\tapi.MultiRequestCtx\n")
805                         fmt.Fprintln(w, "}")
806                         fmt.Fprintln(w)
807
808                         fmt.Fprintf(w, "func (c *%s) Recv() (*%s, error) {\n", streamImpl, replyTyp)
809                         fmt.Fprintf(w, "\tm := new(%s)\n", replyTyp)
810                         fmt.Fprintf(w, "\tstop, err := c.MultiRequestCtx.ReceiveReply(m)\n")
811                         fmt.Fprintf(w, "\tif err != nil { return nil, err }\n")
812                         fmt.Fprintf(w, "\tif stop { return nil, io.EOF }\n")
813                         fmt.Fprintf(w, "\treturn m, nil\n")
814                         fmt.Fprintln(w, "}")
815                         fmt.Fprintln(w)
816                 }
817         }
818
819         fmt.Fprintln(w)
820 }
821
822 func generateServiceMethod(ctx *context, w io.Writer, svc *Service) {
823         reqTyp := camelCaseName(svc.RequestType)
824
825         // method name is same as parameter type name by default
826         method := reqTyp
827         if svc.Stream {
828                 // use Dump as prefix instead of suffix for stream services
829                 if m := strings.TrimSuffix(method, "Dump"); method != m {
830                         method = "Dump" + m
831                 }
832         }
833
834         params := fmt.Sprintf("in *%s", reqTyp)
835         returns := "error"
836
837         if replyType := camelCaseName(svc.ReplyType); replyType != "" {
838                 var replyTyp string
839                 if svc.Stream {
840                         replyTyp = fmt.Sprintf("%s_%sClient", serviceApiName, method)
841                 } else {
842                         replyTyp = fmt.Sprintf("*%s", replyType)
843                 }
844                 returns = fmt.Sprintf("(%s, error)", replyTyp)
845         }
846
847         fmt.Fprintf(w, "\t%s(ctx context.Context, %s) %s", method, params, returns)
848 }