v4 / vlib / strings / builder.c.v
482 lines · 437 sloc · 13.25 KB · 8a793005dd8c68803b6c75dac8be00299a2caf66
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module strings
5
6// strings.Builder is used to efficiently append many strings to a large
7// dynamically growing buffer, then use the resulting large string. Using
8// a string builder is much better for performance/memory usage than doing
9// constantly string concatenation.
10pub type Builder = []u8
11
12// new_builder returns a new string builder, with an initial capacity of `initial_size`.
13pub fn new_builder(initial_size int) Builder {
14 mut res := Builder([]u8{cap: initial_size})
15 unsafe { res.flags.set(.noslices) }
16 return res
17}
18
19// reuse_as_plain_u8_array allows using the Builder instance as a plain []u8 return value.
20// It is useful, when you have accumulated data in the builder, that you want to
21// pass/access as []u8 later, without copying or freeing the buffer.
22// NB: you *should NOT use* the string builder instance after calling this method.
23// Use only the return value after calling this method.
24@[unsafe]
25pub fn (mut b Builder) reuse_as_plain_u8_array() []u8 {
26 unsafe { b.flags.clear(.noslices) }
27 return *b
28}
29
30// write_ptr writes `len` bytes provided byteptr to the accumulated buffer
31@[unsafe]
32pub fn (mut b Builder) write_ptr(ptr &u8, len int) {
33 if len == 0 {
34 return
35 }
36 unsafe { b.push_many(ptr, len) }
37}
38
39// write_rune appends a single rune to the accumulated buffer
40@[manualfree]
41pub fn (mut b Builder) write_rune(r rune) {
42 mut buffer := [5]u8{}
43 res := unsafe { utf32_to_str_no_malloc(u32(r), mut &buffer[0]) }
44 if res.len == 0 {
45 return
46 }
47 unsafe { b.push_many(res.str, res.len) }
48}
49
50// write_runes appends all the given runes to the accumulated buffer.
51pub fn (mut b Builder) write_runes(runes []rune) {
52 mut buffer := [5]u8{}
53 for r in runes {
54 res := unsafe { utf32_to_str_no_malloc(u32(r), mut &buffer[0]) }
55 if res.len == 0 {
56 continue
57 }
58 unsafe { b.push_many(res.str, res.len) }
59 }
60}
61
62// write_u8 appends a single `data` byte to the accumulated buffer
63@[inline]
64pub fn (mut b Builder) write_u8(data u8) {
65 b << data
66}
67
68// write_byte appends a single `data` byte to the accumulated buffer
69@[inline]
70pub fn (mut b Builder) write_byte(data u8) {
71 b << data
72}
73
74// write_decimal appends a decimal representation of the number `n` into the builder `b`,
75// without dynamic allocation. The higher order digits come first, i.e. 6123 will be written
76// with the digit `6` first, then `1`, then `2` and `3` last.
77pub fn (mut b Builder) write_decimal(n i64) {
78 if n == 0 {
79 b.write_u8(0x30)
80 return
81 }
82 mut mag := u64(n)
83 if n < 0 {
84 b.write_u8(`-`)
85 // Wrapping unsigned negation yields the correct magnitude even for `min_i64`,
86 // whose absolute value does not fit in an i64, so this stays allocation-free for
87 // every input without a special case for the signed 64-bit minimum.
88 mag = u64(0) - mag
89 }
90 b.write_u_decimal(mag)
91}
92
93// write_u_decimal appends a decimal representation of the unsigned number `n` into the
94// builder `b`, without dynamic allocation. Unlike `write_decimal`, it covers the entire
95// `u64` range (values above `max_i64`). The higher order digits come first, i.e. 6123
96// will be written with the digit `6` first, then `1`, then `2` and `3` last.
97@[direct_array_access]
98pub fn (mut b Builder) write_u_decimal(n u64) {
99 if n == 0 {
100 b.write_u8(0x30)
101 return
102 }
103
104 mut buf := [20]u8{} // max_u64 == 18446744073709551615, i.e. 20 digits
105 mut x := n
106 mut i := 19
107 for x != 0 {
108 nextx := x / 10
109 r := x % 10
110 buf[i] = u8(r) + 0x30
111 x = nextx
112 i--
113 }
114 unsafe { b.write_ptr(&buf[i + 1], 19 - i) }
115}
116
117// write implements the io.Writer interface, that is why it returns how many bytes were written to the string builder.
118pub fn (mut b Builder) write(data []u8) !int {
119 if data.len == 0 {
120 return 0
121 }
122 unsafe { b.push_many(data.data, data.len) }
123 return data.len
124}
125
126// drain_builder writes all of the `other` builder content, then re-initialises
127// `other`, so that the `other` strings builder is ready to receive new content.
128@[manualfree]
129pub fn (mut b Builder) drain_builder(mut other Builder, other_new_cap int) {
130 if other.len > 0 {
131 b << *other
132 }
133 unsafe { other.free() }
134 other = new_builder(other_new_cap)
135}
136
137// byte_at returns a byte, located at a given index `i`.
138// Note: it can panic, if there are not enough bytes in the strings builder yet.
139@[inline]
140pub fn (b &Builder) byte_at(n int) u8 {
141 return unsafe { (&[]u8(b))[n] }
142}
143
144// write appends the string `s` to the buffer
145@[expand_simple_interpolation; inline]
146pub fn (mut b Builder) write_string(s string) {
147 if s.len == 0 {
148 return
149 }
150 unsafe { b.push_many(s.str, s.len) }
151 // for c in s {
152 // b.buf << c
153 // }
154 // b.buf << []u8(s) // TODO
155}
156
157// write_string2 appends the strings `s1` and `s2` to the buffer.
158@[inline]
159pub fn (mut b Builder) write_string2(s1 string, s2 string) {
160 if s1.len != 0 {
161 unsafe { b.push_many(s1.str, s1.len) }
162 }
163 if s2.len != 0 {
164 unsafe { b.push_many(s2.str, s2.len) }
165 }
166}
167
168// go_back discards the last `n` bytes from the buffer.
169pub fn (mut b Builder) go_back(n int) {
170 b.trim(b.len - n)
171}
172
173// spart returns a part of the buffer as a string
174@[inline]
175pub fn (b &Builder) spart(start_pos int, n int) string {
176 unsafe {
177 mut x := malloc_noscan(n + 1)
178 vmemcpy(x, &u8(b.data) + start_pos, n)
179 x[n] = 0
180 return tos(x, n)
181 }
182}
183
184// cut_last cuts the last `n` bytes from the buffer and returns them.
185pub fn (mut b Builder) cut_last(n int) string {
186 cut_pos := b.len - n
187 res := b.spart(cut_pos, n)
188 b.trim(cut_pos)
189 return res
190}
191
192// cut_to cuts the string after `pos` and returns it.
193// if `pos` is superior to builder length, returns an empty string
194// and cancel further operations
195pub fn (mut b Builder) cut_to(pos int) string {
196 if pos > b.len {
197 return ''
198 }
199 return b.cut_last(b.len - pos)
200}
201
202// go_back_to resets the buffer to the given position `pos`.
203// Note: pos should be < than the existing buffer length.
204pub fn (mut b Builder) go_back_to(pos int) {
205 b.trim(pos)
206}
207
208// writeln appends the string `s`, and then a newline character.
209@[inline]
210pub fn (mut b Builder) writeln(s string) {
211 // for c in s {
212 // b.buf << c
213 // }
214 if s != '' {
215 unsafe { b.push_many(s.str, s.len) }
216 }
217 // b.buf << []u8(s) // TODO
218 b << u8(`\n`)
219}
220
221// writeln2 appends two strings: `s1` + `\n`, and `s2` + `\n`, to the buffer.
222@[inline]
223pub fn (mut b Builder) writeln2(s1 string, s2 string) {
224 if s1 != '' {
225 unsafe { b.push_many(s1.str, s1.len) }
226 }
227 b << u8(`\n`)
228 if s2 != '' {
229 unsafe { b.push_many(s2.str, s2.len) }
230 }
231 b << u8(`\n`)
232}
233
234// last_n(5) returns 'world'
235// buf == 'hello world'
236pub fn (b &Builder) last_n(n int) string {
237 if n > b.len {
238 return ''
239 }
240 return b.spart(b.len - n, n)
241}
242
243// after(6) returns 'world'
244// buf == 'hello world'
245pub fn (b &Builder) after(n int) string {
246 if n >= b.len {
247 return ''
248 }
249 return b.spart(n, b.len - n)
250}
251
252// str returns a copy of all of the accumulated buffer content.
253// Note: after a call to b.str(), the builder b will be empty, and could be used again.
254// The returned string *owns* its own separate copy of the accumulated data that was in
255// the string builder, before the .str() call.
256pub fn (mut b Builder) str() string {
257 b << u8(0)
258 bcopy := unsafe { &u8(memdup_noscan(b.data, b.len)) }
259 s := unsafe { bcopy.vstring_with_len(b.len - 1) }
260 b.clear()
261 return s
262}
263
264// ensure_cap ensures that the buffer has enough space for at least `n` bytes by growing the buffer if necessary.
265pub fn (mut b Builder) ensure_cap(n int) {
266 // Work through the underlying array pointer, instead of taking a pointer
267 // cast to the alias receiver. This keeps self-hosted builds from generating
268 // an invalid `&b` cast in C.
269 mut arr := unsafe { &[]u8(b) }
270 arr.ensure_cap(n)
271}
272
273// grow_len grows the length of the buffer by `n` bytes if necessary
274@[unsafe]
275pub fn (mut b Builder) grow_len(n int) {
276 if n <= 0 {
277 return
278 }
279
280 new_len := b.len + n
281 b.ensure_cap(new_len)
282 unsafe {
283 b.len = new_len
284 }
285}
286
287// free frees the memory block, used for the buffer.
288// Note: do not use the builder, after a call to free().
289@[unsafe]
290pub fn (mut b Builder) free() {
291 if b.data != 0 {
292 mut arr := unsafe { &[]u8(b) }
293 unsafe { arr.free() }
294 }
295}
296
297// write_repeated_rune appends multiple copies of the same rune to the accumulated buffer
298@[direct_array_access]
299pub fn (mut b Builder) write_repeated_rune(r rune, count int) {
300 if count <= 0 {
301 return
302 }
303
304 // Convert rune to UTF-8 bytes once
305 mut buffer := [5]u8{}
306 res := unsafe { utf32_to_str_no_malloc(u32(r), mut &buffer[0]) }
307 if res.len == 0 {
308 return
309 }
310
311 if res.len == 1 {
312 b.ensure_cap(b.len + count)
313 unsafe {
314 vmemset(&u8(b.data) + b.len, buffer[0], count)
315 b.len += count
316 }
317 return
318 } else {
319 total_needed := count * res.len
320 b.ensure_cap(b.len + total_needed)
321
322 mut dest := unsafe { &u8(b.data) + b.len }
323 for _ in 0 .. count {
324 unsafe {
325 vmemcpy(dest, res.str, res.len)
326 dest += res.len
327 }
328 }
329 unsafe {
330 b.len += total_needed
331 }
332 }
333}
334
335// IndentParam holds configuration parameters for the indent() function
336@[params]
337pub struct IndentParam {
338pub mut:
339 block_start rune = `{` // Character that starts a new block (+ indent)
340 block_end rune = `}` // Character that ends a new block (- indent)
341 indent_char rune = ` ` // Character used for indentation (space or tab)
342 indent_count int = 4 // Number of indent_char per indentation level
343 starting_level int // Initial indentation level (0 = no initial indent)
344}
345
346// IndentState represents the current parsing state of the indent() function
347enum IndentState {
348 normal // Normal state, processing regular characters
349 in_string // Inside a string literal, ignoring formatting characters
350}
351
352// indent formats a string by applying structured indentation based on block delimiters.
353// It processes the input string `s` and writes the formatted output to the `Builder` `b`.
354// The function preserves content inside string literals (both single and double quotes) and
355// configures indentation behavior through the `param` structure.
356//
357// Key behaviors:
358// 1. Removes existing indentation at the beginning of lines.
359// 2. Applies new indentation based on block nesting levels.
360// 3. Ignores block delimiters and formatting characters inside string literals.
361// 4. Keeps empty blocks (e.g., {}) on the same line.
362// 5. Inserts newlines after `block_start` and before `block_end` (except for empty blocks).
363// 6. Maintains existing line breaks from the input.
364//
365// Example:
366// ```v
367// import strings
368// input := 'User{name:"John" settings:{theme:"dark"}}'
369// mut b := strings.new_builder(64)
370// b.indent(input, indent_count: 2)
371// println(b.str()) // Formatted output: 'User{\n name:"John" settings:{\n theme:"dark"\n }\n}'
372// ```
373@[direct_array_access]
374pub fn (mut b Builder) indent(s string, param IndentParam) {
375 if s.len == 0 {
376 return
377 }
378
379 mut state := IndentState.normal
380 mut indent_level := param.starting_level
381 mut string_char := `\0`
382 mut at_line_start := true
383 for i := 0; i < s.len; i++ {
384 c := rune(s[i])
385 match state {
386 // Normal state: process characters outside of string literals
387 .normal {
388 match c {
389 `"`, `'` { // Note: quote characters for editor display "
390 state = .in_string
391 string_char = c
392 // Add indentation if at the start of a line
393 if at_line_start {
394 b.write_repeated_rune(param.indent_char,
395 indent_level * param.indent_count)
396 at_line_start = false
397 }
398 // Write the opening quote
399 b.write_rune(c)
400 }
401 param.block_start {
402 // Start of a new block
403 // Add indentation if at the start of a line
404 if at_line_start {
405 b.write_repeated_rune(param.indent_char,
406 indent_level * param.indent_count)
407 at_line_start = false
408 }
409
410 // Write the block start character
411 b.write_rune(c)
412
413 // Check for empty block (e.g., {})
414 // Empty blocks stay on the same line
415 if i + 1 < s.len && s[i + 1] == param.block_end {
416 b.write_rune(param.block_end)
417 i++
418 } else {
419 // Non-empty block: increase indentation and add newline
420 indent_level++
421 b.write_rune(`\n`)
422 at_line_start = true
423 }
424 }
425 param.block_end {
426 // End of a block
427 // Decrease indentation level (but not below 0)
428 if indent_level > 0 {
429 indent_level--
430 }
431
432 // If not at the start of a line, add a newline
433 if !at_line_start {
434 b.write_rune(`\n`)
435 }
436
437 // Add indentation for the block end
438 b.write_repeated_rune(param.indent_char, indent_level * param.indent_count)
439 at_line_start = false
440
441 b.write_rune(c)
442 }
443 ` `, `\t`, `\r`, `\n` {
444 // Whitespace characters
445 // Only write whitespace if not at the start of a line
446 if !at_line_start {
447 b.write_rune(c)
448 }
449
450 // Newline resets the line start flag
451 if c == `\n` {
452 at_line_start = true
453 }
454 }
455 else {
456 // Any other character
457 // Add indentation if at the start of a line
458 if at_line_start {
459 b.write_repeated_rune(param.indent_char,
460 indent_level * param.indent_count)
461 at_line_start = false
462 }
463 b.write_rune(c)
464 }
465 }
466 }
467 .in_string {
468 // Inside a string literal: preserve all characters as-is
469 b.write_rune(c)
470
471 // Check for string termination
472 // The character must match the opening quote and not be escaped
473 if c == string_char {
474 if s[i - 1] != `\\` {
475 state = .normal
476 string_char = `\0`
477 }
478 }
479 }
480 }
481 }
482}
483