Refactor GoVPP
[govpp.git] / cmd / binapi-generator / parse.go
1 // Copyright (c) 2018 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 main
16
17 import (
18         "errors"
19         "fmt"
20         "log"
21         "sort"
22         "strings"
23
24         "github.com/bennyscetbun/jsongo"
25 )
26
27 // Package represents collection of objects parsed from VPP binary API JSON data
28 type Package struct {
29         APIVersion string
30         Enums      []Enum
31         Unions     []Union
32         Types      []Type
33         Messages   []Message
34         Services   []Service
35         RefMap     map[string]string
36 }
37
38 // MessageType represents the type of a VPP message
39 type MessageType int
40
41 const (
42         requestMessage MessageType = iota // VPP request message
43         replyMessage                      // VPP reply message
44         eventMessage                      // VPP event message
45         otherMessage                      // other VPP message
46 )
47
48 // Message represents VPP binary API message
49 type Message struct {
50         Name   string
51         CRC    string
52         Fields []Field
53 }
54
55 // Type represents VPP binary API type
56 type Type struct {
57         Name   string
58         CRC    string
59         Fields []Field
60 }
61
62 // Union represents VPP binary API union
63 type Union struct {
64         Name   string
65         CRC    string
66         Fields []Field
67 }
68
69 // Field represents VPP binary API object field
70 type Field struct {
71         Name     string
72         Type     string
73         Length   int
74         SizeFrom string
75 }
76
77 func (f *Field) IsArray() bool {
78         return f.Length > 0 || f.SizeFrom != ""
79 }
80
81 // Enum represents VPP binary API enum
82 type Enum struct {
83         Name    string
84         Type    string
85         Entries []EnumEntry
86 }
87
88 // EnumEntry represents VPP binary API enum entry
89 type EnumEntry struct {
90         Name  string
91         Value interface{}
92 }
93
94 // Service represents VPP binary API service
95 type Service struct {
96         RequestType string
97         ReplyType   string
98         Stream      bool
99         Events      []string
100 }
101
102 func getSizeOfType(typ *Type) (size int) {
103         for _, field := range typ.Fields {
104                 if n := getBinapiTypeSize(field.Type); n > 0 {
105                         if field.Length > 0 {
106                                 size += n * field.Length
107                         } else {
108                                 size += n
109                         }
110                 }
111         }
112         return size
113 }
114
115 func getTypeByRef(ctx *context, ref string) *Type {
116         for _, typ := range ctx.packageData.Types {
117                 if ref == toApiType(typ.Name) {
118                         return &typ
119                 }
120         }
121         return nil
122 }
123
124 func getUnionSize(ctx *context, union *Union) (maxSize int) {
125         for _, field := range union.Fields {
126                 if typ := getTypeByRef(ctx, field.Type); typ != nil {
127                         if size := getSizeOfType(typ); size > maxSize {
128                                 maxSize = size
129                         }
130                 }
131         }
132         return
133 }
134
135 // toApiType returns name that is used as type reference in VPP binary API
136 func toApiType(name string) string {
137         return fmt.Sprintf("vl_api_%s_t", name)
138 }
139
140 // parsePackage parses provided JSON data into objects prepared for code generation
141 func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) {
142         logf(" %s contains: %d services, %d messages, %d types, %d enums, %d unions (version: %s)",
143                 ctx.packageName,
144                 jsonRoot.Map("services").Len(),
145                 jsonRoot.Map("messages").Len(),
146                 jsonRoot.Map("types").Len(),
147                 jsonRoot.Map("enums").Len(),
148                 jsonRoot.Map("unions").Len(),
149                 jsonRoot.Map("vl_api_version").Get(),
150         )
151
152         pkg := Package{
153                 APIVersion: jsonRoot.Map("vl_api_version").Get().(string),
154                 RefMap:     make(map[string]string),
155         }
156
157         // parse enums
158         enums := jsonRoot.Map("enums")
159         pkg.Enums = make([]Enum, enums.Len())
160         for i := 0; i < enums.Len(); i++ {
161                 enumNode := enums.At(i)
162
163                 enum, err := parseEnum(ctx, enumNode)
164                 if err != nil {
165                         return nil, err
166                 }
167                 pkg.Enums[i] = *enum
168                 pkg.RefMap[toApiType(enum.Name)] = enum.Name
169         }
170
171         // parse types
172         types := jsonRoot.Map("types")
173         pkg.Types = make([]Type, types.Len())
174         for i := 0; i < types.Len(); i++ {
175                 typNode := types.At(i)
176
177                 typ, err := parseType(ctx, typNode)
178                 if err != nil {
179                         return nil, err
180                 }
181                 pkg.Types[i] = *typ
182                 pkg.RefMap[toApiType(typ.Name)] = typ.Name
183         }
184
185         // parse unions
186         unions := jsonRoot.Map("unions")
187         pkg.Unions = make([]Union, unions.Len())
188         for i := 0; i < unions.Len(); i++ {
189                 unionNode := unions.At(i)
190
191                 union, err := parseUnion(ctx, unionNode)
192                 if err != nil {
193                         return nil, err
194                 }
195                 pkg.Unions[i] = *union
196                 pkg.RefMap[toApiType(union.Name)] = union.Name
197         }
198
199         // parse messages
200         messages := jsonRoot.Map("messages")
201         pkg.Messages = make([]Message, messages.Len())
202         for i := 0; i < messages.Len(); i++ {
203                 msgNode := messages.At(i)
204
205                 msg, err := parseMessage(ctx, msgNode)
206                 if err != nil {
207                         return nil, err
208                 }
209                 pkg.Messages[i] = *msg
210         }
211
212         // parse services
213         services := jsonRoot.Map("services")
214         if services.GetType() == jsongo.TypeMap {
215                 pkg.Services = make([]Service, services.Len())
216                 for i, key := range services.GetKeys() {
217                         svcNode := services.At(key)
218
219                         svc, err := parseService(ctx, key.(string), svcNode)
220                         if err != nil {
221                                 return nil, err
222                         }
223                         pkg.Services[i] = *svc
224                 }
225
226                 // sort services
227                 sort.Slice(pkg.Services, func(i, j int) bool {
228                         // dumps first
229                         if pkg.Services[i].Stream != pkg.Services[j].Stream {
230                                 return pkg.Services[i].Stream
231                         }
232                         return pkg.Services[i].RequestType < pkg.Services[j].RequestType
233                 })
234         }
235
236         printPackage(&pkg)
237
238         return &pkg, nil
239 }
240
241 // printPackage prints all loaded objects for package
242 func printPackage(pkg *Package) {
243         if len(pkg.Enums) > 0 {
244                 logf("loaded %d enums:", len(pkg.Enums))
245                 for k, enum := range pkg.Enums {
246                         logf(" - enum #%d\t%+v", k, enum)
247                 }
248         }
249         if len(pkg.Unions) > 0 {
250                 logf("loaded %d unions:", len(pkg.Unions))
251                 for k, union := range pkg.Unions {
252                         logf(" - union #%d\t%+v", k, union)
253                 }
254         }
255         if len(pkg.Types) > 0 {
256                 logf("loaded %d types:", len(pkg.Types))
257                 for _, typ := range pkg.Types {
258                         logf(" - type: %q (%d fields)", typ.Name, len(typ.Fields))
259                 }
260         }
261         if len(pkg.Messages) > 0 {
262                 logf("loaded %d messages:", len(pkg.Messages))
263                 for _, msg := range pkg.Messages {
264                         logf(" - message: %q (%d fields)", msg.Name, len(msg.Fields))
265                 }
266         }
267         if len(pkg.Services) > 0 {
268                 logf("loaded %d services:", len(pkg.Services))
269                 for _, svc := range pkg.Services {
270                         var info string
271                         if svc.Stream {
272                                 info = "(STREAM)"
273                         } else if len(svc.Events) > 0 {
274                                 info = fmt.Sprintf("(EVENTS: %v)", svc.Events)
275                         }
276                         logf(" - service: %q -> %q %s", svc.RequestType, svc.ReplyType, info)
277                 }
278         }
279 }
280
281 // parseEnum parses VPP binary API enum object from JSON node
282 func parseEnum(ctx *context, enumNode *jsongo.JSONNode) (*Enum, error) {
283         if enumNode.Len() == 0 || enumNode.At(0).GetType() != jsongo.TypeValue {
284                 return nil, errors.New("invalid JSON for enum specified")
285         }
286
287         enumName, ok := enumNode.At(0).Get().(string)
288         if !ok {
289                 return nil, fmt.Errorf("enum name is %T, not a string", enumNode.At(0).Get())
290         }
291         enumType, ok := enumNode.At(enumNode.Len() - 1).At("enumtype").Get().(string)
292         if !ok {
293                 return nil, fmt.Errorf("enum type invalid or missing")
294         }
295
296         enum := Enum{
297                 Name: enumName,
298                 Type: enumType,
299         }
300
301         // loop through enum entries, skip first (name) and last (enumtype)
302         for j := 1; j < enumNode.Len()-1; j++ {
303                 if enumNode.At(j).GetType() == jsongo.TypeArray {
304                         entry := enumNode.At(j)
305
306                         if entry.Len() < 2 || entry.At(0).GetType() != jsongo.TypeValue || entry.At(1).GetType() != jsongo.TypeValue {
307                                 return nil, errors.New("invalid JSON for enum entry specified")
308                         }
309
310                         entryName, ok := entry.At(0).Get().(string)
311                         if !ok {
312                                 return nil, fmt.Errorf("enum entry name is %T, not a string", entry.At(0).Get())
313                         }
314                         entryVal := entry.At(1).Get()
315
316                         enum.Entries = append(enum.Entries, EnumEntry{
317                                 Name:  entryName,
318                                 Value: entryVal,
319                         })
320                 }
321         }
322
323         return &enum, nil
324 }
325
326 // parseUnion parses VPP binary API union object from JSON node
327 func parseUnion(ctx *context, unionNode *jsongo.JSONNode) (*Union, error) {
328         if unionNode.Len() == 0 || unionNode.At(0).GetType() != jsongo.TypeValue {
329                 return nil, errors.New("invalid JSON for union specified")
330         }
331
332         unionName, ok := unionNode.At(0).Get().(string)
333         if !ok {
334                 return nil, fmt.Errorf("union name is %T, not a string", unionNode.At(0).Get())
335         }
336         unionCRC, ok := unionNode.At(unionNode.Len() - 1).At("crc").Get().(string)
337         if !ok {
338                 return nil, fmt.Errorf("union crc invalid or missing")
339         }
340
341         union := Union{
342                 Name: unionName,
343                 CRC:  unionCRC,
344         }
345
346         // loop through union fields, skip first (name) and last (crc)
347         for j := 1; j < unionNode.Len()-1; j++ {
348                 if unionNode.At(j).GetType() == jsongo.TypeArray {
349                         fieldNode := unionNode.At(j)
350
351                         field, err := parseField(ctx, fieldNode)
352                         if err != nil {
353                                 return nil, err
354                         }
355
356                         union.Fields = append(union.Fields, *field)
357                 }
358         }
359
360         return &union, nil
361 }
362
363 // parseType parses VPP binary API type object from JSON node
364 func parseType(ctx *context, typeNode *jsongo.JSONNode) (*Type, error) {
365         if typeNode.Len() == 0 || typeNode.At(0).GetType() != jsongo.TypeValue {
366                 return nil, errors.New("invalid JSON for type specified")
367         }
368
369         typeName, ok := typeNode.At(0).Get().(string)
370         if !ok {
371                 return nil, fmt.Errorf("type name is %T, not a string", typeNode.At(0).Get())
372         }
373         typeCRC, ok := typeNode.At(typeNode.Len() - 1).At("crc").Get().(string)
374         if !ok {
375                 return nil, fmt.Errorf("type crc invalid or missing")
376         }
377
378         typ := Type{
379                 Name: typeName,
380                 CRC:  typeCRC,
381         }
382
383         // loop through type fields, skip first (name) and last (crc)
384         for j := 1; j < typeNode.Len()-1; j++ {
385                 if typeNode.At(j).GetType() == jsongo.TypeArray {
386                         fieldNode := typeNode.At(j)
387
388                         field, err := parseField(ctx, fieldNode)
389                         if err != nil {
390                                 return nil, err
391                         }
392
393                         typ.Fields = append(typ.Fields, *field)
394                 }
395         }
396
397         return &typ, nil
398 }
399
400 // parseMessage parses VPP binary API message object from JSON node
401 func parseMessage(ctx *context, msgNode *jsongo.JSONNode) (*Message, error) {
402         if msgNode.Len() == 0 || msgNode.At(0).GetType() != jsongo.TypeValue {
403                 return nil, errors.New("invalid JSON for message specified")
404         }
405
406         msgName, ok := msgNode.At(0).Get().(string)
407         if !ok {
408                 return nil, fmt.Errorf("message name is %T, not a string", msgNode.At(0).Get())
409         }
410         msgCRC, ok := msgNode.At(msgNode.Len() - 1).At("crc").Get().(string)
411         if !ok {
412                 return nil, fmt.Errorf("message crc invalid or missing")
413         }
414
415         msg := Message{
416                 Name: msgName,
417                 CRC:  msgCRC,
418         }
419
420         // loop through message fields, skip first (name) and last (crc)
421         for j := 1; j < msgNode.Len()-1; j++ {
422                 if msgNode.At(j).GetType() == jsongo.TypeArray {
423                         fieldNode := msgNode.At(j)
424
425                         field, err := parseField(ctx, fieldNode)
426                         if err != nil {
427                                 return nil, err
428                         }
429
430                         msg.Fields = append(msg.Fields, *field)
431                 }
432         }
433
434         return &msg, nil
435 }
436
437 // parseField parses VPP binary API object field from JSON node
438 func parseField(ctx *context, field *jsongo.JSONNode) (*Field, error) {
439         if field.Len() < 2 || field.At(0).GetType() != jsongo.TypeValue || field.At(1).GetType() != jsongo.TypeValue {
440                 return nil, errors.New("invalid JSON for field specified")
441         }
442
443         fieldType, ok := field.At(0).Get().(string)
444         if !ok {
445                 return nil, fmt.Errorf("field type is %T, not a string", field.At(0).Get())
446         }
447         fieldName, ok := field.At(1).Get().(string)
448         if !ok {
449                 return nil, fmt.Errorf("field name is %T, not a string", field.At(1).Get())
450         }
451         var fieldLength float64
452         if field.Len() >= 3 {
453                 fieldLength, ok = field.At(2).Get().(float64)
454                 if !ok {
455                         return nil, fmt.Errorf("field length is %T, not an int", field.At(2).Get())
456                 }
457         }
458         var fieldLengthFrom string
459         if field.Len() >= 4 {
460                 fieldLengthFrom, ok = field.At(3).Get().(string)
461                 if !ok {
462                         return nil, fmt.Errorf("field length from is %T, not a string", field.At(3).Get())
463                 }
464         }
465
466         return &Field{
467                 Name:     fieldName,
468                 Type:     fieldType,
469                 Length:   int(fieldLength),
470                 SizeFrom: fieldLengthFrom,
471         }, nil
472 }
473
474 // parseService parses VPP binary API service object from JSON node
475 func parseService(ctx *context, svcName string, svcNode *jsongo.JSONNode) (*Service, error) {
476         if svcNode.Len() == 0 || svcNode.At("reply").GetType() != jsongo.TypeValue {
477                 return nil, errors.New("invalid JSON for service specified")
478         }
479
480         svc := Service{
481                 RequestType: svcName,
482         }
483
484         if replyNode := svcNode.At("reply"); replyNode.GetType() == jsongo.TypeValue {
485                 reply, ok := replyNode.Get().(string)
486                 if !ok {
487                         return nil, fmt.Errorf("service reply is %T, not a string", replyNode.Get())
488                 }
489                 // some binapi messages might have `null` reply (for example: memclnt)
490                 if reply != "null" {
491                         svc.ReplyType = reply
492                 }
493         }
494
495         // stream service (dumps)
496         if streamNode := svcNode.At("stream"); streamNode.GetType() == jsongo.TypeValue {
497                 var ok bool
498                 svc.Stream, ok = streamNode.Get().(bool)
499                 if !ok {
500                         return nil, fmt.Errorf("service stream is %T, not a string", streamNode.Get())
501                 }
502         }
503
504         // events service (event subscription)
505         if eventsNode := svcNode.At("events"); eventsNode.GetType() == jsongo.TypeArray {
506                 for j := 0; j < eventsNode.Len(); j++ {
507                         event := eventsNode.At(j).Get().(string)
508                         svc.Events = append(svc.Events, event)
509                 }
510         }
511
512         // validate service
513         if svc.Stream {
514                 if !strings.HasSuffix(svc.RequestType, "_dump") ||
515                         !strings.HasSuffix(svc.ReplyType, "_details") {
516                         fmt.Printf("Invalid STREAM SERVICE: %+v\n", svc)
517                 }
518         } else if len(svc.Events) > 0 {
519                 if (!strings.HasSuffix(svc.RequestType, "_events") &&
520                         !strings.HasSuffix(svc.RequestType, "_stats")) ||
521                         !strings.HasSuffix(svc.ReplyType, "_reply") {
522                         fmt.Printf("Invalid EVENTS SERVICE: %+v\n", svc)
523                 }
524         } else if svc.ReplyType != "" {
525                 if !strings.HasSuffix(svc.ReplyType, "_reply") {
526                         fmt.Printf("Invalid SERVICE: %+v\n", svc)
527                 }
528         }
529
530         return &svc, nil
531 }
532
533 // convertToGoType translates the VPP binary API type into Go type
534 func convertToGoType(ctx *context, binapiType string) (typ string) {
535         if t, ok := binapiTypes[binapiType]; ok {
536                 // basic types
537                 typ = t
538         } else if r, ok := ctx.packageData.RefMap[binapiType]; ok {
539                 // specific types (enums/types/unions)
540                 typ = camelCaseName(r)
541         } else {
542                 // fallback type
543                 log.Printf("found unknown VPP binary API type %q, using byte", binapiType)
544                 typ = "byte"
545         }
546         return typ
547 }