summaryrefslogtreecommitdiff
path: root/vendor/codeberg.org/gruf/go-format/formatter.go
blob: 640fa3f0472606d828ba46bbec0477a7a977246b (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
350
351
352
package format

import (
	"strings"
)

// 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 *Buffer, v ...interface{}) {
	for _, v := range v {
		appendIfaceOrRValue(format{maxd: f.MaxDepth, buf: buf}, v)
		buf.AppendByte(' ')
	}
	if len(v) > 0 {
		buf.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 *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{
			maxd: f.MaxDepth,
			buf:  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
		MoveUpTo = func() {
			if i := strings.IndexByte(s[idx:], '}'); 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 {
			// Get current window
			str := Str()
			if len(str) < 1 {
				return true
			}

			// Index HAS to fit within platform int
			if !can32bitInt(str) && !can64bitInt(str) {
				return false
			}

			// Build integer from string
			carg = 0
			for _, c := range []byte(str) {
				carg = carg*10 + int(c-'0')
			}

			return true
		}

		// ParseOp parses operands from the current string
		// window, updating 'last' to 'idx'. The string window
		// is ASSUMED to contain only valid operand ASCII. This
		// returns success on parsing of operand logic
		ParseOp = func() bool {
			// Get current window
			str := Str()
			if len(str) < 1 {
				return true
			}

			// (for now) only
			// accept length = 1
			if len(str) > 1 {
				return false
			}

			switch str[0] {
			case 'k':
				fmt.flags |= isKeyBit
			case 'v':
				fmt.flags |= isValBit
			case '?':
				fmt.flags |= vboseBit
			}

			return true
		}

		// 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
				appendIfaceOrRValue(fmt, a[carg])
			} else {
				// No argument found for index
				buf.AppendString(`!{MISSING_ARG}`)
			}
		}

		// Reset will reset the mode to ground, the flags
		// to empty and parsed 'carg' to  empty
		Reset = func() {
			mode = modeNone
			fmt.flags = 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
				buf.AppendString(Str())
				mode = modeOpen
				MoveUp()
			case '}':
				// Enter close mode
				buf.AppendString(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
				buf.AppendByte('{')
				mode = modeNone
				MoveUp()
			case '}':
				// Format arg
				AppendArg()
				Reset()
				MoveUp()
			case ':':
				// Starting operands
				mode = modeOp
				MoveUp()
			default:
				// Bad char, missing a close
				buf.AppendString(`!{MISSING_CLOSE}`)
				mode = modeNone
				MoveUpTo()
			}

		// Encountered close '}'
		case modeClose:
			switch c {
			case '}':
				// Escaped close bracket
				buf.AppendByte('}')
				mode = modeNone
				MoveUp()
			default:
				// Missing an open bracket
				buf.AppendString(`!{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
					buf.AppendString(`!{BAD_INDEX}`)
					mode = modeNone
					MoveUpTo()
				} else {
					// Starting operands
					mode = modeOp
					MoveUp()
				}
			case '}':
				if !ParseIndex() {
					// Unable to parse an integer
					buf.AppendString(`!{BAD_INDEX}`)
				} else {
					// Format arg
					AppendArg()
				}
				Reset()
				MoveUp()
			default:
				// Not a valid index character
				buf.AppendString(`!{BAD_INDEX}`)
				mode = modeNone
				MoveUpTo()
			}

		// Preparing operands
		case modeOp:
			switch c {
			case 'k', 'v', '?':
				// TODO: set flags as received
			case '}':
				if !ParseOp() {
					// Unable to parse operands
					buf.AppendString(`!{BAD_OPERAND}`)
				} else {
					// Format arg
					AppendArg()
				}
				Reset()
				MoveUp()
			default:
				// Not a valid operand char
				buf.AppendString(`!{BAD_OPERAND}`)
				Reset()
				MoveUpTo()
			}
		}
	}

	// Append any remaining
	buf.AppendString(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 *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 *Buffer, s string, a ...interface{}) {
	formatter.Appendf(buf, s, a...)
}