summaryrefslogtreecommitdiff
path: root/vendor/codeberg.org/gruf/go-kv/format/formatter.go
blob: fd8cf98dfaa37777e10a180663fd0150a0322e15 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
package format

import (
	"strconv"
	"strings"

	"codeberg.org/gruf/go-byteutil"
)

// Formatter allows configuring value and string formatting.
type Formatter struct {
	// MaxDepth specifies the max depth of fields the formatter will iterate.
	// Once max depth is reached, value will simply be formatted as "...".
	// e.g.
	//
	// MaxDepth=1
	// type A struct{
	//     Nested B
	// }
	// type B struct{
	//     Nested C
	// }
	// type C struct{
	//     Field string
	// }
	//
	// Append(&buf, A{}) => {Nested={Nested={Field=...}}}
	MaxDepth uint8
}

// Append will append formatted form of supplied values into 'buf'.
func (f *Formatter) Append(buf *byteutil.Buffer, v ...interface{}) {
	fmt := format{Buffer: buf, Config: f}
	for i := 0; i < len(v); i++ {
		fmt.AppendInterfaceOrReflect(v[i])
		fmt.Buffer.B = append(fmt.Buffer.B, ' ')
	}
	if len(v) > 0 {
		fmt.Buffer.Truncate(1)
	}
}

