From 868b541e296dc47748ad03b8f0174c828d996529 Mon Sep 17 00:00:00 2001 From: Ondrej Fabry Date: Thu, 13 Dec 2018 10:21:49 +0100 Subject: Add support for aliases and boolean type - aliases are now generated as new types or arrays (if length > 0) - bool is recognized as a boolean type and generated as Go bool - comment with original JSON is now prepended for each object type - interface Services is now generated at the top of the file to provide overview of what RPC services does the current module consists of - dump services now correctly return slice of the particular details type Change-Id: I788babc1c0f2de33e0febd87e5b200d54065b244 Signed-off-by: Ondrej Fabry --- cmd/binapi-generator/definitions.go | 21 ++--- cmd/binapi-generator/generate.go | 157 ++++++++++++++++++++++++------------ cmd/binapi-generator/objects.go | 21 ++--- cmd/binapi-generator/parse.go | 136 ++++++++++++++++++++++++++----- 4 files changed, 240 insertions(+), 95 deletions(-) (limited to 'cmd') diff --git a/cmd/binapi-generator/definitions.go b/cmd/binapi-generator/definitions.go index 3ad782f..0176278 100644 --- a/cmd/binapi-generator/definitions.go +++ b/cmd/binapi-generator/definitions.go @@ -25,6 +25,8 @@ func getBinapiTypeSize(binapiType string) int { b, err := strconv.Atoi(strings.TrimLeft(binapiType, "uif")) if err == nil { return b / 8 + } else { + return 1 } } return -1 @@ -32,15 +34,16 @@ func getBinapiTypeSize(binapiType string) int { // binapiTypes is a set of types used VPP binary API for translation to Go types var binapiTypes = map[string]string{ - "u8": "uint8", - "i8": "int8", - "u16": "uint16", - "i16": "int16", - "u32": "uint32", - "i32": "int32", - "u64": "uint64", - "i64": "int64", - "f64": "float64", + "bool": "bool", + "u8": "uint8", + "i8": "int8", + "u16": "uint16", + "i16": "int16", + "u32": "uint32", + "i32": "int32", + "u64": "uint64", + "i64": "int64", + "f64": "float64", } func usesInitialism(s string) string { diff --git a/cmd/binapi-generator/generate.go b/cmd/binapi-generator/generate.go index 7cfe338..22b4af6 100644 --- a/cmd/binapi-generator/generate.go +++ b/cmd/binapi-generator/generate.go @@ -35,9 +35,7 @@ type context struct { inputFile string // input file with VPP API in JSON outputFile string // output file with generated Go package - inputData []byte // contents of the input file - inputBuff *bytes.Buffer // contents of the input file currently being read - inputLine int // currently processed line in the input file + inputData []byte // contents of the input file moduleName string // name of the source VPP module packageName string // name of the Go package being generated @@ -87,28 +85,40 @@ func generatePackage(ctx *context, w *bufio.Writer) error { if *includeAPIVer { const APIVerConstName = "VlAPIVersion" - fmt.Fprintf(w, "// %s represents version of the API.\n", APIVerConstName) + fmt.Fprintf(w, "// %s represents version of the binary API module.\n", APIVerConstName) fmt.Fprintf(w, "const %s = %v\n", APIVerConstName, ctx.packageData.APIVersion) fmt.Fprintln(w) } + // generate services + if len(ctx.packageData.Services) > 0 { + generateServices(ctx, w, ctx.packageData.Services) + } + + // TODO: generate implementation for Services interface + // generate enums if len(ctx.packageData.Enums) > 0 { fmt.Fprintf(w, "/* Enums */\n\n") - ctx.inputBuff = bytes.NewBuffer(ctx.inputData) - ctx.inputLine = 0 for _, enum := range ctx.packageData.Enums { generateEnum(ctx, w, &enum) } } + // generate aliases + if len(ctx.packageData.Aliases) > 0 { + fmt.Fprintf(w, "/* Aliases */\n\n") + + for _, alias := range ctx.packageData.Aliases { + generateAlias(ctx, w, &alias) + } + } + // generate types if len(ctx.packageData.Types) > 0 { fmt.Fprintf(w, "/* Types */\n\n") - ctx.inputBuff = bytes.NewBuffer(ctx.inputData) - ctx.inputLine = 0 for _, typ := range ctx.packageData.Types { generateType(ctx, w, &typ) } @@ -118,8 +128,6 @@ func generatePackage(ctx *context, w *bufio.Writer) error { if len(ctx.packageData.Unions) > 0 { fmt.Fprintf(w, "/* Unions */\n\n") - ctx.inputBuff = bytes.NewBuffer(ctx.inputData) - ctx.inputLine = 0 for _, union := range ctx.packageData.Unions { generateUnion(ctx, w, &union) } @@ -129,28 +137,11 @@ func generatePackage(ctx *context, w *bufio.Writer) error { if len(ctx.packageData.Messages) > 0 { fmt.Fprintf(w, "/* Messages */\n\n") - ctx.inputBuff = bytes.NewBuffer(ctx.inputData) - ctx.inputLine = 0 for _, msg := range ctx.packageData.Messages { generateMessage(ctx, w, &msg) } } - // generate services - if len(ctx.packageData.Services) > 0 { - fmt.Fprintf(w, "/* Services */\n\n") - - ctx.inputBuff = bytes.NewBuffer(ctx.inputData) - ctx.inputLine = 0 - fmt.Fprintf(w, "type %s interface {\n", "Services") - for _, svc := range ctx.packageData.Services { - generateService(ctx, w, &svc) - } - fmt.Fprintln(w, "}") - } - - // TODO: generate implementation for Services interface - // generate message registrations fmt.Fprintln(w) fmt.Fprintln(w, "func init() {") @@ -181,13 +172,18 @@ func generateHeader(ctx *context, w io.Writer) { var printObjNum = func(obj string, num int) { if num > 0 { if num > 1 { - obj += "s" + if strings.HasSuffix(obj, "s") { + obj += "es" + } else { + obj += "s" + } } fmt.Fprintf(w, "\t%3d %s\n", num, obj) } } printObjNum("message", len(ctx.packageData.Messages)) printObjNum("type", len(ctx.packageData.Types)) + printObjNum("alias", len(ctx.packageData.Aliases)) printObjNum("enum", len(ctx.packageData.Enums)) printObjNum("union", len(ctx.packageData.Unions)) printObjNum("service", len(ctx.packageData.Services)) @@ -213,44 +209,96 @@ func generateImports(ctx *context, w io.Writer) { // generateComment writes generated comment for the object into w func generateComment(ctx *context, w io.Writer, goName string, vppName string, objKind string) { - fmt.Fprintf(w, "// %s represents the VPP binary API %s '%s'.\n", goName, objKind, vppName) + if objKind == "service" { + fmt.Fprintf(w, "// %s represents VPP binary API services:\n", goName) + } else { + fmt.Fprintf(w, "// %s represents VPP binary API %s '%s':\n", goName, objKind, vppName) + } var isNotSpace = func(r rune) bool { return !unicode.IsSpace(r) } // print out the source of the generated object + mapType := false objFound := false objTitle := fmt.Sprintf(`"%s",`, vppName) + switch objKind { + case "alias", "service": + objTitle = fmt.Sprintf(`"%s": {`, vppName) + mapType = true + } + + inputBuff := bytes.NewBuffer(ctx.inputData) + inputLine := 0 + + var trimIndent string var indent int for { - line, err := ctx.inputBuff.ReadString('\n') + line, err := inputBuff.ReadString('\n') if err != nil { break } - ctx.inputLine++ + inputLine++ + noSpaceAt := strings.IndexFunc(line, isNotSpace) if !objFound { indent = strings.Index(line, objTitle) if indent == -1 { continue } + trimIndent = line[:indent] // If no other non-whitespace character then we are at the message header. if trimmed := strings.TrimSpace(line); trimmed == objTitle { objFound = true fmt.Fprintln(w, "//") } - } else { - if strings.IndexFunc(line, isNotSpace) < indent { - break // end of the object definition in JSON - } + } else if noSpaceAt < indent { + break // end of the definition in JSON for array types + } else if objFound && mapType && noSpaceAt <= indent { + fmt.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent)) + break // end of the definition in JSON for map types (aliases, services) } - fmt.Fprint(w, "//", line) + fmt.Fprintf(w, "//\t%s", strings.TrimPrefix(line, trimIndent)) } fmt.Fprintln(w, "//") } +// generateServices writes generated code for the Services interface into w +func generateServices(ctx *context, w *bufio.Writer, services []Service) { + // generate services comment + generateComment(ctx, w, "Services", "services", "service") + + // generate interface + fmt.Fprintf(w, "type %s interface {\n", "Services") + for _, svc := range ctx.packageData.Services { + generateService(ctx, w, &svc) + } + fmt.Fprintln(w, "}") + + fmt.Fprintln(w) +} + +// generateService writes generated code for the service into w +func generateService(ctx *context, w io.Writer, svc *Service) { + reqTyp := camelCaseName(svc.RequestType) + + // method name is same as parameter type name by default + method := svc.MethodName() + params := fmt.Sprintf("*%s", reqTyp) + returns := "error" + if replyType := camelCaseName(svc.ReplyType); replyType != "" { + repTyp := fmt.Sprintf("*%s", replyType) + if svc.Stream { + repTyp = fmt.Sprintf("[]%s", repTyp) + } + returns = fmt.Sprintf("(%s, error)", repTyp) + } + + fmt.Fprintf(w, "\t%s(%s) %s\n", method, params, returns) +} + // generateEnum writes generated code for the enum into w func generateEnum(ctx *context, w io.Writer, enum *Enum) { name := camelCaseName(enum.Name) @@ -277,6 +325,28 @@ func generateEnum(ctx *context, w io.Writer, enum *Enum) { fmt.Fprintln(w) } +// generateAlias writes generated code for the alias into w +func generateAlias(ctx *context, w io.Writer, alias *Alias) { + name := camelCaseName(alias.Name) + + logf(" writing type %q (%s), length: %d", alias.Name, name, alias.Length) + + // generate struct comment + generateComment(ctx, w, name, alias.Name, "alias") + + // generate struct definition + fmt.Fprintf(w, "type %s ", name) + + if alias.Length > 0 { + fmt.Fprintf(w, "[%d]", alias.Length) + } + + dataType := convertToGoType(ctx, alias.Type) + fmt.Fprintf(w, "%s\n", dataType) + + fmt.Fprintln(w) +} + // generateType writes generated code for the type into w func generateType(ctx *context, w io.Writer, typ *Type) { name := camelCaseName(typ.Name) @@ -494,21 +564,6 @@ func generateField(ctx *context, w io.Writer, fields []Field, i int) { fmt.Fprintln(w) } -// generateService writes generated code for the service into w -func generateService(ctx *context, w io.Writer, svc *Service) { - reqTyp := camelCaseName(svc.RequestType) - - // method name is same as parameter type name by default - method := svc.MethodName() - params := fmt.Sprintf("*%s", reqTyp) - returns := "error" - if replyTyp := camelCaseName(svc.ReplyType); replyTyp != "" { - returns = fmt.Sprintf("(*%s, error)", replyTyp) - } - - fmt.Fprintf(w, "\t%s(%s) %s\n", method, params, returns) -} - // generateMessageNameGetter generates getter for original VPP message name into the provider writer func generateMessageNameGetter(w io.Writer, structName, msgName string) { fmt.Fprintf(w, `func (*%s) GetMessageName() string { diff --git a/cmd/binapi-generator/objects.go b/cmd/binapi-generator/objects.go index 2681085..97318cb 100644 --- a/cmd/binapi-generator/objects.go +++ b/cmd/binapi-generator/objects.go @@ -8,6 +8,7 @@ type Package struct { Enums []Enum Unions []Union Types []Type + Aliases []Alias Messages []Message Services []Service RefMap map[string]string @@ -37,6 +38,13 @@ type Type struct { Fields []Field } +// Alias represents VPP binary API alias +type Alias struct { + Name string + Type string + Length int +} + // Union represents VPP binary API union type Union struct { Name string @@ -105,16 +113,3 @@ func (s Service) IsRequestService() bool { // some binapi messages might have `null` reply (for example: memclnt) return s.ReplyType != "" && s.ReplyType != "null" // not null } - -func getSizeOfType(typ *Type) (size int) { - for _, field := range typ.Fields { - if n := getBinapiTypeSize(field.Type); n > 0 { - if field.Length > 0 { - size += n * field.Length - } else { - size += n - } - } - } - return size -} diff --git a/cmd/binapi-generator/parse.go b/cmd/binapi-generator/parse.go index 2d6fdd4..5dfbe91 100644 --- a/cmd/binapi-generator/parse.go +++ b/cmd/binapi-generator/parse.go @@ -23,26 +23,6 @@ import ( "github.com/bennyscetbun/jsongo" ) -func getTypeByRef(ctx *context, ref string) *Type { - for _, typ := range ctx.packageData.Types { - if ref == toApiType(typ.Name) { - return &typ - } - } - return nil -} - -func getUnionSize(ctx *context, union *Union) (maxSize int) { - for _, field := range union.Fields { - if typ := getTypeByRef(ctx, field.Type); typ != nil { - if size := getSizeOfType(typ); size > maxSize { - maxSize = size - } - } - } - return -} - // toApiType returns name that is used as type reference in VPP binary API func toApiType(name string) string { return fmt.Sprintf("vl_api_%s_t", name) @@ -50,13 +30,14 @@ func toApiType(name string) string { // parsePackage parses provided JSON data into objects prepared for code generation func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) { - logf(" %s contains: %d services, %d messages, %d types, %d enums, %d unions (version: %s)", + logf(" %s contains: %d services, %d messages, %d types, %d enums, %d unions, %d aliases (version: %s)", ctx.packageName, jsonRoot.Map("services").Len(), jsonRoot.Map("messages").Len(), jsonRoot.Map("types").Len(), jsonRoot.Map("enums").Len(), jsonRoot.Map("unions").Len(), + jsonRoot.Map("aliases").Len(), jsonRoot.Map("vl_api_version").Get(), ) @@ -79,6 +60,22 @@ func parsePackage(ctx *context, jsonRoot *jsongo.JSONNode) (*Package, error) { pkg.RefMap[toApiType(enum.Name)] = enum.Name } + // parse aliases + aliases := jsonRoot.Map("aliases") + if aliases.GetType() == jsongo.TypeMap { + pkg.Aliases = make([]Alias, aliases.Len()) + for i, key := range aliases.GetKeys() { + aliasNode := aliases.At(key) + + alias, err := parseAlias(ctx, key.(string), aliasNode) + if err != nil { + return nil, err + } + pkg.Aliases[i] = *alias + pkg.RefMap[toApiType(alias.Name)] = alias.Name + } + } + // parse types types := jsonRoot.Map("types") pkg.Types = make([]Type, types.Len()) @@ -308,6 +305,42 @@ func parseType(ctx *context, typeNode *jsongo.JSONNode) (*Type, error) { return &typ, nil } +const ( + aliasesLength = "length" + aliasesType = "type" +) + +// parseAlias parses VPP binary API alias object from JSON node +func parseAlias(ctx *context, aliasName string, aliasNode *jsongo.JSONNode) (*Alias, error) { + if aliasNode.Len() == 0 || aliasNode.At(aliasesType).GetType() != jsongo.TypeValue { + return nil, errors.New("invalid JSON for alias specified") + } + + alias := Alias{ + Name: aliasName, + } + + if typeNode := aliasNode.At(aliasesType); typeNode.GetType() == jsongo.TypeValue { + typ, ok := typeNode.Get().(string) + if !ok { + return nil, fmt.Errorf("alias type is %T, not a string", typeNode.Get()) + } + if typ != "null" { + alias.Type = typ + } + } + + if lengthNode := aliasNode.At(aliasesLength); lengthNode.GetType() == jsongo.TypeValue { + length, ok := lengthNode.Get().(float64) + if !ok { + return nil, fmt.Errorf("alias length is %T, not a float64", lengthNode.Get()) + } + alias.Length = int(length) + } + + return &alias, nil +} + // parseMessage parses VPP binary API message object from JSON node func parseMessage(ctx *context, msgNode *jsongo.JSONNode) (*Message, error) { if msgNode.Len() == 0 || msgNode.At(0).GetType() != jsongo.TypeValue { @@ -364,7 +397,7 @@ func parseField(ctx *context, field *jsongo.JSONNode) (*Field, error) { if field.Len() >= 3 { fieldLength, ok = field.At(2).Get().(float64) if !ok { - return nil, fmt.Errorf("field length is %T, not an int", field.At(2).Get()) + return nil, fmt.Errorf("field length is %T, not float64", field.At(2).Get()) } } var fieldLengthFrom string @@ -461,3 +494,62 @@ func convertToGoType(ctx *context, binapiType string) (typ string) { } return typ } + +func getSizeOfType(typ *Type) (size int) { + for _, field := range typ.Fields { + size += getSizeOfBinapiTypeLength(field.Type, field.Length) + } + return size +} + +func getSizeOfBinapiTypeLength(typ string, length int) (size int) { + if n := getBinapiTypeSize(typ); n > 0 { + if length > 0 { + return n * length + } else { + return n + } + } + return +} + +func getTypeByRef(ctx *context, ref string) *Type { + for _, typ := range ctx.packageData.Types { + if ref == toApiType(typ.Name) { + return &typ + } + } + return nil +} + +func getAliasByRef(ctx *context, ref string) *Alias { + for _, alias := range ctx.packageData.Aliases { + if ref == toApiType(alias.Name) { + return &alias + } + } + return nil +} + +func getUnionSize(ctx *context, union *Union) (maxSize int) { + for _, field := range union.Fields { + typ := getTypeByRef(ctx, field.Type) + if typ != nil { + if size := getSizeOfType(typ); size > maxSize { + maxSize = size + } + continue + } + alias := getAliasByRef(ctx, field.Type) + if alias != nil { + if size := getSizeOfBinapiTypeLength(alias.Type, alias.Length); size > maxSize { + maxSize = size + } + continue + } else { + logf("no type or alias found for union %s field type %q", union.Name, field.Type) + continue + } + } + return +} -- cgit 1.2.3-korg