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