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