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