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}