В данной статье хотелось бы рассмотреть некоторые возможности кодогенарации в рамках языка Go, которые могут частично заменить встроенную рефлексию и не потерять типобезопасность на этапе компиляции.
Язык программирования Go предоставляет мощные инструменты для кодогенерации. Очень часто Go ругают за отсутствие обобщений (generics) и это в самом деле может стать проблемой. И вот тут на помощь приходит кодогенерация которая на первый взгляд довольно трудна для небольших рутинных операций, но тем не менее является достаточно гибким инструментом. Уже существует некоторое количество готовых библиотек кодогенерации покрывающих базовые потребности в обобщениях. Это и «эталонный» stringer и более полезные jsonenums с ffjson А мощный gen и вовсе позволяет добавить в Go немного функциональщины, в том числе добавляет аналог так не хватаемого многим forEach для пользовательских типов. Ко всему прочему gen довольно легко расширяется собственными генераторами. К сожалению gen ограничен кодогенерацией методов для конкретных типов.
Собственно тему кодогенерации я решил затронуть не от хорошей жизни, а из за того, что столкнулся с небольшой задачей для которой не смог найти другого подходящего решения.
Задача следующая, есть список констант:
type Color int
const (
Green Color = iota
Red
Blue
Black
)
Colors = [...]Color{Green, Red, Blue, Black}
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "", []byte(source), 0)
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)
}
}
}
}
}
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)
}
type Colors []Color
func (c Colors)List() []Color {
return []Color{Green, Red, Blue, Black}
}
Colors{}.List()
К сожалению, не доступен сервер mySQL