1 | // Copyright 2014 The Go Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style |
3 | // license that can be found in the LICENSE file. |
4 | |
5 | // Stringer is a tool to automate the creation of methods that satisfy the fmt.Stringer |
6 | // interface. Given the name of a (signed or unsigned) integer type T that has constants |
7 | // defined, stringer will create a new self-contained Go source file implementing |
8 | // |
9 | // func (t T) String() string |
10 | // |
11 | // The file is created in the same package and directory as the package that defines T. |
12 | // It has helpful defaults designed for use with go generate. |
13 | // |
14 | // Stringer works best with constants that are consecutive values such as created using iota, |
15 | // but creates good code regardless. In the future it might also provide custom support for |
16 | // constant sets that are bit patterns. |
17 | // |
18 | // For example, given this snippet, |
19 | // |
20 | // package painkiller |
21 | // |
22 | // type Pill int |
23 | // |
24 | // const ( |
25 | // Placebo Pill = iota |
26 | // Aspirin |
27 | // Ibuprofen |
28 | // Paracetamol |
29 | // Acetaminophen = Paracetamol |
30 | // ) |
31 | // |
32 | // running this command |
33 | // |
34 | // stringer -type=Pill |
35 | // |
36 | // in the same directory will create the file pill_string.go, in package painkiller, |
37 | // containing a definition of |
38 | // |
39 | // func (Pill) String() string |
40 | // |
41 | // That method will translate the value of a Pill constant to the string representation |
42 | // of the respective constant name, so that the call fmt.Print(painkiller.Aspirin) will |
43 | // print the string "Aspirin". |
44 | // |
45 | // Typically this process would be run using go generate, like this: |
46 | // |
47 | // //go:generate stringer -type=Pill |
48 | // |
49 | // If multiple constants have the same value, the lexically first matching name will |
50 | // be used (in the example, Acetaminophen will print as "Paracetamol"). |
51 | // |
52 | // With no arguments, it processes the package in the current directory. |
53 | // Otherwise, the arguments must name a single directory holding a Go package |
54 | // or a set of Go source files that represent a single Go package. |
55 | // |
56 | // The -type flag accepts a comma-separated list of types so a single run can |
57 | // generate methods for multiple types. The default output file is t_string.go, |
58 | // where t is the lower-cased name of the first type listed. It can be overridden |
59 | // with the -output flag. |
60 | // |
61 | // The -linecomment flag tells stringer to generate the text of any line comment, trimmed |
62 | // of leading spaces, instead of the constant name. For instance, if the constants above had a |
63 | // Pill prefix, one could write |
64 | // |
65 | // PillAspirin // Aspirin |
66 | // |
67 | // to suppress it in the output. |
68 | package main // import "golang.org/x/tools/cmd/stringer" |
69 | |
70 | import ( |
71 | "bytes" |
72 | "flag" |
73 | "fmt" |
74 | "go/ast" |
75 | "go/constant" |
76 | "go/format" |
77 | "go/token" |
78 | "go/types" |
79 | "log" |
80 | "os" |
81 | "path/filepath" |
82 | "sort" |
83 | "strings" |
84 | |
85 | "golang.org/x/tools/go/packages" |
86 | ) |
87 | |
88 | var ( |
89 | typeNames = flag.String("type", "", "comma-separated list of type names; must be set") |
90 | output = flag.String("output", "", "output file name; default srcdir/<type>_string.go") |
91 | trimprefix = flag.String("trimprefix", "", "trim the `prefix` from the generated constant names") |
92 | linecomment = flag.Bool("linecomment", false, "use line comment text as printed text when present") |
93 | buildTags = flag.String("tags", "", "comma-separated list of build tags to apply") |
94 | ) |
95 | |
96 | // Usage is a replacement usage function for the flags package. |
97 | func Usage() { |
98 | fmt.Fprintf(os.Stderr, "Usage of stringer:\n") |
99 | fmt.Fprintf(os.Stderr, "\tstringer [flags] -type T [directory]\n") |
100 | fmt.Fprintf(os.Stderr, "\tstringer [flags] -type T files... # Must be a single package\n") |
101 | fmt.Fprintf(os.Stderr, "For more information, see:\n") |
102 | fmt.Fprintf(os.Stderr, "\thttps://pkg.go.dev/golang.org/x/tools/cmd/stringer\n") |
103 | fmt.Fprintf(os.Stderr, "Flags:\n") |
104 | flag.PrintDefaults() |
105 | } |
106 | |
107 | func main() { |
108 | log.SetFlags(0) |
109 | log.SetPrefix("stringer: ") |
110 | flag.Usage = Usage |
111 | flag.Parse() |
112 | if len(*typeNames) == 0 { |
113 | flag.Usage() |
114 | os.Exit(2) |
115 | } |
116 | types := strings.Split(*typeNames, ",") |
117 | var tags []string |
118 | if len(*buildTags) > 0 { |
119 | tags = strings.Split(*buildTags, ",") |
120 | } |
121 | |
122 | // We accept either one directory or a list of files. Which do we have? |
123 | args := flag.Args() |
124 | if len(args) == 0 { |
125 | // Default: process whole package in current directory. |
126 | args = []string{"."} |
127 | } |
128 | |
129 | // Parse the package once. |
130 | var dir string |
131 | g := Generator{ |
132 | trimPrefix: *trimprefix, |
133 | lineComment: *linecomment, |
134 | } |
135 | // TODO(suzmue): accept other patterns for packages (directories, list of files, import paths, etc). |
136 | if len(args) == 1 && isDirectory(args[0]) { |
137 | dir = args[0] |
138 | } else { |
139 | if len(tags) != 0 { |
140 | log.Fatal("-tags option applies only to directories, not when files are specified") |
141 | } |
142 | dir = filepath.Dir(args[0]) |
143 | } |
144 | |
145 | g.parsePackage(args, tags) |
146 | |
147 | // Print the header and package clause. |
148 | g.Printf("// Code generated by \"stringer %s\"; DO NOT EDIT.\n", strings.Join(os.Args[1:], " ")) |
149 | g.Printf("\n") |
150 | g.Printf("package %s", g.pkg.name) |
151 | g.Printf("\n") |
152 | g.Printf("import \"strconv\"\n") // Used by all methods. |
153 | |
154 | // Run generate for each type. |
155 | for _, typeName := range types { |
156 | g.generate(typeName) |
157 | } |
158 | |
159 | // Format the output. |
160 | src := g.format() |
161 | |
162 | // Write to file. |
163 | outputName := *output |
164 | if outputName == "" { |
165 | baseName := fmt.Sprintf("%s_string.go", types[0]) |
166 | outputName = filepath.Join(dir, strings.ToLower(baseName)) |
167 | } |
168 | err := os.WriteFile(outputName, src, 0644) |
169 | if err != nil { |
170 | log.Fatalf("writing output: %s", err) |
171 | } |
172 | } |
173 | |
174 | // isDirectory reports whether the named file is a directory. |
175 | func isDirectory(name string) bool { |
176 | info, err := os.Stat(name) |
177 | if err != nil { |
178 | log.Fatal(err) |
179 | } |
180 | return info.IsDir() |
181 | } |
182 | |
183 | // Generator holds the state of the analysis. Primarily used to buffer |
184 | // the output for format.Source. |
185 | type Generator struct { |
186 | buf bytes.Buffer // Accumulated output. |
187 | pkg *Package // Package we are scanning. |
188 | |
189 | trimPrefix string |
190 | lineComment bool |
191 | } |
192 | |
193 | func (g *Generator) Printf(format string, args ...interface{}) { |
194 | fmt.Fprintf(&g.buf, format, args...) |
195 | } |
196 | |
197 | // File holds a single parsed file and associated data. |
198 | type File struct { |
199 | pkg *Package // Package to which this file belongs. |
200 | file *ast.File // Parsed AST. |
201 | // These fields are reset for each type being generated. |
202 | typeName string // Name of the constant type. |
203 | values []Value // Accumulator for constant values of that type. |
204 | |
205 | trimPrefix string |
206 | lineComment bool |
207 | } |
208 | |
209 | type Package struct { |
210 | name string |
211 | defs map[*ast.Ident]types.Object |
212 | files []*File |
213 | } |
214 | |
215 | // parsePackage analyzes the single package constructed from the patterns and tags. |
216 | // parsePackage exits if there is an error. |
217 | func (g *Generator) parsePackage(patterns []string, tags []string) { |
218 | cfg := &packages.Config{ |
219 | Mode: packages.NeedName | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedSyntax, |
220 | // TODO: Need to think about constants in test files. Maybe write type_string_test.go |
221 | // in a separate pass? For later. |
222 | Tests: false, |
223 | BuildFlags: []string{fmt.Sprintf("-tags=%s", strings.Join(tags, " "))}, |
224 | } |
225 | pkgs, err := packages.Load(cfg, patterns...) |
226 | if err != nil { |
227 | log.Fatal(err) |
228 | } |
229 | if len(pkgs) != 1 { |
230 | log.Fatalf("error: %d packages found", len(pkgs)) |
231 | } |
232 | g.addPackage(pkgs[0]) |
233 | } |
234 | |
235 | // addPackage adds a type checked Package and its syntax files to the generator. |
236 | func (g *Generator) addPackage(pkg *packages.Package) { |
237 | g.pkg = &Package{ |
238 | name: pkg.Name, |
239 | defs: pkg.TypesInfo.Defs, |
240 | files: make([]*File, len(pkg.Syntax)), |
241 | } |
242 | |
243 | for i, file := range pkg.Syntax { |
244 | g.pkg.files[i] = &File{ |
245 | file: file, |
246 | pkg: g.pkg, |
247 | trimPrefix: g.trimPrefix, |
248 | lineComment: g.lineComment, |
249 | } |
250 | } |
251 | } |
252 | |
253 | // generate produces the String method for the named type. |
254 | func (g *Generator) generate(typeName string) { |
255 | values := make([]Value, 0, 100) |
256 | for _, file := range g.pkg.files { |
257 | // Set the state for this run of the walker. |
258 | file.typeName = typeName |
259 | file.values = nil |
260 | if file.file != nil { |
261 | ast.Inspect(file.file, file.genDecl) |
262 | values = append(values, file.values...) |
263 | } |
264 | } |
265 | |
266 | if len(values) == 0 { |
267 | log.Fatalf("no values defined for type %s", typeName) |
268 | } |
269 | // Generate code that will fail if the constants change value. |
270 | g.Printf("func _() {\n") |
271 | g.Printf("\t// An \"invalid array index\" compiler error signifies that the constant values have changed.\n") |
272 | g.Printf("\t// Re-run the stringer command to generate them again.\n") |
273 | g.Printf("\tvar x [1]struct{}\n") |
274 | for _, v := range values { |
275 | g.Printf("\t_ = x[%s - %s]\n", v.originalName, v.str) |
276 | } |
277 | g.Printf("}\n") |
278 | runs := splitIntoRuns(values) |
279 | // The decision of which pattern to use depends on the number of |
280 | // runs in the numbers. If there's only one, it's easy. For more than |
281 | // one, there's a tradeoff between complexity and size of the data |
282 | // and code vs. the simplicity of a map. A map takes more space, |
283 | // but so does the code. The decision here (crossover at 10) is |
284 | // arbitrary, but considers that for large numbers of runs the cost |
285 | // of the linear scan in the switch might become important, and |
286 | // rather than use yet another algorithm such as binary search, |
287 | // we punt and use a map. In any case, the likelihood of a map |
288 | // being necessary for any realistic example other than bitmasks |
289 | // is very low. And bitmasks probably deserve their own analysis, |
290 | // to be done some other day. |
291 | switch { |
292 | case len(runs) == 1: |
293 | g.buildOneRun(runs, typeName) |
294 | case len(runs) <= 10: |
295 | g.buildMultipleRuns(runs, typeName) |
296 | default: |
297 | g.buildMap(runs, typeName) |
298 | } |
299 | } |
300 | |
301 | // splitIntoRuns breaks the values into runs of contiguous sequences. |
302 | // For example, given 1,2,3,5,6,7 it returns {1,2,3},{5,6,7}. |
303 | // The input slice is known to be non-empty. |
304 | func splitIntoRuns(values []Value) [][]Value { |
305 | // We use stable sort so the lexically first name is chosen for equal elements. |
306 | sort.Stable(byValue(values)) |
307 | // Remove duplicates. Stable sort has put the one we want to print first, |
308 | // so use that one. The String method won't care about which named constant |
309 | // was the argument, so the first name for the given value is the only one to keep. |
310 | // We need to do this because identical values would cause the switch or map |
311 | // to fail to compile. |
312 | j := 1 |
313 | for i := 1; i < len(values); i++ { |
314 | if values[i].value != values[i-1].value { |
315 | values[j] = values[i] |
316 | j++ |
317 | } |
318 | } |
319 | values = values[:j] |
320 | runs := make([][]Value, 0, 10) |
321 | for len(values) > 0 { |
322 | // One contiguous sequence per outer loop. |
323 | i := 1 |
324 | for i < len(values) && values[i].value == values[i-1].value+1 { |
325 | i++ |
326 | } |
327 | runs = append(runs, values[:i]) |
328 | values = values[i:] |
329 | } |
330 | return runs |
331 | } |
332 | |
333 | // format returns the gofmt-ed contents of the Generator's buffer. |
334 | func (g *Generator) format() []byte { |
335 | src, err := format.Source(g.buf.Bytes()) |
336 | if err != nil { |
337 | // Should never happen, but can arise when developing this code. |
338 | // The user can compile the output to see the error. |
339 | log.Printf("warning: internal error: invalid Go generated: %s", err) |
340 | log.Printf("warning: compile the package to analyze the error") |
341 | return g.buf.Bytes() |
342 | } |
343 | return src |
344 | } |
345 | |
346 | // Value represents a declared constant. |
347 | type Value struct { |
348 | originalName string // The name of the constant. |
349 | name string // The name with trimmed prefix. |
350 | // The value is stored as a bit pattern alone. The boolean tells us |
351 | // whether to interpret it as an int64 or a uint64; the only place |
352 | // this matters is when sorting. |
353 | // Much of the time the str field is all we need; it is printed |
354 | // by Value.String. |
355 | value uint64 // Will be converted to int64 when needed. |
356 | signed bool // Whether the constant is a signed type. |
357 | str string // The string representation given by the "go/constant" package. |
358 | } |
359 | |
360 | func (v *Value) String() string { |
361 | return v.str |
362 | } |
363 | |
364 | // byValue lets us sort the constants into increasing order. |
365 | // We take care in the Less method to sort in signed or unsigned order, |
366 | // as appropriate. |
367 | type byValue []Value |
368 | |
369 | func (b byValue) Len() int { return len(b) } |
370 | func (b byValue) Swap(i, j int) { b[i], b[j] = b[j], b[i] } |
371 | func (b byValue) Less(i, j int) bool { |
372 | if b[i].signed { |
373 | return int64(b[i].value) < int64(b[j].value) |
374 | } |
375 | return b[i].value < b[j].value |
376 | } |
377 | |
378 | // genDecl processes one declaration clause. |
379 | func (f *File) genDecl(node ast.Node) bool { |
380 | decl, ok := node.(*ast.GenDecl) |
381 | if !ok || decl.Tok != token.CONST { |
382 | // We only care about const declarations. |
383 | return true |
384 | } |
385 | // The name of the type of the constants we are declaring. |
386 | // Can change if this is a multi-element declaration. |
387 | typ := "" |
388 | // Loop over the elements of the declaration. Each element is a ValueSpec: |
389 | // a list of names possibly followed by a type, possibly followed by values. |
390 | // If the type and value are both missing, we carry down the type (and value, |
391 | // but the "go/types" package takes care of that). |
392 | for _, spec := range decl.Specs { |
393 | vspec := spec.(*ast.ValueSpec) // Guaranteed to succeed as this is CONST. |
394 | if vspec.Type == nil && len(vspec.Values) > 0 { |
395 | // "X = 1". With no type but a value. If the constant is untyped, |
396 | // skip this vspec and reset the remembered type. |
397 | typ = "" |
398 | |
399 | // If this is a simple type conversion, remember the type. |
400 | // We don't mind if this is actually a call; a qualified call won't |
401 | // be matched (that will be SelectorExpr, not Ident), and only unusual |
402 | // situations will result in a function call that appears to be |
403 | // a type conversion. |
404 | ce, ok := vspec.Values[0].(*ast.CallExpr) |
405 | if !ok { |
406 | continue |
407 | } |
408 | id, ok := ce.Fun.(*ast.Ident) |
409 | if !ok { |
410 | continue |
411 | } |
412 | typ = id.Name |
413 | } |
414 | if vspec.Type != nil { |
415 | // "X T". We have a type. Remember it. |
416 | ident, ok := vspec.Type.(*ast.Ident) |
417 | if !ok { |
418 | continue |
419 | } |
420 | typ = ident.Name |
421 | } |
422 | if typ != f.typeName { |
423 | // This is not the type we're looking for. |
424 | continue |
425 | } |
426 | // We now have a list of names (from one line of source code) all being |
427 | // declared with the desired type. |
428 | // Grab their names and actual values and store them in f.values. |
429 | for _, name := range vspec.Names { |
430 | if name.Name == "_" { |
431 | continue |
432 | } |
433 | // This dance lets the type checker find the values for us. It's a |
434 | // bit tricky: look up the object declared by the name, find its |
435 | // types.Const, and extract its value. |
436 | obj, ok := f.pkg.defs[name] |
437 | if !ok { |
438 | log.Fatalf("no value for constant %s", name) |
439 | } |
440 | info := obj.Type().Underlying().(*types.Basic).Info() |
441 | if info&types.IsInteger == 0 { |
442 | log.Fatalf("can't handle non-integer constant type %s", typ) |
443 | } |
444 | value := obj.(*types.Const).Val() // Guaranteed to succeed as this is CONST. |
445 | if value.Kind() != constant.Int { |
446 | log.Fatalf("can't happen: constant is not an integer %s", name) |
447 | } |
448 | i64, isInt := constant.Int64Val(value) |
449 | u64, isUint := constant.Uint64Val(value) |
450 | if !isInt && !isUint { |
451 | log.Fatalf("internal error: value of %s is not an integer: %s", name, value.String()) |
452 | } |
453 | if !isInt { |
454 | u64 = uint64(i64) |
455 | } |
456 | v := Value{ |
457 | originalName: name.Name, |
458 | value: u64, |
459 | signed: info&types.IsUnsigned == 0, |
460 | str: value.String(), |
461 | } |
462 | if c := vspec.Comment; f.lineComment && c != nil && len(c.List) == 1 { |
463 | v.name = strings.TrimSpace(c.Text()) |
464 | } else { |
465 | v.name = strings.TrimPrefix(v.originalName, f.trimPrefix) |
466 | } |
467 | f.values = append(f.values, v) |
468 | } |
469 | } |
470 | return false |
471 | } |
472 | |
473 | // Helpers |
474 | |
475 | // usize returns the number of bits of the smallest unsigned integer |
476 | // type that will hold n. Used to create the smallest possible slice of |
477 | // integers to use as indexes into the concatenated strings. |
478 | func usize(n int) int { |
479 | switch { |
480 | case n < 1<<8: |
481 | return 8 |
482 | case n < 1<<16: |
483 | return 16 |
484 | default: |
485 | // 2^32 is enough constants for anyone. |
486 | return 32 |
487 | } |
488 | } |
489 | |
490 | // declareIndexAndNameVars declares the index slices and concatenated names |
491 | // strings representing the runs of values. |
492 | func (g *Generator) declareIndexAndNameVars(runs [][]Value, typeName string) { |
493 | var indexes, names []string |
494 | for i, run := range runs { |
495 | index, name := g.createIndexAndNameDecl(run, typeName, fmt.Sprintf("_%d", i)) |
496 | if len(run) != 1 { |
497 | indexes = append(indexes, index) |
498 | } |
499 | names = append(names, name) |
500 | } |
501 | g.Printf("const (\n") |
502 | for _, name := range names { |
503 | g.Printf("\t%s\n", name) |
504 | } |
505 | g.Printf(")\n\n") |
506 | |
507 | if len(indexes) > 0 { |
508 | g.Printf("var (") |
509 | for _, index := range indexes { |
510 | g.Printf("\t%s\n", index) |
511 | } |
512 | g.Printf(")\n\n") |
513 | } |
514 | } |
515 | |
516 | // declareIndexAndNameVar is the single-run version of declareIndexAndNameVars |
517 | func (g *Generator) declareIndexAndNameVar(run []Value, typeName string) { |
518 | index, name := g.createIndexAndNameDecl(run, typeName, "") |
519 | g.Printf("const %s\n", name) |
520 | g.Printf("var %s\n", index) |
521 | } |
522 | |
523 | // createIndexAndNameDecl returns the pair of declarations for the run. The caller will add "const" and "var". |
524 | func (g *Generator) createIndexAndNameDecl(run []Value, typeName string, suffix string) (string, string) { |
525 | b := new(bytes.Buffer) |
526 | indexes := make([]int, len(run)) |
527 | for i := range run { |
528 | b.WriteString(run[i].name) |
529 | indexes[i] = b.Len() |
530 | } |
531 | nameConst := fmt.Sprintf("_%s_name%s = %q", typeName, suffix, b.String()) |
532 | nameLen := b.Len() |
533 | b.Reset() |
534 | fmt.Fprintf(b, "_%s_index%s = [...]uint%d{0, ", typeName, suffix, usize(nameLen)) |
535 | for i, v := range indexes { |
536 | if i > 0 { |
537 | fmt.Fprintf(b, ", ") |
538 | } |
539 | fmt.Fprintf(b, "%d", v) |
540 | } |
541 | fmt.Fprintf(b, "}") |
542 | return b.String(), nameConst |
543 | } |
544 | |
545 | // declareNameVars declares the concatenated names string representing all the values in the runs. |
546 | func (g *Generator) declareNameVars(runs [][]Value, typeName string, suffix string) { |
547 | g.Printf("const _%s_name%s = \"", typeName, suffix) |
548 | for _, run := range runs { |
549 | for i := range run { |
550 | g.Printf("%s", run[i].name) |
551 | } |
552 | } |
553 | g.Printf("\"\n") |
554 | } |
555 | |
556 | // buildOneRun generates the variables and String method for a single run of contiguous values. |
557 | func (g *Generator) buildOneRun(runs [][]Value, typeName string) { |
558 | values := runs[0] |
559 | g.Printf("\n") |
560 | g.declareIndexAndNameVar(values, typeName) |
561 | // The generated code is simple enough to write as a Printf format. |
562 | lessThanZero := "" |
563 | if values[0].signed { |
564 | lessThanZero = "i < 0 || " |
565 | } |
566 | if values[0].value == 0 { // Signed or unsigned, 0 is still 0. |
567 | g.Printf(stringOneRun, typeName, usize(len(values)), lessThanZero) |
568 | } else { |
569 | g.Printf(stringOneRunWithOffset, typeName, values[0].String(), usize(len(values)), lessThanZero) |
570 | } |
571 | } |
572 | |
573 | // Arguments to format are: |
574 | // |
575 | // [1]: type name |
576 | // [2]: size of index element (8 for uint8 etc.) |
577 | // [3]: less than zero check (for signed types) |
578 | const stringOneRun = `func (i %[1]s) String() string { |
579 | if %[3]si >= %[1]s(len(_%[1]s_index)-1) { |
580 | return "%[1]s(" + strconv.FormatInt(int64(i), 10) + ")" |
581 | } |
582 | return _%[1]s_name[_%[1]s_index[i]:_%[1]s_index[i+1]] |
583 | } |
584 | ` |
585 | |
586 | // Arguments to format are: |
587 | // [1]: type name |
588 | // [2]: lowest defined value for type, as a string |
589 | // [3]: size of index element (8 for uint8 etc.) |
590 | // [4]: less than zero check (for signed types) |
591 | /* |
592 | */ |
593 | const stringOneRunWithOffset = `func (i %[1]s) String() string { |
594 | i -= %[2]s |
595 | if %[4]si >= %[1]s(len(_%[1]s_index)-1) { |
596 | return "%[1]s(" + strconv.FormatInt(int64(i + %[2]s), 10) + ")" |
597 | } |
598 | return _%[1]s_name[_%[1]s_index[i] : _%[1]s_index[i+1]] |
599 | } |
600 | ` |
601 | |
602 | // buildMultipleRuns generates the variables and String method for multiple runs of contiguous values. |
603 | // For this pattern, a single Printf format won't do. |
604 | func (g *Generator) buildMultipleRuns(runs [][]Value, typeName string) { |
605 | g.Printf("\n") |
606 | g.declareIndexAndNameVars(runs, typeName) |
607 | g.Printf("func (i %s) String() string {\n", typeName) |
608 | g.Printf("\tswitch {\n") |
609 | for i, values := range runs { |
610 | if len(values) == 1 { |
611 | g.Printf("\tcase i == %s:\n", &values[0]) |
612 | g.Printf("\t\treturn _%s_name_%d\n", typeName, i) |
613 | continue |
614 | } |
615 | if values[0].value == 0 && !values[0].signed { |
616 | // For an unsigned lower bound of 0, "0 <= i" would be redundant. |
617 | g.Printf("\tcase i <= %s:\n", &values[len(values)-1]) |
618 | } else { |
619 | g.Printf("\tcase %s <= i && i <= %s:\n", &values[0], &values[len(values)-1]) |
620 | } |
621 | if values[0].value != 0 { |
622 | g.Printf("\t\ti -= %s\n", &values[0]) |
623 | } |
624 | g.Printf("\t\treturn _%s_name_%d[_%s_index_%d[i]:_%s_index_%d[i+1]]\n", |
625 | typeName, i, typeName, i, typeName, i) |
626 | } |
627 | g.Printf("\tdefault:\n") |
628 | g.Printf("\t\treturn \"%s(\" + strconv.FormatInt(int64(i), 10) + \")\"\n", typeName) |
629 | g.Printf("\t}\n") |
630 | g.Printf("}\n") |
631 | } |
632 | |
633 | // buildMap handles the case where the space is so sparse a map is a reasonable fallback. |
634 | // It's a rare situation but has simple code. |
635 | func (g *Generator) buildMap(runs [][]Value, typeName string) { |
636 | g.Printf("\n") |
637 | g.declareNameVars(runs, typeName, "") |
638 | g.Printf("\nvar _%s_map = map[%s]string{\n", typeName, typeName) |
639 | n := 0 |
640 | for _, values := range runs { |
641 | for _, value := range values { |
642 | g.Printf("\t%s: _%s_name[%d:%d],\n", &value, typeName, n, n+len(value.name)) |
643 | n += len(value.name) |
644 | } |
645 | } |
646 | g.Printf("}\n\n") |
647 | g.Printf(stringMap, typeName) |
648 | } |
649 | |
650 | // Argument to format is the type name. |
651 | const stringMap = `func (i %[1]s) String() string { |
652 | if str, ok := _%[1]s_map[i]; ok { |
653 | return str |
654 | } |
655 | return "%[1]s(" + strconv.FormatInt(int64(i), 10) + ")" |
656 | } |
657 | ` |
658 |
Members