640 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			640 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Go
		
	
	
	
| // Copyright 2014 The Go Authors. All rights reserved.
 | |
| // Use of this source code is governed by a BSD-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| // Stringer is a tool to automate the creation of methods that satisfy the fmt.Stringer
 | |
| // interface. Given the name of a (signed or unsigned) integer type T that has constants
 | |
| // defined, stringer will create a new self-contained Go source file implementing
 | |
| //	func (t T) String() string
 | |
| // The file is created in the same package and directory as the package that defines T.
 | |
| // It has helpful defaults designed for use with go generate.
 | |
| //
 | |
| // Stringer works best with constants that are consecutive values such as created using iota,
 | |
| // but creates good code regardless. In the future it might also provide custom support for
 | |
| // constant sets that are bit patterns.
 | |
| //
 | |
| // For example, given this snippet,
 | |
| //
 | |
| //	package painkiller
 | |
| //
 | |
| //	type Pill int
 | |
| //
 | |
| //	const (
 | |
| //		Placebo Pill = iota
 | |
| //		Aspirin
 | |
| //		Ibuprofen
 | |
| //		Paracetamol
 | |
| //		Acetaminophen = Paracetamol
 | |
| //	)
 | |
| //
 | |
| // running this command
 | |
| //
 | |
| //	stringer -type=Pill
 | |
| //
 | |
| // in the same directory will create the file pill_string.go, in package painkiller,
 | |
| // containing a definition of
 | |
| //
 | |
| //	func (Pill) String() string
 | |
| //
 | |
| // That method will translate the value of a Pill constant to the string representation
 | |
| // of the respective constant name, so that the call fmt.Print(painkiller.Aspirin) will
 | |
| // print the string "Aspirin".
 | |
| //
 | |
| // Typically this process would be run using go generate, like this:
 | |
| //
 | |
| //	//go:generate stringer -type=Pill
 | |
| //
 | |
| // If multiple constants have the same value, the lexically first matching name will
 | |
| // be used (in the example, Acetaminophen will print as "Paracetamol").
 | |
| //
 | |
| // With no arguments, it processes the package in the current directory.
 | |
| // Otherwise, the arguments must name a single directory holding a Go package
 | |
| // or a set of Go source files that represent a single Go package.
 | |
| //
 | |
| // The -type flag accepts a comma-separated list of types so a single run can
 | |
| // generate methods for multiple types. The default output file is t_string.go,
 | |
| // where t is the lower-cased name of the first type listed. It can be overridden
 | |
| // with the -output flag.
 | |
| //
 | |
| package main // import "golang.org/x/tools/cmd/stringer"
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"flag"
 | |
| 	"fmt"
 | |
| 	"go/ast"
 | |
| 	"go/build"
 | |
| 	"go/format"
 | |
| 	"go/parser"
 | |
| 	"go/token"
 | |
| 	"io/ioutil"
 | |
| 	"log"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"sort"
 | |
| 	"strings"
 | |
| 
 | |
| 	"golang.org/x/tools/go/exact"
 | |
| 	"golang.org/x/tools/go/types"
 | |
| 
 | |
| 	_ "golang.org/x/tools/go/gcimporter"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	typeNames = flag.String("type", "", "comma-separated list of type names; must be set")
 | |
| 	output    = flag.String("output", "", "output file name; default srcdir/<type>_string.go")
 | |
| )
 | |
| 
 | |
| // Usage is a replacement usage function for the flags package.
 | |
| func Usage() {
 | |
| 	fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
 | |
| 	fmt.Fprintf(os.Stderr, "\tstringer [flags] -type T [directory]\n")
 | |
| 	fmt.Fprintf(os.Stderr, "\tstringer [flags[ -type T files... # Must be a single package\n")
 | |
| 	fmt.Fprintf(os.Stderr, "For more information, see:\n")
 | |
| 	fmt.Fprintf(os.Stderr, "\thttp://godoc.org/golang.org/x/tools/cmd/stringer\n")
 | |
| 	fmt.Fprintf(os.Stderr, "Flags:\n")
 | |
| 	flag.PrintDefaults()
 | |
| 	os.Exit(2)
 | |
| }
 | |
| 
 | |
