/
generator.go
190 lines (170 loc) · 4.86 KB
/
generator.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// Copyright (c) 2021 Hervé Gouchet. All rights reserved.
// Use of this source code is governed by the MIT License
// that can be found in the LICENSE file.
package genum
import (
"bytes"
"fmt"
"strconv"
)
const (
shortName = "e"
mixedName = "v"
strName = "s"
srcName = "data"
increment = "iota"
returnNotFound = "return \"\", false\n"
returnErr = "return fmt.Errorf(\"%s expects %s but got %%s\", %s)\n"
lookupFunc = "func lookup%[1]s(%[2]s %[1]s) (%[3]s string, ok bool) {\n"
unnamed = "_"
zero = "0"
maxHumanScale = 10
)
// Layout returns the generation configuration based on the given settings.
func Layout(s Settings, args []string) []Configurator {
if s == nil {
return nil
}
var cnf []Configurator
if s.Bitmask() {
cnf = append(cnf, ParseBitmask(s.SrcFile(), s.TypeName(), s.JoinPrefix(), s.TrimPrefix()))
} else {
cnf = append(cnf, ParseEnums(s.SrcFile(), s.TypeName(), s.TypeKind(), s.JoinPrefix(), s.TrimPrefix(), s.Iota()))
}
cnf = append(
cnf,
PrintHeader(s.PackageName(), args, dependencies(s)),
PrintEnums(s.TypeName(), s.Iota(), s.Commented()),
)
if s.Bitmask() {
cnf = append(cnf, PrintBitmask(s.TypeName()))
}
if s.Stringer() || s.Validator() {
cnf = append(cnf, PrintLookup(s.TypeName(), s.TypeKind()))
}
if s.Stringer() {
cnf = append(cnf, PrintStringer(s.StringFormater(), s.TypeName(), s.TypeKind()))
}
if s.Validator() {
cnf = append(cnf, PrintValidator(s.TypeName()))
}
if s.JSONMarshaler() {
cnf = append(cnf, PrintJSONMarshaler(s.TypeName(), s.TypeKind()))
}
if s.TextMarshaler() {
cnf = append(cnf, PrintTextMarshaler(s.StringFormater(), s.TypeName()))
}
return append(cnf, WriteFile(s.DstFilename()))
}
// Generate generates the enum file based on these options.
func Generate(opts ...Configurator) (err error) {
gen := new(Generator)
for _, opt := range opts {
err = opt(gen)
if err != nil {
return
}
}
return
}
// Generator represents an enum generator.
type Generator struct {
enums []Enum
basic bool
buf bytes.Buffer
err error
}
func (g *Generator) advanceString(enumType string) error {
// Variable with enums names.
g.printf("\n")
g.printf("var _%sNames = map[%s]string{\n", enumType, enumType)
for _, e := range g.enums {
g.printf("%s: %q,\n", e.Text, e.RawText)
}
g.printf("}\n")
// Lookup method based on the precomputed map of Enums.
g.printf("\n")
g.printf(lookupFunc, enumType, shortName, strName)
g.printf("%s, ok = _%sNames[%s]\n", strName, enumType, shortName)
g.printf("return %s, ok\n", strName)
g.printf("}\n")
return nil
}
func (g *Generator) averageString(enumType string) error {
// Lookup method (with human readable switch case)
g.printf("\n")
g.printf(lookupFunc, enumType, shortName, strName)
g.printf("switch %s {\n", shortName)
for _, e := range g.enums {
g.printf("case %s:\n", e.Text)
g.printf("return %q, true\n", e.RawText)
}
g.printf("default:\n")
g.printf(returnNotFound)
g.printf("}\n")
g.printf("}\n")
return nil
}
func (g *Generator) basicString(enumType string, enumKind Kind) error {
var (
buf = new(bytes.Buffer)
pos = make([]int, len(g.enums))
)
for p, e := range g.enums {
_, _ = buf.WriteString(e.RawText)
pos[p] = buf.Len()
}
// Constant with all enums names concatenated together.
g.printf("\n")
g.printf("const _%sNames = %q\n", enumType, buf.String())
max := buf.Len()
buf.Reset()
for i, v := range pos {
if i > 0 {
_, _ = buf.WriteString(", ")
}
_, _ = buf.WriteString(strconv.Itoa(v))
}
// Variable with positions of any enums names.
g.printf("\n")
g.printf("var _%sIndexes = [...]uint%d{0, %s}\n", enumType, unsignedSize(max), buf.String())
// Lookup method based on a slice of contant names.
g.printf("\n")
g.printf(lookupFunc, enumType, shortName, strName)
var guardRail string
if enumKind.IsSigned() && g.enums[0].Value != zero {
guardRail = shortName + " < 0 || "
g.printf("%s -= %s\n", shortName, g.enums[0].Value)
}
g.printf("if %s%s >= %s(len(_%sIndexes)-1) {\n", guardRail, shortName, enumType, enumType)
g.printf(returnNotFound)
g.printf("}\n")
g.printf("return _%[1]sNames[_%[1]sIndexes[%[2]s]:_%[1]sIndexes[%[2]s+1]], true\n", enumType, shortName)
g.printf("}\n")
return nil
}
type mode uint8
const (
// basic is the default mode, where enums are integers.
basic mode = iota
// average is used when the list of enums is human scale with integers.
average
// advance is used to handle enums not matching constraints of other modes.
advance
)
func (g Generator) mode() mode {
n := len(g.enums)
if n == 0 || g.basic {
return basic
}
if n > maxHumanScale {
return advance
}
return average
}
// printf formats according to a format specifier and writes to the Generator's buffer if no error has already occurred.
func (g *Generator) printf(format string, a ...interface{}) {
if g.err == nil {
_, g.err = fmt.Fprintf(&g.buf, format, a...)
}
}