From e3dfd8889315af38c4eef1eb4247dc07a51899c7 Mon Sep 17 00:00:00 2001 From: kim Date: Tue, 29 Jul 2025 09:23:20 +0200 Subject: [performance] bump codeberg.org/gruf/go-kv to v2 (#4341) updates our codeberg.org/gruf/go-kv log key-value formatting library to latest version, which comes with some maaaaaaajor speed boosts in the form of: - very minimal reflect.Value{} usage - caching prepared formatting functions per type ~~still a work-in-progress until i make a release tag on the go-kv repository, which itself is waiting on published benchmark results in the README and finishing writing some code comments~~ benchmarks so far show this to be ~3x faster than the "fmt" stdlib package on average, when run across a wide variety (106 different types) of test cases, while still creating more visually friendly log output and actually recursing down nested struct ptrs Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4341 Co-authored-by: kim Co-committed-by: kim --- vendor/codeberg.org/gruf/go-kv/v2/format/abi.go | 262 +++++++++ vendor/codeberg.org/gruf/go-kv/v2/format/args.go | 247 +++++++++ vendor/codeberg.org/gruf/go-kv/v2/format/array.go | 231 ++++++++ vendor/codeberg.org/gruf/go-kv/v2/format/format.go | 614 +++++++++++++++++++++ .../gruf/go-kv/v2/format/formatting.go | 161 ++++++ vendor/codeberg.org/gruf/go-kv/v2/format/map.go | 130 +++++ .../codeberg.org/gruf/go-kv/v2/format/methods.go | 153 +++++ .../codeberg.org/gruf/go-kv/v2/format/pointer.go | 128 +++++ vendor/codeberg.org/gruf/go-kv/v2/format/slice.go | 158 ++++++ vendor/codeberg.org/gruf/go-kv/v2/format/struct.go | 173 ++++++ vendor/codeberg.org/gruf/go-kv/v2/format/type.go | 150 +++++ 11 files changed, 2407 insertions(+) create mode 100644 vendor/codeberg.org/gruf/go-kv/v2/format/abi.go create mode 100644 vendor/codeberg.org/gruf/go-kv/v2/format/args.go create mode 100644 vendor/codeberg.org/gruf/go-kv/v2/format/array.go create mode 100644 vendor/codeberg.org/gruf/go-kv/v2/format/format.go create mode 100644 vendor/codeberg.org/gruf/go-kv/v2/format/formatting.go create mode 100644 vendor/codeberg.org/gruf/go-kv/v2/format/map.go create mode 100644 vendor/codeberg.org/gruf/go-kv/v2/format/methods.go create mode 100644 vendor/codeberg.org/gruf/go-kv/v2/format/pointer.go create mode 100644 vendor/codeberg.org/gruf/go-kv/v2/format/slice.go create mode 100644 vendor/codeberg.org/gruf/go-kv/v2/format/struct.go create mode 100644 vendor/codeberg.org/gruf/go-kv/v2/format/type.go (limited to 'vendor/codeberg.org/gruf/go-kv/v2/format') diff --git a/vendor/codeberg.org/gruf/go-kv/v2/format/abi.go b/vendor/codeberg.org/gruf/go-kv/v2/format/abi.go new file mode 100644 index 000000000..815660a5e --- /dev/null +++ b/vendor/codeberg.org/gruf/go-kv/v2/format/abi.go @@ -0,0 +1,262 @@ +//go:build go1.24 && !go1.25 + +package format + +import ( + "reflect" + "unsafe" +) + +const ( + // see: go/src/internal/abi/type.go + abi_KindDirectIface uint8 = 1 << 5 + abi_KindMask uint8 = (1 << 5) - 1 +) + +// abi_Type is a copy of the memory layout of abi.Type{}. +// +// see: go/src/internal/abi/type.go +type abi_Type struct { + _ uintptr + PtrBytes uintptr + _ uint32 + _ uint8 + _ uint8 + _ uint8 + Kind_ uint8 + _ func(unsafe.Pointer, unsafe.Pointer) bool + _ *byte + _ int32 + _ int32 +} + +// abi_EmptyInterface is a copy of the memory layout of abi.EmptyInterface{}, +// which is to say also the memory layout of any method-less interface. +// +// see: go/src/internal/abi/iface.go +type abi_EmptyInterface struct { + Type *abi_Type + Data unsafe.Pointer +} + +// see: go/src/internal/abi/type.go Type.Kind() +func abi_Type_Kind(t reflect.Type) uint8 { + iface := (*reflect_nonEmptyInterface)(unsafe.Pointer(&t)) + atype := (*abi_Type)(unsafe.Pointer(iface.word)) + return atype.Kind_ & abi_KindMask +} + +// see: go/src/internal/abi/type.go Type.IfaceIndir() +func abi_Type_IfaceIndir(t reflect.Type) bool { + iface := (*reflect_nonEmptyInterface)(unsafe.Pointer(&t)) + atype := (*abi_Type)(unsafe.Pointer(iface.word)) + return atype.Kind_&abi_KindDirectIface == 0 +} + +// pack_iface packs a new reflect.nonEmptyInterface{} using shielded itab +// pointer and data (word) pointer, returning a pointer for caller casting. +func pack_iface(itab uintptr, word unsafe.Pointer) unsafe.Pointer { + return unsafe.Pointer(&reflect_nonEmptyInterface{ + itab: itab, + word: word, + }) +} + +// get_iface_ITab generates a new value of given type, +// casts it to the generic param interface type, and +// returns the .itab portion of the reflect.nonEmptyInterface{}. +// this is useful for later calls to pack_iface for known type. +func get_iface_ITab[I any](t reflect.Type) uintptr { + s := reflect.New(t).Elem().Interface().(I) + i := (*reflect_nonEmptyInterface)(unsafe.Pointer(&s)) + return i.itab +} + +// unpack_eface returns the .Data portion of an abi.EmptyInterface{}. +func unpack_eface(a any) unsafe.Pointer { + return (*abi_EmptyInterface)(unsafe.Pointer((&a))).Data +} + +// add returns the ptr addition of starting ptr and a delta. +func add(ptr unsafe.Pointer, delta uintptr) unsafe.Pointer { + return unsafe.Pointer(uintptr(ptr) + delta) +} + +// typeof is short-hand for reflect.TypeFor[T](). +func typeof[T any]() reflect.Type { + return reflect.TypeFor[T]() +} + +// see: go/src/reflect/value.go +type reflect_flag uintptr + +const ( + // see: go/src/reflect/value.go + reflect_flagKindWidth = 5 // there are 27 kinds + reflect_flagKindMask reflect_flag = 1< SingleTermLine || !IsSafeASCII(v) { + // Requires quoting AND escaping + s.B = strconv.AppendQuote(s.B, v) + } else if ContainsDoubleQuote(v) { + // Contains double quotes, needs escaping + s.B = append(s.B, '"') + s.B = AppendEscape(s.B, v) + s.B = append(s.B, '"') + } else if s.A.WithType() || + len(v) == 0 || ContainsSpaceOrTab(v) { + // Contains space / empty, needs quotes + s.B = append(s.B, '"') + s.B = append(s.B, v...) + s.B = append(s.B, '"') + } else { + // All else write as-is + s.B = append(s.B, v...) + } + case s.A.AsQuotedText(): + s.B = strconv.AppendQuote(s.B, v) + case s.A.AsQuotedASCII(): + s.B = strconv.AppendQuoteToASCII(s.B, v) + default: + s.B = append(s.B, v...) + } +} + +func appendInt(s *State, v int64) { + args := s.A.Int + if args == zeroArgs.Int { + args = defaultArgs.Int + } + if args.Pad > 0 { + const zeros = `00000000000000000000` + if args.Pad > len(zeros) { + panic("cannot pad > " + zeros) + } + if v == 0 { + s.B = append(s.B, zeros[:args.Pad]...) + return + } + abs := abs64(v) + chars := int(v / int64(args.Base)) + if v%int64(args.Base) != 0 { + chars++ + } + if abs != v { + s.B = append(s.B, '-') + v = abs + } + if n := args.Pad - chars; n > 0 { + s.B = append(s.B, zeros[:n]...) + } + } + s.B = strconv.AppendInt(s.B, v, args.Base) +} + +func appendUint(s *State, v uint64) { + args := s.A.Int + if args == zeroArgs.Int { + args = defaultArgs.Int + } + if args.Pad > 0 { + const zeros = `00000000000000000000` + if args.Pad > len(zeros) { + panic("cannot pad > " + zeros) + } + if v == 0 { + s.B = append(s.B, zeros[:args.Pad]...) + return + } + chars := int(v / uint64(args.Base)) + if v%uint64(args.Base) != 0 { + chars++ + } + if n := args.Pad - chars; n > 0 { + s.B = append(s.B, zeros[:n]...) + } + } + s.B = strconv.AppendUint(s.B, v, args.Base) +} + +func appendFloat(s *State, v float64, bits int) { + args := s.A.Float + if args == zeroArgs.Float { + args = defaultArgs.Float + } + s.B = strconv.AppendFloat(s.B, float64(v), args.Fmt, args.Prec, bits) +} + +func appendComplex(s *State, r, i float64, bits int) { + args := s.A.Complex + if args == zeroArgs.Complex { + args = defaultArgs.Complex + } + s.B = strconv.AppendFloat(s.B, float64(r), args.Real.Fmt, args.Real.Prec, bits) + s.B = append(s.B, '+') + s.B = strconv.AppendFloat(s.B, float64(i), args.Imag.Fmt, args.Imag.Prec, bits) + s.B = append(s.B, 'i') +} + +func appendPointer(s *State, v unsafe.Pointer) { + if v != nil { + s.B = append(s.B, "0x"...) + s.B = strconv.AppendUint(s.B, uint64(uintptr(v)), 16) + } else { + appendNil(s) + } +} + +func appendNilType(s *State, typestr string) { + if s.A.WithType() { + s.B = append(s.B, "("+typestr+")()"...) + } else { + s.B = append(s.B, ""...) + } +} + +func appendNil(s *State) { + s.B = append(s.B, ""...) +} + +func abs64(i int64) int64 { + u := uint64(i >> 63) + return (i ^ int64(u)) + int64(u&1) +} diff --git a/vendor/codeberg.org/gruf/go-kv/v2/format/formatting.go b/vendor/codeberg.org/gruf/go-kv/v2/format/formatting.go new file mode 100644 index 000000000..e09edbefc --- /dev/null +++ b/vendor/codeberg.org/gruf/go-kv/v2/format/formatting.go @@ -0,0 +1,161 @@ +package format + +import ( + "strings" + "unicode" + "unicode/utf8" +) + +const ( + // SingleTermLine: beyond a certain length of string, all of the + // extra checks to handle quoting/not-quoting add a significant + // amount of extra processing time. Quoting in this manner only really + // effects readability on a single line, so a max string length that + // encompasses the maximum number of columns on *most* terminals was + // selected. This was chosen using the metric that 1080p is one of the + // most common display resolutions, and that a relatively small font size + // of 7 requires 223 columns. So 256 should be >= $COLUMNS (fullscreen) + // in 99% of usecases (these figures all pulled out of my ass). + SingleTermLine = 256 +) + +// IsSafeASCII checks whether string is printable (i.e. non-control char) ASCII text. +func IsSafeASCII(str string) bool { + for _, r := range str { + if (r < ' ' && r != '\t') || + r >= 0x7f { + return false + } + } + return true +} + +// ContainsSpaceOrTab checks if "s" contains space or tabs. EXPECTS ASCII. +func ContainsSpaceOrTab(s string) bool { + if i := strings.IndexByte(s, ' '); i >= 0 { + return true // note using indexbyte as it is ASM. + } else if i := strings.IndexByte(s, '\t'); i >= 0 { + return true + } + return false +} + +// ContainsDoubleQuote checks if "s" contains a double quote. EXPECTS ASCII. +func ContainsDoubleQuote(s string) bool { + return (strings.IndexByte(s, '"') >= 0) +} + +// AppendEscape will append 's' to 'buf' and escape any double quotes. EXPECTS ASCII. +func AppendEscape(buf []byte, str string) []byte { + for i := range str { + switch str[i] { + case '\\': + // Append delimited '\' + buf = append(buf, '\\', '\\') + + case '"': + // Append delimited '"' + buf = append(buf, '\\', '"') + default: + // Append char as-is + buf = append(buf, str[i]) + } + } + return buf +} + +const hex = "0123456789abcdef" + +// AppendEscapeByte ... +func AppendEscapeByte(buf []byte, c byte) []byte { + switch c { + case '\a': + return append(buf, `\a`...) + case '\b': + return append(buf, `\b`...) + case '\f': + return append(buf, `\f`...) + case '\n': + return append(buf, `\n`...) + case '\r': + return append(buf, `\r`...) + case '\t': + return append(buf, `\t`...) + case '\v': + return append(buf, `\v`...) + case '\\': + return append(buf, `\\`...) + default: + if c < ' ' { + return append(buf, '\\', 'x', hex[c>>4], hex[c&0xF]) + } + return append(buf, c) + } +} + +// AppendQuoteByte ... +func AppendQuoteByte(buf []byte, c byte) []byte { + if c == '\'' { + return append(buf, `'\''`...) + } + buf = append(buf, '\'') + buf = AppendEscapeByte(buf, c) + buf = append(buf, '\'') + return buf +} + +// AppendEscapeRune ... +func AppendEscapeRune(buf []byte, r rune) []byte { + if unicode.IsPrint(r) { + return utf8.AppendRune(buf, r) + } + switch r { + case '\a': + return append(buf, `\a`...) + case '\b': + return append(buf, `\b`...) + case '\f': + return append(buf, `\f`...) + case '\n': + return append(buf, `\n`...) + case '\r': + return append(buf, `\r`...) + case '\t': + return append(buf, `\t`...) + case '\v': + return append(buf, `\v`...) + case '\\': + return append(buf, `\\`...) + default: + switch { + case r < ' ' || r == 0x7f: + buf = append(buf, `\x`...) + buf = append(buf, hex[byte(r)>>4]) + buf = append(buf, hex[byte(r)&0xF]) + case !utf8.ValidRune(r): + r = 0xFFFD + fallthrough + case r < 0x10000: + buf = append(buf, `\u`...) + buf = append(buf, + hex[r>>uint(12)&0xF], + hex[r>>uint(8)&0xF], + hex[r>>uint(4)&0xF], + hex[r>>uint(0)&0xF], + ) + default: + buf = append(buf, `\U`...) + buf = append(buf, + hex[r>>uint(28)&0xF], + hex[r>>uint(24)&0xF], + hex[r>>uint(20)&0xF], + hex[r>>uint(16)&0xF], + hex[r>>uint(12)&0xF], + hex[r>>uint(8)&0xF], + hex[r>>uint(4)&0xF], + hex[r>>uint(0)&0xF], + ) + } + } + return buf +} diff --git a/vendor/codeberg.org/gruf/go-kv/v2/format/map.go b/vendor/codeberg.org/gruf/go-kv/v2/format/map.go new file mode 100644 index 000000000..038fb74ac --- /dev/null +++ b/vendor/codeberg.org/gruf/go-kv/v2/format/map.go @@ -0,0 +1,130 @@ +package format + +import "unsafe" + +// iterMapType returns a FormatFunc capable of iterating +// and formatting the given map type currently in typenode{}. +// note this will fetch sub-FormatFuncs for key / value types. +func (fmt *Formatter) iterMapType(t typenode) FormatFunc { + + // Key / value types. + key := t.rtype.Key() + elem := t.rtype.Elem() + + // Get nested k / v typenodes with appropriate flags. + flagsKey := reflect_map_key_flags(key) + flagsVal := reflect_map_elem_flags(elem) + kt := t.next(t.rtype.Key(), flagsKey) + vt := t.next(t.rtype.Elem(), flagsVal) + + // Get key format func. + kfn := fmt.loadOrGet(kt) + if kfn == nil { + panic("unreachable") + } + + // Get value format func. + vfn := fmt.loadOrGet(vt) + if vfn == nil { + panic("unreachable") + } + + // Final map type. + rtype := t.rtype + flags := t.flags + + // Map type string with ptrs / refs. + typestrPtrs := t.typestr_with_ptrs() + typestrRefs := t.typestr_with_refs() + + if !t.needs_typestr() { + return func(s *State) { + if s.P == nil || *(*unsafe.Pointer)(s.P) == nil { + // Append nil. + appendNil(s) + return + } + + // Build reflect value, and then a map iter. + v := build_reflect_value(rtype, s.P, flags) + i := map_iter(v) + + // Prepend object brace. + s.B = append(s.B, '{') + + // Before len. + l := len(s.B) + + for i.Next() { + // Pass to key fn. + s.P = map_key(i) + kfn(s) + + // Add key seperator. + s.B = append(s.B, '=') + + // Pass to elem fn. + s.P = map_elem(i) + vfn(s) + + // Add comma pair seperator. + s.B = append(s.B, ',', ' ') + } + + if len(s.B) != l { + // Drop final ", ". + s.B = s.B[:len(s.B)-2] + } + + // Append object brace. + s.B = append(s.B, '}') + } + } + + return func(s *State) { + if s.P == nil || *(*unsafe.Pointer)(s.P) == nil { + // Append nil value with type. + appendNilType(s, typestrPtrs) + return + } + + // Build reflect value, and then a map iter. + v := build_reflect_value(rtype, s.P, flags) + i := map_iter(v) + + // Include type info. + if s.A.WithType() { + s.B = append(s.B, typestrRefs...) + } + + // Prepend object brace. + s.B = append(s.B, '{') + + // Before len. + l := len(s.B) + + for i.Next() { + // Pass to key fn. + s.P = map_key(i) + kfn(s) + + // Add key seperator. + s.B = append(s.B, '=') + + // Pass to elem fn. + s.P = map_elem(i) + vfn(s) + + // Add comma pair seperator. + s.B = append(s.B, ',', ' ') + } + + if len(s.B) != l { + // Drop final ", ". + s.B = s.B[:len(s.B)-2] + } + + // Append object brace. + s.B = append(s.B, '}') + } +} diff --git a/vendor/codeberg.org/gruf/go-kv/v2/format/methods.go b/vendor/codeberg.org/gruf/go-kv/v2/format/methods.go new file mode 100644 index 000000000..7c1795771 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-kv/v2/format/methods.go @@ -0,0 +1,153 @@ +package format + +import ( + "reflect" + "unsafe" +) + +type Stringer interface{ String() string } + +var ( + // stringer type for implement checks. + stringerType = typeof[Stringer]() + + // error type for implement checks. + errorType = typeof[error]() +) + +// getMethodType returns a *possible* FormatFunc to handle case +// of a type that implements any known interface{} types, else nil. +func getMethodType(t typenode) FormatFunc { + switch { + case t.rtype.Implements(stringerType): + switch t.rtype.Kind() { + case reflect.Interface: + return getInterfaceStringerType(t) + default: + return getConcreteStringerType(t) + } + case t.rtype.Implements(errorType): + switch t.rtype.Kind() { + case reflect.Interface: + return getInterfaceErrorType(t) + default: + return getConcreteErrorType(t) + } + default: + return nil + } +} + +// getInterfaceStringerType returns a FormatFunc to handle case of an interface{} +// type that implements Stringer{}, i.e. Stringer{} itself and any superset of. +func getInterfaceStringerType(t typenode) FormatFunc { + switch t.indirect() && !t.iface_indir() { + case true: + return with_typestr_ptrs(t, func(s *State) { + s.P = *(*unsafe.Pointer)(s.P) + if s.P == nil || (*reflect_nonEmptyInterface)(s.P).word == nil { + appendNil(s) + return + } + v := *(*Stringer)(s.P) + appendString(s, v.String()) + }) + case false: + return with_typestr_ptrs(t, func(s *State) { + if s.P == nil || (*reflect_nonEmptyInterface)(s.P).word == nil { + appendNil(s) + return + } + v := *(*Stringer)(s.P) + appendString(s, v.String()) + }) + default: + panic("unreachable") + } +} + +// getConcreteStringerType returns a FormatFunc to handle case of concrete +// (i.e. non-interface{}) type that has a Stringer{} method receiver. +func getConcreteStringerType(t typenode) FormatFunc { + itab := get_iface_ITab[Stringer](t.rtype) + switch t.indirect() && !t.iface_indir() { + case true: + return with_typestr_ptrs(t, func(s *State) { + s.P = *(*unsafe.Pointer)(s.P) + if s.P == nil { + appendNil(s) + return + } + v := *(*Stringer)(pack_iface(itab, s.P)) + appendString(s, v.String()) + }) + case false: + return with_typestr_ptrs(t, func(s *State) { + if s.P == nil { + appendNil(s) + return + } + v := *(*Stringer)(pack_iface(itab, s.P)) + appendString(s, v.String()) + }) + default: + panic("unreachable") + } +} + +// getInterfaceErrorType returns a FormatFunc to handle case of an interface{} +// type that implements error{}, i.e. error{} itself and any superset of. +func getInterfaceErrorType(t typenode) FormatFunc { + switch t.indirect() && !t.iface_indir() { + case true: + return with_typestr_ptrs(t, func(s *State) { + s.P = *(*unsafe.Pointer)(s.P) + if s.P == nil || (*reflect_nonEmptyInterface)(s.P).word == nil { + appendNil(s) + return + } + v := *(*error)(s.P) + appendString(s, v.Error()) + }) + case false: + return with_typestr_ptrs(t, func(s *State) { + if s.P == nil || (*reflect_nonEmptyInterface)(s.P).word == nil { + appendNil(s) + return + } + v := *(*error)(s.P) + appendString(s, v.Error()) + }) + default: + panic("unreachable") + } +} + +// getConcreteErrorType returns a FormatFunc to handle case of concrete +// (i.e. non-interface{}) type that has an error{} method receiver. +func getConcreteErrorType(t typenode) FormatFunc { + itab := get_iface_ITab[error](t.rtype) + switch t.indirect() && !t.iface_indir() { + case true: + return with_typestr_ptrs(t, func(s *State) { + s.P = *(*unsafe.Pointer)(s.P) + if s.P == nil { + appendNil(s) + return + } + v := *(*error)(pack_iface(itab, s.P)) + appendString(s, v.Error()) + }) + case false: + return with_typestr_ptrs(t, func(s *State) { + if s.P == nil { + appendNil(s) + return + } + v := *(*error)(pack_iface(itab, s.P)) + appendString(s, v.Error()) + }) + default: + panic("unreachable") + } +} diff --git a/vendor/codeberg.org/gruf/go-kv/v2/format/pointer.go b/vendor/codeberg.org/gruf/go-kv/v2/format/pointer.go new file mode 100644 index 000000000..1f860aba9 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-kv/v2/format/pointer.go @@ -0,0 +1,128 @@ +package format + +import ( + "reflect" + "unsafe" +) + +// derefPointerType returns a FormatFunc capable of dereferencing +// and formatting the given pointer type currently in typenode{}. +// note this will fetch a sub-FormatFunc for resulting value type. +func (fmt *Formatter) derefPointerType(t typenode) FormatFunc { + var n int + rtype := t.rtype + flags := t.flags + + // Iteratively dereference pointer types. + for rtype.Kind() == reflect.Pointer { + + // If this is actual indirect + // memory, increase dereferences. + if flags&reflect_flagIndir != 0 { + n++ + } + + // Get next elem type. + rtype = rtype.Elem() + + // Get next set of dereferenced elem type flags. + flags = reflect_pointer_elem_flags(flags, rtype) + } + + // Wrap value as typenode. + vt := t.next(rtype, flags) + + // Get value format func. + fn := fmt.loadOrGet(vt) + if fn == nil { + panic("unreachable") + } + + if !t.needs_typestr() { + if n <= 0 { + // No derefs are needed. + return func(s *State) { + if s.P == nil { + // Final check. + appendNil(s) + return + } + + // Format + // final + // value. + fn(s) + } + } + + return func(s *State) { + // Deref n number times. + for i := n; i > 0; i-- { + + if s.P == nil { + // Nil check. + appendNil(s) + return + } + + // Further deref pointer value. + s.P = *(*unsafe.Pointer)(s.P) + } + + if s.P == nil { + // Final check. + appendNil(s) + return + } + + // Format + // final + // value. + fn(s) + } + } + + // Final type string with ptrs. + typestr := t.typestr_with_ptrs() + + if n <= 0 { + // No derefs are needed. + return func(s *State) { + if s.P == nil { + // Final nil value check. + appendNilType(s, typestr) + return + } + + // Format + // final + // value. + fn(s) + } + } + + return func(s *State) { + // Deref n number times. + for i := n; i > 0; i-- { + if s.P == nil { + // Check for nil value. + appendNilType(s, typestr) + return + } + + // Further deref pointer value. + s.P = *(*unsafe.Pointer)(s.P) + } + + if s.P == nil { + // Final nil value check. + appendNilType(s, typestr) + return + } + + // Format + // final + // value. + fn(s) + } +} diff --git a/vendor/codeberg.org/gruf/go-kv/v2/format/slice.go b/vendor/codeberg.org/gruf/go-kv/v2/format/slice.go new file mode 100644 index 000000000..f76e85410 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-kv/v2/format/slice.go @@ -0,0 +1,158 @@ +package format + +// iterSliceType returns a FormatFunc capable of iterating +// and formatting the given slice type currently in typenode{}. +// note this will fetch a sub-FormatFunc for the slice element +// type, and also handle special cases of []byte, []rune slices. +func (fmt *Formatter) iterSliceType(t typenode) FormatFunc { + + // Get nested element type. + elem := t.rtype.Elem() + esz := elem.Size() + + // Get nested elem typenode with flags. + flags := reflect_slice_elem_flags(elem) + et := t.next(elem, flags) + + // Get elem format func. + fn := fmt.loadOrGet(et) + if fn == nil { + panic("unreachable") + } + + if !t.needs_typestr() { + return func(s *State) { + ptr := s.P + + // Get data as unsafe slice header. + hdr := (*unsafeheader_Slice)(ptr) + if hdr == nil || hdr.Data == nil { + + // Append nil. + appendNil(s) + return + } + + // Prepend array brace. + s.B = append(s.B, '[') + + if hdr.Len > 0 { + for i := 0; i < hdr.Len; i++ { + // Format at array index. + offset := esz * uintptr(i) + s.P = add(hdr.Data, offset) + fn(s) + + // Append separator. + s.B = append(s.B, ',') + } + + // Drop final comma. + s.B = s.B[:len(s.B)-1] + } + + // Append array brace. + s.B = append(s.B, ']') + } + } + + // Slice type string with ptrs / refs. + typestrPtrs := t.typestr_with_ptrs() + typestrRefs := t.typestr_with_refs() + + return func(s *State) { + ptr := s.P + + // Get data as unsafe slice header. + hdr := (*unsafeheader_Slice)(ptr) + if hdr == nil || hdr.Data == nil { + + // Append nil value with type. + appendNilType(s, typestrPtrs) + return + } + + // Open / close braces. + var open, close uint8 + open, close = '[', ']' + + // Include type info. + if s.A.WithType() { + s.B = append(s.B, typestrRefs...) + open, close = '{', '}' + } + + // Prepend array brace. + s.B = append(s.B, open) + + if hdr.Len > 0 { + for i := 0; i < hdr.Len; i++ { + // Format at array index. + offset := esz * uintptr(i) + s.P = add(hdr.Data, offset) + fn(s) + + // Append separator. + s.B = append(s.B, ',') + } + + // Drop final comma. + s.B = s.B[:len(s.B)-1] + } + + // Append array brace. + s.B = append(s.B, close) + } +} + +func wrapByteSlice(t typenode, fn FormatFunc) FormatFunc { + if !t.needs_typestr() { + return func(s *State) { + if s.A.AsText() || s.A.AsQuotedText() || s.A.AsQuotedASCII() { + appendString(s, *(*string)(s.P)) + } else { + fn(s) + } + } + } + typestr := t.typestr_with_ptrs() + return func(s *State) { + if s.A.AsText() || s.A.AsQuotedText() || s.A.AsQuotedASCII() { + if s.A.WithType() { + s.B = append(s.B, "("+typestr+")("...) + appendString(s, *(*string)(s.P)) + s.B = append(s.B, ")"...) + } else { + appendString(s, *(*string)(s.P)) + } + } else { + fn(s) + } + } +} + +func wrapRuneSlice(t typenode, fn FormatFunc) FormatFunc { + if !t.needs_typestr() { + return func(s *State) { + if s.A.AsText() || s.A.AsQuotedText() || s.A.AsQuotedASCII() { + appendString(s, string(*(*[]rune)(s.P))) + } else { + fn(s) + } + } + } + typestr := t.typestr_with_ptrs() + return func(s *State) { + if s.A.AsText() || s.A.AsQuotedText() || s.A.AsQuotedASCII() { + if s.A.WithType() { + s.B = append(s.B, "("+typestr+")("...) + appendString(s, string(*(*[]rune)(s.P))) + s.B = append(s.B, ")"...) + } else { + appendString(s, string(*(*[]rune)(s.P))) + } + } else { + fn(s) + } + } +} diff --git a/vendor/codeberg.org/gruf/go-kv/v2/format/struct.go b/vendor/codeberg.org/gruf/go-kv/v2/format/struct.go new file mode 100644 index 000000000..cc1c8634d --- /dev/null +++ b/vendor/codeberg.org/gruf/go-kv/v2/format/struct.go @@ -0,0 +1,173 @@ +package format + +// field stores the minimum necessary +// data for iterating and formatting +// each field in a given struct. +type field struct { + format FormatFunc + name string + offset uintptr +} + +// iterStructType returns a FormatFunc capable of iterating +// and formatting the given struct type currently in typenode{}. +// note this will fetch sub-FormatFuncs for each struct field. +func (fmt *Formatter) iterStructType(t typenode) FormatFunc { + // Number of struct fields. + n := t.rtype.NumField() + + // Gather format functions. + fields := make([]field, n) + for i := 0; i < n; i++ { + + // Get struct field at index. + sfield := t.rtype.Field(i) + rtype := sfield.Type + + // Get nested field typenode with appropriate flags. + flags := reflect_struct_field_flags(t.flags, rtype) + ft := t.next(sfield.Type, flags) + + // Get field format func. + fn := fmt.loadOrGet(ft) + if fn == nil { + panic("unreachable") + } + + // Set field info. + fields[i] = field{ + format: fn, + name: sfield.Name, + offset: sfield.Offset, + } + } + + // Handle no. fields. + switch len(fields) { + case 0: + return emptyStructType(t) + case 1: + return iterSingleFieldStructType(t, fields[0]) + default: + return iterMultiFieldStructType(t, fields) + } +} + +func emptyStructType(t typenode) FormatFunc { + if !t.needs_typestr() { + return func(s *State) { + // Append empty object. + s.B = append(s.B, "{}"...) + } + } + + // Struct type string with refs. + typestr := t.typestr_with_refs() + + // Append empty object + // with type information. + return func(s *State) { + if s.A.WithType() { + s.B = append(s.B, typestr...) + } + s.B = append(s.B, "{}"...) + } +} + +func iterSingleFieldStructType(t typenode, field field) FormatFunc { + if field.format == nil { + panic("nil func") + } + + if !t.needs_typestr() { + return func(s *State) { + // Wrap 'fn' with braces + field name. + s.B = append(s.B, "{"+field.name+"="...) + field.format(s) + s.B = append(s.B, "}"...) + } + } + + // Struct type string with refs. + typestr := t.typestr_with_refs() + + return func(s *State) { + // Include type info. + if s.A.WithType() { + s.B = append(s.B, typestr...) + } + + // Wrap 'fn' with braces + field name. + s.B = append(s.B, "{"+field.name+"="...) + field.format(s) + s.B = append(s.B, "}"...) + } +} + +func iterMultiFieldStructType(t typenode, fields []field) FormatFunc { + for _, field := range fields { + if field.format == nil { + panic("nil func") + } + } + + if !t.needs_typestr() { + return func(s *State) { + ptr := s.P + + // Prepend object brace. + s.B = append(s.B, '{') + + for i := 0; i < len(fields); i++ { + // Get struct field ptr via offset. + s.P = add(ptr, fields[i].offset) + + // Append field name and value separator. + s.B = append(s.B, fields[i].name+"="...) + + // Format i'th field. + fields[i].format(s) + s.B = append(s.B, ',', ' ') + } + + // Drop final ", ". + s.B = s.B[:len(s.B)-2] + + // Append object brace. + s.B = append(s.B, '}') + } + } + + // Struct type string with refs. + typestr := t.typestr_with_refs() + + return func(s *State) { + ptr := s.P + + // Include type info. + if s.A.WithType() { + s.B = append(s.B, typestr...) + } + + // Prepend object brace. + s.B = append(s.B, '{') + + for i := 0; i < len(fields); i++ { + // Get struct field ptr via offset. + s.P = add(ptr, fields[i].offset) + + // Append field name and value separator. + s.B = append(s.B, fields[i].name+"="...) + + // Format i'th field. + fields[i].format(s) + s.B = append(s.B, ',', ' ') + } + + // Drop final ", ". + s.B = s.B[:len(s.B)-2] + + // Append object brace. + s.B = append(s.B, '}') + } +} diff --git a/vendor/codeberg.org/gruf/go-kv/v2/format/type.go b/vendor/codeberg.org/gruf/go-kv/v2/format/type.go new file mode 100644 index 000000000..ec5557b05 --- /dev/null +++ b/vendor/codeberg.org/gruf/go-kv/v2/format/type.go @@ -0,0 +1,150 @@ +package format + +import ( + "reflect" + "strings" +) + +// typenode ... +type typenode struct { + typeinfo + parent *typenode +} + +// typeinfo ... +type typeinfo struct { + rtype reflect.Type + flags reflect_flag +} + +// new_typenode returns a new typenode{} with reflect.Type and flags. +func new_typenode(t reflect.Type, flags reflect_flag) typenode { + return typenode{typeinfo: typeinfo{ + rtype: t, + flags: flags, + }} +} + +// key returns data (i.e. type value info) +// to store a FormatFunc under in a cache. +func (n typenode) key() typeinfo { + return n.typeinfo +} + +// indirect returns whether reflect_flagIndir is set for given type flags. +func (n typenode) indirect() bool { + return n.flags&reflect_flagIndir != 0 +} + +// iface_indir returns the result of abi.Type{}.IfaceIndir() for underlying type. +func (n typenode) iface_indir() bool { + return abi_Type_IfaceIndir(n.rtype) +} + +// next ... +func (n typenode) next(t reflect.Type, flags reflect_flag) typenode { + child := new_typenode(t, flags) + child.parent = &n + return child +} + +// visit ... +func (n typenode) visit() bool { + t := n.rtype + + // Check if type is already encountered further up tree. + for node := n.parent; node != nil; node = node.parent { + if node.rtype == t { + return false + } + } + + return true +} + +// needs_typestr returns whether the type contained in the +// receiving typenode{} needs type string information prefixed +// when the TypeMask argument flag bit is set. Certain types +// don't need this as the parent type already indicates this. +func (n typenode) needs_typestr() bool { + if n.parent == nil { + return true + } + switch p := n.parent.rtype; p.Kind() { + case reflect.Pointer: + return n.parent.needs_typestr() + case reflect.Slice, + reflect.Array, + reflect.Map: + return false + default: + return true + } +} + +// typestr_with_ptrs returns the type string for +// current typenode{} with asterisks for pointers. +func (n typenode) typestr_with_ptrs() string { + t := n.rtype + + // Check for parent. + if n.parent == nil { + return t.String() + } + + // Get parent type. + p := n.parent.rtype + + // If parent is not ptr, then + // this was not a deref'd ptr. + if p.Kind() != reflect.Pointer { + return t.String() + } + + // Return un-deref'd + // ptr (parent) type. + return p.String() +} + +// typestr_with_refs returns the type string for +// current typenode{} with ampersands for pointers. +func (n typenode) typestr_with_refs() string { + t := n.rtype + + // Check for parent. + if n.parent == nil { + return t.String() + } + + // Get parent type. + p := n.parent.rtype + + var d int + + // Count number of dereferences. + for p.Kind() == reflect.Pointer { + p = p.Elem() + d++ + } + + if d <= 0 { + // Prefer just returning our + // own string if possible, to + // reduce number of strings + // we need to allocate. + return t.String() + } + + // Value type str. + str := t.String() + + // Return with type ptrs + // symbolized by 'refs'. + var buf strings.Builder + buf.Grow(len(str) + d) + for i := 0; i < d; i++ { + buf.WriteByte('&') + } + buf.WriteString(str) + return buf.String() +} -- cgit v1.2.3