blob: a1fa9fdb1f467faf60e17c81429fec6df3610d4e [file] [log] [blame]
Rob Pike5216eb82014-12-22 13:33:16 +11001Generating code
222 Dec 2014
3Tags: programming, technical
4
5Rob Pike
6
7* Generating code
8
9A property of universal computationTuring completenessis that a computer program can write a computer program.
10This is a powerful idea that is not appreciated as often as it might be, even though it happens frequently.
11It's a big part of the definition of a compiler, for instance.
12It's also how the `go` `test` command works: it scans the packages to be tested,
13writes out a Go program containing a test harness customized for the package,
14and then compiles and runs it.
15Modern computers are so fast this expensive-sounding sequence can complete in a fraction of a second.
16
17There are lots of other examples of programs that write programs.
Pravendra Singh83eaf872017-05-26 00:23:31 +053018[[https://godoc.org/golang.org/x/tools/cmd/goyacc][Yacc]], for instance, reads in a description of a grammar and writes out a program to parse that grammar.
Rob Pike5216eb82014-12-22 13:33:16 +110019The protocol buffer "compiler" reads an interface description and emits structure definitions,
20methods, and other support code.
21Configuration tools of all sorts work like this too, examining metadata or the environment
22and emitting scaffolding customized to the local state.
23
24Programs that write programs are therefore important elements in software engineering,
25but programs like Yacc that produce source code need to be integrated into the build
26process so their output can be compiled.
27When an external build tool like Make is being used, this is usually easy to do.
28But in Go, whose go tool gets all necessary build information from the Go source, there is a problem.
29There is simply no mechanism to run Yacc from the go tool alone.
30
31Until now, that is.
32
Brad Fitzpatrick67889872018-04-13 19:58:08 +000033The [[https://blog.golang.org/go1.4][latest Go release]], 1.4,
Rob Pike5216eb82014-12-22 13:33:16 +110034includes a new command that makes it easier to run such tools.
35It's called `go` `generate`, and it works by scanning for special comments in Go source code
36that identify general commands to run.
37It's important to understand that `go` `generate` is not part of `go` `build`.
38It contains no dependency analysis and must be run explicitly before running `go` `build`.
39It is intended to be used by the author of the Go package, not its clients.
40
41The `go` `generate` command is easy to use.
42As a warmup, here's how to use it to generate a Yacc grammar.
Pravendra Singh83eaf872017-05-26 00:23:31 +053043
44First, install Go's Yacc tool:
45
46 go get golang.org/x/tools/cmd/goyacc
47
Rob Pike5216eb82014-12-22 13:33:16 +110048Say you have a Yacc input file called `gopher.y` that defines a grammar for your new language.
49To produce the Go source file implementing the grammar,
Pravendra Singh83eaf872017-05-26 00:23:31 +053050you would normally invoke the command like this:
Rob Pike5216eb82014-12-22 13:33:16 +110051
Pravendra Singh83eaf872017-05-26 00:23:31 +053052 goyacc -o gopher.go -p parser gopher.y
Rob Pike5216eb82014-12-22 13:33:16 +110053
54The `-o` option names the output file while `-p` specifies the package name.
55
56To have `go` `generate` drive the process, in any one of the regular (non-generated) `.go` files
57in the same directory, add this comment anywhere in the file:
58
Pravendra Singh83eaf872017-05-26 00:23:31 +053059 //go:generate goyacc -o gopher.go -p parser gopher.y
Rob Pike5216eb82014-12-22 13:33:16 +110060
61This text is just the command above prefixed by a special comment recognized by `go` `generate`.
62The comment must start at the beginning of the line and have no spaces between the `//` and the `go:generate`.
63After that marker, the rest of the line specifies a command for `go` `generate` to run.
64
65Now run it. Change to the source directory and run `go` `generate`, then `go` `build` and so on:
66
67 $ cd $GOPATH/myrepo/gopher
68 $ go generate
69 $ go build
70 $ go test
71
72That's it.
73Assuming there are no errors, the `go` `generate` command will invoke `yacc` to create `gopher.go`,
74at which point the directory holds the full set of Go source files, so we can build, test, and work normally.
75Every time `gopher.y` is modified, just rerun `go` `generate` to regenerate the parser.
76
77For more details about how `go` `generate` works, including options, environment variables,
Agniva De Sarker7edc9622018-04-14 00:23:09 +053078and so on, see the [[https://golang.org/s/go1.4-generate][design document]].
Rob Pike5216eb82014-12-22 13:33:16 +110079
80Go generate does nothing that couldn't be done with Make or some other build mechanism,
81but it comes with the `go` toolno extra installation requiredand fits nicely into the Go ecosystem.
82Just keep in mind that it is for package authors, not clients,
83if only for the reason that the program it invokes might not be available on the target machine.
84Also, if the containing package is intended for import by `go` `get`,
85once the file is generated (and tested!) it must be checked into the
86source code repository to be available to clients.
87
88Now that we have it, let's use it for something new.
89As a very different example of how `go` `generate` can help, there is a new program available in the
90`golang.org/x/tools` repository called `stringer`.
91It automatically writes string methods for sets of integer constants.
92It's not part of the released distribution, but it's easy to install:
93
94 $ go get golang.org/x/tools/cmd/stringer
95
96Here's an example from the documentation for
Brad Fitzpatrick67889872018-04-13 19:58:08 +000097[[https://godoc.org/golang.org/x/tools/cmd/stringer][`stringer`]].
Rob Pike5216eb82014-12-22 13:33:16 +110098Imagine we have some code that contains a set of integer constants defining different types of pills:
99
100 package painkiller
101
102 type Pill int
103
104 const (
105 Placebo Pill = iota
106 Aspirin
107 Ibuprofen
108 Paracetamol
109 Acetaminophen = Paracetamol
110 )
111
112For debugging, we'd like these constants to pretty-print themselves, which means we want a method with signature,
113
114 func (p Pill) String() string
115
116It's easy to write one by hand, perhaps like this:
117
118 func (p Pill) String() string {
119 switch p {
120 case Placebo:
121 return "Placebo"
122 case Aspirin:
123 return "Aspirin"
124 case Ibuprofen:
125 return "Ibuprofen"
126 case Paracetamol: // == Acetaminophen
127 return "Paracetamol"
128 }
129 return fmt.Sprintf("Pill(%d)", p)
130 }
131
132There are other ways to write this function, of course.
133We could use a slice of strings indexed by Pill, or a map, or some other technique.
134Whatever we do, we need to maintain it if we change the set of pills, and we need to make sure it's correct.
135(The two names for paracetamol make this trickier than it might otherwise be.)
136Plus the very question of which approach to take depends on the types and values:
137signed or unsigned, dense or sparse, zero-based or not, and so on.
138
139The `stringer` program takes care of all these details.
140Although it can be run in isolation, it is intended to be driven by `go` `generate`.
141To use it, add a generate comment to the source, perhaps near the type definition:
142
143 //go:generate stringer -type=Pill
144
145This rule specifies that `go` `generate` should run the `stringer` tool to generate a `String` method for type `Pill`.
146The output is automatically written to `pill_string.go` (a default we could override with the
147`-output` flag).
148
149Let's run it:
150
151 $ go generate
152 $ cat pill_string.go
Robin Eklind933f9aa2019-03-15 07:08:50 +0000153 // Code generated by stringer -type Pill pill.go; DO NOT EDIT.
Russ Cox7fd29cb2020-03-09 23:23:49 -0400154
Carlos Souzaeee245a2018-10-21 13:54:43 +0000155 package painkiller
Russ Cox7fd29cb2020-03-09 23:23:49 -0400156
Rob Pike5216eb82014-12-22 13:33:16 +1100157 import "fmt"
Russ Cox7fd29cb2020-03-09 23:23:49 -0400158
Rob Pike5216eb82014-12-22 13:33:16 +1100159 const _Pill_name = "PlaceboAspirinIbuprofenParacetamol"
Russ Cox7fd29cb2020-03-09 23:23:49 -0400160
Rob Pikeb35b5e32014-12-23 08:00:39 +1100161 var _Pill_index = [...]uint8{0, 7, 14, 23, 34}
Russ Cox7fd29cb2020-03-09 23:23:49 -0400162
Rob Pike5216eb82014-12-22 13:33:16 +1100163 func (i Pill) String() string {
Rob Piked100f692014-12-23 10:09:38 +1100164 if i < 0 || i+1 >= Pill(len(_Pill_index)) {
Rob Pike5216eb82014-12-22 13:33:16 +1100165 return fmt.Sprintf("Pill(%d)", i)
166 }
Rob Pikeb35b5e32014-12-23 08:00:39 +1100167 return _Pill_name[_Pill_index[i]:_Pill_index[i+1]]
Rob Pike5216eb82014-12-22 13:33:16 +1100168 }
169 $
170
171Every time we change the definition of `Pill` or the constants, all we need to do is run
172
173 $ go generate
174
175to update the `String` method.
176And of course if we've got multiple types set up this way in the same package,
177that single command will update all their `String` methods with a single command.
178
179There's no question the generated method is ugly.
180That's OK, though, because humans don't need to work on it; machine-generated code is often ugly.
181It's working hard to be efficient.
182All the names are smashed together into a single string,
183which saves memory (only one string header for all the names, even if there are zillions of them).
184Then an array, `_Pill_index`, maps from value to name by a simple, efficient technique.
185Note too that `_Pill_index` is an array (not a slice; one more header eliminated) of `uint8`,
186the smallest integer sufficient to span the space of values.
187If there were more values, or there were negatives ones,
188the generated type of `_Pill_index` might change to `uint16` or `int8`: whatever works best.
189
190The approach used by the methods printed by `stringer` varies according to the properties of the constant set.
191For instance, if the constants are sparse, it might use a map.
192Here's a trivial example based on a constant set representing powers of two:
193
194 const _Power_name = "p0p1p2p3p4p5..."
195
196 var _Power_map = map[Power]string{
197 1: _Power_name[0:2],
198 2: _Power_name[2:4],
199 4: _Power_name[4:6],
200 8: _Power_name[6:8],
201 16: _Power_name[8:10],
202 32: _Power_name[10:12],
203 ...,
204 }
205
206 func (i Power) String() string {
207 if str, ok := _Power_map[i]; ok {
208 return str
209 }
210 return fmt.Sprintf("Power(%d)", i)
211 }
212
Rob Pike5216eb82014-12-22 13:33:16 +1100213In short, generating the method automatically allows us to do a better job than we would expect a human to do.
214
215There are lots of other uses of `go` `generate` already installed in the Go tree.
216Examples include generating Unicode tables in the `unicode` package,
217creating efficient methods for encoding and decoding arrays in `encoding/gob`,
218producing time zone data in the `time` package, and so on.
219
220Please use `go` `generate` creatively.
221It's there to encourage experimentation.
222
223And even if you don't, use the new `stringer` tool to write your `String` methods for your integer constants.
224Let the machine do the work.