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