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}