1// Package ufmt provides utility functions for formatting strings, similarly to2// the Go package "fmt", of which only a subset is currently supported (hence3// 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 ufmt78import (9 "errors"10 "io"11 "strconv"12 "strings"13 "unicode/utf8"14)1516// buffer accumulates formatted output as a byte slice.17type buffer []byte1819func (b *buffer) write(p []byte) {20 *b = append(*b, p...)21}2223func (b *buffer) writeString(s string) {24 *b = append(*b, s...)25}2627func (b *buffer) writeByte(c byte) {28 *b = append(*b, c)29}3031func (b *buffer) writeRune(r rune) {32 *b = utf8.AppendRune(*b, r)33}3435// printer holds state for formatting operations.36type printer struct {37 buf buffer38}3940func newPrinter() *printer {41 return &printer{}42}4344// 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}5152// 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 }5960 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}8485// 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}9091// Sprintf offers similar functionality to Go's fmt.Sprintf, or the sprintf92// 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 point103// %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+78107// %E: Formats a float with scientific notation; 1.23456E+78108// %F: The same as %f109// %g: Formats a float value with %e for large exponents, and %f with full precision for smaller numbers110// %G: Formats a float value with %G for large exponents, and %F with full precision for smaller numbers111// %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 type119// - nil: <nil>120// - bool: true/false121// - integers: base 10122// - float64: %g format123// - string: verbatim124// - types with String()/Error(): method result125// - 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}134135// 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 := 0140 argLen := len(args)141142 for i := 0; i < end; {143 isLast := i == end-1144 c := sTor[i]145146 if isLast || c != '%' {147 // we don't check for invalid format like a one ending with "%"148 p.buf.writeRune(c)149 i++150 continue151 }152153 length := -1154 precision := -1155 i++ // skip '%'156157 digits := func() string {158 start := i159 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 }167168 if l := digits(); l != "" {169 var err error170 length, err = strconv.Atoi(l)171 if err != nil {172 panic("ufmt: invalid length specification")173 }174 }175176 if i < end && sTor[i] == '.' {177 i++ // skip '.'178 if l := digits(); l != "" {179 var err error180 precision, err = strconv.Atoi(l)181 if err != nil {182 panic("ufmt: invalid precision specification")183 }184 }185 }186187 if i >= end {188 panic("ufmt: invalid format string")189 }190191 verb := sTor[i]192 if verb == '%' {193 p.buf.writeRune('%')194 i++195 continue196 }197198 if argNum >= argLen {199 panic("ufmt: not enough arguments")200 }201 arg := args[argNum]202 argNum++203204 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 argument224 default:225 p.buf.writeString("(unhandled verb: %" + string(verb) + ")")226 }227228 i++229 }230231 if argNum < argLen {232 panic("ufmt: too many arguments")233 }234}235236// writeValue handles %v formatting237func 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}279280// writeStringWithLength handles %s formatting with length specification281func writeStringWithLength(p *printer, verb rune, arg any, length int) {282 var s string283 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 = v290 default:291 s = fallback(verb, v)292 }293294 if length > 0 && len(s) < length {295 s = strings.Repeat(" ", length-len(s)) + s296 }297 p.buf.writeString(s)298}299300// writeChar handles %c formatting301func 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}322323// writeInt handles %d formatting324func 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}350351// writeFloatWithPrecision handles floating-point formatting with precision352func 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 = 2363 default:364 precision = 6365 }366 }367 p.buf = strconv.AppendFloat(p.buf, v, format, precision, 64)368 default:369 p.buf.writeString(fallback(verb, v))370 }371}372373// writeBool handles %t formatting374func 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}386387// writeHex handles %x formatting388func 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}396397// writeQuotedString handles %q formatting398func 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}406407// writeType handles %T formatting408func 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}442443// 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}450451// 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.Builder457 n, err = Fprintf(&out, format, a...)458 print(out.String())459 return n, err460}461462// Appendf formats according to a format specifier, appends the result to the byte463// 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}469470// 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}478479// 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.Builder486 n, err = Fprint(&out, a...)487 print(out.String())488 return n, err489}490491// 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}498499// 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}506507// 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.Builder513 n, err = Fprintln(&out, a...)514 print(out.String())515 return n, err516}517518// 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}525526// 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}533534// This function is used to mimic Go's fmt.Sprintf535// 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.f544func fallback(verb rune, arg any) string {545 var s string546 switch v := arg.(type) {547 case string:548 s = "string=" + v549 case (interface{ String() string }):550 s = "string=" + v.String()551 case error:552 // note: also "string=" in Go fmt553 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 aliases558 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}572573// 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", nil578 case int:579 return "int", nil580 case int8:581 return "int8", nil582 case int16:583 return "int16", nil584 case int32:585 return "int32", nil586 case int64:587 return "int64", nil588 case uint:589 return "uint", nil590 case uint8:591 return "uint8", nil592 case uint16:593 return "uint16", nil594 case uint32:595 return "uint32", nil596 case uint64:597 return "uint64", nil598 case float32:599 return "float32", nil600 case float64:601 return "float64", nil602 case bool:603 return "bool", nil604 default:605 return "", errors.New("unsupported type")606 }607}608609// errMsg implements the error interface for formatted error strings.610type errMsg struct {611 msg string612}613614// Error returns the formatted error message.615func (e *errMsg) Error() string {616 return e.msg617}618619// 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}624Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.