Expose version info control flags
[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         for _, field := range union.Fields {
289                 g.P("// ", field.GoName, " *", getFieldType(g, field))
290         }
291
292         // generate data field
293         maxSize := getUnionSize(union)
294         g.P(fieldUnionData, " [", maxSize, "]byte")
295
296         // generate end of the struct
297         g.P("}")
298         g.P()
299
300         // generate methods for fields
301         for _, field := range union.Fields {
302                 genUnionFieldMethods(g, union.GoName, field)
303         }
304         g.P()
305 }
306
307 func genUnionFieldMethods(g *GenFile, structName string, field *Field) {
308         getterStruct := fieldGoType(g, field)
309
310         // Constructor
311         g.P("func ", structName, field.GoName, "(a ", getterStruct, ") (u ", structName, ") {")
312         g.P("   u.Set", field.GoName, "(a)")
313         g.P("   return")
314         g.P("}")
315
316         // Setter
317         g.P("func (u *", structName, ") Set", field.GoName, "(a ", getterStruct, ") {")
318         g.P("   buf := ", govppCodecPkg.Ident("NewBuffer"), "(u.", fieldUnionData, "[:])")
319         encodeField(g, field, "a", func(name string) string {
320                 return "a." + name
321         }, 0)
322         g.P("}")
323
324         // Getter
325         g.P("func (u *", structName, ") Get", field.GoName, "() (a ", getterStruct, ") {")
326         g.P("   buf := ", govppCodecPkg.Ident("NewBuffer"), "(u.", fieldUnionData, "[:])")
327         decodeField(g, field, "a", func(name string) string {
328                 return "a." + name
329         }, 0)
330         g.P("   return")
331         g.P("}")
332         g.P()
333 }
334
335 func genField(g *GenFile, fields []*Field, i int) {
336         field := fields[i]
337
338         logf(" gen FIELD[%d] %s (%s) - type: %q (array: %v/%v)", i, field.GoName, field.Name, field.Type, field.Array, field.Length)
339
340         gotype := getFieldType(g, field)
341         tags := structTags{
342                 "binapi": fieldTagBinapi(field),
343                 "json":   fieldTagJson(field),
344         }
345
346         g.P(field.GoName, " ", gotype, tags)
347 }
348
349 func fieldTagJson(field *Field) string {
350         if field.FieldSizeOf != nil {
351                 return "-"
352         }
353         return fmt.Sprintf("%s,omitempty", field.Name)
354 }
355
356 func fieldTagBinapi(field *Field) string {
357         typ := fromApiType(field.Type)
358         if field.Array {
359                 if field.Length > 0 {
360                         typ = fmt.Sprintf("%s[%d]", typ, field.Length)
361                 } else if field.SizeFrom != "" {
362                         typ = fmt.Sprintf("%s[%s]", typ, field.SizeFrom)
363                 } else {
364                         typ = fmt.Sprintf("%s[]", typ)
365                 }
366         }
367         tag := []string{
368                 typ,
369                 fmt.Sprintf("name=%s", field.Name),
370         }
371         if limit, ok := field.Meta["limit"]; ok && limit.(int) > 0 {
372                 tag = append(tag, fmt.Sprintf("limit=%s", limit))
373         }
374         if def, ok := field.Meta["default"]; ok && def != nil {
375                 switch fieldActualType(field) {
376                 case I8, I16, I32, I64:
377                         def = int(def.(float64))
378                 case U8, U16, U32, U64:
379                         def = uint(def.(float64))
380                 case F64:
381                         def = def.(float64)
382                 }
383                 tag = append(tag, fmt.Sprintf("default=%v", def))
384         }
385         return strings.Join(tag, ",")
386 }
387
388 type structTags map[string]string
389
390 func (tags structTags) String() string {
391         if len(tags) == 0 {
392                 return ""
393         }
394         var keys []string
395         for k := range tags {
396                 keys = append(keys, k)
397         }
398         sort.Strings(keys)
399         var ss []string
400         for _, key := range keys {
401                 tag := tags[key]
402                 ss = append(ss, fmt.Sprintf(`%s:%s`, key, strconv.Quote(tag)))
403         }
404         return "`" + strings.Join(ss, " ") + "`"
405 }
406
407 func genMessages(g *GenFile) {
408         if len(g.file.Messages) == 0 {
409                 return
410         }
411
412         for _, msg := range g.file.Messages {
413                 genMessage(g, msg)
414         }
415
416         // generate registrations
417         initFnName := fmt.Sprintf("file_%s_binapi_init", g.file.PackageName)
418
419         g.P("func init() { ", initFnName, "() }")
420         g.P("func ", initFnName, "() {")
421         for _, msg := range g.file.Messages {
422                 id := fmt.Sprintf("%s_%s", msg.Name, msg.CRC)
423                 g.P(govppApiPkg.Ident("RegisterMessage"), "((*", msg.GoIdent, ")(nil), ", strconv.Quote(id), ")")
424         }
425         g.P("}")
426         g.P()
427
428         // generate list of messages
429         g.P("// Messages returns list of all messages in this module.")
430         g.P("func AllMessages() []", govppApiPkg.Ident("Message"), " {")
431         g.P("return []", govppApiPkg.Ident("Message"), "{")
432         for _, msg := range g.file.Messages {
433                 g.P("(*", msg.GoIdent, ")(nil),")
434         }
435         g.P("}")
436         g.P("}")
437 }
438
439 func genMessage(g *GenFile, msg *Message) {
440         logf("gen MESSAGE %s (%s) - %d fields", msg.GoName, msg.Name, len(msg.Fields))
441
442         genTypeComment(g, msg.GoIdent.GoName, msg.Name, "message")
443
444         // generate message definition
445         if len(msg.Fields) == 0 {
446                 g.P("type ", msg.GoIdent, " struct {}")
447         } else {
448                 g.P("type ", msg.GoIdent, " struct {")
449                 for i := range msg.Fields {
450                         genField(g, msg.Fields, i)
451                 }
452                 g.P("}")
453         }
454         g.P()
455
456         genMessageMethods(g, msg)
457
458         // encoding methods
459         genMessageSize(g, msg.GoIdent.GoName, msg.Fields)
460         genMessageMarshal(g, msg.GoIdent.GoName, msg.Fields)
461         genMessageUnmarshal(g, msg.GoIdent.GoName, msg.Fields)
462
463         g.P()
464 }
465
466 func genMessageMethods(g *GenFile, msg *Message) {
467         // Reset method
468         g.P("func (m *", msg.GoIdent.GoName, ") Reset() { *m = ", msg.GoIdent.GoName, "{} }")
469
470         // GetMessageName method
471         g.P("func (*", msg.GoIdent.GoName, ") GetMessageName() string { return ", strconv.Quote(msg.Name), " }")
472
473         // GetCrcString method
474         g.P("func (*", msg.GoIdent.GoName, ") GetCrcString() string { return ", strconv.Quote(msg.CRC), " }")
475
476         // GetMessageType method
477         g.P("func (*", msg.GoIdent.GoName, ") GetMessageType() api.MessageType {")
478         g.P("   return ", apiMsgType(msg.msgType))
479         g.P("}")
480
481         g.P()
482 }
483
484 func apiMsgType(t msgType) GoIdent {
485         switch t {
486         case msgTypeRequest:
487                 return govppApiPkg.Ident("RequestMessage")
488         case msgTypeReply:
489                 return govppApiPkg.Ident("ReplyMessage")
490         case msgTypeEvent:
491                 return govppApiPkg.Ident("EventMessage")
492         default:
493                 return govppApiPkg.Ident("OtherMessage")
494         }
495 }