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