Added INFO.yaml
[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         "strconv"
21         "strings"
22
23         "git.fd.io/govpp.git/binapigen/vppapi"
24 )
25
26 // generatedCodeVersion indicates a version of the generated code.
27 // It is incremented whenever an incompatibility between the generated code and
28 // GoVPP api package is introduced; the generated code references
29 // a constant, api.GoVppAPIPackageIsVersionN (where N is generatedCodeVersion).
30 const generatedCodeVersion = 2
31
32 // file options
33 const (
34         optFileVersion = "version"
35 )
36
37 type File struct {
38         Desc vppapi.File
39
40         Generate       bool
41         FilenamePrefix string
42         PackageName    GoPackageName
43         GoImportPath   GoImportPath
44
45         Version string
46         Imports []string
47
48         Enums   []*Enum
49         Unions  []*Union
50         Structs []*Struct
51         Aliases []*Alias
52
53         Messages []*Message
54         Service  *Service
55 }
56
57 func newFile(gen *Generator, apifile *vppapi.File, packageName GoPackageName, importPath GoImportPath) (*File, error) {
58         file := &File{
59                 Desc:         *apifile,
60                 PackageName:  packageName,
61                 GoImportPath: importPath,
62         }
63         if apifile.Options != nil {
64                 file.Version = apifile.Options[optFileVersion]
65         }
66
67         file.FilenamePrefix = path.Join(gen.opts.OutputDir, file.Desc.Name)
68
69         for _, imp := range apifile.Imports {
70                 file.Imports = append(file.Imports, normalizeImport(imp))
71         }
72
73         for _, enumType := range apifile.EnumTypes {
74                 file.Enums = append(file.Enums, newEnum(gen, file, enumType))
75         }
76         for _, aliasType := range apifile.AliasTypes {
77                 file.Aliases = append(file.Aliases, newAlias(gen, file, aliasType))
78         }
79         for _, structType := range apifile.StructTypes {
80                 file.Structs = append(file.Structs, newStruct(gen, file, structType))
81         }
82         for _, unionType := range apifile.UnionTypes {
83                 file.Unions = append(file.Unions, newUnion(gen, file, unionType))
84         }
85
86         for _, msg := range apifile.Messages {
87                 file.Messages = append(file.Messages, newMessage(gen, file, msg))
88         }
89         if apifile.Service != nil {
90                 file.Service = newService(gen, file, *apifile.Service)
91         }
92
93         for _, t := range file.Aliases {
94                 if err := t.resolveDependencies(gen); err != nil {
95                         return nil, err
96                 }
97         }
98         for _, t := range file.Structs {
99                 if err := t.resolveDependencies(gen); err != nil {
100                         return nil, err
101                 }
102         }
103         for _, t := range file.Unions {
104                 if err := t.resolveDependencies(gen); err != nil {
105                         return nil, err
106                 }
107         }
108         for _, m := range file.Messages {
109                 if err := m.resolveDependencies(gen); err != nil {
110                         return nil, err
111                 }
112         }
113         if file.Service != nil {
114                 for _, rpc := range file.Service.RPCs {
115                         if err := rpc.resolveMessages(gen); err != nil {
116                                 return nil, err
117                         }
118                 }
119         }
120
121         return file, nil
122 }
123
124 func (file *File) isTypesFile() bool {
125         return strings.HasSuffix(file.Desc.Name, "_types")
126 }
127
128 func (file *File) hasService() bool {
129         return file.Service != nil && len(file.Service.RPCs) > 0
130 }
131
132 func (file *File) importedFiles(gen *Generator) []*File {
133         var files []*File
134         for _, imp := range file.Imports {
135                 impFile, ok := gen.FilesByName[imp]
136                 if !ok {
137                         logf("file %s import %s not found API files", file.Desc.Name, imp)
138                         continue
139                 }
140                 files = append(files, impFile)
141         }
142         return files
143 }
144
145 func (file *File) dependsOnFile(gen *Generator, dep string) bool {
146         for _, imp := range file.Imports {
147                 if imp == dep {
148                         return true
149                 }
150                 impFile, ok := gen.FilesByName[imp]
151                 if ok && impFile.dependsOnFile(gen, dep) {
152                         return true
153                 }
154         }
155         return false
156 }
157
158 const (
159         enumFlagSuffix = "_flags"
160 )
161
162 func isEnumFlag(enum *Enum) bool {
163         return strings.HasSuffix(enum.Name, enumFlagSuffix)
164 }
165
166 type Enum struct {
167         vppapi.EnumType
168
169         GoIdent
170 }
171
172 func newEnum(gen *Generator, file *File, apitype vppapi.EnumType) *Enum {
173         typ := &Enum{
174                 EnumType: apitype,
175                 GoIdent: GoIdent{
176                         GoName:       camelCaseName(apitype.Name),
177                         GoImportPath: file.GoImportPath,
178                 },
179         }
180         gen.enumsByName[typ.Name] = typ
181         return typ
182 }
183
184 type Alias struct {
185         vppapi.AliasType
186
187         GoIdent
188
189         TypeBasic  *string
190         TypeStruct *Struct
191         TypeUnion  *Union
192 }
193
194 func newAlias(gen *Generator, file *File, apitype vppapi.AliasType) *Alias {
195         typ := &Alias{
196                 AliasType: apitype,
197                 GoIdent: GoIdent{
198                         GoName:       camelCaseName(apitype.Name),
199                         GoImportPath: file.GoImportPath,
200                 },
201         }
202         gen.aliasesByName[typ.Name] = typ
203         return typ
204 }
205
206 func (a *Alias) resolveDependencies(gen *Generator) error {
207         if err := a.resolveType(gen); err != nil {
208                 return fmt.Errorf("unable to resolve field: %w", err)
209         }
210         return nil
211 }
212
213 func (a *Alias) resolveType(gen *Generator) error {
214         if _, ok := BaseTypesGo[a.Type]; ok {
215                 return nil
216         }
217         typ := fromApiType(a.Type)
218         if t, ok := gen.structsByName[typ]; ok {
219                 a.TypeStruct = t
220                 return nil
221         }
222         if t, ok := gen.unionsByName[typ]; ok {
223                 a.TypeUnion = t
224                 return nil
225         }
226         return fmt.Errorf("unknown type: %q", a.Type)
227 }
228
229 type Struct struct {
230         vppapi.StructType
231
232         GoIdent
233
234         Fields []*Field
235 }
236
237 func newStruct(gen *Generator, file *File, apitype vppapi.StructType) *Struct {
238         typ := &Struct{
239                 StructType: apitype,
240                 GoIdent: GoIdent{
241                         GoName:       camelCaseName(apitype.Name),
242                         GoImportPath: file.GoImportPath,
243                 },
244         }
245         gen.structsByName[typ.Name] = typ
246         for i, fieldType := range apitype.Fields {
247                 field := newField(gen, file, typ, fieldType, i)
248                 typ.Fields = append(typ.Fields, field)
249         }
250         return typ
251 }
252
253 func (m *Struct) resolveDependencies(gen *Generator) (err error) {
254         for _, field := range m.Fields {
255                 if err := field.resolveDependencies(gen); err != nil {
256                         return fmt.Errorf("unable to resolve for struct %s: %w", m.Name, err)
257                 }
258         }
259         return nil
260 }
261
262 type Union struct {
263         vppapi.UnionType
264
265         GoIdent
266
267         Fields []*Field
268 }
269
270 func newUnion(gen *Generator, file *File, apitype vppapi.UnionType) *Union {
271         typ := &Union{
272                 UnionType: apitype,
273                 GoIdent: GoIdent{
274                         GoName:       withSuffix(camelCaseName(apitype.Name), "Union"),
275                         GoImportPath: file.GoImportPath,
276                 },
277         }
278         gen.unionsByName[typ.Name] = typ
279         for i, fieldType := range apitype.Fields {
280                 field := newField(gen, file, typ, fieldType, i)
281                 typ.Fields = append(typ.Fields, field)
282         }
283         return typ
284 }
285
286 func (m *Union) resolveDependencies(gen *Generator) (err error) {
287         for _, field := range m.Fields {
288                 if err := field.resolveDependencies(gen); err != nil {
289                         return err
290                 }
291         }
292         return nil
293 }
294
295 // msgType determines message header fields
296 type msgType int
297
298 const (
299         msgTypeBase    msgType = iota // msg_id
300         msgTypeRequest                // msg_id, client_index, context
301         msgTypeReply                  // msg_id, context
302         msgTypeEvent                  // msg_id, client_index
303 )
304
305 // common message fields
306 const (
307         fieldMsgID       = "_vl_msg_id"
308         fieldClientIndex = "client_index"
309         fieldContext     = "context"
310         fieldRetval      = "retval"
311 )
312
313 // field options
314 const (
315         optFieldDefault = "default"
316 )
317
318 type Message struct {
319         vppapi.Message
320
321         CRC string
322
323         GoIdent
324
325         Fields []*Field
326
327         msgType msgType
328 }
329
330 func newMessage(gen *Generator, file *File, apitype vppapi.Message) *Message {
331         msg := &Message{
332                 Message: apitype,
333                 CRC:     strings.TrimPrefix(apitype.CRC, "0x"),
334                 GoIdent: newGoIdent(file, apitype.Name),
335         }
336         gen.messagesByName[apitype.Name] = msg
337         var n int
338         for _, fieldType := range apitype.Fields {
339                 if n == 0 {
340                         // skip header fields
341                         switch strings.ToLower(fieldType.Name) {
342                         case fieldMsgID, fieldClientIndex, fieldContext:
343                                 continue
344                         }
345                 }
346                 n++
347                 field := newField(gen, file, msg, fieldType, n)
348                 msg.Fields = append(msg.Fields, field)
349         }
350         return msg
351 }
352
353 func (m *Message) resolveDependencies(gen *Generator) (err error) {
354         if m.msgType, err = getMsgType(m.Message); err != nil {
355                 return err
356         }
357         for _, field := range m.Fields {
358                 if err := field.resolveDependencies(gen); err != nil {
359                         return err
360                 }
361         }
362         return nil
363 }
364
365 func getMsgType(m vppapi.Message) (msgType, error) {
366         if len(m.Fields) == 0 {
367                 return msgType(-1), fmt.Errorf("message %s has no fields", m.Name)
368         }
369         var typ msgType
370         var wasClientIndex bool
371         for i, field := range m.Fields {
372                 switch i {
373                 case 0:
374                         if field.Name != fieldMsgID {
375                                 return msgType(-1), fmt.Errorf("message %s is missing ID field", m.Name)
376                         }
377                 case 1:
378                         if field.Name == fieldClientIndex {
379                                 // "client_index" as the second member,
380                                 // this might be an event message or a request
381                                 typ = msgTypeEvent
382                                 wasClientIndex = true
383                         } else if field.Name == fieldContext {
384                                 // reply needs "context" as the second member
385                                 typ = msgTypeReply
386                         }
387                 case 2:
388                         if field.Name == fieldContext && wasClientIndex {
389                                 // request needs "client_index" as the second member
390                                 // and "context" as the third member
391                                 typ = msgTypeRequest
392                         }
393                 }
394         }
395         return typ, nil
396 }
397
398 func getRetvalField(m *Message) *Field {
399         for _, field := range m.Fields {
400                 if field.Name == fieldRetval {
401                         return field
402                 }
403         }
404         return nil
405 }
406
407 // Field represents a field for message or struct/union types.
408 type Field struct {
409         vppapi.Field
410
411         GoName string
412
413         // Index defines field index in parent.
414         Index int
415
416         // DefaultValue is a default value of field or
417         // nil if default value is not defined for field.
418         DefaultValue interface{}
419
420         // Reference to actual type of this field.
421         //
422         // For fields with built-in types all of these are nil,
423         // otherwise only one is set to non-nil value.
424         TypeEnum   *Enum
425         TypeAlias  *Alias
426         TypeStruct *Struct
427         TypeUnion  *Union
428
429         // Parent in which this field is declared.
430         ParentMessage *Message
431         ParentStruct  *Struct
432         ParentUnion   *Union
433
434         // Field reference for fields with variable size.
435         FieldSizeOf   *Field
436         FieldSizeFrom *Field
437 }
438
439 func newField(gen *Generator, file *File, parent interface{}, apitype vppapi.Field, index int) *Field {
440         typ := &Field{
441                 Field:  apitype,
442                 GoName: camelCaseName(apitype.Name),
443                 Index:  index,
444         }
445         switch p := parent.(type) {
446         case *Message:
447                 typ.ParentMessage = p
448         case *Struct:
449                 typ.ParentStruct = p
450         case *Union:
451                 typ.ParentUnion = p
452         default:
453                 panic(fmt.Sprintf("invalid field parent: %T", parent))
454         }
455         if apitype.Meta != nil {
456                 if val, ok := apitype.Meta[optFieldDefault]; ok {
457                         typ.DefaultValue = val
458                 }
459         }
460         return typ
461 }
462
463 func (f *Field) resolveDependencies(gen *Generator) error {
464         if err := f.resolveType(gen); err != nil {
465                 return fmt.Errorf("unable to resolve field type: %w", err)
466         }
467         if err := f.resolveFields(gen); err != nil {
468                 return fmt.Errorf("unable to resolve fields: %w", err)
469         }
470         return nil
471 }
472
473 func (f *Field) resolveType(gen *Generator) error {
474         if _, ok := BaseTypesGo[f.Type]; ok {
475                 return nil
476         }
477         typ := fromApiType(f.Type)
478         if t, ok := gen.structsByName[typ]; ok {
479                 f.TypeStruct = t
480                 return nil
481         }
482         if t, ok := gen.enumsByName[typ]; ok {
483                 f.TypeEnum = t
484                 return nil
485         }
486         if t, ok := gen.aliasesByName[typ]; ok {
487                 f.TypeAlias = t
488                 return nil
489         }
490         if t, ok := gen.unionsByName[typ]; ok {
491                 f.TypeUnion = t
492                 return nil
493         }
494         return fmt.Errorf("unknown type: %q", f.Type)
495 }
496
497 func (f *Field) resolveFields(gen *Generator) error {
498         var fields []*Field
499         if f.ParentMessage != nil {
500                 fields = f.ParentMessage.Fields
501         } else if f.ParentStruct != nil {
502                 fields = f.ParentStruct.Fields
503         }
504         if f.SizeFrom != "" {
505                 for _, field := range fields {
506                         if field.Name == f.SizeFrom {
507                                 f.FieldSizeFrom = field
508                                 break
509                         }
510                 }
511         } else {
512                 for _, field := range fields {
513                         if field.SizeFrom == f.Name {
514                                 f.FieldSizeOf = field
515                                 break
516                         }
517                 }
518         }
519         return nil
520 }
521
522 type Service struct {
523         vppapi.Service
524
525         RPCs []*RPC
526 }
527
528 func newService(gen *Generator, file *File, apitype vppapi.Service) *Service {
529         svc := &Service{
530                 Service: apitype,
531         }
532         for _, rpc := range apitype.RPCs {
533                 svc.RPCs = append(svc.RPCs, newRpc(file, svc, rpc))
534         }
535         return svc
536 }
537
538 const (
539         serviceNoReply = "null"
540 )
541
542 type RPC struct {
543         VPP vppapi.RPC
544
545         GoName string
546
547         Service *Service
548
549         MsgRequest *Message
550         MsgReply   *Message
551         MsgStream  *Message
552 }
553
554 func newRpc(file *File, service *Service, apitype vppapi.RPC) *RPC {
555         rpc := &RPC{
556                 VPP:     apitype,
557                 GoName:  camelCaseName(apitype.Request),
558                 Service: service,
559         }
560         return rpc
561 }
562
563 func (rpc *RPC) resolveMessages(gen *Generator) error {
564         msg, ok := gen.messagesByName[rpc.VPP.Request]
565         if !ok {
566                 return fmt.Errorf("rpc %v: no message for request type %v", rpc.GoName, rpc.VPP.Request)
567         }
568         rpc.MsgRequest = msg
569
570         if rpc.VPP.Reply != "" && rpc.VPP.Reply != serviceNoReply {
571                 msg, ok := gen.messagesByName[rpc.VPP.Reply]
572                 if !ok {
573                         return fmt.Errorf("rpc %v: no message for reply type %v", rpc.GoName, rpc.VPP.Reply)
574                 }
575                 rpc.MsgReply = msg
576         }
577         if rpc.VPP.StreamMsg != "" {
578                 msg, ok := gen.messagesByName[rpc.VPP.StreamMsg]
579                 if !ok {
580                         return fmt.Errorf("rpc %v: no message for stream type %v", rpc.GoName, rpc.VPP.StreamMsg)
581                 }
582                 rpc.MsgStream = msg
583         }
584         return nil
585 }
586
587 // GoIdent is a Go identifier, consisting of a name and import path.
588 // The name is a single identifier and may not be a dot-qualified selector.
589 type GoIdent struct {
590         GoName       string
591         GoImportPath GoImportPath
592 }
593
594 func (id GoIdent) String() string {
595         return fmt.Sprintf("%q.%v", id.GoImportPath, id.GoName)
596 }
597
598 func newGoIdent(f *File, fullName string) GoIdent {
599         name := strings.TrimPrefix(fullName, string(f.PackageName)+".")
600         return GoIdent{
601                 GoName:       camelCaseName(name),
602                 GoImportPath: f.GoImportPath,
603         }
604 }
605
606 // GoImportPath is a Go import path for a package.
607 type GoImportPath string
608
609 func (p GoImportPath) String() string {
610         return strconv.Quote(string(p))
611 }
612
613 func (p GoImportPath) Ident(s string) GoIdent {
614         return GoIdent{GoName: s, GoImportPath: p}
615 }
616
617 type GoPackageName string
618
619 func cleanPackageName(name string) GoPackageName {
620         return GoPackageName(sanitizedName(name))
621 }
622
623 // baseName returns the last path element of the name, with the last dotted suffix removed.
624 func baseName(name string) string {
625         // First, find the last element
626         if i := strings.LastIndex(name, "/"); i >= 0 {
627                 name = name[i+1:]
628         }
629         // Now drop the suffix
630         if i := strings.LastIndex(name, "."); i >= 0 {
631                 name = name[:i]
632         }
633         return name
634 }
635
636 // normalizeImport returns the last path element of the import, with all dotted suffixes removed.
637 func normalizeImport(imp string) string {
638         imp = path.Base(imp)
639         if idx := strings.Index(imp, "."); idx >= 0 {
640                 imp = imp[:idx]
641         }
642         return imp
643 }