format.gno
8.10 Kb · 408 lines
1package ufmt
2
3import (
4 "strconv"
5 "unicode/utf8"
6)
7
8// This file is the low level: padding, integers, floats, strings. It is a port
9// of format.go in Go's fmt, kept close enough to read side by side.
10
11const (
12 ldigits = "0123456789abcdefx"
13 udigits = "0123456789ABCDEFX"
14)
15
16// clearFlags resets everything a verb's specifier sets.
17func (p *printer) clearFlags() {
18 p.plus, p.minus, p.sharp, p.space, p.zero = false, false, false, false, false
19 p.plusV, p.sharpV = false, false
20 p.wid, p.prec = 0, 0
21 p.widPresent, p.precPresent = false, false
22}
23
24// writePadding writes n pad bytes: zeros when the 0 flag is set, else spaces.
25func (p *printer) writePadding(n int) {
26 if n <= 0 {
27 return
28 }
29 c := byte(' ')
30 if p.zero {
31 c = '0'
32 }
33 for i := 0; i < n; i++ {
34 p.buf = append(p.buf, c)
35 }
36}
37
38// pad writes b, padded to the width in runes, on the side the - flag says.
39func (p *printer) pad(b []byte) {
40 if !p.widPresent || p.wid == 0 {
41 p.buf = append(p.buf, b...)
42 return
43 }
44 width := p.wid - utf8.RuneCount(b)
45 if !p.minus {
46 p.writePadding(width)
47 p.buf = append(p.buf, b...)
48 } else {
49 p.buf = append(p.buf, b...)
50 p.writePadding(width)
51 }
52}
53
54// padString is pad for a string.
55func (p *printer) padString(s string) {
56 if !p.widPresent || p.wid == 0 {
57 p.buf = append(p.buf, s...)
58 return
59 }
60 width := p.wid - utf8.RuneCountInString(s)
61 if !p.minus {
62 p.writePadding(width)
63 p.buf = append(p.buf, s...)
64 } else {
65 p.buf = append(p.buf, s...)
66 p.writePadding(width)
67 }
68}
69
70// padNoZero pads with spaces whatever the 0 flag says.
71func (p *printer) padNoZero(b []byte) {
72 oldZero := p.zero
73 p.zero = false
74 p.pad(b)
75 p.zero = oldZero
76}
77
78func (p *printer) fmtBoolean(v bool) {
79 if v {
80 p.padString("true")
81 } else {
82 p.padString("false")
83 }
84}
85
86// fmtInteger formats u, the magnitude, in base, with a '-' when negative.
87func (p *printer) fmtInteger(u uint64, negative bool, base int, verb rune, digits string) {
88 // Fast path: no flags, width or precision, lowercase digits.
89 if !p.widPresent && !p.precPresent && !p.plus && !p.space && !p.sharp &&
90 verb != 'O' && (base != 16 || digits[10] == 'a') {
91 if negative {
92 p.buf = append(p.buf, '-')
93 }
94 p.buf = strconv.AppendUint(p.buf, u, base)
95 return
96 }
97
98 prec := 0
99 if p.precPresent {
100 prec = p.prec
101 // %.0d of zero prints nothing but its padding.
102 if prec == 0 && u == 0 {
103 oldZero := p.zero
104 p.zero = false
105 p.writePadding(p.wid)
106 p.zero = oldZero
107 return
108 }
109 } else if p.zero && !p.minus && p.widPresent {
110 // Zero padding is a precision in disguise, minus room for the sign.
111 prec = p.wid
112 if negative || p.plus || p.space {
113 prec--
114 }
115 }
116
117 size := 68 // 64 binary digits, a sign and a 0b/0x/0o prefix
118 if prec+4 > size {
119 size = prec + 4
120 }
121 buf := make([]byte, size)
122 i := size
123
124 switch base {
125 case 10:
126 for u >= 10 {
127 i--
128 next := u / 10
129 buf[i] = byte('0' + u - next*10)
130 u = next
131 }
132 case 16:
133 for u >= 16 {
134 i--
135 buf[i] = digits[u&0xF]
136 u >>= 4
137 }
138 case 8:
139 for u >= 8 {
140 i--
141 buf[i] = byte('0' + u&7)
142 u >>= 3
143 }
144 case 2:
145 for u >= 2 {
146 i--
147 buf[i] = byte('0' + u&1)
148 u >>= 1
149 }
150 }
151 i--
152 buf[i] = digits[u]
153 for i > 0 && prec > size-i {
154 i--
155 buf[i] = '0'
156 }
157
158 if p.sharp {
159 switch base {
160 case 2:
161 i--
162 buf[i] = 'b'
163 i--
164 buf[i] = '0'
165 case 8:
166 if buf[i] != '0' {
167 i--
168 buf[i] = '0'
169 }
170 case 16:
171 i--
172 buf[i] = digits[16]
173 i--
174 buf[i] = '0'
175 }
176 }
177 if verb == 'O' {
178 i--
179 buf[i] = 'o'
180 i--
181 buf[i] = '0'
182 }
183
184 if negative {
185 i--
186 buf[i] = '-'
187 } else if p.plus {
188 i--
189 buf[i] = '+'
190 } else if p.space {
191 i--
192 buf[i] = ' '
193 }
194
195 // The zeros, if any, are already in buf: pad the rest with spaces.
196 p.padNoZero(buf[i:])
197}
198
199// fmtUnicode formats u as "U+0078", or "U+0078 'x'" with the # flag.
200func (p *printer) fmtUnicode(u uint64) {
201 prec := 4
202 if p.precPresent && p.prec > 4 {
203 prec = p.prec
204 }
205 hex := strconv.FormatUint(u, 16)
206 b := []byte("U+")
207 for n := len(hex); n < prec; n++ {
208 b = append(b, '0')
209 }
210 for i := 0; i < len(hex); i++ {
211 c := hex[i]
212 if c >= 'a' {
213 c -= 'a' - 'A'
214 }
215 b = append(b, c)
216 }
217 if p.sharp && u <= utf8.MaxRune && strconv.IsPrint(rune(u)) {
218 b = append(b, ' ', '\'')
219 b = utf8.AppendRune(b, rune(u))
220 b = append(b, '\'')
221 }
222 p.padNoZero(b)
223}
224
225// fmtC formats u as the rune it encodes, or U+FFFD when it encodes none.
226func (p *printer) fmtC(u uint64) {
227 r := rune(utf8.RuneError)
228 if u <= utf8.MaxRune {
229 r = rune(u)
230 }
231 p.pad(utf8.AppendRune(nil, r))
232}
233
234// fmtQc formats u as a single-quoted rune literal.
235func (p *printer) fmtQc(u uint64) {
236 r := rune(utf8.RuneError)
237 if u <= utf8.MaxRune {
238 r = rune(u)
239 }
240 if p.plus {
241 p.pad(strconv.AppendQuoteRuneToASCII(nil, r))
242 } else {
243 p.pad(strconv.AppendQuoteRune(nil, r))
244 }
245}
246
247// fmtFloat formats v, with prec digits (-1 for the shortest) unless the verb
248// set its own precision.
249func (p *printer) fmtFloat(v float64, size int, verb rune, prec int) {
250 if p.precPresent {
251 prec = p.prec
252 }
253 // num[0] is reserved for the sign.
254 num := strconv.AppendFloat([]byte{0}, v, byte(verb), prec, size)
255 if num[1] == '-' || num[1] == '+' {
256 num = num[1:]
257 } else {
258 num[0] = '+'
259 }
260 if p.space && num[0] == '+' && !p.plus {
261 num[0] = ' '
262 }
263 // Infinities and NaN do not look like numbers, so they are never zero padded.
264 if num[1] == 'I' || num[1] == 'N' {
265 oldZero := p.zero
266 p.zero = false
267 if num[1] == 'N' && !p.space && !p.plus {
268 num = num[1:]
269 }
270 p.pad(num)
271 p.zero = oldZero
272 return
273 }
274
275 // The # flag keeps the decimal point, and for %g the trailing zeros.
276 if p.sharp && verb != 'b' {
277 digits := 0
278 if verb == 'v' || verb == 'g' || verb == 'G' || verb == 'x' {
279 digits = prec
280 if digits == -1 {
281 digits = 6
282 }
283 }
284 var tail []byte
285 hasDecimalPoint := false
286 sawNonzeroDigit := false
287 for i := 1; i < len(num); i++ {
288 c := num[i]
289 if c == '.' {
290 hasDecimalPoint = true
291 continue
292 }
293 if c == 'p' || c == 'P' || ((c == 'e' || c == 'E') && verb != 'x' && verb != 'X') {
294 tail = append(tail, num[i:]...)
295 num = num[:i]
296 break
297 }
298 if c != '0' {
299 sawNonzeroDigit = true
300 }
301 if sawNonzeroDigit {
302 digits--
303 }
304 }
305 if !hasDecimalPoint {
306 // A lone leading zero counts as one significant digit.
307 if len(num) == 2 && num[1] == '0' {
308 digits--
309 }
310 num = append(num, '.')
311 }
312 for digits > 0 {
313 num = append(num, '0')
314 digits--
315 }
316 num = append(num, tail...)
317 }
318
319 if p.plus || num[0] != '+' {
320 // Zero padding goes after the sign.
321 if p.zero && !p.minus && p.widPresent && p.wid > len(num) {
322 p.buf = append(p.buf, num[0])
323 p.writePadding(p.wid - len(num))
324 p.buf = append(p.buf, num[1:]...)
325 return
326 }
327 p.pad(num)
328 return
329 }
330 p.pad(num[1:])
331}
332
333// truncate cuts s to the precision, counted in runes.
334func (p *printer) truncate(s string) string {
335 if p.precPresent {
336 n := p.prec
337 for i := range s {
338 n--
339 if n < 0 {
340 return s[:i]
341 }
342 }
343 }
344 return s
345}
346
347func (p *printer) fmtS(s string) {
348 p.padString(p.truncate(s))
349}
350
351// fmtSx formats s as hex, two digits a byte. The space flag separates bytes,
352// and # prefixes 0x, once or before each byte when spaced.
353func (p *printer) fmtSx(s string, digits string) {
354 length := len(s)
355 if p.precPresent && p.prec < length {
356 length = p.prec
357 }
358 width := 2 * length
359 if width > 0 {
360 if p.space {
361 if p.sharp {
362 width *= 2
363 }
364 width += length - 1
365 } else if p.sharp {
366 width += 2
367 }
368 } else {
369 if p.widPresent {
370 p.writePadding(p.wid)
371 }
372 return
373 }
374 if p.widPresent && p.wid > width && !p.minus {
375 p.writePadding(p.wid - width)
376 }
377 if p.sharp {
378 p.buf = append(p.buf, '0', digits[16])
379 }
380 for i := 0; i < length; i++ {
381 if p.space && i > 0 {
382 p.buf = append(p.buf, ' ')
383 if p.sharp {
384 p.buf = append(p.buf, '0', digits[16])
385 }
386 }
387 c := s[i]
388 p.buf = append(p.buf, digits[c>>4], digits[c&0xF])
389 }
390 if p.widPresent && p.wid > width && p.minus {
391 p.writePadding(p.wid - width)
392 }
393}
394
395// fmtQ formats s as a double-quoted Go string, backquoted with the # flag when
396// it can be, and ASCII only with the + flag.
397func (p *printer) fmtQ(s string) {
398 s = p.truncate(s)
399 if p.sharp && strconv.CanBackquote(s) {
400 p.padString("`" + s + "`")
401 return
402 }
403 if p.plus {
404 p.pad(strconv.AppendQuoteToASCII(nil, s))
405 } else {
406 p.pad(strconv.AppendQuote(nil, s))
407 }
408}