1 | // Copyright (c) 2019-2023 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. |
4 | module 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. |
10 | pub type Builder = []u8 |
11 | |
12 | // new_builder returns a new string builder, with an initial capacity of `initial_size` |
13 | pub 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] |
25 | pub 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] |
32 | pub 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] |
41 | pub fn (mut b Builder) write_rune(r rune) { |
42 | mut buffer := [5]u8{} |
43 | res := unsafe { utf32_to_str_no_malloc(u32(r), &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 |
51 | pub 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), &buffer[0]) } |
55 | if res.len == 0 { |
56 | continue |
57 | } |
58 | unsafe { b.push_many(res.str, res.len) } |
59 | } |
60 | } |
61 | |
62 | // clear clears the buffer contents |
63 | pub fn (mut b Builder) clear() { |
64 | b = []u8{cap: b.cap} |
65 | } |
66 | |
67 | // write_u8 appends a single `data` byte to the accumulated buffer |
68 | pub fn (mut b Builder) write_u8(data u8) { |
69 | b << data |
70 | } |
71 | |
72 | // write_byte appends a single `data` byte to the accumulated buffer |
73 | pub fn (mut b Builder) write_byte(data byte) { |
74 | b << data |
75 | } |
76 | |
77 | // write implements the Writer interface |
78 | pub fn (mut b Builder) write(data []u8) !int { |
79 | if data.len == 0 { |
80 | return 0 |
81 | } |
82 | b << data |
83 | return data.len |
84 | } |
85 | |
86 | // drain_builder writes all of the `other` builder content, then re-initialises |
87 | // `other`, so that the `other` strings builder is ready to receive new content. |
88 | [manualfree] |
89 | pub fn (mut b Builder) drain_builder(mut other Builder, other_new_cap int) { |
90 | if other.len > 0 { |
91 | b << *other |
92 | } |
93 | unsafe { other.free() } |
94 | other = new_builder(other_new_cap) |
95 | } |
96 | |
97 | // byte_at returns a byte, located at a given index `i`. |
98 | // Note: it can panic, if there are not enough bytes in the strings builder yet. |
99 | [inline] |
100 | pub fn (b &Builder) byte_at(n int) u8 { |
101 | return unsafe { (&[]u8(b))[n] } |
102 | } |
103 | |
104 | // write appends the string `s` to the buffer |
105 | [inline] |
106 | pub fn (mut b Builder) write_string(s string) { |
107 | if s.len == 0 { |
108 | return |
109 | } |
110 | unsafe { b.push_many(s.str, s.len) } |
111 | // for c in s { |
112 | // b.buf << c |
113 | // } |
114 | // b.buf << []u8(s) // TODO |
115 | } |
116 | |
117 | // go_back discards the last `n` bytes from the buffer |
118 | pub fn (mut b Builder) go_back(n int) { |
119 | b.trim(b.len - n) |
120 | } |
121 | |
122 | [inline] |
123 | fn (b &Builder) spart(start_pos int, n int) string { |
124 | unsafe { |
125 | mut x := malloc_noscan(n + 1) |
126 | vmemcpy(x, &u8(b.data) + start_pos, n) |
127 | x[n] = 0 |
128 | return tos(x, n) |
129 | } |
130 | } |
131 | |
132 | // cut_last cuts the last `n` bytes from the buffer and returns them |
133 | pub fn (mut b Builder) cut_last(n int) string { |
134 | cut_pos := b.len - n |
135 | res := b.spart(cut_pos, n) |
136 | b.trim(cut_pos) |
137 | return res |
138 | } |
139 | |
140 | // cut_to cuts the string after `pos` and returns it. |
141 | // if `pos` is superior to builder length, returns an empty string |
142 | // and cancel further operations |
143 | pub fn (mut b Builder) cut_to(pos int) string { |
144 | if pos > b.len { |
145 | return '' |
146 | } |
147 | return b.cut_last(b.len - pos) |
148 | } |
149 | |
150 | // go_back_to resets the buffer to the given position `pos` |
151 | // Note: pos should be < than the existing buffer length. |
152 | pub fn (mut b Builder) go_back_to(pos int) { |
153 | b.trim(pos) |
154 | } |
155 | |
156 | // writeln appends the string `s`, and then a newline character. |
157 | [inline] |
158 | pub fn (mut b Builder) writeln(s string) { |
159 | // for c in s { |
160 | // b.buf << c |
161 | // } |
162 | if s.len > 0 { |
163 | unsafe { b.push_many(s.str, s.len) } |
164 | } |
165 | // b.buf << []u8(s) // TODO |
166 | b << u8(`\n`) |
167 | } |
168 | |
169 | // last_n(5) returns 'world' |
170 | // buf == 'hello world' |
171 | pub fn (b &Builder) last_n(n int) string { |
172 | if n > b.len { |
173 | return '' |
174 | } |
175 | return b.spart(b.len - n, n) |
176 | } |
177 | |
178 | // after(6) returns 'world' |
179 | // buf == 'hello world' |
180 | pub fn (b &Builder) after(n int) string { |
181 | if n >= b.len { |
182 | return '' |
183 | } |
184 | return b.spart(n, b.len - n) |
185 | } |
186 | |
187 | // str returns a copy of all of the accumulated buffer content. |
188 | // Note: after a call to b.str(), the builder b will be empty, and could be used again. |
189 | // The returned string *owns* its own separate copy of the accumulated data that was in |
190 | // the string builder, before the .str() call. |
191 | pub fn (mut b Builder) str() string { |
192 | b << u8(0) |
193 | bcopy := unsafe { &u8(memdup_noscan(b.data, b.len)) } |
194 | s := unsafe { bcopy.vstring_with_len(b.len - 1) } |
195 | b.trim(0) |
196 | return s |
197 | } |
198 | |
199 | // ensure_cap ensures that the buffer has enough space for at least `n` bytes by growing the buffer if necessary |
200 | pub fn (mut b Builder) ensure_cap(n int) { |
201 | // code adapted from vlib/builtin/array.v |
202 | if n <= b.cap { |
203 | return |
204 | } |
205 | |
206 | new_data := vcalloc(n * b.element_size) |
207 | if b.data != unsafe { nil } { |
208 | unsafe { vmemcpy(new_data, b.data, b.len * b.element_size) } |
209 | // TODO: the old data may be leaked when no GC is used (ref-counting?) |
210 | if b.flags.has(.noslices) { |
211 | unsafe { free(b.data) } |
212 | } |
213 | } |
214 | unsafe { |
215 | b.data = new_data |
216 | b.offset = 0 |
217 | b.cap = n |
218 | } |
219 | } |
220 | |
221 | // free frees the memory block, used for the buffer. |
222 | // Note: do not use the builder, after a call to free(). |
223 | [unsafe] |
224 | pub fn (mut b Builder) free() { |
225 | if b.data != 0 { |
226 | unsafe { free(b.data) } |
227 | unsafe { |
228 | b.data = nil |
229 | } |
230 | } |
231 | } |