forked from dave/jennifer
/
tokens.go
335 lines (305 loc) · 8.99 KB
/
tokens.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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
package jen
import (
"fmt"
"io"
"strconv"
"strings"
)
type tokenType string
const (
packageToken tokenType = "package"
identifierToken tokenType = "identifier"
qualifiedToken tokenType = "qualified"
keywordToken tokenType = "keyword"
operatorToken tokenType = "operator"
delimiterToken tokenType = "delimiter"
literalToken tokenType = "literal"
literalRuneToken tokenType = "literal_rune"
literalByteToken tokenType = "literal_byte"
nullToken tokenType = "null"
layoutToken tokenType = "layout"
mustImportToken tokenType = "must_import"
)
type token struct {
typ tokenType
content interface{}
}
func (t token) isNull(f *File) bool {
if t.typ == packageToken {
// package token is null if the path is a dot-import or the local package path
return f.isDotImport(t.content.(string)) || f.isLocal(t.content.(string))
}
return t.typ == nullToken
}
func (t token) render(f *File, w io.Writer, s *Statement) error {
switch t.typ {
case literalToken:
var out string
switch t.content.(type) {
case bool, string, int, complex128:
// default constant types can be left bare
out = fmt.Sprintf("%#v", t.content)
case float64:
out = fmt.Sprintf("%#v", t.content)
if !strings.Contains(out, ".") && !strings.Contains(out, "e") {
// If the formatted value is not in scientific notation, and does not have a dot, then
// we add ".0". Otherwise it will be interpreted as an int.
// See:
// https://github.com/pedidopago/jennifer/issues/39
// https://github.com/golang/go/issues/26363
out += ".0"
}
case float32, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr:
// other built-in types need specific type info
out = fmt.Sprintf("%T(%#v)", t.content, t.content)
case complex64:
// fmt package already renders parenthesis for complex64
out = fmt.Sprintf("%T%#v", t.content, t.content)
default:
panic(fmt.Sprintf("unsupported type for literal: %T", t.content))
}
if _, err := w.Write([]byte(out)); err != nil {
return err
}
case literalRuneToken:
if _, err := w.Write([]byte(strconv.QuoteRune(t.content.(rune)))); err != nil {
return err
}
case literalByteToken:
if _, err := w.Write([]byte(fmt.Sprintf("byte(%#v)", t.content))); err != nil {
return err
}
case keywordToken, operatorToken, layoutToken, delimiterToken:
if _, err := w.Write([]byte(fmt.Sprintf("%s", t.content))); err != nil {
return err
}
if t.content.(string) == "default" {
// Special case for Default, which must always be followed by a colon
if _, err := w.Write([]byte(":")); err != nil {
return err
}
}
case packageToken:
path := t.content.(string)
alias := f.register(path)
if _, err := w.Write([]byte(alias)); err != nil {
return err
}
case mustImportToken:
path := t.content.(string)
_ = f.register(path)
case identifierToken:
if _, err := w.Write([]byte(t.content.(string))); err != nil {
return err
}
case nullToken: // notest
// do nothing (should never render a null token)
}
return nil
}
// Null adds a null item. Null items render nothing and are not followed by a
// separator in lists.
func Null() *Statement {
return newStatement().Null()
}
// Null adds a null item. Null items render nothing and are not followed by a
// separator in lists.
func (g *Group) Null() *Statement {
s := Null()
g.items = append(g.items, s)
return s
}
// Null adds a null item. Null items render nothing and are not followed by a
// separator in lists.
func (s *Statement) Null() *Statement {
t := token{
typ: nullToken,
}
*s = append(*s, t)
return s
}
// Empty adds an empty item. Empty items render nothing but are followed by a
// separator in lists.
func Empty() *Statement {
return newStatement().Empty()
}
// Empty adds an empty item. Empty items render nothing but are followed by a
// separator in lists.
func (g *Group) Empty() *Statement {
s := Empty()
g.items = append(g.items, s)
return s
}
// Empty adds an empty item. Empty items render nothing but are followed by a
// separator in lists.
func (s *Statement) Empty() *Statement {
t := token{
typ: operatorToken,
content: "",
}
*s = append(*s, t)
return s
}
// Op renders the provided operator / token.
func Op(op string) *Statement {
return newStatement().Op(op)
}
// Op renders the provided operator / token.
func (g *Group) Op(op string) *Statement {
s := Op(op)
g.items = append(g.items, s)
return s
}
// MustImport adds the provided package path to the list of imported packages.
func (g *Group) MustImport(path string) *Statement {
s := MustImport(path)
g.items = append(g.items, s)
return s
}
// Op renders the provided operator / token.
func (s *Statement) Op(op string) *Statement {
t := token{
typ: operatorToken,
content: op,
}
*s = append(*s, t)
return s
}
// Dot renders a period followed by an identifier. Use for fields and selectors.
func Dot(name string) *Statement {
// notest
// don't think this can be used in valid code?
return newStatement().Dot(name)
}
// Dot renders a period followed by an identifier. Use for fields and selectors.
func (g *Group) Dot(name string) *Statement {
// notest
// don't think this can be used in valid code?
s := Dot(name)
g.items = append(g.items, s)
return s
}
// Dot renders a period followed by an identifier. Use for fields and selectors.
func (s *Statement) Dot(name string) *Statement {
d := token{
typ: delimiterToken,
content: ".",
}
t := token{
typ: identifierToken,
content: name,
}
*s = append(*s, d, t)
return s
}
// Id renders an identifier.
func Id(name string) *Statement {
return newStatement().Id(name)
}
// Id renders an identifier.
func (g *Group) Id(name string) *Statement {
s := Id(name)
g.items = append(g.items, s)
return s
}
// Id renders an identifier.
func (s *Statement) Id(name string) *Statement {
t := token{
typ: identifierToken,
content: name,
}
*s = append(*s, t)
return s
}
// Qual renders a qualified identifier. Imports are automatically added when
// used with a File. If the path matches the local path, the package name is
// omitted. If package names conflict they are automatically renamed. Note that
// it is not possible to reliably determine the package name given an arbitrary
// package path, so a sensible name is guessed from the path and added as an
// alias. The names of all standard library packages are known so these do not
// need to be aliased. If more control is needed of the aliases, see
// [File.ImportName](#importname) or [File.ImportAlias](#importalias).
func Qual(path, name string) *Statement {
return newStatement().Qual(path, name)
}
// MustImport imports the package at the provided path.
func MustImport(path string) *Statement {
return newStatement().MustImport(path)
}
// Qual renders a qualified identifier. Imports are automatically added when
// used with a File. If the path matches the local path, the package name is
// omitted. If package names conflict they are automatically renamed. Note that
// it is not possible to reliably determine the package name given an arbitrary
// package path, so a sensible name is guessed from the path and added as an
// alias. The names of all standard library packages are known so these do not
// need to be aliased. If more control is needed of the aliases, see
// [File.ImportName](#importname) or [File.ImportAlias](#importalias).
func (g *Group) Qual(path, name string) *Statement {
s := Qual(path, name)
g.items = append(g.items, s)
return s
}
// Qual renders a qualified identifier. Imports are automatically added when
// used with a File. If the path matches the local path, the package name is
// omitted. If package names conflict they are automatically renamed. Note that
// it is not possible to reliably determine the package name given an arbitrary
// package path, so a sensible name is guessed from the path and added as an
// alias. The names of all standard library packages are known so these do not
// need to be aliased. If more control is needed of the aliases, see
// [File.ImportName](#importname) or [File.ImportAlias](#importalias).
func (s *Statement) Qual(path, name string) *Statement {
g := &Group{
close: "",
items: []Code{
token{
typ: packageToken,
content: path,
},
token{
typ: identifierToken,
content: name,
},
},
name: "qual",
open: "",
separator: ".",
}
*s = append(*s, g)
return s
}
// MustImport forces the import of the provided path.
func (s *Statement) MustImport(path string) *Statement {
g := &Group{
close: "",
items: []Code{
token{
typ: mustImportToken,
content: path,
},
},
name: "must_import",
open: "",
separator: "",
}
*s = append(*s, g)
return s
}
// Line inserts a blank line.
func Line() *Statement {
return newStatement().Line()
}
// Line inserts a blank line.
func (g *Group) Line() *Statement {
s := Line()
g.items = append(g.items, s)
return s
}
// Line inserts a blank line.
func (s *Statement) Line() *Statement {
t := token{
typ: layoutToken,
content: "\n",
}
*s = append(*s, t)
return s
}