c5a976bb8830add9c078efdf8106f31a002f5142
[govpp.git] / binapigen / binapigen.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         "strings"
22
23         "git.fd.io/govpp.git/binapigen/vppapi"
24 )
25
26 type File struct {
27         vppapi.File
28
29         Generate bool
30
31         PackageName string
32         Imports     []string
33
34         Enums    []*Enum
35         Unions   []*Union
36         Structs  []*Struct
37         Aliases  []*Alias
38         Messages []*Message
39
40         imports map[string]string
41         refmap  map[string]string
42 }
43
44 func newFile(gen *Generator, apifile *vppapi.File) (*File, error) {
45         file := &File{
46                 File:        *apifile,
47                 PackageName: sanitizedName(apifile.Name),
48                 imports:     make(map[string]string),
49                 refmap:      make(map[string]string),
50         }
51
52         sortFileObjects(&file.File)
53
54         for _, imp := range apifile.Imports {
55                 file.Imports = append(file.Imports, normalizeImport(imp))
56         }
57         for _, enum := range apifile.EnumTypes {
58                 file.Enums = append(file.Enums, newEnum(gen, file, enum))
59         }
60         for _, alias := range apifile.AliasTypes {
61                 file.Aliases = append(file.Aliases, newAlias(gen, file, alias))
62         }
63         for _, structType := range apifile.StructTypes {
64                 file.Structs = append(file.Structs, newStruct(gen, file, structType))
65         }
66         for _, union := range apifile.UnionTypes {
67                 file.Unions = append(file.Unions, newUnion(gen, file, union))
68         }
69         for _, msg := range apifile.Messages {
70                 file.Messages = append(file.Messages, newMessage(gen, file, msg))
71         }
72
73         return file, nil
74 }
75
76 func (file *File) isTypes() bool {
77         return strings.HasSuffix(file.File.Name, "_types")
78 }
79
80 func (file *File) hasService() bool {
81         return file.Service != nil && len(file.Service.RPCs) > 0
82 }
83
84 func (file *File) addRef(typ string, name string, ref interface{}) {
85         apiName := toApiType(name)
86         if _, ok := file.refmap[apiName]; ok {
87                 logf("%s type %v already in refmap", typ, apiName)
88                 return
89         }
90         file.refmap[apiName] = name
91 }
92
93 func (file *File) importedFiles(gen *Generator) []*File {
94         var files []*File
95         for _, imp := range file.Imports {
96                 impFile, ok := gen.FilesByName[imp]
97                 if !ok {
98                         logf("file %s import %s not found API files", file.Name, imp)
99                         continue
100                 }
101                 files = append(files, impFile)
102         }
103         return files
104 }
105
106 func (file *File) loadTypeImports(gen *Generator, typeFiles []*File) {
107         if len(typeFiles) == 0 {
108                 return
109         }
110         for _, t := range file.Structs {
111                 for _, imp := range typeFiles {
112                         if _, ok := file.imports[t.Name]; ok {
113                                 break
114                         }
115                         for _, at := range imp.File.StructTypes {
116                                 if at.Name != t.Name {
117                                         continue
118                                 }
119                                 if len(at.Fields) != len(t.Fields) {
120                                         continue
121                                 }
122                                 file.imports[t.Name] = imp.PackageName
123                         }
124                 }
125         }
126         for _, t := range file.AliasTypes {
127                 for _, imp := range typeFiles {
128                         if _, ok := file.imports[t.Name]; ok {
129                                 break
130                         }
131                         for _, at := range imp.File.AliasTypes {
132                                 if at.Name != t.Name {
133                                         continue
134                                 }
135                                 if at.Length != t.Length {
136                                         continue
137                                 }
138                                 if at.Type != t.Type {
139                                         continue
140                                 }
141                                 file.imports[t.Name] = imp.PackageName
142                         }
143                 }
144         }
145         for _, t := range file.EnumTypes {
146                 for _, imp := range typeFiles {
147                         if _, ok := file.imports[t.Name]; ok {
148                                 break
149                         }
150                         for _, at := range imp.File.EnumTypes {
151                                 if at.Name != t.Name {
152                                         continue
153                                 }
154                                 if at.Type != t.Type {
155                                         continue
156                                 }
157                                 file.imports[t.Name] = imp.PackageName
158                         }
159                 }
160         }
161         for _, t := range file.UnionTypes {
162                 for _, imp := range typeFiles {
163                         if _, ok := file.imports[t.Name]; ok {
164                                 break
165                         }
166                         for _, at := range imp.File.UnionTypes {
167                                 if at.Name != t.Name {
168                                         continue
169                                 }
170                                 file.imports[t.Name] = imp.PackageName
171                                 /*if gen.ImportTypes {
172                                         imp.Generate = true
173                                 }*/
174                         }
175                 }
176         }
177 }
178
179 type Enum struct {
180         vppapi.EnumType
181
182         GoName string
183 }
184
185 func newEnum(gen *Generator, file *File, apitype vppapi.EnumType) *Enum {
186         typ := &Enum{
187                 EnumType: apitype,
188                 GoName:   camelCaseName(apitype.Name),
189         }
190         gen.enumsByName[fmt.Sprintf("%s.%s", file.Name, typ.Name)] = typ
191         file.addRef("enum", typ.Name, typ)
192         return typ
193 }
194
195 type Alias struct {
196         vppapi.AliasType
197
198         GoName string
199 }
200
201 func newAlias(gen *Generator, file *File, apitype vppapi.AliasType) *Alias {
202         typ := &Alias{
203                 AliasType: apitype,
204                 GoName:    camelCaseName(apitype.Name),
205         }
206         gen.aliasesByName[fmt.Sprintf("%s.%s", file.Name, typ.Name)] = typ
207         file.addRef("alias", typ.Name, typ)
208         return typ
209 }
210
211 type Struct struct {
212         vppapi.StructType
213
214         GoName string
215
216         Fields []*Field
217 }
218
219 func newStruct(gen *Generator, file *File, apitype vppapi.StructType) *Struct {
220         typ := &Struct{
221                 StructType: apitype,
222                 GoName:     camelCaseName(apitype.Name),
223         }
224         for _, fieldType := range apitype.Fields {
225                 field := newField(gen, file, fieldType)
226                 field.ParentStruct = typ
227                 typ.Fields = append(typ.Fields, field)
228         }
229         gen.structsByName[fmt.Sprintf("%s.%s", file.Name, typ.Name)] = typ
230         file.addRef("struct", typ.Name, typ)
231         return typ
232 }
233
234 type Union struct {
235         vppapi.UnionType
236
237         GoName string
238
239         Fields []*Field
240 }
241
242 func newUnion(gen *Generator, file *File, apitype vppapi.UnionType) *Union {
243         typ := &Union{
244                 UnionType: apitype,
245                 GoName:    camelCaseName(apitype.Name),
246         }
247         gen.unionsByName[fmt.Sprintf("%s.%s", file.Name, typ.Name)] = typ
248         for _, fieldType := range apitype.Fields {
249                 field := newField(gen, file, fieldType)
250                 field.ParentUnion = typ
251                 typ.Fields = append(typ.Fields, field)
252         }
253         file.addRef("union", typ.Name, typ)
254         return typ
255 }
256
257 type Message struct {
258         vppapi.Message
259
260         GoName string
261
262         Fields []*Field
263 }
264
265 func newMessage(gen *Generator, file *File, apitype vppapi.Message) *Message {
266         msg := &Message{
267                 Message: apitype,
268                 GoName:  camelCaseName(apitype.Name),
269         }
270         for _, fieldType := range apitype.Fields {
271                 field := newField(gen, file, fieldType)
272                 field.ParentMessage = msg
273                 msg.Fields = append(msg.Fields, field)
274         }
275         return msg
276 }
277
278 type Field struct {
279         vppapi.Field
280
281         GoName string
282
283         // Field parent
284         ParentMessage *Message
285         ParentStruct  *Struct
286         ParentUnion   *Union
287
288         // Type reference
289         Enum   *Enum
290         Alias  *Alias
291         Struct *Struct
292         Union  *Union
293 }
294
295 func newField(gen *Generator, file *File, apitype vppapi.Field) *Field {
296         typ := &Field{
297                 Field:  apitype,
298                 GoName: camelCaseName(apitype.Name),
299         }
300         return typ
301 }
302
303 type Service = vppapi.Service
304 type RPC = vppapi.RPC
305
306 func sortFileObjects(file *vppapi.File) {
307         // sort imports
308         sort.SliceStable(file.Imports, func(i, j int) bool {
309                 return file.Imports[i] < file.Imports[j]
310         })
311         // sort enum types
312         sort.SliceStable(file.EnumTypes, func(i, j int) bool {
313                 return file.EnumTypes[i].Name < file.EnumTypes[j].Name
314         })
315         // sort alias types
316         sort.Slice(file.AliasTypes, func(i, j int) bool {
317                 return file.AliasTypes[i].Name < file.AliasTypes[j].Name
318         })
319         // sort struct types
320         sort.SliceStable(file.StructTypes, func(i, j int) bool {
321                 return file.StructTypes[i].Name < file.StructTypes[j].Name
322         })
323         // sort union types
324         sort.SliceStable(file.UnionTypes, func(i, j int) bool {
325                 return file.UnionTypes[i].Name < file.UnionTypes[j].Name
326         })
327         // sort messages
328         sort.SliceStable(file.Messages, func(i, j int) bool {
329                 return file.Messages[i].Name < file.Messages[j].Name
330         })
331         // sort services
332         if file.Service != nil {
333                 sort.Slice(file.Service.RPCs, func(i, j int) bool {
334                         // dumps first
335                         if file.Service.RPCs[i].Stream != file.Service.RPCs[j].Stream {
336                                 return file.Service.RPCs[i].Stream
337                         }
338                         return file.Service.RPCs[i].RequestMsg < file.Service.RPCs[j].RequestMsg
339                 })
340         }
341 }
342
343 func sanitizedName(name string) string {
344         switch name {
345         case "interface":
346                 return "interfaces"
347         case "map":
348                 return "maps"
349         default:
350                 return name
351         }
352 }
353
354 func normalizeImport(imp string) string {
355         imp = path.Base(imp)
356         if idx := strings.Index(imp, "."); idx >= 0 {
357                 imp = imp[:idx]
358         }
359         return imp
360 }