Fix encode/decode for []bool
[govpp.git] / binapigen / generate.go
1 //  Copyright (c) 2020 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 binapigen
16
17 import (
18         "fmt"
19         "path"
20         "sort"
21         "strconv"
22         "strings"
23
24         "git.fd.io/govpp.git/internal/version"
25 )
26
27 // library dependencies
28 const (
29         strconvPkg = GoImportPath("strconv")
30
31         govppApiPkg   = GoImportPath("git.fd.io/govpp.git/api")
32         govppCodecPkg = GoImportPath("git.fd.io/govpp.git/codec")
33 )
34
35 // generated names
36 const (
37         apiName    = "APIFile"    // API file name
38         apiVersion = "APIVersion" // API version number
39         apiCrc     = "VersionCrc" // version checksum
40
41         fieldUnionData = "XXX_UnionData" // name for the union data field
42 )
43
44 // option keys
45 const (
46         msgStatus     = "status"
47         msgDeprecated = "deprecated"
48         msgInProgress = "in_progress"
49 )
50
51 // generated option messages
52 const (
53         deprecatedMsg = "the message will be removed in the future versions"
54         inProgressMsg = "the message form may change in the future versions"
55 )
56
57 func GenerateAPI(gen *Generator, file *File) *GenFile {
58         logf("----------------------------")
59         logf(" Generate API - %s", file.Desc.Name)
60         logf("----------------------------")
61
62         filename := path.Join(file.FilenamePrefix, file.Desc.Name+".ba.go")
63         g := gen.NewGenFile(filename, file.GoImportPath)
64         g.file = file
65
66         g.P("// Code generated by GoVPP's binapi-generator. DO NOT EDIT.")
67         if !gen.opts.NoVersionInfo {
68                 g.P("// versions:")
69                 g.P("//  binapi-generator: ", version.Version())
70                 g.P("//  VPP:              ", g.gen.vppVersion)
71                 if !gen.opts.NoSourcePathInfo {
72                         g.P("// source: ", g.file.Desc.Path)
73                 }
74         }
75         g.P()
76
77         genPackageComment(g)
78         g.P("package ", file.PackageName)
79         g.P()
80
81         for _, imp := range g.file.Imports {
82                 genImport(g, imp)
83         }
84
85         // generate version assertion
86         g.P("// This is a compile-time assertion to ensure that this generated file")
87         g.P("// is compatible with the GoVPP api package it is being compiled against.")
88         g.P("// A compilation error at this line likely means your copy of the")
89         g.P("// GoVPP api package needs to be updated.")
90         g.P("const _ = ", govppApiPkg.Ident("GoVppAPIPackageIsVersion"), generatedCodeVersion)
91         g.P()
92
93         if !file.isTypesFile() {
94                 g.P("const (")
95                 g.P(apiName, " = ", strconv.Quote(g.file.Desc.Name))
96                 g.P(apiVersion, " = ", strconv.Quote(g.file.Version))
97                 g.P(apiCrc, " = ", g.file.Desc.CRC)
98                 g.P(")")
99                 g.P()
100         }
101
102         for _, enum := range g.file.Enums {
103                 genEnum(g, enum)
104         }
105         for _, alias := range g.file.Aliases {
106                 genAlias(g, alias)
107         }
108         for _, typ := range g.file.Structs {
109                 genStruct(g, typ)
110         }
111         for _, union := range g.file.Unions {
112                 genUnion(g, union)
113         }
114         genMessages(g)
115
116         return g
117 }
118
119 func genPackageComment(g *GenFile) {
120         apifile := g.file.Desc.Name + ".api"
121         g.P("// Package ", g.file.PackageName, " contains generated bindings for API file ", apifile, ".")
122         g.P("//")
123         g.P("// Contents:")
124         printObjNum := func(obj string, num int) {
125                 if num > 0 {
126                         if num > 1 {
127                                 if strings.HasSuffix(obj, "s") {
128                                         obj += "es"
129                                 } else {
130                                         obj += "s"
131                                 }
132                         }
133                         g.P("// ", fmt.Sprintf("%3d", num), " ", obj)
134                 }
135         }
136         printObjNum("alias", len(g.file.Aliases))
137         printObjNum("enum", len(g.file.Enums))
138         printObjNum("struct", len(g.file.Structs))
139         printObjNum("union", len(g.file.Unions))
140         printObjNum("message", len(g.file.Messages))
141         g.P("//")
142 }
143
144 func genImport(g *GenFile, imp string) {
145         impFile, ok := g.gen.FilesByName[imp]
146         if !ok {
147                 return
148         }
149         if impFile.GoImportPath == g.file.GoImportPath {
150                 // Skip generating imports for types in the same package
151                 return
152         }
153         // Generate imports for all dependencies, even if not used
154         g.Import(impFile.GoImportPath)
155 }
156
157 func genTypeComment(g *GenFile, goName string, vppName string, objKind string) {
158         g.P("// ", goName, " defines ", objKind, " '", vppName, "'.")
159 }
160
161 func genTypeOptionComment(g *GenFile, options map[string]string) {
162         // all messages for API versions < 1.0.0 are in_progress by default
163         if msg, ok := options[msgInProgress]; ok || options[msgStatus] == msgInProgress ||
164                 len(g.file.Version) > 1 && g.file.Version[0:2] == "0." {
165                 if msg == "" {
166                         msg = inProgressMsg
167                 }
168                 g.P("// InProgress: ", msg)
169         }
170         if msg, ok := options[msgDeprecated]; ok || options[msgStatus] == msgDeprecated {
171                 if msg == "" {
172                         msg = deprecatedMsg
173                 }
174                 g.P("// Deprecated: ", msg)
175         }
176 }
177
178 func genEnum(g *GenFile, enum *Enum) {
179         logf("gen ENUM %s (%s) - %d entries", enum.GoName, enum.Name, len(enum.Entries))
180
181         genTypeComment(g, enum.GoName, enum.Name, "enum")
182
183         gotype := BaseTypesGo[enum.Type]
184
185         g.P("type ", enum.GoName, " ", gotype)
186         g.P()
187
188         // generate enum entries
189         g.P("const (")
190         for _, entry := range enum.Entries {
191                 g.P(entry.Name, " ", enum.GoName, " = ", entry.Value)
192         }
193         g.P(")")
194         g.P()
195
196         // generate enum conversion maps
197         g.P("var (")
198         g.P(enum.GoName, "_name = map[", gotype, "]string{")
199         for _, entry := range enum.Entries {
200                 g.P(entry.Value, ": ", strconv.Quote(entry.Name), ",")
201         }
202         g.P("}")
203         g.P(enum.GoName, "_value = map[string]", gotype, "{")
204         for _, entry := range enum.Entries {
205                 g.P(strconv.Quote(entry.Name), ": ", entry.Value, ",")
206         }
207         g.P("}")
208         g.P(")")
209         g.P()
210
211         if enum.IsFlag || isEnumFlag(enum) {
212                 size := BaseTypeSizes[enum.Type] * 8
213                 g.P("func (x ", enum.GoName, ") String() string {")
214                 g.P("   s, ok := ", enum.GoName, "_name[", gotype, "(x)]")
215                 g.P("   if ok { return s }")
216                 g.P("   str := func(n ", gotype, ") string {")
217                 g.P("           s, ok := ", enum.GoName, "_name[", gotype, "(n)]")
218                 g.P("           if ok {")
219                 g.P("                   return s")
220                 g.P("           }")
221                 g.P("           return \"", enum.GoName, "(\" + ", strconvPkg.Ident("Itoa"), "(int(n)) + \")\"")
222                 g.P("   }")
223                 g.P("   for i := ", gotype, "(0); i <= ", size, "; i++ {")
224                 g.P("           val := ", gotype, "(x)")
225                 g.P("           if val&(1<<i) != 0 {")
226                 g.P("                   if s != \"\" {")
227                 g.P("                           s += \"|\"")
228                 g.P("                   }")
229                 g.P("                   s += str(1<<i)")
230                 g.P("           }")
231                 g.P("   }")
232                 g.P("   if s == \"\" {")
233                 g.P("           return str(", gotype, "(x))")
234                 g.P("   }")
235                 g.P("   return s")
236                 g.P("}")
237                 g.P()
238         } else {
239                 g.P("func (x ", enum.GoName, ") String() string {")
240                 g.P("   s, ok := ", enum.GoName, "_name[", gotype, "(x)]")
241                 g.P("   if ok { return s }")
242                 g.P("   return \"", enum.GoName, "(\" + ", strconvPkg.Ident("Itoa"), "(int(x)) + \")\"")
243                 g.P("}")
244                 g.P()
245         }
246 }
247
248 func genAlias(g *GenFile, alias *Alias) {
249         logf("gen ALIAS %s (%s) - type: %s length: %d", alias.GoName, alias.Name, alias.Type, alias.Length)
250
251         genTypeComment(g, alias.GoName, alias.Name, "alias")
252
253         var gotype string
254         switch {
255         case alias.TypeStruct != nil:
256                 gotype = g.GoIdent(alias.TypeStruct.GoIdent)
257         case alias.TypeUnion != nil:
258                 gotype = g.GoIdent(alias.TypeUnion.GoIdent)
259         default:
260                 gotype = BaseTypesGo[alias.Type]
261         }
262         if alias.Length > 0 {
263                 gotype = fmt.Sprintf("[%d]%s", alias.Length, gotype)
264         }
265
266         g.P("type ", alias.GoName, " ", gotype)
267         g.P()
268
269         // generate alias-specific methods
270         switch alias.Name {
271         case "ip4_address":
272                 genIPConversion(g, alias.GoName, 4)
273         case "ip6_address":
274                 genIPConversion(g, alias.GoName, 16)
275         case "address_with_prefix":
276                 genAddressWithPrefixConversion(g, alias.GoName)
277         case "mac_address":
278                 genMacAddressConversion(g, alias.GoName)
279         case "timestamp":
280                 genTimestampConversion(g, alias.GoName)
281         }
282 }
283
284 func genStruct(g *GenFile, typ *Struct) {
285         logf("gen STRUCT %s (%s) - %d fields", typ.GoName, typ.Name, len(typ.Fields))
286
287         genTypeComment(g, typ.GoName, typ.Name, "type")
288
289         if len(typ.Fields) == 0 {
290                 g.P("type ", typ.GoName, " struct {}")
291         } else {
292                 g.P("type ", typ.GoName, " struct {")
293                 for i := range typ.Fields {
294                         genField(g, typ.Fields, i)
295                 }
296                 g.P("}")
297         }
298         g.P()
299
300         // generate type-specific methods
301         switch typ.Name {
302         case "address":
303                 genAddressConversion(g, typ.GoName)
304         case "prefix":
305                 genPrefixConversion(g, typ.GoName)
306         case "ip4_prefix":
307                 genIPPrefixConversion(g, typ.GoName, 4)
308         case "ip6_prefix":
309                 genIPPrefixConversion(g, typ.GoName, 6)
310         }
311 }
312
313 func genUnion(g *GenFile, union *Union) {
314         logf("gen UNION %s (%s) - %d fields", union.GoName, union.Name, len(union.Fields))
315
316         genTypeComment(g, union.GoName, union.Name, "union")
317
318         g.P("type ", union.GoName, " struct {")
319
320         // generate field comments
321         g.P("// ", union.GoName, " can be one of:")
322         for _, field := range union.Fields {
323                 g.P("// - ", field.GoName, " *", getFieldType(g, field))
324         }
325
326         // generate data field
327         maxSize := getUnionSize(union)
328         g.P(fieldUnionData, " [", maxSize, "]byte")
329
330         // generate end of the struct
331         g.P("}")
332         g.P()
333
334         // generate methods for fields
335         for _, field := range union.Fields {
336                 genUnionField(g, union, field)
337         }
338         g.P()
339 }
340
341 func genUnionField(g *GenFile, union *Union, field *Field) {
342         fieldType := fieldGoType(g, field)
343         constructorName := union.GoName + field.GoName
344
345         // Constructor
346         g.P("func ", constructorName, "(a ", fieldType, ") (u ", union.GoName, ") {")
347         g.P("   u.Set", field.GoName, "(a)")
348         g.P("   return")
349         g.P("}")
350
351         // Setter
352         g.P("func (u *", union.GoName, ") Set", field.GoName, "(a ", fieldType, ") {")
353         g.P("   buf := ", govppCodecPkg.Ident("NewBuffer"), "(u.", fieldUnionData, "[:])")
354         encodeField(g, field, "a", func(name string) string {
355                 return "a." + name
356         }, 0)
357         g.P("}")
358
359         // Getter
360         g.P("func (u *", union.GoName, ") Get", field.GoName, "() (a ", fieldType, ") {")
361         g.P("   buf := ", govppCodecPkg.Ident("NewBuffer"), "(u.", fieldUnionData, "[:])")
362         decodeField(g, field, "a", func(name string) string {
363                 return "a." + name
364         }, 0)
365         g.P("   return")
366         g.P("}")
367
368         g.P()
369 }
370
371 func withSuffix(s string, suffix string) string {
372         if strings.HasSuffix(s, suffix) {
373                 return s
374         }
375         return s + suffix
376 }
377
378 func genField(g *GenFile, fields []*Field, i int) {
379         field := fields[i]
380
381         logf(" gen FIELD[%d] %s (%s) - type: %q (array: %v/%v)", i, field.GoName, field.Name, field.Type, field.Array, field.Length)
382
383         gotype := getFieldType(g, field)
384         tags := structTags{
385                 "binapi": fieldTagBinapi(field),
386                 "json":   fieldTagJson(field),
387         }
388
389         g.P(field.GoName, " ", gotype, tags)
390 }
391
392 func fieldTagJson(field *Field) string {
393         if field.FieldSizeOf != nil {
394                 return "-"
395         }
396         return fmt.Sprintf("%s,omitempty", field.Name)
397 }
398
399 func fieldTagBinapi(field *Field) string {
400         typ := fromApiType(field.Type)
401         if field.Array {
402                 if field.Length > 0 {
403                         typ = fmt.Sprintf("%s[%d]", typ, field.Length)
404                 } else if field.SizeFrom != "" {
405                         typ = fmt.Sprintf("%s[%s]", typ, field.SizeFrom)
406                 } else {
407                         typ = fmt.Sprintf("%s[]", typ)
408                 }
409         }
410         tag := []string{
411                 typ,
412                 fmt.Sprintf("name=%s", field.Name),
413         }
414         if limit, ok := field.Meta["limit"]; ok && limit.(int) > 0 {
415                 tag = append(tag, fmt.Sprintf("limit=%s", limit))
416         }
417         if def, ok := field.Meta["default"]; ok && def != nil {
418                 switch fieldActualType(field) {
419                 case I8, I16, I32, I64:
420                         def = int(def.(float64))
421                 case U8, U16, U32, U64:
422                         def = uint(def.(float64))
423                 case F64:
424                         def = def.(float64)
425                 }
426                 tag = append(tag, fmt.Sprintf("default=%v", def))
427         }
428         return strings.Join(tag, ",")
429 }
430
431 type structTags map[string]string
432
433 func (tags structTags) String() string {
434         if len(tags) == 0 {
435                 return ""
436         }
437         var keys []string
438         for k := range tags {
439                 keys = append(keys, k)
440         }
441         sort.Strings(keys)
442         var ss []string
443         for _, key := range keys {
444                 tag := tags[key]
445                 ss = append(ss, fmt.Sprintf(`%s:%s`, key, strconv.Quote(tag)))
446         }
447         return "`" + strings.Join(ss, " ") + "`"
448 }
449
450 func genMessages(g *GenFile) {
451         if len(g.file.Messages) == 0 {
452                 return
453         }
454
455         for _, msg := range g.file.Messages {
456                 genMessage(g, msg)
457         }
458
459         // generate registrations
460         initFnName := fmt.Sprintf("file_%s_binapi_init", g.file.PackageName)
461
462         g.P("func init() { ", initFnName, "() }")
463         g.P("func ", initFnName, "() {")
464         for _, msg := range g.file.Messages {
465                 id := fmt.Sprintf("%s_%s", msg.Name, msg.CRC)
466                 g.P(govppApiPkg.Ident("RegisterMessage"), "((*", msg.GoIdent, ")(nil), ", strconv.Quote(id), ")")
467         }
468         g.P("}")
469         g.P()
470
471         // generate list of messages
472         g.P("// Messages returns list of all messages in this module.")
473         g.P("func AllMessages() []", govppApiPkg.Ident("Message"), " {")
474         g.P("return []", govppApiPkg.Ident("Message"), "{")
475         for _, msg := range g.file.Messages {
476                 g.P("(*", msg.GoIdent, ")(nil),")
477         }
478         g.P("}")
479         g.P("}")
480 }
481
482 func genMessage(g *GenFile, msg *Message) {
483         logf("gen MESSAGE %s (%s) - %d fields", msg.GoName, msg.Name, len(msg.Fields))
484
485         genTypeComment(g, msg.GoIdent.GoName, msg.Name, "message")
486         genTypeOptionComment(g, msg.Options)
487
488         // generate message definition
489         if len(msg.Fields) == 0 {
490                 g.P("type ", msg.GoIdent, " struct {}")
491         } else {
492                 g.P("type ", msg.GoIdent, " struct {")
493                 for i := range msg.Fields {
494                         genField(g, msg.Fields, i)
495                 }
496                 g.P("}")
497         }
498         g.P()
499
500         genMessageMethods(g, msg)
501
502         // encoding methods
503         genMessageSize(g, msg.GoIdent.GoName, msg.Fields)
504         genMessageMarshal(g, msg.GoIdent.GoName, msg.Fields)
505         genMessageUnmarshal(g, msg.GoIdent.GoName, msg.Fields)
506
507         g.P()
508 }
509
510 func genMessageMethods(g *GenFile, msg *Message) {
511         // Reset method
512         g.P("func (m *", msg.GoIdent.GoName, ") Reset() { *m = ", msg.GoIdent.GoName, "{} }")
513
514         // GetMessageName method
515         g.P("func (*", msg.GoIdent.GoName, ") GetMessageName() string { return ", strconv.Quote(msg.Name), " }")
516
517         // GetCrcString method
518         g.P("func (*", msg.GoIdent.GoName, ") GetCrcString() string { return ", strconv.Quote(msg.CRC), " }")
519
520         // GetMessageType method
521         g.P("func (*", msg.GoIdent.GoName, ") GetMessageType() api.MessageType {")
522         g.P("   return ", apiMsgType(msg.msgType))
523         g.P("}")
524
525         g.P()
526 }
527
528 func apiMsgType(t msgType) GoIdent {
529         switch t {
530         case msgTypeRequest:
531                 return govppApiPkg.Ident("RequestMessage")
532         case msgTypeReply:
533                 return govppApiPkg.Ident("ReplyMessage")
534         case msgTypeEvent:
535                 return govppApiPkg.Ident("EventMessage")
536         default:
537                 return govppApiPkg.Ident("OtherMessage")
538         }
539 }