2022-02-10 22:06:57 +00:00
|
|
|
//go:build !solution
|
2020-02-21 20:43:16 +00:00
|
|
|
|
|
|
|
package varfmt
|
|
|
|
|
2024-06-03 17:25:40 +00:00
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"unicode/utf8"
|
|
|
|
)
|
|
|
|
|
|
|
|
// fmt.Sprintf does 1 alloc
|
|
|
|
// this does 3-4 allocations and is a bit slower than fmt.Sprintf
|
|
|
|
// can be further optimized, by casting args slice
|
|
|
|
// to string without allocating new space (somehow)
|
|
|
|
func Sprintf(format string, args ...any) string {
|
|
|
|
// init output strings.Builder and num rune buffer
|
|
|
|
// cant use strings.Builder for storing num since
|
|
|
|
// builder can't clear its contents without reallocating
|
2024-06-03 17:56:45 +00:00
|
|
|
output, num := strings.Builder{}, make([]rune, 0, 2) // first alloc here
|
2024-06-03 17:25:40 +00:00
|
|
|
// convert args slice to strArgs string slice
|
2024-06-03 17:56:45 +00:00
|
|
|
strArgs, maxLen := make([]string, len(args), cap(args)), 0 // second alloc here
|
2024-06-03 17:25:40 +00:00
|
|
|
for i, arg := range args {
|
2024-06-03 17:56:45 +00:00
|
|
|
strArgs[i] = fmt.Sprint(arg) // third and other allocs happen here
|
|
|
|
// could be further optimized
|
2024-06-03 17:25:40 +00:00
|
|
|
if len(strArgs[i]) > maxLen {
|
|
|
|
maxLen = len(strArgs[i])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// allocate maximum possible length of the resulting string
|
2024-06-03 17:56:45 +00:00
|
|
|
output.Grow(len(format) + len(args)*maxLen) // third alloc here
|
2024-06-03 17:25:40 +00:00
|
|
|
start, argIndex := 0, 0
|
|
|
|
// decode runes one by one
|
|
|
|
for start < len(format) {
|
|
|
|
r, n := utf8.DecodeRuneInString(format[start:])
|
|
|
|
start += n
|
|
|
|
// if specifier is found, start forming index
|
|
|
|
if r == '{' {
|
|
|
|
numLength := 0
|
|
|
|
for {
|
|
|
|
r, n = utf8.DecodeRuneInString(format[start:])
|
|
|
|
start += n
|
|
|
|
if r == '}' {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
numLength++
|
|
|
|
// append new rune if the number is bigger
|
|
|
|
// than the current buffer
|
|
|
|
if numLength >= len(num) {
|
2024-06-03 17:56:45 +00:00
|
|
|
num = append(num, r) // further allocs happen here
|
2024-06-03 17:25:40 +00:00
|
|
|
} else {
|
|
|
|
num[numLength] = r
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var index int
|
|
|
|
// if specifier is empty get arg at argIndex
|
|
|
|
// otherwise get arg at the specified index from num
|
|
|
|
if len(num) == 0 {
|
|
|
|
index = argIndex
|
|
|
|
} else {
|
|
|
|
var err error
|
|
|
|
index, err = strconv.Atoi(string(num))
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
output.WriteString(strArgs[index])
|
|
|
|
argIndex++
|
|
|
|
// set len of num to 0, but keep capacity
|
|
|
|
num = num[:0]
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
output.WriteRune(r)
|
|
|
|
}
|
|
|
|
return output.String()
|
2020-02-21 20:43:16 +00:00
|
|
|
}
|