Overview

In Go, there is an unwritten habit that many people like to use generated code, for example, the directory structure of the project, the stub code of grpc are generated with tools, and for small things like static files embedded in the code, automatic generation of enum type String form, etc. Anyway, the pattern of generated code can always be seen.

In fact, this may be related to the Go official is also to promote this way, for example, Go from version 1.4 onwards go generate function, although I started to write the draft of this article more than 2 years ago, but so long actually not how seriously to understand this function. Recently, I tried to use this feature, so I really got to know go genreate, and in this article I’ll try to summarize what go generate is about.

How it works

The way go generate works is that when you type in the command line.

  1. [[email protected].io]# go generate . /…

go will look in your current directory for a comment containing //go:generate, which is usually followed by a command, such as this comment.

  1. [[email protected].io]# //go:generate mygentool arg1 arg2 -on

It’s actually the same as if you ran it from this directory: mygentool arg1 arg2 -on, but the difference is that you don’t get any additional metadata if you run it locally, but if you run it from go generate, go will add some additional properties to you by default, which can be verified with this program (note that generate utility needs to be in your PATH directory, and if it is in the current directory, don’t forget to add the current directory to your PATH environment variable)

  1. [[email protected].io]# go generate . /…
  2. args[0]: mygentool
  3. args[1]: arg1
  4. args[2]: arg2
  5. args[3]: -on
  6. GO111MODULE=auto
  7. GOPATH=/xxxx/software/go/gopath
  8. GOROOT=/xxxx/software/go/goroot/go
  9. GOARCH=amd64
  10. GOOS=linux
  11. GOFILE=sample.go
  12. GOLINE=3
  13. GOPACKAGE=main
  14. cwd: /xxxx/blog_codes/golang/tools/generator/example1

As you can see, Go passes in a lot of environment variables for us by default, such as what file the comment is in, what line it’s on, what the package name is, and what directory you’re executing the go generate command from. With these parameters, we can do a lot of interesting things.

stringer

Now look at an example mentioned in the official Go blog: Go Generate, which is an example of a stringer, which is really a way to add a string to the enum type: the

  1. [[email protected].io]# cat example.go
  2. package painkiller
  3. //go:generate stringer -type=Pill
  4. type Pill int
  5. const (
  6. Placebo Pill = iota
  7. Aspirin
  8. Ibuprofen
  9. Paracetamol
  10. Acetaminophen = Paracetamol
  11. )

Then execute the command.

  1. [[email protected].io]# go get golang.org/x/tools/cmd/stringer
  2. [[email protected].io]# go generate

You can see that the current directory will have an extra file: pill_string.go, then try to do a test: `

  1. [[email protected].io]# cat pill_test.go
  2. func TestPill_String(t *testing.T) {
  3. var p painkiller.Pill
  4. if p.String() ! = "Placebo" {
  5. t.Fatalf("p should equal to Placebo")
  6. }
  7. }

You can see that the enum type has a String method, and the return value of this method is the String value of the Enum.
By looking at the stringer code, you can see that stringer is an executable, and then supports these parameters.

  1. [[email protected].io]# cat stringer.go
  2. var (
  3. typeNames = flag.String("type", "", "comma-separated list of type names; must be set")
  4. output = flag.String("output", "", "output file name; default srcdir/<type>_string.go")
  5. trimprefix = flag.String("trimprefix", "", "trim the `prefix` from the generated constant names")
  6. linecomment = flag.Bool("linecomment", false, "use line comment text as printed text when present")
  7. buildTags = flag.String("tags", "", "comma-separated list of build tags to apply")
  8. )

It is implemented by ast (ast.Inspect(file.file, file.genDecl)) parsing your file, then finding the specified name, traversing its values, then merging those values into an array, and finally enough to build the stringer’s structure.

yacc

This is actually a metaprogramming idea, where we specify a metadata and create code from that metadata (e.g. create a struct, which then automatically generates a lot of built-in methods, kind of like proto → go code, but more advanced. > go code, but more advanced and feature-rich.) For metaprogramming knowledge, I wrote an article on python metaclasses, which you can read: python metaclass analysis.

  1. [[email protected].io]# go get golang.org/x/tools/cmd/goyacc

Then edit your yacc file, for example I copied one from repo, and create the go file containing the go generate command: ```

  1. [[email protected].io]# cat main.go
  2. package main
  3. //go:generate goyacc -o calc.go calc.y
  4. func main() {
  5. }

Then run go generate . /…, I’m not familiar with yacc, but from the code it looks like a syntax rule defined by clac, which should be used to parse the syntax of a specific rule, which I’m not good at, so I won’t expand on it, but the function is still used in this way, without departing from the basic method of operation.

Sample code

All the code for this article can be found in this repo: https://github.com/liuliqiang/blog_codes/tree/master/golang/tools/generator

Reference projects