📜 ⬆️ ⬇️

Go Code Generation

In this article, I would like to consider some of the possibilities of code generation within the Go language, which can partially replace the built-in reflection and not lose type safety at the compilation stage.
The programming language Go provides powerful tools for code generation. Very often, Go is scolded for the lack of generics, and this can in fact become a problem. And this is where code generation comes to the rescue, which at first glance is rather difficult for small routine operations, but nevertheless is a fairly flexible tool. There are already a number of ready-made code generation libraries covering basic needs for generalizations. This is both a “reference” stringer and more useful jsonenums with ffjson. And the powerful gen allows you to add a bit of functionality to Go, including adding an analog that many forEach doesn’t have for user types. On top of this, gen is fairly easy to extend with its own generators. Unfortunately, gen is limited to code generation of methods for specific types.
Actually, I decided to touch upon the topic of code generation not from a good life, but because I ran into a small task for which I could not find another suitable solution.

The task is the following; there is a list of constants:
type Color int const ( Green Color = iota Red Blue Black ) 

It is necessary to have an array (list) containing all Color constants, for example, for output in a palette.
 Colors = [...]Color{Green, Red, Blue, Black} 

At the same time, it would be desirable that the Colors be formed automatically in order to exclude the possibility of forgetting to add or remove an element when changing the number of constants of the Color type.

The key tools will be the following standard packages:
go / ast /
go / parser /
go / token /

Using these packages, we are able to get ast ( abstract syntax tree ) of any file with source code in the go language. AST we get literally in two lines:
 fset := token.NewFileSet() f, err := parser.ParseFile(fset, "", []byte(source), 0) 

As arguments for ParseFile, you can pass either the path to the file or the text content (see https://golang.org/pkg/go/parser/#ParseFile for details). Now the variable f will contain ast which can be used to generate the necessary code.
In order to create a list containing all constants of a given type (Color), it is necessary to go through ast and find the nodes describing constants. This is done in a rather trivial way, although not without features. The fact is that Go allows you to define non-typed constants or a list of constants with auto increment through the iota construction . For such constants, their type in ast will not be defined, the value and type will be calculated already at the compilation stage. Therefore, it is necessary to take into account the features of the syntax when parsing ast.
Below is a sample code that takes into account the definition of constants via iota.
')
ast bypass
 typeName := "Color" //       typ := "" //      ast consts := make([]string, 0) //     for _, decl := range f.Decls { //   , , ,   .. switch decl := decl.(type) { case *ast.GenDecl: switch decl.Tok { case token.CONST: //    for _, spec := range decl.Specs { vspec := spec.(*ast.ValueSpec) //     if vspec.Type == nil && len(vspec.Values) > 0 { //    "X = 1" //         //     ,       const typ = "" continue } if vspec.Type != nil { //"const Green Color" -    if ident, ok := vspec.Type.(*ast.Ident); ok { typ = ident.Name } else { continue } } if typ == typeName { //    ,      consts consts = append(consts, vspec.Names[0].Name) } } } } } 


The similar code is commented in more detail in the stringer package.
Now it remains to generate a function that returns a list of all existing Color.
code generation
 var constListTmpl = `//CODE GENERATED AUTOMATICALLY //THIS FILE SHOULD NOT BE EDITED BY HAND package {{.Package}} type {{.Name}}s []{{.Name}} func (c {{.Name}}s)List() []{{.Name}} { return []{{.Name}}{{"{"}}{{.List}}{{"}"}} } ` templateData := struct { Package string Name string List string }{ Package: "main", Name: typeName, List: strings.Join(consts, ", "), } t := template.Must(template.New("const-list").Parse(constListTmpl)) if err := t.Execute(os.Stdout, templateData); err != nil { fmt.Println(err) } 



At the output we get the following function:
 type Colors []Color func (c Colors)List() []Color { return []Color{Green, Red, Blue, Black} } 

Using function:
 Colors{}.List() 

Listing example https://play.golang.org/p/Mck9Y66Z1b

A ready-to-use const_list generator based on a stringer generator.

Source: https://habr.com/ru/post/306672/


All Articles