commit 58c57e5d156ad0200cc046b4e354dac0c504e43e Author: alvaroloes Date: Tue Dec 29 13:14:54 2015 +0000 Added a method to the generated code to get the enum value from the string name. Fixed all tests. diff --git a/endtoend_test.go b/endtoend_test.go new file mode 100644 index 0000000..c86f21c --- /dev/null +++ b/endtoend_test.go @@ -0,0 +1,111 @@ +// 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. + +// go command is not available on android + +// +build !android + +package main + +import ( + "fmt" + "go/build" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" +) + +// This file contains a test that compiles and runs each program in testdata +// after generating the string method for its type. The rule is that for testdata/x.go +// we run stringer -type X and then compile and run the program. The resulting +// binary panics if the String method for X is not correct, including for error cases. + +func TestEndToEnd(t *testing.T) { + dir, err := ioutil.TempDir("", "stringer") + if err != nil { + t.Fatal(err) + } +// defer os.RemoveAll(dir) + // Create stringer in temporary directory. + stringer := filepath.Join(dir, "stringer.exe") + err = run("go", "build", "-o", stringer, "enumer.go", "stringer.go") + if err != nil { + t.Fatalf("building stringer: %s", err) + } + // Read the testdata directory. + fd, err := os.Open("testdata") + if err != nil { + t.Fatal(err) + } + defer fd.Close() + names, err := fd.Readdirnames(-1) + if err != nil { + t.Fatalf("Readdirnames: %s", err) + } + // Generate, compile, and run the test programs. + for _, name := range names { + if !strings.HasSuffix(name, ".go") { + t.Errorf("%s is not a Go file", name) + continue + } + if name == "cgo.go" && !build.Default.CgoEnabled { + t.Logf("cgo is no enabled for %s", name) + continue + } + // Names are known to be ASCII and long enough. + typeName := fmt.Sprintf("%c%s", name[0]+'A'-'a', name[1:len(name)-len(".go")]) + stringerCompileAndRun(t, dir, stringer, typeName, name) + } +} + +// stringerCompileAndRun runs stringer for the named file and compiles and +// runs the target binary in directory dir. That binary will panic if the String method is incorrect. +func stringerCompileAndRun(t *testing.T, dir, stringer, typeName, fileName string) { + t.Logf("run: %s %s\n", fileName, typeName) + source := filepath.Join(dir, fileName) + err := copy(source, filepath.Join("testdata", fileName)) + if err != nil { + t.Fatalf("copying file to temporary directory: %s", err) + } + stringSource := filepath.Join(dir, typeName+"_string.go") + // Run stringer in temporary directory. + err = run(stringer, "-type", typeName, "-output", stringSource, source) + if err != nil { + t.Fatal(err) + } + // Run the binary in the temporary directory. + err = run("go", "run", stringSource, source) + if err != nil { + t.Fatal(err) + } +} + +// copy copies the from file to the to file. +func copy(to, from string) error { + toFd, err := os.Create(to) + if err != nil { + return err + } + defer toFd.Close() + fromFd, err := os.Open(from) + if err != nil { + return err + } + defer fromFd.Close() + _, err = io.Copy(toFd, fromFd) + return err +} + +// run runs a single command and returns an error if it does not succeed. +// os/exec should have this function, to be honest. +func run(name string, arg ...string) error { + cmd := exec.Command(name, arg...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} diff --git a/enumer.go b/enumer.go new file mode 100644 index 0000000..11639de --- /dev/null +++ b/enumer.go @@ -0,0 +1,37 @@ +package main +import "fmt" + +// Arguments to format are: +// [1]: type name +const stringValueToNameMap = `func %[1]sString(s string) (%[1]s, error) { + if val, ok := _%[1]sNameToValue_map[s]; ok { + return val, nil + } + return 0, fmt.Errorf("%%s does not belong to %[1]s values", s) +} +` + +func (g *Generator) buildValueToNameMap(runs [][]Value, typeName string, runsThreshold int) { + // At this moment, either "g.declareIndexAndNameVars()" or "g.declareNameVars()" has been called + g.Printf("\nvar _%sNameToValue_map = map[string]%s{\n", typeName, typeName) + thereAreRuns := len(runs) > 1 && len(runs) <= runsThreshold + n := 0 + var runID string + for i, values := range runs { + for _, value := range values { + if thereAreRuns { + runID = "_" + fmt.Sprintf("%d",i) + } else { + runID = "" + } + + g.Printf("\t_%s_name%s[%d:%d]: %s,\n", typeName, runID, n, n+len(value.name), &value) + n += len(value.name) + } + if thereAreRuns { + n = 0 + } + } + g.Printf("}\n\n") + g.Printf(stringValueToNameMap, typeName) +} diff --git a/golden_test.go b/golden_test.go new file mode 100644 index 0000000..c932dc7 --- /dev/null +++ b/golden_test.go @@ -0,0 +1,359 @@ +// 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. + +// This file contains simple golden tests for various examples. +// Besides validating the results when the implementation changes, +// it provides a way to look at the generated code without having +// to execute the print statements in one's head. + +package main + +import ( + "strings" + "testing" +) + +// Golden represents a test case. +type Golden struct { + name string + input string // input; the package clause is provided when running the test. + output string // exected output. +} + +var golden = []Golden{ + {"day", day_in, day_out}, + {"offset", offset_in, offset_out}, + {"gap", gap_in, gap_out}, + {"num", num_in, num_out}, + {"unum", unum_in, unum_out}, + {"prime", prime_in, prime_out}, +} + +// Each example starts with "type XXX [u]int", with a single space separating them. + +// Simple test: enumeration of type int starting at 0. +const day_in = `type Day int +const ( + Monday Day = iota + Tuesday + Wednesday + Thursday + Friday + Saturday + Sunday +) +` + +const day_out = ` +const _Day_name = "MondayTuesdayWednesdayThursdayFridaySaturdaySunday" + +var _Day_index = [...]uint8{0, 6, 13, 22, 30, 36, 44, 50} + +func (i Day) String() string { + if i < 0 || i >= Day(len(_Day_index)-1) { + return fmt.Sprintf("Day(%d)", i) + } + return _Day_name[_Day_index[i]:_Day_index[i+1]] +} + +var _DayNameToValue_map = map[string]Day{ + _Day_name[0:6]: 0, + _Day_name[6:13]: 1, + _Day_name[13:22]: 2, + _Day_name[22:30]: 3, + _Day_name[30:36]: 4, + _Day_name[36:44]: 5, + _Day_name[44:50]: 6, +} + +func DayString(s string) (Day, error) { + if val, ok := _DayNameToValue_map[s]; ok { + return val, nil + } + return 0, fmt.Errorf("%s does not belong to Day values", s) +} +` + +// Enumeration with an offset. +// Also includes a duplicate. +const offset_in = `type Number int +const ( + _ Number = iota + One + Two + Three + AnotherOne = One // Duplicate; note that AnotherOne doesn't appear below. +) +` + +const offset_out = ` +const _Number_name = "OneTwoThree" + +var _Number_index = [...]uint8{0, 3, 6, 11} + +func (i Number) String() string { + i -= 1 + if i < 0 || i >= Number(len(_Number_index)-1) { + return fmt.Sprintf("Number(%d)", i+1) + } + return _Number_name[_Number_index[i]:_Number_index[i+1]] +} + +var _NumberNameToValue_map = map[string]Number{ + _Number_name[0:3]: 1, + _Number_name[3:6]: 2, + _Number_name[6:11]: 3, +} + +func NumberString(s string) (Number, error) { + if val, ok := _NumberNameToValue_map[s]; ok { + return val, nil + } + return 0, fmt.Errorf("%s does not belong to Number values", s) +} +` + +// Gaps and an offset. +const gap_in = `type Gap int +const ( + Two Gap = 2 + Three Gap = 3 + Five Gap = 5 + Six Gap = 6 + Seven Gap = 7 + Eight Gap = 8 + Nine Gap = 9 + Eleven Gap = 11 +) +` + +const gap_out = ` +const ( + _Gap_name_0 = "TwoThree" + _Gap_name_1 = "FiveSixSevenEightNine" + _Gap_name_2 = "Eleven" +) + +var ( + _Gap_index_0 = [...]uint8{0, 3, 8} + _Gap_index_1 = [...]uint8{0, 4, 7, 12, 17, 21} + _Gap_index_2 = [...]uint8{0, 6} +) + +func (i Gap) String() string { + switch { + case 2 <= i && i <= 3: + i -= 2 + return _Gap_name_0[_Gap_index_0[i]:_Gap_index_0[i+1]] + case 5 <= i && i <= 9: + i -= 5 + return _Gap_name_1[_Gap_index_1[i]:_Gap_index_1[i+1]] + case i == 11: + return _Gap_name_2 + default: + return fmt.Sprintf("Gap(%d)", i) + } +} + +var _GapNameToValue_map = map[string]Gap{ + _Gap_name_0[0:3]: 2, + _Gap_name_0[3:8]: 3, + _Gap_name_1[0:4]: 5, + _Gap_name_1[4:7]: 6, + _Gap_name_1[7:12]: 7, + _Gap_name_1[12:17]: 8, + _Gap_name_1[17:21]: 9, + _Gap_name_2[0:6]: 11, +} + +func GapString(s string) (Gap, error) { + if val, ok := _GapNameToValue_map[s]; ok { + return val, nil + } + return 0, fmt.Errorf("%s does not belong to Gap values", s) +} +` + +// Signed integers spanning zero. +const num_in = `type Num int +const ( + m_2 Num = -2 + iota + m_1 + m0 + m1 + m2 +) +` + +const num_out = ` +const _Num_name = "m_2m_1m0m1m2" + +var _Num_index = [...]uint8{0, 3, 6, 8, 10, 12} + +func (i Num) String() string { + i -= -2 + if i < 0 || i >= Num(len(_Num_index)-1) { + return fmt.Sprintf("Num(%d)", i+-2) + } + return _Num_name[_Num_index[i]:_Num_index[i+1]] +} + +var _NumNameToValue_map = map[string]Num{ + _Num_name[0:3]: -2, + _Num_name[3:6]: -1, + _Num_name[6:8]: 0, + _Num_name[8:10]: 1, + _Num_name[10:12]: 2, +} + +func NumString(s string) (Num, error) { + if val, ok := _NumNameToValue_map[s]; ok { + return val, nil + } + return 0, fmt.Errorf("%s does not belong to Num values", s) +} +` + +// Unsigned integers spanning zero. +const unum_in = `type Unum uint +const ( + m_2 Unum = iota + 253 + m_1 +) + +const ( + m0 Unum = iota + m1 + m2 +) +` + +const unum_out = ` +const ( + _Unum_name_0 = "m0m1m2" + _Unum_name_1 = "m_2m_1" +) + +var ( + _Unum_index_0 = [...]uint8{0, 2, 4, 6} + _Unum_index_1 = [...]uint8{0, 3, 6} +) + +func (i Unum) String() string { + switch { + case 0 <= i && i <= 2: + return _Unum_name_0[_Unum_index_0[i]:_Unum_index_0[i+1]] + case 253 <= i && i <= 254: + i -= 253 + return _Unum_name_1[_Unum_index_1[i]:_Unum_index_1[i+1]] + default: + return fmt.Sprintf("Unum(%d)", i) + } +} + +var _UnumNameToValue_map = map[string]Unum{ + _Unum_name_0[0:2]: 0, + _Unum_name_0[2:4]: 1, + _Unum_name_0[4:6]: 2, + _Unum_name_1[0:3]: 253, + _Unum_name_1[3:6]: 254, +} + +func UnumString(s string) (Unum, error) { + if val, ok := _UnumNameToValue_map[s]; ok { + return val, nil + } + return 0, fmt.Errorf("%s does not belong to Unum values", s) +} +` + +// Enough gaps to trigger a map implementation of the method. +// Also includes a duplicate to test that it doesn't cause problems +const prime_in = `type Prime int +const ( + p2 Prime = 2 + p3 Prime = 3 + p5 Prime = 5 + p7 Prime = 7 + p77 Prime = 7 // Duplicate; note that p77 doesn't appear below. + p11 Prime = 11 + p13 Prime = 13 + p17 Prime = 17 + p19 Prime = 19 + p23 Prime = 23 + p29 Prime = 29 + p37 Prime = 31 + p41 Prime = 41 + p43 Prime = 43 +) +` + +const prime_out = ` +const _Prime_name = "p2p3p5p7p11p13p17p19p23p29p37p41p43" + +var _Prime_map = map[Prime]string{ + 2: _Prime_name[0:2], + 3: _Prime_name[2:4], + 5: _Prime_name[4:6], + 7: _Prime_name[6:8], + 11: _Prime_name[8:11], + 13: _Prime_name[11:14], + 17: _Prime_name[14:17], + 19: _Prime_name[17:20], + 23: _Prime_name[20:23], + 29: _Prime_name[23:26], + 31: _Prime_name[26:29], + 41: _Prime_name[29:32], + 43: _Prime_name[32:35], +} + +func (i Prime) String() string { + if str, ok := _Prime_map[i]; ok { + return str + } + return fmt.Sprintf("Prime(%d)", i) +} + +var _PrimeNameToValue_map = map[string]Prime{ + _Prime_name[0:2]: 2, + _Prime_name[2:4]: 3, + _Prime_name[4:6]: 5, + _Prime_name[6:8]: 7, + _Prime_name[8:11]: 11, + _Prime_name[11:14]: 13, + _Prime_name[14:17]: 17, + _Prime_name[17:20]: 19, + _Prime_name[20:23]: 23, + _Prime_name[23:26]: 29, + _Prime_name[26:29]: 31, + _Prime_name[29:32]: 41, + _Prime_name[32:35]: 43, +} + +func PrimeString(s string) (Prime, error) { + if val, ok := _PrimeNameToValue_map[s]; ok { + return val, nil + } + return 0, fmt.Errorf("%s does not belong to Prime values", s) +} +` + +func TestGolden(t *testing.T) { + for _, test := range golden { + var g Generator + input := "package test\n" + test.input + file := test.name + ".go" + g.parsePackage(".", []string{file}, input) + // Extract the name and type of the constant from the first line. + tokens := strings.SplitN(test.input, " ", 3) + if len(tokens) != 3 { + t.Fatalf("%s: need type declaration on first line", test.name) + } + g.generate(tokens[1]) + got := string(g.format()) + if got != test.output { + t.Errorf("%s: got\n====\n%s====\nexpected\n====%s", test.name, got, test.output) + } + } +} diff --git a/stringer.go b/stringer.go new file mode 100644 index 0000000..81efa59 --- /dev/null +++ b/stringer.go @@ -0,0 +1,640 @@ +// 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 ( + "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/_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() +} + +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("// Code 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) + } + // ENUMER: This is the only addition over the original stringer code. Everything else is in enumer.go + g.buildValueToNameMap(runs, typeName, 10) +} + +// 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]s(len(_%[1]s_index)-1) { + 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]s(len(_%[1]s_index)-1) { + 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) +} +` diff --git a/testdata/cgo.go b/testdata/cgo.go new file mode 100644 index 0000000..ef38f95 --- /dev/null +++ b/testdata/cgo.go @@ -0,0 +1,32 @@ +// 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. + +// Import "C" shouldn't be imported. + +package main + +/* +#define HELLO 1 +*/ +import "C" + +import "fmt" + +type Cgo uint32 + +const ( + // MustScanSubDirs indicates that events were coalesced hierarchically. + MustScanSubDirs Cgo = 1 << iota +) + +func main() { + _ = C.HELLO + ck(MustScanSubDirs, "MustScanSubDirs") +} + +func ck(day Cgo, str string) { + if fmt.Sprint(day) != str { + panic("cgo.go: " + str) + } +} diff --git a/testdata/day.go b/testdata/day.go new file mode 100644 index 0000000..35fa8dc --- /dev/null +++ b/testdata/day.go @@ -0,0 +1,39 @@ +// 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. + +// Simple test: enumeration of type int starting at 0. + +package main + +import "fmt" + +type Day int + +const ( + Monday Day = iota + Tuesday + Wednesday + Thursday + Friday + Saturday + Sunday +) + +func main() { + ck(Monday, "Monday") + ck(Tuesday, "Tuesday") + ck(Wednesday, "Wednesday") + ck(Thursday, "Thursday") + ck(Friday, "Friday") + ck(Saturday, "Saturday") + ck(Sunday, "Sunday") + ck(-127, "Day(-127)") + ck(127, "Day(127)") +} + +func ck(day Day, str string) { + if fmt.Sprint(day) != str { + panic("day.go: " + str) + } +} diff --git a/testdata/gap.go b/testdata/gap.go new file mode 100644 index 0000000..bc8a90c --- /dev/null +++ b/testdata/gap.go @@ -0,0 +1,44 @@ +// 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. + +// Gaps and an offset. + +package main + +import "fmt" + +type Gap int + +const ( + Two Gap = 2 + Three Gap = 3 + Five Gap = 5 + Six Gap = 6 + Seven Gap = 7 + Eight Gap = 8 + Nine Gap = 9 + Eleven Gap = 11 +) + +func main() { + ck(0, "Gap(0)") + ck(1, "Gap(1)") + ck(Two, "Two") + ck(Three, "Three") + ck(4, "Gap(4)") + ck(Five, "Five") + ck(Six, "Six") + ck(Seven, "Seven") + ck(Eight, "Eight") + ck(Nine, "Nine") + ck(10, "Gap(10)") + ck(Eleven, "Eleven") + ck(12, "Gap(12)") +} + +func ck(gap Gap, str string) { + if fmt.Sprint(gap) != str { + panic("gap.go: " + str) + } +} diff --git a/testdata/num.go b/testdata/num.go new file mode 100644 index 0000000..0d5ab10 --- /dev/null +++ b/testdata/num.go @@ -0,0 +1,35 @@ +// 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. + +// Signed integers spanning zero. + +package main + +import "fmt" + +type Num int + +const ( + m_2 Num = -2 + iota + m_1 + m0 + m1 + m2 +) + +func main() { + ck(-3, "Num(-3)") + ck(m_2, "m_2") + ck(m_1, "m_1") + ck(m0, "m0") + ck(m1, "m1") + ck(m2, "m2") + ck(3, "Num(3)") +} + +func ck(num Num, str string) { + if fmt.Sprint(num) != str { + panic("num.go: " + str) + } +} diff --git a/testdata/number.go b/testdata/number.go new file mode 100644 index 0000000..7f1c824 --- /dev/null +++ b/testdata/number.go @@ -0,0 +1,34 @@ +// 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. + +// Enumeration with an offset. +// Also includes a duplicate. + +package main + +import "fmt" + +type Number int + +const ( + _ Number = iota + One + Two + Three + AnotherOne = One // Duplicate; note that AnotherOne doesn't appear below. +) + +func main() { + ck(One, "One") + ck(Two, "Two") + ck(Three, "Three") + ck(AnotherOne, "One") + ck(127, "Number(127)") +} + +func ck(num Number, str string) { + if fmt.Sprint(num) != str { + panic("number.go: " + str) + } +} diff --git a/testdata/prime.go b/testdata/prime.go new file mode 100644 index 0000000..f551a1a --- /dev/null +++ b/testdata/prime.go @@ -0,0 +1,56 @@ +// 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. + +// Enough gaps to trigger a map implementation of the method. +// Also includes a duplicate to test that it doesn't cause problems + +package main + +import "fmt" + +type Prime int + +const ( + p2 Prime = 2 + p3 Prime = 3 + p5 Prime = 5 + p7 Prime = 7 + p77 Prime = 7 // Duplicate; note that p77 doesn't appear below. + p11 Prime = 11 + p13 Prime = 13 + p17 Prime = 17 + p19 Prime = 19 + p23 Prime = 23 + p29 Prime = 29 + p37 Prime = 31 + p41 Prime = 41 + p43 Prime = 43 +) + +func main() { + ck(0, "Prime(0)") + ck(1, "Prime(1)") + ck(p2, "p2") + ck(p3, "p3") + ck(4, "Prime(4)") + ck(p5, "p5") + ck(p7, "p7") + ck(p77, "p7") + ck(p11, "p11") + ck(p13, "p13") + ck(p17, "p17") + ck(p19, "p19") + ck(p23, "p23") + ck(p29, "p29") + ck(p37, "p37") + ck(p41, "p41") + ck(p43, "p43") + ck(44, "Prime(44)") +} + +func ck(prime Prime, str string) { + if fmt.Sprint(prime) != str { + panic("prime.go: " + str) + } +} diff --git a/testdata/unum.go b/testdata/unum.go new file mode 100644 index 0000000..2f8508f --- /dev/null +++ b/testdata/unum.go @@ -0,0 +1,38 @@ +// 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. + +// Unsigned integers spanning zero. + +package main + +import "fmt" + +type Unum uint8 + +const ( + m_2 Unum = iota + 253 + m_1 +) + +const ( + m0 Unum = iota + m1 + m2 +) + +func main() { + ck(^Unum(0)-3, "Unum(252)") + ck(m_2, "m_2") + ck(m_1, "m_1") + ck(m0, "m0") + ck(m1, "m1") + ck(m2, "m2") + ck(3, "Unum(3)") +} + +func ck(unum Unum, str string) { + if fmt.Sprint(unum) != str { + panic("unum.go: " + str) + } +} diff --git a/testdata/unum2.go b/testdata/unum2.go new file mode 100644 index 0000000..edbbedf --- /dev/null +++ b/testdata/unum2.go @@ -0,0 +1,31 @@ +// 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. + +// Unsigned integers - check maximum size + +package main + +import "fmt" + +type Unum2 uint8 + +const ( + Zero Unum2 = iota + One + Two +) + +func main() { + ck(Zero, "Zero") + ck(One, "One") + ck(Two, "Two") + ck(3, "Unum2(3)") + ck(255, "Unum2(255)") +} + +func ck(unum Unum2, str string) { + if fmt.Sprint(unum) != str { + panic("unum.go: " + str) + } +} diff --git a/util_test.go b/util_test.go new file mode 100644 index 0000000..1aeba6e --- /dev/null +++ b/util_test.go @@ -0,0 +1,77 @@ +// 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. + +// This file contains tests for some of the internal functions. + +package main + +import ( + "fmt" + "testing" +) + +// Helpers to save typing in the test cases. +type u []uint64 +type uu [][]uint64 + +type SplitTest struct { + input u + output uu + signed bool +} + +var ( + m2 = uint64(2) + m1 = uint64(1) + m0 = uint64(0) + m_1 = ^uint64(0) // -1 when signed. + m_2 = ^uint64(0) - 1 // -2 when signed. +) + +var splitTests = []SplitTest{ + // No need for a test for the empty case; that's picked off before splitIntoRuns. + // Single value. + {u{1}, uu{u{1}}, false}, + // Out of order. + {u{3, 2, 1}, uu{u{1, 2, 3}}, true}, + // Out of order. + {u{3, 2, 1}, uu{u{1, 2, 3}}, false}, + // A gap at the beginning. + {u{1, 33, 32, 31}, uu{u{1}, u{31, 32, 33}}, true}, + // A gap in the middle, in mixed order. + {u{33, 7, 32, 31, 9, 8}, uu{u{7, 8, 9}, u{31, 32, 33}}, true}, + // Gaps throughout + {u{33, 44, 1, 32, 45, 31}, uu{u{1}, u{31, 32, 33}, u{44, 45}}, true}, + // Unsigned values spanning 0. + {u{m1, m0, m_1, m2, m_2}, uu{u{m0, m1, m2}, u{m_2, m_1}}, false}, + // Signed values spanning 0 + {u{m1, m0, m_1, m2, m_2}, uu{u{m_2, m_1, m0, m1, m2}}, true}, +} + +func TestSplitIntoRuns(t *testing.T) { +Outer: + for n, test := range splitTests { + values := make([]Value, len(test.input)) + for i, v := range test.input { + values[i] = Value{"", v, test.signed, fmt.Sprint(v)} + } + runs := splitIntoRuns(values) + if len(runs) != len(test.output) { + t.Errorf("#%d: %v: got %d runs; expected %d", n, test.input, len(runs), len(test.output)) + continue + } + for i, run := range runs { + if len(run) != len(test.output[i]) { + t.Errorf("#%d: got %v; expected %v", n, runs, test.output) + continue Outer + } + for j, v := range run { + if v.value != test.output[i][j] { + t.Errorf("#%d: got %v; expected %v", n, runs, test.output) + continue Outer + } + } + } + } +}