ufmt.gno
15.58 Kb · 625 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 if i < end && sTor[i] >= '0' && sTor[i] <= '9' {
158 start := i
159 for i < end && sTor[i] >= '0' && sTor[i] <= '9' {
160 i++
161 }
162 if i > start {
163 var n int64
164 var err error
165 n, err = strconv.ParseInt(string(sTor[start:i]), 10, 0)
166 if err != nil {
167 panic("ufmt: invalid length specification")
168 }
169 length = int(n)
170 }
171 }
172
173 if i < end && sTor[i] == '.' {
174 i++ // skip '.'
175 start := i
176 for i < end && sTor[i] >= '0' && sTor[i] <= '9' {
177 i++
178 }
179 if i > start {
180 precStr := string(sTor[start:i])
181 var err error
182 precision, err = strconv.Atoi(precStr)
183 if err != nil {
184 panic("ufmt: invalid precision specification")
185 }
186 }
187 }
188
189 if i >= end {
190 panic("ufmt: invalid format string")
191 }
192
193 verb := sTor[i]
194 if verb == '%' {
195 p.buf.writeRune('%')
196 i++
197 continue
198 }
199
200 if argNum >= argLen {
201 panic("ufmt: not enough arguments")
202 }
203 arg := args[argNum]
204 argNum++
205
206 switch verb {
207 case 'v':
208 writeValue(p, verb, arg)
209 case 's':
210 writeStringWithLength(p, verb, arg, length)
211 case 'c':
212 writeChar(p, verb, arg)
213 case 'd':
214 writeInt(p, verb, arg)
215 case 'e', 'E', 'f', 'F', 'g', 'G':
216 writeFloatWithPrecision(p, verb, arg, precision)
217 case 't':
218 writeBool(p, verb, arg)
219 case 'x':
220 writeHex(p, verb, arg)
221 case 'q':
222 writeQuotedString(p, verb, arg)
223 case 'T':
224 writeType(p, arg)
225 // % handled before, as it does not consume an argument
226 default:
227 p.buf.writeString("(unhandled verb: %" + string(verb) + ")")
228 }
229
230 i++
231 }
232
233 if argNum < argLen {
234 panic("ufmt: too many arguments")
235 }
236}
237
238// writeValue handles %v formatting
239func writeValue(p *printer, verb rune, arg any) {
240 switch v := arg.(type) {
241 case nil:
242 p.buf.writeString("<nil>")
243 case bool:
244 writeBool(p, verb, v)
245 case int:
246 p.buf.writeString(strconv.Itoa(v))
247 case int8:
248 p.buf.writeString(strconv.Itoa(int(v)))
249 case int16:
250 p.buf.writeString(strconv.Itoa(int(v)))
251 case int32:
252 p.buf.writeString(strconv.Itoa(int(v)))
253 case int64:
254 p.buf.writeString(strconv.Itoa(int(v)))
255 case uint:
256 p.buf.writeString(strconv.FormatUint(uint64(v), 10))
257 case uint8:
258 p.buf.writeString(strconv.FormatUint(uint64(v), 10))
259 case uint16:
260 p.buf.writeString(strconv.FormatUint(uint64(v), 10))
261 case uint32:
262 p.buf.writeString(strconv.FormatUint(uint64(v), 10))
263 case uint64:
264 p.buf.writeString(strconv.FormatUint(v, 10))
265 case float64:
266 p.buf.writeString(strconv.FormatFloat(v, 'g', -1, 64))
267 case string:
268 p.buf.writeString(v)
269 case []byte:
270 p.buf.write(v)
271 case []rune:
272 p.buf.writeString(string(v))
273 case (interface{ String() string }):
274 p.buf.writeString(v.String())
275 case error:
276 p.buf.writeString(v.Error())
277 default:
278 p.buf.writeString(fallback(verb, v))
279 }
280}
281
282// writeStringWithLength handles %s formatting with length specification
283func writeStringWithLength(p *printer, verb rune, arg any, length int) {
284 var s string
285 switch v := arg.(type) {
286 case (interface{ String() string }):
287 s = v.String()
288 case error:
289 s = v.Error()
290 case string:
291 s = v
292 default:
293 s = fallback(verb, v)
294 }
295
296 if length > 0 && len(s) > length {
297 s = s[:length]
298 }
299 p.buf.writeString(s)
300}
301
302// writeChar handles %c formatting
303func writeChar(p *printer, verb rune, arg any) {
304 switch v := arg.(type) {
305 // rune is int32. Exclude overflowing numeric types and dups (byte, int32):
306 case rune:
307 p.buf.writeString(string(v))
308 case int:
309 p.buf.writeRune(rune(v))
310 case int8:
311 p.buf.writeRune(rune(v))
312 case int16:
313 p.buf.writeRune(rune(v))
314 case uint:
315 p.buf.writeRune(rune(v))
316 case uint8:
317 p.buf.writeRune(rune(v))
318 case uint16:
319 p.buf.writeRune(rune(v))
320 default:
321 p.buf.writeString(fallback(verb, v))
322 }
323}
324
325// writeInt handles %d formatting
326func writeInt(p *printer, verb rune, arg any) {
327 switch v := arg.(type) {
328 case int:
329 p.buf.writeString(strconv.Itoa(v))
330 case int8:
331 p.buf.writeString(strconv.Itoa(int(v)))
332 case int16:
333 p.buf.writeString(strconv.Itoa(int(v)))
334 case int32:
335 p.buf.writeString(strconv.Itoa(int(v)))
336 case int64:
337 p.buf.writeString(strconv.Itoa(int(v)))
338 case uint:
339 p.buf.writeString(strconv.FormatUint(uint64(v), 10))
340 case uint8:
341 p.buf.writeString(strconv.FormatUint(uint64(v), 10))
342 case uint16:
343 p.buf.writeString(strconv.FormatUint(uint64(v), 10))
344 case uint32:
345 p.buf.writeString(strconv.FormatUint(uint64(v), 10))
346 case uint64:
347 p.buf.writeString(strconv.FormatUint(v, 10))
348 default:
349 p.buf.writeString(fallback(verb, v))
350 }
351}
352
353// writeFloatWithPrecision handles floating-point formatting with precision
354func writeFloatWithPrecision(p *printer, verb rune, arg any, precision int) {
355 switch v := arg.(type) {
356 case float64:
357 format := byte(verb)
358 if format == 'F' {
359 format = 'f'
360 }
361 if precision < 0 {
362 switch format {
363 case 'e', 'E':
364 precision = 2
365 default:
366 precision = 6
367 }
368 }
369 p.buf = strconv.AppendFloat(p.buf, v, format, precision, 64)
370 default:
371 p.buf.writeString(fallback(verb, v))
372 }
373}
374
375// writeBool handles %t formatting
376func writeBool(p *printer, verb rune, arg any) {
377 switch v := arg.(type) {
378 case bool:
379 if v {
380 p.buf.writeString("true")
381 } else {
382 p.buf.writeString("false")
383 }
384 default:
385 p.buf.writeString(fallback(verb, v))
386 }
387}
388
389// writeHex handles %x formatting
390func writeHex(p *printer, verb rune, arg any) {
391 switch v := arg.(type) {
392 case uint8:
393 p.buf.writeString(strconv.FormatUint(uint64(v), 16))
394 default:
395 p.buf.writeString("(unhandled)")
396 }
397}
398
399// writeQuotedString handles %q formatting
400func writeQuotedString(p *printer, verb rune, arg any) {
401 switch v := arg.(type) {
402 case string:
403 p.buf.writeString(strconv.Quote(v))
404 default:
405 p.buf.writeString("(unhandled)")
406 }
407}
408
409// writeType handles %T formatting
410func writeType(p *printer, arg any) {
411 switch arg.(type) {
412 case bool:
413 p.buf.writeString("bool")
414 case int:
415 p.buf.writeString("int")
416 case int8:
417 p.buf.writeString("int8")
418 case int16:
419 p.buf.writeString("int16")
420 case int32:
421 p.buf.writeString("int32")
422 case int64:
423 p.buf.writeString("int64")
424 case uint:
425 p.buf.writeString("uint")
426 case uint8:
427 p.buf.writeString("uint8")
428 case uint16:
429 p.buf.writeString("uint16")
430 case uint32:
431 p.buf.writeString("uint32")
432 case uint64:
433 p.buf.writeString("uint64")
434 case string:
435 p.buf.writeString("string")
436 case []byte:
437 p.buf.writeString("[]byte")
438 case []rune:
439 p.buf.writeString("[]rune")
440 default:
441 p.buf.writeString("unknown")
442 }
443}
444
445// Fprintf formats according to a format specifier and writes to w.
446// Returns the number of bytes written and any write error encountered.
447func Fprintf(w io.Writer, format string, a ...any) (n int, err error) {
448 p := newPrinter()
449 p.doPrintf(format, a)
450 return w.Write(p.buf)
451}
452
453// Printf formats according to a format specifier and writes to standard output.
454// Returns the number of bytes written and any write error encountered.
455//
456// XXX: Replace with os.Stdout handling when available.
457func Printf(format string, a ...any) (n int, err error) {
458 var out strings.Builder
459 n, err = Fprintf(&out, format, a...)
460 print(out.String())
461 return n, err
462}
463
464// Appendf formats according to a format specifier, appends the result to the byte
465// slice, and returns the updated slice.
466func Appendf(b []byte, format string, a ...any) []byte {
467 p := newPrinter()
468 p.doPrintf(format, a)
469 return append(b, p.buf...)
470}
471
472// Fprint formats using default formats and writes to w.
473// Spaces are added between arguments.
474// Returns the number of bytes written and any write error encountered.
475func Fprint(w io.Writer, a ...any) (n int, err error) {
476 p := newPrinter()
477 p.doPrint(a)
478 return w.Write(p.buf)
479}
480
481// Print formats using default formats and writes to standard output.
482// Spaces are added between arguments.
483// Returns the number of bytes written and any write error encountered.
484//
485// XXX: Replace with os.Stdout handling when available.
486func Print(a ...any) (n int, err error) {
487 var out strings.Builder
488 n, err = Fprint(&out, a...)
489 print(out.String())
490 return n, err
491}
492
493// Append formats using default formats, appends to b, and returns the updated slice.
494// Spaces are added between arguments.
495func Append(b []byte, a ...any) []byte {
496 p := newPrinter()
497 p.doPrint(a)
498 return append(b, p.buf...)
499}
500
501// Fprintln formats using default formats and writes to w with newline.
502// Returns the number of bytes written and any write error encountered.
503func Fprintln(w io.Writer, a ...any) (n int, err error) {
504 p := newPrinter()
505 p.doPrintln(a)
506 return w.Write(p.buf)
507}
508
509// Println formats using default formats and writes to standard output with newline.
510// Returns the number of bytes written and any write error encountered.
511//
512// XXX: Replace with os.Stdout handling when available.
513func Println(a ...any) (n int, err error) {
514 var out strings.Builder
515 n, err = Fprintln(&out, a...)
516 print(out.String())
517 return n, err
518}
519
520// Sprintln formats using default formats and returns the string with newline.
521// Spaces are always added between arguments.
522func Sprintln(a ...any) string {
523 p := newPrinter()
524 p.doPrintln(a)
525 return string(p.buf)
526}
527
528// Appendln formats using default formats, appends to b, and returns the updated slice.
529// Appends a newline after the last argument.
530func Appendln(b []byte, a ...any) []byte {
531 p := newPrinter()
532 p.doPrintln(a)
533 return append(b, p.buf...)
534}
535
536// This function is used to mimic Go's fmt.Sprintf
537// specific behaviour of showing verb/type mismatches,
538// where for example:
539//
540// fmt.Sprintf("%d", "foo") gives "%!d(string=foo)"
541//
542// Here:
543//
544// fallback("s", 8) -> "%!s(int=8)"
545// fallback("d", nil) -> "%!d(<nil>)", and so on.f
546func fallback(verb rune, arg any) string {
547 var s string
548 switch v := arg.(type) {
549 case string:
550 s = "string=" + v
551 case (interface{ String() string }):
552 s = "string=" + v.String()
553 case error:
554 // note: also "string=" in Go fmt
555 s = "string=" + v.Error()
556 case float64:
557 s = "float64=" + Sprintf("%f", v)
558 case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
559 // note: rune, byte would be dups, being aliases
560 if typename, e := typeToString(v); e == nil {
561 s = typename + "=" + Sprintf("%d", v)
562 } else {
563 panic("ufmt: unexpected type error")
564 }
565 case bool:
566 s = "bool=" + strconv.FormatBool(v)
567 case nil:
568 s = "<nil>"
569 default:
570 s = "(unhandled)"
571 }
572 return "%!" + string(verb) + "(" + s + ")"
573}
574
575// typeToString returns the name of basic Go types as string.
576func typeToString(v any) (string, error) {
577 switch v.(type) {
578 case string:
579 return "string", nil
580 case int:
581 return "int", nil
582 case int8:
583 return "int8", nil
584 case int16:
585 return "int16", nil
586 case int32:
587 return "int32", nil
588 case int64:
589 return "int64", nil
590 case uint:
591 return "uint", nil
592 case uint8:
593 return "uint8", nil
594 case uint16:
595 return "uint16", nil
596 case uint32:
597 return "uint32", nil
598 case uint64:
599 return "uint64", nil
600 case float32:
601 return "float32", nil
602 case float64:
603 return "float64", nil
604 case bool:
605 return "bool", nil
606 default:
607 return "", errors.New("unsupported type")
608 }
609}
610
611// errMsg implements the error interface for formatted error strings.
612type errMsg struct {
613 msg string
614}
615
616// Error returns the formatted error message.
617func (e *errMsg) Error() string {
618 return e.msg
619}
620
621// Errorf formats according to a format specifier and returns an error value.
622// Supports the same verbs as Sprintf. See Sprintf documentation for details.
623func Errorf(format string, args ...any) error {
624 return &errMsg{Sprintf(format, args...)}
625}