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