// Appendf will append the formatted string with supplied values into 'buf'.
// Supported format directives:
// - '{}'   => format supplied arg, in place
// - '{0}'  => format arg at index 0 of supplied, in place
// - '{:?}' => format supplied arg verbosely, in place
// - '{:k}' => format supplied arg as key, in place
// - '{:v}' => format supplied arg as value, in place
//
// To escape either of '{}' simply append an additional brace e.g.
// - '{{'     => '{'
// - '}}'     => '}'
// - '{{}}'   => '{}'
// - '{{:?}}' => '{:?}'
//
// More formatting directives might be included in the future.
func (f *Formatter) Appendf(buf *byteutil.Buffer, s string, a ...interface{}) {
	const (
		// ground state
		modeNone = uint8(0)

		// prev reached '{'
		modeOpen = uint8(1)

		// prev reached '}'
		modeClose = uint8(2)

		// parsing directive index
		modeIdx = uint8(3)

		// parsing directive operands
		modeOp = uint8(4)
	)

	var (
		// mode is current parsing mode
		mode uint8

		// arg is the current arg index
		arg int

		// carg is current directive-set arg index
		carg int

		// last is the trailing cursor to see slice windows
		last int

		// idx is the current index in 's'
		idx int

		// fmt is the base argument formatter
		fmt = format{
			Config: f,
			Buffer: buf,
		}

		// NOTE: these functions are defined here as function
		// locals as it turned out to be better for performance
		// doing it this way, than encapsulating their logic in
		// some kind of parsing structure. Maybe if the parser
		// was pooled along with the buffers it might work out
		// better, but then it makes more internal functions i.e.
		// .Append() .Appendf() less accessible outside package.
		//
		// Currently, passing '-gcflags "-l=4"' causes a not
		// insignificant decrease in ns/op, which is likely due
		// to more aggressive function inlining, which this
		// function can obviously stand to benefit from :)

		// Str returns current string window slice, and updates
		// the trailing cursor 'last' to current 'idx'
		Str = func() string {
			str := s[last:idx]
			last = idx
			return str
		}

		// MoveUp moves the trailing cursor 'last' just past 'idx'
		MoveUp = func() {
			last = idx + 1
		}

		// MoveUpTo moves the trailing cursor 'last' either up to
		// closest '}', or current 'idx', whichever is furthest.
		// NOTE: by calling bytealg.IndexByteString() directly (and
		// not the strconv pass-through, we shave-off complexity
		// which allows this function to be inlined).
		MoveUpTo = func() {
			i := strings.IndexByte(s[idx:], '}')
			if i >= 0 {
				idx += i
			}
			MoveUp()
		}

		// ParseIndex parses an integer from the current string
		// window, updating 'last' to 'idx'. The string window
		// is ASSUMED to contain only valid ASCII numbers. This
		// only returns false if number exceeds platform int size
		ParseIndex = func() bool {
			var str string

			// Get current window
			if str = Str(); len(str) < 1 {
				return true
			}

			// Index HAS to fit within platform integer size
			if !(strconv.IntSize == 32 && (0 < len(s) && len(s) < 10)) ||
				!(strconv.IntSize == 64 && (0 < len(s) && len(s) < 19)) {
				return false
			}

			carg = 0

			// Build integer from string
			for i := 0; i < len(str); i++ {
				carg = carg*10 + int(str[i]-'0')
			}

			return true
		}

		// ValidOp checks that for current ending idx, that a valid
		// operand was achieved -- only 0, 1 are valid numbers.
		ValidOp = func() bool {
			diff := (idx - last)
			last = idx
			return diff < 2
		}

		// AppendArg will take either the directive-set, or
		// iterated arg index, check within bounds of 'a' and
		// append the that argument formatted to the buffer.
		// On failure, it will append an error string
		AppendArg = func() {
			// Look for idx
			if carg < 0 {
				carg = arg
			}

			// Incr idx
			arg++

			if carg < len(a) {
				// Append formatted argument value
				fmt.AppendInterfaceOrReflect(a[carg])
			} else {
				// No argument found for index
				fmt.Buffer.B = append(fmt.Buffer.B, `!{MISSING_ARG}`...)
			}
		}

		// Reset will reset the mode to ground, the flags
		// to empty and parsed 'carg' to  empty
		Reset = func() {
			mode = modeNone
			fmt.CurDepth = 0
			fmt.Flags = 0
			fmt.VType = ""
			fmt.Derefs = 0
			carg = -1
		}
	)

	for idx = 0; idx < len(s); idx++ {
		// Get next char
		c := s[idx]

		switch mode {
		// Ground mode
		case modeNone:
			switch c {
			case '{':
				// Enter open mode
				fmt.Buffer.B = append(fmt.Buffer.B, Str()...)
				mode = modeOpen
				MoveUp()
			case '}':
				// Enter close mode
				fmt.Buffer.B = append(fmt.Buffer.B, Str()...)
				mode = modeClose
				MoveUp()
			}

		// Encountered open '{'
		case modeOpen:
			switch c {
			case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
				// Starting index
				mode = modeIdx
				MoveUp()
			case '{':
				// Escaped bracket
				fmt.Buffer.B = append(fmt.Buffer.B, '{')
				mode = modeNone
				MoveUp()
			case '}':
				// Format arg
				AppendArg()
				Reset()
				MoveUp()
			case ':':
				// Starting operands
				mode = modeOp
				MoveUp()
			default:
				// Bad char, missing a close
				fmt.Buffer.B = append(fmt.Buffer.B, `!{MISSING_CLOSE}`...)
				mode = modeNone
				MoveUpTo()
			}

		// Encountered close '}'
		case modeClose:
			switch c {
			case '}':
				// Escaped close bracket
				fmt.Buffer.B = append(fmt.Buffer.B, '}')
				mode = modeNone
				MoveUp()
			default:
				// Missing an open bracket
				fmt.Buffer.B = append(fmt.Buffer.B, `!{MISSING_OPEN}`...)
				mode = modeNone
				MoveUp()
			}

		// Preparing index
		case modeIdx:
			switch c {
			case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
			case ':':
				if !ParseIndex() {
					// Unable to parse an integer
					fmt.Buffer.B = append(fmt.Buffer.B, `!{BAD_INDEX}`...)
					mode = modeNone
					MoveUpTo()
				} else {
					// Starting operands
					mode = modeOp
					MoveUp()
				}
			case '}':
				if !ParseIndex() {
					// Unable to parse an integer
					fmt.Buffer.B = append(fmt.Buffer.B, `!{BAD_INDEX}`...)
				} else {
					// Format arg
					AppendArg()
				}
				Reset()
				MoveUp()
			default:
				// Not a valid index character
				fmt.Buffer.B = append(fmt.Buffer.B, `!{BAD_INDEX}`...)
				mode = modeNone
				MoveUpTo()
			}

		// Preparing operands
		case modeOp:
			switch c {
			case 'k':
				fmt.Flags |= IsKeyBit
			case 'v':
				fmt.Flags |= IsValBit
			case '?':
				fmt.Flags |= VboseBit
			case '}':
				if !ValidOp() {
					// Bad operands parsed
					fmt.Buffer.B = append(fmt.Buffer.B, `!{BAD_OPERAND}`...)
				} else {
					// Format arg
					AppendArg()
				}
				Reset()
				MoveUp()
			default:
				// Not a valid operand char
				fmt.Buffer.B = append(fmt.Buffer.B, `!{BAD_OPERAND}`...)
				Reset()
				MoveUpTo()
			}
		}
	}

	// Append any remaining
	fmt.Buffer.B = append(fmt.Buffer.B, s[last:]...)
}

// formatter is the default formatter instance.
var formatter = Formatter{
	MaxDepth: 10,
}

// Append will append formatted form of supplied values into 'buf' using default formatter.
// See Formatter.Append() for more documentation.
func Append(buf *byteutil.Buffer, v ...interface{}) {
	formatter.Append(buf, v...)
}

// Appendf will append the formatted string with supplied values into 'buf' using default formatter.
// See Formatter.Appendf() for more documentation.
func Appendf(buf *byteutil.Buffer, s string, a ...interface{}) {
	formatter.Appendf(buf, s, a...)
}