Added a method to the generated code to get the enum value from the string name. Fixed all tests.

This commit is contained in:
alvaroloes 2015-12-29 13:14:54 +00:00
commit 58c57e5d15
13 changed files with 1533 additions and 0 deletions

111
endtoend_test.go Normal file
View File

@ -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()
}

37
enumer.go Normal file
View File

@ -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)
}

359
golden_test.go Normal file
View File

@ -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)
}
}
}

640
stringer.go Normal file
View File

@ -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/<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()
}
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)
}
`

32
testdata/cgo.go vendored Normal file
View File

@ -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)
}
}

39
testdata/day.go vendored Normal file
View File

@ -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)
}
}

44
testdata/gap.go vendored Normal file
View File

@ -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)
}
}

35
testdata/num.go vendored Normal file
View File

@ -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)
}
}

34
testdata/number.go vendored Normal file
View File

@ -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)
}
}

56
testdata/prime.go vendored Normal file
View File

@ -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)
}
}

38
testdata/unum.go vendored Normal file
View File

@ -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)
}
}

31
testdata/unum2.go vendored Normal file
View File

@ -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)
}
}

77
util_test.go Normal file
View File

@ -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
}
}
}
}
}