diff options
Diffstat (limited to 'tools/go_generics/generics.go')
-rw-r--r-- | tools/go_generics/generics.go | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/tools/go_generics/generics.go b/tools/go_generics/generics.go new file mode 100644 index 000000000..033923442 --- /dev/null +++ b/tools/go_generics/generics.go @@ -0,0 +1,274 @@ +// Copyright 2018 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// go_generics reads a Go source file and writes a new version of that file with +// a few transformations applied to each. Namely: +// +// 1. Global types can be explicitly renamed with the -t option. For example, +// if -t=A=B is passed in, all references to A will be replaced with +// references to B; a function declaration like: +// +// func f(arg *A) +// +// would be renamed to: +// +// fun f(arg *B) +// +// 2. Global type definitions and their method sets will be removed when they're +// being renamed with -t. For example, if -t=A=B is passed in, the following +// definition and methods that existed in the input file wouldn't exist at +// all in the output file: +// +// type A struct{} +// +// func (*A) f() {} +// +// 3. All global types, variables, constants and functions (not methods) are +// prefixed and suffixed based on the option -prefix and -suffix arguments. +// For example, if -suffix=A is passed in, the following globals: +// +// func f() +// type t struct{} +// +// would be renamed to: +// +// func fA() +// type tA struct{} +// +// Some special tags are also modified. For example: +// +// "state:.(t)" +// +// would become: +// +// "state:.(tA)" +// +// 4. The package is renamed to the value via the -p argument. +// 5. Value of constants can be modified with -c argument. +// +// Note that not just the top-level declarations are renamed, all references to +// them are also properly renamed as well, taking into account visibility rules +// and shadowing. For example, if -suffix=A is passed in, the following: +// +// var b = 100 +// +// func f() { +// g(b) +// b := 0 +// g(b) +// } +// +// Would be replaced with: +// +// var bA = 100 +// +// func f() { +// g(bA) +// b := 0 +// g(b) +// } +// +// Note that the second call to g() kept "b" as an argument because it refers to +// the local variable "b". +// +// Unfortunately, go_generics does not handle anonymous fields with renamed types. +package main + +import ( + "bytes" + "flag" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/token" + "io/ioutil" + "os" + "regexp" + "strings" + + "gvisor.googlesource.com/gvisor/tools/go_generics/globals" +) + +var ( + input = flag.String("i", "", "input `file`") + output = flag.String("o", "", "output `file`") + suffix = flag.String("suffix", "", "`suffix` to add to each global symbol") + prefix = flag.String("prefix", "", "`prefix` to add to each global symbol") + packageName = flag.String("p", "main", "output package `name`") + printAST = flag.Bool("ast", false, "prints the AST") + types = make(mapValue) + consts = make(mapValue) + imports = make(mapValue) +) + +// mapValue implements flag.Value. We use a mapValue flag instead of a regular +// string flag when we want to allow more than one instance of the flag. For +// example, we allow several "-t A=B" arguments, and will rename them all. +type mapValue map[string]string + +func (m mapValue) String() string { + var b bytes.Buffer + first := true + for k, v := range m { + if !first { + b.WriteRune(',') + } else { + first = false + } + b.WriteString(k) + b.WriteRune('=') + b.WriteString(v) + } + return b.String() +} + +func (m mapValue) Set(s string) error { + sep := strings.Index(s, "=") + if sep == -1 { + return fmt.Errorf("missing '=' from '%s'", s) + } + + m[s[:sep]] = s[sep+1:] + + return nil +} + +// stateTagRegexp matches against the 'typed' state tags. +var stateTagRegexp = regexp.MustCompile(`^(.*[^a-z0-9_])state:"\.\(([^\)]*)\)"(.*)$`) + +var identifierRegexp = regexp.MustCompile(`^(.*[^a-zA-Z_])([a-zA-Z_][a-zA-Z0-9_]*)(.*)$`) + +func main() { + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "Usage: %s [options]\n", os.Args[0]) + flag.PrintDefaults() + } + + flag.Var(types, "t", "rename type A to B when `A=B` is passed in. Multiple such mappings are allowed.") + flag.Var(consts, "c", "reassign constant A to value B when `A=B` is passed in. Multiple such mappings are allowed.") + flag.Var(imports, "import", "specifies the import libraries to use when types are not local. `name=path` specifies that 'name', used in types as name.type, refers to the package living in 'path'.") + flag.Parse() + + if *input == "" || *output == "" { + flag.Usage() + os.Exit(1) + } + + // Parse the input file. + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, *input, nil, parser.ParseComments|parser.DeclarationErrors|parser.SpuriousErrors) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } + + // Print the AST if requested. + if *printAST { + ast.Print(fset, f) + } + + cmap := ast.NewCommentMap(fset, f, f.Comments) + + // Update imports based on what's used in types and consts. + maps := []mapValue{types, consts} + importDecl, err := updateImports(maps, imports) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } + types = maps[0] + consts = maps[1] + + // Reassign all specified constants. + for _, decl := range f.Decls { + d, ok := decl.(*ast.GenDecl) + if !ok || d.Tok != token.CONST { + continue + } + + for _, gs := range d.Specs { + s := gs.(*ast.ValueSpec) + for i, id := range s.Names { + if n, ok := consts[id.Name]; ok { + s.Values[i] = &ast.BasicLit{Value: n} + } + } + } + } + + // Go through all globals and their uses in the AST and rename the types + // with explicitly provided names, and rename all types, variables, + // consts and functions with the provided prefix and suffix. + globals.Visit(fset, f, func(ident *ast.Ident, kind globals.SymKind) { + if n, ok := types[ident.Name]; ok && kind == globals.KindType { + ident.Name = n + } else { + switch kind { + case globals.KindType, globals.KindVar, globals.KindConst, globals.KindFunction: + ident.Name = *prefix + ident.Name + *suffix + case globals.KindTag: + // Modify the state tag appropriately. + if m := stateTagRegexp.FindStringSubmatch(ident.Name); m != nil { + if t := identifierRegexp.FindStringSubmatch(m[2]); t != nil { + ident.Name = m[1] + `state:".(` + t[1] + *prefix + t[2] + *suffix + t[3] + `)"` + m[3] + } + } + } + } + }) + + // Remove the definition of all types that are being remapped. + set := make(typeSet) + for _, v := range types { + set[v] = struct{}{} + } + removeTypes(set, f) + + // Add the new imports, if any, to the top. + if importDecl != nil { + newDecls := make([]ast.Decl, 0, len(f.Decls)+1) + newDecls = append(newDecls, importDecl) + newDecls = append(newDecls, f.Decls...) + f.Decls = newDecls + } + + // Update comments to remove the ones potentially associated with the + // type T that we removed. + f.Comments = cmap.Filter(f).Comments() + + // If there are file (package) comments, delete them. + if f.Doc != nil { + for i, cg := range f.Comments { + if cg == f.Doc { + f.Comments = append(f.Comments[:i], f.Comments[i+1:]...) + break + } + } + } + + // Write the output file. + f.Name.Name = *packageName + + var buf bytes.Buffer + if err := format.Node(&buf, fset, f); err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } + + if err := ioutil.WriteFile(*output, buf.Bytes(), 0644); err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } +} |