| func main() {
 | |
| 	log.SetFlags(0)
 | |
| 	log.SetPrefix("stringer: ")
 | |
| 	flag.Usage = Usage
 | |
| 	flag.Parse()
 | |
| 	if len(*typeNames) == 0 {
 | |
| 		flag.Usage()
 | |
| 		os.Exit(2)
 | |
| 	}
 | |
| 	types := strings.Split(*typeNames, ",")
 | |
| 
 | |
| 	// We accept either one directory or a list of files. Which do we have?
 | |
| 	args := flag.Args()
 | |
| 	if len(args) == 0 {
 | |
| 		// Default: process whole package in current directory.
 | |
| 		args = []string{"."}
 | |
| 	}
 | |
| 
 | |
| 	// Parse the package once.
 | |
| 	var (
 | |
| 		dir string
 | |
| 		g   Generator
 | |
| 	)
 | |
| 	if len(args) == 1 && isDirectory(args[0]) {
 | |
| 		dir = args[0]
 | |
| 		g.parsePackageDir(args[0])
 | |
| 	} else {
 | |
| 		dir = filepath.Dir(args[0])
 | |
| 		g.parsePackageFiles(args)
 | |
| 	}
 | |
| 
 | |
| 	// Print the header and package clause.
 | |
| 	g.Printf("// generated by stringer %s; DO NOT EDIT\n", strings.Join(os.Args[1:], " "))
 | |
| 	g.Printf("\n")
 | |
| 	g.Printf("package %s", g.pkg.name)
 | |
| 	g.Printf("\n")
 | |
| 	g.Printf("import \"fmt\"\n") // Used by all methods.
 | |
| 
 | |
| 	// Run generate for each type.
 | |
| 	for _, typeName := range types {
 | |
| 		g.generate(typeName)
 | |
| 	}
 | |
| 
 | |
| 	// Format the output.
 | |
| 	src := g.format()
 | |
| 
 | |
| 	// Write to file.
 | |
| 	outputName := *output
 | |
| 	if outputName == "" {
 | |
| 		baseName := fmt.Sprintf("%s_string.go", types[0])
 | |
| 		outputName = filepath.Join(dir, strings.ToLower(baseName))
 | |
| 	}
 | |
| 	err := ioutil.WriteFile(outputName, src, 0644)
 | |
| 	if err != nil {
 | |
| 		log.Fatalf("writing output: %s", err)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // isDirectory reports whether the named file is a directory.
 | |
| func isDirectory(name string) bool {
 | |
| 	info, err := os.Stat(name)
 | |
| 	if err != nil {
 | |
| 		log.Fatal(err)
 | |
| 	}
 | |
| 	return info.IsDir()
 | |
| }
 | |
| 
 | |
| // Generator holds the state of the analysis. Primarily used to buffer
 | |
| // the output for format.Source.
 | |
| type Generator struct {
 | |
| 	buf bytes.Buffer // Accumulated output.
 | |
| 	pkg *Package     // Package we are scanning.
 | |
| }
 | |
| 
 | |
| func (g *Generator) Printf(format string, args ...interface{}) {
 | |
| 	fmt.Fprintf(&g.buf, format, args...)
 | |
| }
 | |
| 
 | |
| // File holds a single parsed file and associated data.
 | |
| type File struct {
 | |
| 	pkg  *Package  // Package to which this file belongs.
 | |
| 	file *ast.File // Parsed AST.
 | |
| 	// These fields are reset for each type being generated.
 | |
| 	typeName string  // Name of the constant type.
 | |
| 	values   []Value // Accumulator for constant values of that type.
 | |
| }
 | |
| 
 | |
| type Package struct {
 | |
| 	dir      string
 | |
| 	name     string
 | |
| 	defs     map[*ast.Ident]types.Object
 | |
| 	files    []*File
 | |
| 	typesPkg *types.Package
 | |
| }
 | |
| 
 | |
| // parsePackageDir parses the package residing in the directory.
 | |
| func (g *Generator) parsePackageDir(directory string) {
 | |
| 	pkg, err := build.Default.ImportDir(directory, 0)
 | |
| 	if err != nil {
 | |
| 		log.Fatalf("cannot process directory %s: %s", directory, err)
 | |
| 	}
 | |
| 	var names []string
 | |
| 	names = append(names, pkg.GoFiles...)
 | |
| 	names = append(names, pkg.CgoFiles...)
 | |
| 	// TODO: Need to think about constants in test files. Maybe write type_string_test.go
 | |
| 	// in a separate pass? For later.
 | |
| 	// names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package.
 | |
| 	names = append(names, pkg.SFiles...)
 | |
| 	names = prefixDirectory(directory, names)
 | |
| 	g.parsePackage(directory, names, nil)
 | |
| }
 | |
| 
 | |
| // parsePackageFiles parses the package occupying the named files.
 | |
| func (g *Generator) parsePackageFiles(names []string) {
 | |
| 	g.parsePackage(".", names, nil)
 | |
| }
 | |
| 
 | |
| // prefixDirectory places the directory name on the beginning of each name in the list.
 | |
| func prefixDirectory(directory string, names []string) []string {
 | |
| 	if directory == "." {
 | |
| 		return names
 | |
| 	}
 | |
| 	ret := make([]string, len(names))
 | |
| 	for i, name := range names {
 | |
| 		ret[i] = filepath.Join(directory, name)
 | |
| 	}
 | |
| 	return ret
 | |
| }
 | |
| 
 | |
| // parsePackage analyzes the single package constructed from the named files.
 | |
| // If text is non-nil, it is a string to be used instead of the content of the file,
 | |
| // to be used for testing. parsePackage exits if there is an error.
 | |
| func (g *Generator) parsePackage(directory string, names []string, text interface{}) {
 | |
| 	var files []*File
 | |
| 	var astFiles []*ast.File
 | |
| 	g.pkg = new(Package)
 | |
| 	fs := token.NewFileSet()
 | |
| 	for _, name := range names {
 | |
| 		if !strings.HasSuffix(name, ".go") {
 | |
| 			continue
 | |
| 		}
 | |
| 		parsedFile, err := parser.ParseFile(fs, name, text, 0)
 | |
| 		if err != nil {
 | |
| 			log.Fatalf("parsing package: %s: %s", name, err)
 | |
| 		}
 | |
| 		astFiles = append(astFiles, parsedFile)
 | |
| 		files = append(files, &File{
 | |
| 			file: parsedFile,
 | |
| 			pkg:  g.pkg,
 | |
| 		})
 | |
| 	}
 | |
| 	if len(astFiles) == 0 {
 | |
| 		log.Fatalf("%s: no buildable Go files", directory)
 | |
| 	}
 | |
| 	g.pkg.name = astFiles[0].Name.Name
 | |
| 	g.pkg.files = files
 | |
| 	g.pkg.dir = directory
 | |
| 	// Type check the package.
 | |
| 	g.pkg.check(fs, astFiles)
 | |
| }
 | |
| 
 | |
| // check type-checks the package. The package must be OK to proceed.
 | |
| func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) {
 | |
| 	pkg.defs = make(map[*ast.Ident]types.Object)
 | |
| 	config := types.Config{FakeImportC: true}
 | |
| 	info := &types.Info{
 | |
| 		Defs: pkg.defs,
 | |
| 	}
 | |
| 	typesPkg, err := config.Check(pkg.dir, fs, astFiles, info)
 | |
| 	if err != nil {
 | |
| 		log.Fatalf("checking package: %s", err)
 | |
| 	}
 | |
| 	pkg.typesPkg = typesPkg
 | |
| }
 | |
| 
 | |
| // generate produces the String method for the named type.
 | |
| func (g *Generator) generate(typeName string) {
 | |
| 	values := make([]Value, 0, 100)
 | |
| 	for _, file := range g.pkg.files {
 | |
| 		// Set the state for this run of the walker.
 | |
| 		file.typeName = typeName
 | |
| 		file.values = nil
 | |
| 		if file.file != nil {
 | |
| 			ast.Inspect(file.file, file.genDecl)
 | |
| 			values = append(values, file.values...)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(values) == 0 {
 | |
| 		log.Fatalf("no values defined for type %s", typeName)
 | |
| 	}
 | |
| 	runs := splitIntoRuns(values)
 | |
| 	// The decision of which pattern to use depends on the number of
 | |
| 	// runs in the numbers. If there's only one, it's easy. For more than
 | |
| 	// one, there's a tradeoff between complexity and size of the data
 | |
| 	// and code vs. the simplicity of a map. A map takes more space,
 | |
| 	// but so does the code. The decision here (crossover at 10) is
 | |
| 	// arbitrary, but considers that for large numbers of runs the cost
 | |
| 	// of the linear scan in the switch might become important, and
 | |
| 	// rather than use yet another algorithm such as binary search,
 | |
| 	// we punt and use a map. In any case, the likelihood of a map
 | |
| 	// being necessary for any realistic example other than bitmasks
 | |
| 	// is very low. And bitmasks probably deserve their own analysis,
 | |
| 	// to be done some other day.
 | |
| 	switch {
 | |
| 	case len(runs) == 1:
 | |
| 		g.buildOneRun(runs, typeName)
 | |
| 	case len(runs) <= 10:
 | |
| 		g.buildMultipleRuns(runs, typeName)
 | |
| 	default:
 | |
| 		g.buildMap(runs, typeName)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // splitIntoRuns breaks the values into runs of contiguous sequences.
 | |
| // For example, given 1,2,3,5,6,7 it returns {1,2,3},{5,6,7}.
 | |
| // The input slice is known to be non-empty.
 | |
| func splitIntoRuns(values []Value) [][]Value {
 | |
| 	// We use stable sort so the lexically first name is chosen for equal elements.
 | |
| 	sort.Stable(byValue(values))
 | |
| 	// Remove duplicates. Stable sort has put the one we want to print first,
 | |
| 	// so use that one. The String method won't care about which named constant
 | |
| 	// was the argument, so the first name for the given value is the only one to keep.
 | |
| 	// We need to do this because identical values would cause the switch or map
 | |
| 	// to fail to compile.
 | |
| 	j := 1
 | |
| 	for i := 1; i < len(values); i++ {
 | |
| 		if values[i].value != values[i-1].value {
 | |
| 			values[j] = values[i]
 | |
| 			j++
 | |
| 		}
 | |
| 	}
 | |
| 	values = values[:j]
 | |
| 	runs := make([][]Value, 0, 10)
 | |
| 	for len(values) > 0 {
 | |
| 		// One contiguous sequence per outer loop.
 | |
| 		i := 1
 | |
| 		for i < len(values) && values[i].value == values[i-1].value+1 {
 | |
| 			i++
 | |
| 		}
 | |
| 		runs = append(runs, values[:i])
 | |
| 		values = values[i:]
 | |
| 	}
 | |
| 	return runs
 | |
| }
 | |
| 
 | |
| // format returns the gofmt-ed contents of the Generator's buffer.
 | |
| func (g *Generator) format() []byte {
 | |
| 	src, err := format.Source(g.buf.Bytes())
 | |
| 	if err != nil {
 | |
| 		// Should never happen, but can arise when developing this code.
 | |
| 		// The user can compile the output to see the error.
 | |
| 		log.Printf("warning: internal error: invalid Go generated: %s", err)
 | |
| 		log.Printf("warning: compile the package to analyze the error")
 | |
| 		return g.buf.Bytes()
 | |
| 	}
 | |
| 	return src
 | |
| }
 | |
| 
 | |
| // Value represents a declared constant.
 | |
| type Value struct {
 | |
| 	name string // The name of the constant.
 | |
| 	// The value is stored as a bit pattern alone. The boolean tells us
 | |
| 	// whether to interpret it as an int64 or a uint64; the only place
 | |
| 	// this matters is when sorting.
 | |
| 	// Much of the time the str field is all we need; it is printed
 | |
| 	// by Value.String.
 | |
| 	value  uint64 // Will be converted to int64 when needed.
 | |
| 	signed bool   // Whether the constant is a signed type.
 | |
| 	str    string // The string representation given by the "go/exact" package.
 | |
| }
 | |
| 
 | |
| func (v *Value) String() string {
 | |
| 	return v.str
 | |
| }
 | |
| 
 | |
| // byValue lets us sort the constants into increasing order.
 | |
| // We take care in the Less method to sort in signed or unsigned order,
 | |
| // as appropriate.
 | |
| type byValue []Value
 | |
| 
 | |
| func (b byValue) Len() int      { return len(b) }
 | |
| func (b byValue) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
 | |
| func (b byValue) Less(i, j int) bool {
 | |
| 	if b[i].signed {
 | |
| 		return int64(b[i].value) < int64(b[j].value)
 | |
| 	}
 | |
| 	return b[i].value < b[j].value
 | |
| }
 | |
| 
 | |
| // genDecl processes one declaration clause.
 | |
| func (f *File) genDecl(node ast.Node) bool {
 | |
| 	decl, ok := node.(*ast.GenDecl)
 | |
| 	if !ok || decl.Tok != token.CONST {
 | |
| 		// We only care about const declarations.
 | |
| 		return true
 | |
| 	}
 | |
| 	// The name of the type of the constants we are declaring.
 | |
| 	// Can change if this is a multi-element declaration.
 | |
| 	typ := ""
 | |
| 	// Loop over the elements of the declaration. Each element is a ValueSpec:
 | |
| 	// a list of names possibly followed by a type, possibly followed by values.
 | |
| 	// If the type and value are both missing, we carry down the type (and value,
 | |
| 	// but the "go/types" package takes care of that).
 | |
| 	for _, spec := range decl.Specs {
 | |
| 		vspec := spec.(*ast.ValueSpec) // Guaranteed to succeed as this is CONST.
 | |
| 		if vspec.Type == nil && len(vspec.Values) > 0 {
 | |
| 			// "X = 1". With no type but a value, the constant is untyped.
 | |
| 			// Skip this vspec and reset the remembered type.
 | |
| 			typ = ""
 | |
| 			continue
 | |
| 		}
 | |
| 		if vspec.Type != nil {
 | |
| 			// "X T". We have a type. Remember it.
 | |
| 			ident, ok := vspec.Type.(*ast.Ident)
 | |
| 			if !ok {
 | |
| 				continue
 | |
| 			}
 | |
| 			typ = ident.Name
 | |
| 		}
 | |
| 		if typ != f.typeName {
 | |
| 			// This is not the type we're looking for.
 | |
| 			continue
 | |
| 		}
 | |
| 		// We now have a list of names (from one line of source code) all being
 | |
| 		// declared with the desired type.
 | |
| 		// Grab their names and actual values and store them in f.values.
 | |
| 		for _, name := range vspec.Names {
 | |
| 			if name.Name == "_" {
 | |
| 				continue
 | |
| 			}
 | |
| 			// This dance lets the type checker find the values for us. It's a
 | |
| 			// bit tricky: look up the object declared by the name, find its
 | |
| 			// types.Const, and extract its value.
 | |
| 			obj, ok := f.pkg.defs[name]
 | |
| 			if !ok {
 | |
| 				log.Fatalf("no value for constant %s", name)
 | |
| 			}
 | |
| 			info := obj.Type().Underlying().(*types.Basic).Info()
 | |
| 			if info&types.IsInteger == 0 {
 | |
| 				log.Fatalf("can't handle non-integer constant type %s", typ)
 | |
| 			}
 | |
| 			value := obj.(*types.Const).Val() // Guaranteed to succeed as this is CONST.
 | |
| 			if value.Kind() != exact.Int {
 | |
| 				log.Fatalf("can't happen: constant is not an integer %s", name)
 | |
| 			}
 | |
| 			i64, isInt := exact.Int64Val(value)
 | |
| 			u64, isUint := exact.Uint64Val(value)
 | |
| 			if !isInt && !isUint {
 | |
| 				log.Fatalf("internal error: value of %s is not an integer: %s", name, value.String())
 | |
| 			}
 | |
| 			if !isInt {
 | |
| 				u64 = uint64(i64)
 | |
| 			}
 | |
| 			v := Value{
 | |
| 				name:   name.Name,
 | |
| 				value:  u64,
 | |
| 				signed: info&types.IsUnsigned == 0,
 | |
| 				str:    value.String(),
 | |
| 			}
 | |
| 			f.values = append(f.values, v)
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| // Helpers
 | |
| 
 | |
| // usize returns the number of bits of the smallest unsigned integer
 | |
| // type that will hold n. Used to create the smallest possible slice of
 | |
| // integers to use as indexes into the concatenated strings.
 | |
| func usize(n int) int {
 | |
| 	switch {
 | |
| 	case n < 1<<8:
 | |
| 		return 8
 | |
| 	case n < 1<<16:
 | |
| 		return 16
 | |
| 	default:
 | |
| 		// 2^32 is enough constants for anyone.
 | |
| 		return 32
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // declareIndexAndNameVars declares the index slices and concatenated names
 | |
| // strings representing the runs of values.
 | |
| func (g *Generator) declareIndexAndNameVars(runs [][]Value, typeName string) {
 | |
| 	var indexes, names []string
 | |
| 	for i, run := range runs {
 | |
| 		index, name := g.createIndexAndNameDecl(run, typeName, fmt.Sprintf("_%d", i))
 | |
| 		indexes = append(indexes, index)
 | |
| 		names = append(names, name)
 | |
| 	}
 | |
| 	g.Printf("const (\n")
 | |
| 	for _, name := range names {
 | |
| 		g.Printf("\t%s\n", name)
 | |
| 	}
 | |
| 	g.Printf(")\n\n")
 | |
| 	g.Printf("var (")
 | |
| 	for _, index := range indexes {
 | |
| 		g.Printf("\t%s\n", index)
 | |
| 	}
 | |
| 	g.Printf(")\n\n")
 | |
| }
 | |
| 
 | |
| // declareIndexAndNameVar is the single-run version of declareIndexAndNameVars
 | |
| func (g *Generator) declareIndexAndNameVar(run []Value, typeName string) {
 | |
| 	index, name := g.createIndexAndNameDecl(run, typeName, "")
 | |
| 	g.Printf("const %s\n", name)
 | |
| 	g.Printf("var %s\n", index)
 | |
| }
 | |
| 
 | |
| // createIndexAndNameDecl returns the pair of declarations for the run. The caller will add "const" and "var".
 | |
| func (g *Generator) createIndexAndNameDecl(run []Value, typeName string, suffix string) (string, string) {
 | |
| 	b := new(bytes.Buffer)
 | |
| 	indexes := make([]int, len(run))
 | |
| 	for i := range run {
 | |
| 		b.WriteString(run[i].name)
 | |
| 		indexes[i] = b.Len()
 | |
| 	}
 | |
| 	nameConst := fmt.Sprintf("_%s_name%s = %q", typeName, suffix, b.String())
 | |
| 	nameLen := b.Len()
 | |
| 	b.Reset()
 | |
| 	fmt.Fprintf(b, "_%s_index%s = [...]uint%d{0, ", typeName, suffix, usize(nameLen))
 | |
| 	for i, v := range indexes {
 | |
| 		if i > 0 {
 | |
| 			fmt.Fprintf(b, ", ")
 | |
| 		}
 | |
| 		fmt.Fprintf(b, "%d", v)
 | |
| 	}
 | |
| 	fmt.Fprintf(b, "}")
 | |
| 	return b.String(), nameConst
 | |
| }
 | |
| 
 | |
| // declareNameVars declares the concatenated names string representing all the values in the runs.
 | |
| func (g *Generator) declareNameVars(runs [][]Value, typeName string, suffix string) {
 | |
| 	g.Printf("const _%s_name%s = \"", typeName, suffix)
 | |
| 	for _, run := range runs {
 | |
| 		for i := range run {
 | |
| 			g.Printf("%s", run[i].name)
 | |
| 		}
 | |
| 	}
 | |
| 	g.Printf("\"\n")
 | |
| }
 | |
| 
 | |
| // buildOneRun generates the variables and String method for a single run of contiguous values.
 | |
| func (g *Generator) buildOneRun(runs [][]Value, typeName string) {
 | |
| 	values := runs[0]
 | |
| 	g.Printf("\n")
 | |
| 	g.declareIndexAndNameVar(values, typeName)
 | |
| 	// The generated code is simple enough to write as a Printf format.
 | |
| 	lessThanZero := ""
 | |
| 	if values[0].signed {
 | |
| 		lessThanZero = "i < 0 || "
 | |
| 	}
 | |
| 	if values[0].value == 0 { // Signed or unsigned, 0 is still 0.
 | |
| 		g.Printf(stringOneRun, typeName, usize(len(values)), lessThanZero)
 | |
| 	} else {
 | |
| 		g.Printf(stringOneRunWithOffset, typeName, values[0].String(), usize(len(values)), lessThanZero)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Arguments to format are:
 | |
| //	[1]: type name
 | |
| //	[2]: size of index element (8 for uint8 etc.)
 | |
| //	[3]: less than zero check (for signed types)
 | |
| const stringOneRun = `func (i %[1]s) String() string {
 | |
| 	if %[3]si+1 >= %[1]s(len(_%[1]s_index)) {
 | |
| 		return fmt.Sprintf("%[1]s(%%d)", i)
 | |
| 	}
 | |
| 	return _%[1]s_name[_%[1]s_index[i]:_%[1]s_index[i+1]]
 | |
| }
 | |
| `
 | |
| 
 | |
| // Arguments to format are:
 | |
| //	[1]: type name
 | |
| //	[2]: lowest defined value for type, as a string
 | |
| //	[3]: size of index element (8 for uint8 etc.)
 | |
| //	[4]: less than zero check (for signed types)
 | |
| /*
 | |
|  */
 | |
| const stringOneRunWithOffset = `func (i %[1]s) String() string {
 | |
| 	i -= %[2]s
 | |
| 	if %[4]si+1 >= %[1]s(len(_%[1]s_index)) {
 | |
| 		return fmt.Sprintf("%[1]s(%%d)", i + %[2]s)
 | |
| 	}
 | |
| 	return _%[1]s_name[_%[1]s_index[i] : _%[1]s_index[i+1]]
 | |
| }
 | |
| `
 | |
| 
 | |
| // buildMultipleRuns generates the variables and String method for multiple runs of contiguous values.
 | |
| // For this pattern, a single Printf format won't do.
 | |
| func (g *Generator) buildMultipleRuns(runs [][]Value, typeName string) {
 | |
| 	g.Printf("\n")
 | |
| 	g.declareIndexAndNameVars(runs, typeName)
 | |
| 	g.Printf("func (i %s) String() string {\n", typeName)
 | |
| 	g.Printf("\tswitch {\n")
 | |
| 	for i, values := range runs {
 | |
| 		if len(values) == 1 {
 | |
| 			g.Printf("\tcase i == %s:\n", &values[0])
 | |
| 			g.Printf("\t\treturn _%s_name_%d\n", typeName, i)
 | |
| 			continue
 | |
| 		}
 | |
| 		g.Printf("\tcase %s <= i && i <= %s:\n", &values[0], &values[len(values)-1])
 | |
| 		if values[0].value != 0 {
 | |
| 			g.Printf("\t\ti -= %s\n", &values[0])
 | |
| 		}
 | |
| 		g.Printf("\t\treturn _%s_name_%d[_%s_index_%d[i]:_%s_index_%d[i+1]]\n",
 | |
| 			typeName, i, typeName, i, typeName, i)
 | |
| 	}
 | |
| 	g.Printf("\tdefault:\n")
 | |
| 	g.Printf("\t\treturn fmt.Sprintf(\"%s(%%d)\", i)\n", typeName)
 | |
| 	g.Printf("\t}\n")
 | |
| 	g.Printf("}\n")
 | |
| }
 | |
| 
 | |
| // buildMap handles the case where the space is so sparse a map is a reasonable fallback.
 | |
| // It's a rare situation but has simple code.
 | |
| func (g *Generator) buildMap(runs [][]Value, typeName string) {
 | |
| 	g.Printf("\n")
 | |
| 	g.declareNameVars(runs, typeName, "")
 | |
| 	g.Printf("\nvar _%s_map = map[%s]string{\n", typeName, typeName)
 | |
| 	n := 0
 | |
| 	for _, values := range runs {
 | |
| 		for _, value := range values {
 | |
| 			g.Printf("\t%s: _%s_name[%d:%d],\n", &value, typeName, n, n+len(value.name))
 | |
| 			n += len(value.name)
 | |
| 		}
 | |
| 	}
 | |
| 	g.Printf("}\n\n")
 | |
| 	g.Printf(stringMap, typeName)
 | |
| }
 | |
| 
 | |
| // Argument to format is the type name.
 | |
| const stringMap = `func (i %[1]s) String() string {
 | |
| 	if str, ok := _%[1]s_map[i]; ok {
 | |
| 		return str
 | |
| 	}
 | |
| 	return fmt.Sprintf("%[1]s(%%d)", i)
 | |
| }
 | |
| `
 |