ufmt.gno
15.49 Kb · 623 lines
1// Package ufmt provides utility functions for formatting strings, similarly to
2// the Go package "fmt", of which only a subset is currently supported (hence
3// the name µfmt - micro fmt). It includes functions like Printf, Sprintf,
4// Fprintf, and Errorf.
5// Supported formatting verbs are documented in the Sprintf function.
6package ufmt
7
8import (
9 "errors"
10 "io"
11 "strconv"
12 "strings"
13 "unicode/utf8"
14)
15
16// buffer accumulates formatted output as a byte slice.
17type buffer []byte
18
19func (b *buffer) write(p []byte) {
20 *b = append(*b, p...)
21}
22
23func (b *buffer) writeString(s string) {
24 *b = append(*b, s...)
25}
26
27func (b *buffer) writeByte(c byte) {
28 *b = append(*b, c)
29}
30
31func (b *buffer) writeRune(r rune) {
32 *b = utf8.AppendRune(*b, r)
33}
34
35// printer holds state for formatting operations.
36type printer struct {
37 buf buffer
38}
39
40func newPrinter() *printer {
41 return &printer{}
42}
43
44// Sprint formats using the default formats for its operands and returns the resulting string.
45// Sprint writes the given arguments with spaces between arguments.
46func Sprint(a ...any) string {
47 p := newPrinter()
48 p.doPrint(a)
49 return string(p.buf)
50}
51
52// doPrint formats arguments using default formats and writes to printer's buffer.
53// Spaces are added between arguments.
54func (p *printer) doPrint(args []any) {
55 for argNum, arg := range args {
56 if argNum > 0 {
57 p.buf.writeRune(' ')
58 }
59
60 switch v := arg.(type) {
61 case string:
62 p.buf.writeString(v)
63 case (interface{ String() string }):
64 p.buf.writeString(v.String())
65 case error:
66 p.buf.writeString(v.Error())
67 case float64:
68 p.buf.writeString(Sprintf("%f", v))
69 case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
70 p.buf.writeString(Sprintf("%d", v))
71 case bool:
72 if v {
73 p.buf.writeString("true")
74 } else {
75 p.buf.writeString("false")
76 }
77 case nil:
78 p.buf.writeString("<nil>")
79 default:
80 p.buf.writeString("(unhandled)")
81 }
82 }
83}
84
85// doPrintln appends a newline after formatting arguments with doPrint.
86func (p *printer) doPrintln(a []any) {
87 p.doPrint(a)
88 p.buf.writeByte('\n')
89}
90
91// Sprintf offers similar functionality to Go's fmt.Sprintf, or the sprintf
92// equivalent available in many languages, including C/C++.
93// The number of args passed must exactly match the arguments consumed by the format.
94// A limited number of formatting verbs and features are currently supported.
95//
96// Supported verbs:
97//
98// %s: Places a string value directly.
99// If the value implements the interface interface{ String() string },
100// the String() method is called to retrieve the value. Same about Error()
101// string.
102// %c: Formats the character represented by Unicode code point
103// %d: Formats an integer value using package "strconv".
104// Currently supports only uint, uint64, int, int64.
105// %f: Formats a float value, with a default precision of 6.
106// %e: Formats a float with scientific notation; 1.23456e+78
107// %E: Formats a float with scientific notation; 1.23456E+78
108// %F: The same as %f
109// %g: Formats a float value with %e for large exponents, and %f with full precision for smaller numbers
110// %G: Formats a float value with %G for large exponents, and %F with full precision for smaller numbers
111// %t: Formats a boolean value to "true" or "false".
112// %x: Formats an integer value as a hexadecimal string.
113// Currently supports only uint8, []uint8, [32]uint8.
114// %c: Formats a rune value as a string.
115// Currently supports only rune, int.
116// %q: Formats a string value as a quoted string.
117// %T: Formats the type of the value.
118// %v: Formats the value with a default representation appropriate for the value's type
119// - nil: <nil>
120// - bool: true/false
121// - integers: base 10
122// - float64: %g format
123// - string: verbatim
124// - types with String()/Error(): method result
125// - others: (unhandled)
126// %%: Outputs a literal %. Does not consume an argument.
127//
128// Unsupported verbs or type mismatches produce error strings like "%!d(string=foo)".
129func Sprintf(format string, a ...any) string {
130 p := newPrinter()
131 p.doPrintf(format, a)
132 return string(p.buf)
133}
134
135// doPrintf parses the format string and writes formatted arguments to the buffer.
136func (p *printer) doPrintf(format string, args []any) {
137 sTor := []rune(format)
138 end := len(sTor)
139 argNum := 0
140 argLen := len(args)
141
142 for i := 0; i < end; {
143 isLast := i == end-1
144 c := sTor[i]
145
146 if isLast || c != '%' {
147 // we don't check for invalid format like a one ending with "%"
148 p.buf.writeRune(c)
149 i++
150 continue
151 }
152
153 length := -1
154 precision := -1
155 i++ // skip '%'
156
157 digits := func() string {
158 start := i
159 for i < end && sTor[i] >= '0' && sTor[i] <= '9' {
160 i++
161 }
162 if i > start {
163 return string(sTor[start:i])
164 }
165 return ""
166 }
167
168 if l := digits(); l != "" {
169 var err error
170 length, err = strconv.Atoi(l)
171 if err != nil {
172 panic("ufmt: invalid length specification")
173 }
174 }
175
176 if i < end && sTor[i] == '.' {
177 i++ // skip '.'
178 if l := digits(); l != "" {
179 var err error
180 precision, err = strconv.Atoi(l)
181 if err != nil {
182 panic("ufmt: invalid precision specification")
183 }
184 }
185 }
186
187 if i >= end {
188 panic("ufmt: invalid format string")
189 }
190
191 verb := sTor[i]
192 if verb == '%' {
193 p.buf.writeRune('%')
194 i++
195 continue
196 }
197
198 if argNum >= argLen {
199 panic("ufmt: not enough arguments")
200 }
201 arg := args[argNum]
202 argNum++
203
204 switch verb {
205 case 'v':
206 writeValue(p, verb, arg)
207 case 's':
208 writeStringWithLength(p, verb, arg, length)
209 case 'c':
210 writeChar(p, verb, arg)
211 case 'd':
212 writeInt(p, verb, arg)
213 case 'e', 'E', 'f', 'F', 'g', 'G':
214 writeFloatWithPrecision(p, verb, arg, precision)
215 case 't':
216 writeBool(p, verb, arg)
217 case 'x':
218 writeHex(p, verb, arg)
219 case 'q':
220 writeQuotedString(p, verb, arg)
221 case 'T':
222 writeType(p, arg)
223 // % handled before, as it does not consume an argument
224 default:
225 p.buf.writeString("(unhandled verb: %" + string(verb) + ")")
226 }
227
228 i++
229 }
230
231 if argNum < argLen {
232 panic("ufmt: too many arguments")
233 }
234}
235
236// writeValue handles %v formatting
237func writeValue(p *printer, verb rune, arg any) {
238 switch v := arg.(type) {
239 case nil:
240 p.buf.writeString("<nil>")
241 case bool:
242 writeBool(p, verb, v)
243 case int:
244 p.buf.writeString(strconv.Itoa(v))
245 case int8:
246 p.buf.writeString(strconv.Itoa(int(v)))
247 case int16:
248 p.buf.writeString(strconv.Itoa(int(v)))
249 case int32:
250 p.buf.writeString(strconv.Itoa(int(v)))
251 case int64:
252 p.buf.writeString(strconv.Itoa(int(v)))
253 case uint:
254 p.buf.writeString(strconv.FormatUint(uint64(v), 10))
255 case uint8:
256 p.buf.writeString(strconv.FormatUint(uint64(v), 10))
257 case uint16:
258 p.buf.writeString(strconv.FormatUint(uint64(v), 10))
259 case uint32:
260 p.buf.writeString(strconv.FormatUint(uint64(v), 10))
261 case uint64:
262 p.buf.writeString(strconv.FormatUint(v, 10))
263 case float64:
264 p.buf.writeString(strconv.FormatFloat(v, 'g', -1, 64))
265 case string:
266 p.buf.writeString(v)
267 case []byte:
268 p.buf.write(v)
269 case []rune:
270 p.buf.writeString(string(v))
271 case (interface{ String() string }):
272 p.buf.writeString(v.String())
273 case error:
274 p.buf.writeString(v.Error())
275 default:
276 p.buf.writeString(fallback(verb, v))
277 }
278}
279
280// writeStringWithLength handles %s formatting with length specification
281func writeStringWithLength(p *printer, verb rune, arg any, length int) {
282 var s string
283 switch v := arg.(type) {
284 case (interface{ String() string }):
285 s = v.String()
286 case error:
287 s = v.Error()
288 case string:
289 s = v
290 default:
291 s = fallback(verb, v)
292 }
293
294 if length > 0 && len(s) < length {
295 s = strings.Repeat(" ", length-len(s)) + s
296 }
297 p.buf.writeString(s)
298}
299
300// writeChar handles %c formatting
301func writeChar(p *printer, verb rune, arg any) {
302 switch v := arg.(type) {
303 // rune is int32. Exclude overflowing numeric types and dups (byte, int32):
304 case rune:
305 p.buf.writeString(string(v))
306 case int:
307 p.buf.writeRune(rune(v))
308 case int8:
309 p.buf.writeRune(rune(v))
310 case int16:
311 p.buf.writeRune(rune(v))
312 case uint:
313 p.buf.writeRune(rune(v))
314 case uint8:
315 p.buf.writeRune(rune(v))
316 case uint16:
317 p.buf.writeRune(rune(v))
318 default:
319 p.buf.writeString(fallback(verb, v))
320 }
321}
322
323// writeInt handles %d formatting
324func writeInt(p *printer, verb rune, arg any) {
325 switch v := arg.(type) {
326 case int:
327 p.buf.writeString(strconv.Itoa(v))
328 case int8:
329 p.buf.writeString(strconv.Itoa(int(v)))
330 case int16:
331 p.buf.writeString(strconv.Itoa(int(v)))
332 case int32:
333 p.buf.writeString(strconv.Itoa(int(v)))
334 case int64:
335 p.buf.writeString(strconv.Itoa(int(v)))
336 case uint:
337 p.buf.writeString(strconv.FormatUint(uint64(v), 10))
338 case uint8:
339 p.buf.writeString(strconv.FormatUint(uint64(v), 10))
340 case uint16:
341 p.buf.writeString(strconv.FormatUint(uint64(v), 10))
342 case uint32:
343 p.buf.writeString(strconv.FormatUint(uint64(v), 10))
344 case uint64:
345 p.buf.writeString(strconv.FormatUint(v, 10))
346 default:
347 p.buf.writeString(fallback(verb, v))
348 }
349}
350
351// writeFloatWithPrecision handles floating-point formatting with precision
352func writeFloatWithPrecision(p *printer, verb rune, arg any, precision int) {
353 switch v := arg.(type) {
354 case float64:
355 format := byte(verb)
356 if format == 'F' {
357 format = 'f'
358 }
359 if precision < 0 {
360 switch format {
361 case 'e', 'E':
362 precision = 2
363 default:
364 precision = 6
365 }
366 }
367 p.buf = strconv.AppendFloat(p.buf, v, format, precision, 64)
368 default:
369 p.buf.writeString(fallback(verb, v))
370 }
371}
372
373// writeBool handles %t formatting
374func writeBool(p *printer, verb rune, arg any) {
375 switch v := arg.(type) {
376 case bool:
377 if v {
378 p.buf.writeString("true")
379 } else {
380 p.buf.writeString("false")
381 }
382 default:
383 p.buf.writeString(fallback(verb, v))
384 }
385}
386
387// writeHex handles %x formatting
388func writeHex(p *printer, verb rune, arg any) {
389 switch v := arg.(type) {
390 case uint8:
391 p.buf.writeString(strconv.FormatUint(uint64(v), 16))
392 default:
393 p.buf.writeString("(unhandled)")
394 }
395}
396
397// writeQuotedString handles %q formatting
398func writeQuotedString(p *printer, verb rune, arg any) {
399 switch v := arg.(type) {
400 case string:
401 p.buf.writeString(strconv.Quote(v))
402 default:
403 p.buf.writeString("(unhandled)")
404 }
405}
406
407// writeType handles %T formatting
408func writeType(p *printer, arg any) {
409 switch arg.(type) {
410 case bool:
411 p.buf.writeString("bool")
412 case int:
413 p.buf.writeString("int")
414 case int8:
415 p.buf.writeString("int8")
416 case int16:
417 p.buf.writeString("int16")
418 case int32:
419 p.buf.writeString("int32")
420 case int64:
421 p.buf.writeString("int64")
422 case uint:
423 p.buf.writeString("uint")
424 case uint8:
425 p.buf.writeString("uint8")
426 case uint16:
427 p.buf.writeString("uint16")
428 case uint32:
429 p.buf.writeString("uint32")
430 case uint64:
431 p.buf.writeString("uint64")
432 case string:
433 p.buf.writeString("string")
434 case []byte:
435 p.buf.writeString("[]byte")
436 case []rune:
437 p.buf.writeString("[]rune")
438 default:
439 p.buf.writeString("unknown")
440 }
441}
442
443// Fprintf formats according to a format specifier and writes to w.
444// Returns the number of bytes written and any write error encountered.
445func Fprintf(w io.Writer, format string, a ...any) (n int, err error) {
446 p := newPrinter()
447 p.doPrintf(format, a)
448 return w.Write(p.buf)
449}
450
451// Printf formats according to a format specifier and writes to standard output.
452// Returns the number of bytes written and any write error encountered.
453//
454// XXX: Replace with os.Stdout handling when available.
455func Printf(format string, a ...any) (n int, err error) {
456 var out strings.Builder
457 n, err = Fprintf(&out, format, a...)
458 print(out.String())
459 return n, err
460}
461
462// Appendf formats according to a format specifier, appends the result to the byte
463// slice, and returns the updated slice.
464func Appendf(b []byte, format string, a ...any) []byte {
465 p := newPrinter()
466 p.doPrintf(format, a)
467 return append(b, p.buf...)
468}
469
470// Fprint formats using default formats and writes to w.
471// Spaces are added between arguments.
472// Returns the number of bytes written and any write error encountered.
473func Fprint(w io.Writer, a ...any) (n int, err error) {
474 p := newPrinter()
475 p.doPrint(a)
476 return w.Write(p.buf)
477}
478
479// Print formats using default formats and writes to standard output.
480// Spaces are added between arguments.
481// Returns the number of bytes written and any write error encountered.
482//
483// XXX: Replace with os.Stdout handling when available.
484func Print(a ...any) (n int, err error) {
485 var out strings.Builder
486 n, err = Fprint(&out, a...)
487 print(out.String())
488 return n, err
489}
490
491// Append formats using default formats, appends to b, and returns the updated slice.
492// Spaces are added between arguments.
493func Append(b []byte, a ...any) []byte {
494 p := newPrinter()
495 p.doPrint(a)
496 return append(b, p.buf...)
497}
498
499// Fprintln formats using default formats and writes to w with newline.
500// Returns the number of bytes written and any write error encountered.
501func Fprintln(w io.Writer, a ...any) (n int, err error) {
502 p := newPrinter()
503 p.doPrintln(a)
504 return w.Write(p.buf)
505}
506
507// Println formats using default formats and writes to standard output with newline.
508// Returns the number of bytes written and any write error encountered.
509//
510// XXX: Replace with os.Stdout handling when available.
511func Println(a ...any) (n int, err error) {
512 var out strings.Builder
513 n, err = Fprintln(&out, a...)
514 print(out.String())
515 return n, err
516}
517
518// Sprintln formats using default formats and returns the string with newline.
519// Spaces are always added between arguments.
520func Sprintln(a ...any) string {
521 p := newPrinter()
522 p.doPrintln(a)
523 return string(p.buf)
524}
525
526// Appendln formats using default formats, appends to b, and returns the updated slice.
527// Appends a newline after the last argument.
528func Appendln(b []byte, a ...any) []byte {
529 p := newPrinter()
530 p.doPrintln(a)
531 return append(b, p.buf...)
532}
533
534// This function is used to mimic Go's fmt.Sprintf
535// specific behaviour of showing verb/type mismatches,
536// where for example:
537//
538// fmt.Sprintf("%d", "foo") gives "%!d(string=foo)"
539//
540// Here:
541//
542// fallback("s", 8) -> "%!s(int=8)"
543// fallback("d", nil) -> "%!d(<nil>)", and so on.f
544func fallback(verb rune, arg any) string {
545 var s string
546 switch v := arg.(type) {
547 case string:
548 s = "string=" + v
549 case (interface{ String() string }):
550 s = "string=" + v.String()
551 case error:
552 // note: also "string=" in Go fmt
553 s = "string=" + v.Error()
554 case float64:
555 s = "float64=" + Sprintf("%f", v)
556 case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
557 // note: rune, byte would be dups, being aliases
558 if typename, e := typeToString(v); e == nil {
559 s = typename + "=" + Sprintf("%d", v)
560 } else {
561 panic("ufmt: unexpected type error")
562 }
563 case bool:
564 s = "bool=" + strconv.FormatBool(v)
565 case nil:
566 s = "<nil>"
567 default:
568 s = "(unhandled)"
569 }
570 return "%!" + string(verb) + "(" + s + ")"
571}
572
573// typeToString returns the name of basic Go types as string.
574func typeToString(v any) (string, error) {
575 switch v.(type) {
576 case string:
577 return "string", nil
578 case int:
579 return "int", nil
580 case int8:
581 return "int8", nil
582 case int16:
583 return "int16", nil
584 case int32:
585 return "int32", nil
586 case int64:
587 return "int64", nil
588 case uint:
589 return "uint", nil
590 case uint8:
591 return "uint8", nil
592 case uint16:
593 return "uint16", nil
594 case uint32:
595 return "uint32", nil
596 case uint64:
597 return "uint64", nil
598 case float32:
599 return "float32", nil
600 case float64:
601 return "float64", nil
602 case bool:
603 return "bool", nil
604 default:
605 return "", errors.New("unsupported type")
606 }
607}
608
609// errMsg implements the error interface for formatted error strings.
610type errMsg struct {
611 msg string
612}
613
614// Error returns the formatted error message.
615func (e *errMsg) Error() string {
616 return e.msg
617}
618
619// Errorf formats according to a format specifier and returns an error value.
620// Supports the same verbs as Sprintf. See Sprintf documentation for details.
621func Errorf(format string, args ...any) error {
622 return &errMsg{Sprintf(format, args...)}
623}