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