1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package format
16
17 import (
18 "fmt"
19 "os"
20 "strings"
21 "text/tabwriter"
22
23 "cuelang.org/go/cue/ast"
24 "cuelang.org/go/cue/errors"
25 "cuelang.org/go/cue/literal"
26 "cuelang.org/go/cue/token"
27 )
28
29
30
31
32 type printer struct {
33 cfg *config
34
35 allowed whiteSpace
36 requested whiteSpace
37 indentStack []whiteSpace
38
39 pos token.Position
40 lineout line
41
42 lastTok token.Token
43
44 output []byte
45 indent int
46 spaceBefore bool
47
48 errs errors.Error
49 }
50
51 type line int
52
53 func (p *printer) init(cfg *config) {
54 p.cfg = cfg
55 p.pos = token.Position{Line: 1, Column: 1}
56 }
57
58 func (p *printer) errf(n ast.Node, format string, args ...interface{}) {
59 p.errs = errors.Append(p.errs, errors.Newf(n.Pos(), format, args...))
60 }
61
62 const debug = false
63
64 func (p *printer) internalError(msg ...interface{}) {
65 if debug {
66 fmt.Print(p.pos.String() + ": ")
67 fmt.Println(msg...)
68 panic("go/printer")
69 }
70 }
71
72 func (p *printer) lineFor(pos token.Pos) int {
73 return pos.Line()
74 }
75
76 func (p *printer) Print(v interface{}) {
77 var (
78 impliedComma = false
79 isLit bool
80 data string
81 nextWS whiteSpace
82 )
83 switch x := v.(type) {
84 case *line:
85 *x = p.lineout
86
87 case token.Token:
88 s := x.String()
89 before, after := mayCombine(p.lastTok, x)
90 if before && !p.spaceBefore {
91
92
93
94
95
96
97 if p.allowed&blank != 0 {
98 p.internalError("whitespace buffer not empty")
99 }
100 p.allowed |= blank
101 }
102 if after {
103 nextWS = blank
104 }
105 data = s
106 switch x {
107 case token.EOF:
108 data = ""
109 p.allowed = newline
110 p.allowed &^= newsection
111 case token.LPAREN, token.LBRACK, token.LBRACE:
112 case token.RPAREN, token.RBRACK, token.RBRACE:
113 impliedComma = true
114 }
115 p.lastTok = x
116
117 case *ast.BasicLit:
118 data = x.Value
119 switch x.Kind {
120 case token.STRING:
121
122
123
124
125
126 if p.indent < 6 {
127 data = literal.IndentTabs(data, p.cfg.Indent+p.indent+1)
128 }
129
130 case token.INT:
131 if len(data) > 1 &&
132 data[0] == '0' &&
133 data[1] >= '0' && data[1] <= '9' {
134 data = "0o" + data[1:]
135 }
136
137 if p := strings.IndexByte(data, '.'); p >= 0 && data[p+1] > '9' {
138 data = data[:p+1] + "0" + data[p+1:]
139 }
140
141
142 if p := strings.IndexByte(data, 'E'); p != -1 && p < len(data)-1 {
143 data = strings.ToLower(data)
144 }
145
146 case token.FLOAT:
147
148 switch p := strings.IndexByte(data, '.'); {
149 case p < 0:
150 case p == 0:
151 data = "0" + data
152 case p == len(data)-1:
153 data += "0"
154 case data[p+1] > '9':
155 data = data[:p+1] + "0" + data[p+1:]
156 }
157 if strings.IndexByte(data, 'E') != -1 {
158 data = strings.ToLower(data)
159 }
160 }
161
162 isLit = true
163 impliedComma = true
164 p.lastTok = x.Kind
165
166 case *ast.Ident:
167 data = x.Name
168 if !ast.IsValidIdent(data) {
169 p.errf(x, "invalid identifier %q", x.Name)
170 data = "*bad identifier*"
171 }
172 impliedComma = true
173 p.lastTok = token.IDENT
174
175 case string:
176
177
178 isLit = true
179 data = x
180 impliedComma = true
181 p.lastTok = token.STRING
182
183 case *ast.CommentGroup:
184 rel := x.Pos().RelPos()
185 if x.Line {
186 rel = token.Blank
187 }
188 switch rel {
189 case token.NoRelPos:
190 case token.Newline, token.NewSection:
191 case token.Blank, token.Elided:
192 p.allowed |= blank
193 fallthrough
194 case token.NoSpace:
195 p.allowed &^= newline | newsection | formfeed | declcomma
196 }
197 return
198
199 case *ast.Attribute:
200 isLit = true
201 data = x.Text
202 impliedComma = true
203 p.lastTok = token.ATTRIBUTE
204
205 case *ast.Comment:
206
207 isLit = true
208 data = x.Text
209 p.lastTok = token.COMMENT
210
211 case whiteSpace:
212 p.allowed |= x
213 return
214
215 case token.Pos:
216
217
218
219 if x.HasRelPos() {
220 if p.allowed&nooverride == 0 {
221 requested := p.allowed
222 switch x.RelPos() {
223 case token.NoSpace:
224 requested &^= newline | newsection | formfeed
225 case token.Blank:
226 requested |= blank
227 requested &^= newline | newsection | formfeed
228 case token.Newline:
229 requested |= newline
230 case token.NewSection:
231 requested |= newsection
232 }
233 p.writeWhitespace(requested)
234 p.allowed = 0
235 p.requested = 0
236 }
237
238 }
239 return
240
241 default:
242 fmt.Fprintf(os.Stderr, "print: unsupported argument %v (%T)\n", x, x)
243 panic("go/printer type")
244 }
245
246 p.writeWhitespace(p.allowed)
247 p.allowed = 0
248 p.requested = 0
249 p.writeString(data, isLit)
250 p.allowed = nextWS
251 _ = impliedComma
252 }
253
254 func (p *printer) writeWhitespace(ws whiteSpace) {
255 if ws&comma != 0 {
256 switch {
257 case ws&(newsection|newline|formfeed) != 0,
258 ws&trailcomma == 0:
259 p.writeByte(',', 1)
260 }
261 }
262 if ws&indent != 0 {
263 p.markLineIndent(ws)
264 }
265 if ws&unindent != 0 {
266 p.markUnindentLine()
267 }
268 switch {
269 case ws&newsection != 0:
270 p.maybeIndentLine(ws)
271 p.writeByte('\f', 2)
272 p.lineout += 2
273 p.spaceBefore = true
274 case ws&formfeed != 0:
275 p.maybeIndentLine(ws)
276 p.writeByte('\f', 1)
277 p.lineout++
278 p.spaceBefore = true
279 case ws&newline != 0:
280 p.maybeIndentLine(ws)
281 p.writeByte('\n', 1)
282 p.lineout++
283 p.spaceBefore = true
284 case ws&declcomma != 0:
285 p.writeByte(',', 1)
286 p.writeByte(' ', 1)
287 p.spaceBefore = true
288 case ws&noblank != 0:
289 case ws&vtab != 0:
290 p.writeByte('\v', 1)
291 p.spaceBefore = true
292 case ws&blank != 0:
293 p.writeByte(' ', 1)
294 p.spaceBefore = true
295 }
296 }
297
298 func (p *printer) markLineIndent(ws whiteSpace) {
299 p.indentStack = append(p.indentStack, ws)
300 }
301
302 func (p *printer) markUnindentLine() (wasUnindented bool) {
303 last := len(p.indentStack) - 1
304 if ws := p.indentStack[last]; ws&indented != 0 {
305 p.indent--
306 wasUnindented = true
307 }
308 p.indentStack = p.indentStack[:last]
309 return wasUnindented
310 }
311
312 func (p *printer) maybeIndentLine(ws whiteSpace) {
313 if ws&unindent == 0 && len(p.indentStack) > 0 {
314 last := len(p.indentStack) - 1
315 if ws := p.indentStack[last]; ws&indented != 0 || ws&indent == 0 {
316 return
317 }
318 p.indentStack[last] |= indented
319 p.indent++
320 }
321 }
322
323 func (f *formatter) matchUnindent() whiteSpace {
324 f.allowed |= unindent
325
326
327
328
329
330
331
332
333
334 return 0
335 }
336
337
338
339
340
341
342
343
344
345
346
347 func (p *printer) writeString(s string, isLit bool) {
348 if s != "" {
349 p.spaceBefore = false
350 }
351
352 if isLit {
353
354
355
356
357 p.output = append(p.output, tabwriter.Escape)
358 }
359
360 p.output = append(p.output, s...)
361
362 if isLit {
363 p.output = append(p.output, tabwriter.Escape)
364 }
365
366 nLines := 0
367 var li int
368 for i := 0; i < len(s); i++ {
369
370 if s[i] == '\n' {
371 nLines++
372 li = i
373 }
374 }
375 p.pos.Offset += len(s)
376 if nLines > 0 {
377 p.pos.Line += nLines
378 c := len(s) - li
379 p.pos.Column = c
380 } else {
381 p.pos.Column += len(s)
382 }
383 }
384
385 func (p *printer) writeByte(ch byte, n int) {
386 for i := 0; i < n; i++ {
387 p.output = append(p.output, ch)
388 }
389
390
391 p.pos.Offset += n
392 if ch == '\n' || ch == '\f' {
393 p.pos.Line += n
394 p.pos.Column = 1
395
396 n := p.cfg.Indent + p.indent
397 for i := 0; i < n; i++ {
398 p.output = append(p.output, '\t')
399 }
400
401
402 p.pos.Offset += n
403 p.pos.Column += n
404
405 return
406 }
407 p.pos.Column += n
408 }
409
410 func mayCombine(prev, next token.Token) (before, after bool) {
411 s := next.String()
412 if 'a' <= s[0] && s[0] < 'z' {
413 return true, true
414 }
415 switch prev {
416 case token.IQUO, token.IREM, token.IDIV, token.IMOD:
417 return false, false
418 case token.INT:
419 before = next == token.PERIOD
420 case token.ADD:
421 before = s[0] == '+'
422 case token.SUB:
423 before = s[0] == '-'
424 case token.QUO:
425 before = s[0] == '*'
426 }
427 return before, false
428 }
429
View as plain text