1 | // [Blend2D] |
2 | // 2D Vector Graphics Powered by a JIT Compiler. |
3 | // |
4 | // [License] |
5 | // Zlib - See LICENSE.md file in the package. |
6 | |
7 | #include "./blapi-build_p.h" |
8 | #include "./blarray_p.h" |
9 | #include "./blstring_p.h" |
10 | #include "./blruntime_p.h" |
11 | |
12 | // ============================================================================ |
13 | // [Global Variables] |
14 | // ============================================================================ |
15 | |
16 | static BLWrap<BLStringImpl> blNullStringImpl; |
17 | static const char blNullStringData[1] = "" ; |
18 | |
19 | // ============================================================================ |
20 | // [BLString - Internal] |
21 | // ============================================================================ |
22 | |
23 | static constexpr size_t blStringImplSizeOf(size_t n = 0) noexcept { |
24 | return blContainerSizeOf(sizeof(BLStringImpl) - 4 + 1, 1, n); |
25 | } |
26 | |
27 | static constexpr size_t blStringCapacityOf(size_t implSize) noexcept { |
28 | return blContainerCapacityOf(blStringImplSizeOf(), 1, implSize); |
29 | } |
30 | |
31 | static constexpr size_t blStringMaximumCapacity() noexcept { |
32 | return blStringCapacityOf(SIZE_MAX); |
33 | } |
34 | |
35 | static BL_INLINE size_t blStringFittingCapacity(size_t n) noexcept { |
36 | return blContainerFittingCapacity(blStringImplSizeOf(), 1, n); |
37 | } |
38 | |
39 | static BL_INLINE size_t blStringGrowingCapacity(size_t n) noexcept { |
40 | return blContainerGrowingCapacity(blStringImplSizeOf(), 1, n, BL_ALLOC_HINT_STRING); |
41 | } |
42 | |
43 | static BL_INLINE BLStringImpl* blStringImplNew(size_t n) noexcept { |
44 | uint16_t memPoolData; |
45 | BLStringImpl* impl = blRuntimeAllocImplT<BLStringImpl>(blStringImplSizeOf(n), &memPoolData); |
46 | |
47 | if (BL_UNLIKELY(!impl)) |
48 | return impl; |
49 | |
50 | blImplInit(impl, BL_IMPL_TYPE_STRING, BL_IMPL_TRAIT_MUTABLE, memPoolData); |
51 | impl->data = reinterpret_cast<char*>(impl->reserved); |
52 | impl->size = 0; |
53 | impl->capacity = n; |
54 | impl->reserved[0] = 0; |
55 | impl->reserved[1] = 0; |
56 | impl->reserved[2] = 0; |
57 | impl->reserved[3] = 0; |
58 | |
59 | return impl; |
60 | } |
61 | |
62 | // Cannot be static, called by `BLVariant` implementation. |
63 | BLResult blStringImplDelete(BLStringImpl* impl) noexcept { |
64 | uint8_t* implBase = reinterpret_cast<uint8_t*>(impl); |
65 | size_t implSize = blStringImplSizeOf(impl->capacity); |
66 | uint32_t implTraits = impl->implTraits; |
67 | uint32_t memPoolData = impl->memPoolData; |
68 | |
69 | if (implTraits & BL_IMPL_TRAIT_EXTERNAL) { |
70 | implSize = blStringImplSizeOf() + sizeof(BLExternalImplPreface); |
71 | implBase -= sizeof(BLExternalImplPreface); |
72 | blImplDestroyExternal(impl); |
73 | } |
74 | |
75 | if (implTraits & BL_IMPL_TRAIT_FOREIGN) |
76 | return BL_SUCCESS; |
77 | else |
78 | return blRuntimeFreeImpl(implBase, implSize, memPoolData); |
79 | } |
80 | |
81 | static BL_INLINE BLResult blStringImplRelease(BLStringImpl* impl) noexcept { |
82 | if (blImplDecRefAndTest(impl)) |
83 | return blStringImplDelete(impl); |
84 | return BL_SUCCESS; |
85 | } |
86 | |
87 | static BL_NOINLINE BLResult blStringRealloc(BLStringCore* self, size_t n) noexcept { |
88 | BLStringImpl* oldI = self->impl; |
89 | BLStringImpl* newI = blStringImplNew(n); |
90 | |
91 | if (BL_UNLIKELY(!newI)) |
92 | return blTraceError(BL_ERROR_OUT_OF_MEMORY); |
93 | |
94 | size_t size = oldI->size; |
95 | BL_ASSERT(size <= n); |
96 | |
97 | self->impl = newI; |
98 | newI->size = size; |
99 | |
100 | char* dst = newI->data; |
101 | memcpy(dst, oldI->data, size); |
102 | dst[size] = '\0'; |
103 | |
104 | return blStringImplRelease(oldI); |
105 | } |
106 | |
107 | // ============================================================================ |
108 | // [BLString - Init / Reset] |
109 | // ============================================================================ |
110 | |
111 | BLResult blStringInit(BLStringCore* self) noexcept { |
112 | self->impl = &blNullStringImpl; |
113 | return BL_SUCCESS; |
114 | } |
115 | |
116 | BLResult blStringReset(BLStringCore* self) noexcept { |
117 | BLStringImpl* selfI = self->impl; |
118 | self->impl = &blNullStringImpl; |
119 | return blStringImplRelease(selfI); |
120 | } |
121 | |
122 | // ============================================================================ |
123 | // [BLString - Storage] |
124 | // ============================================================================ |
125 | |
126 | size_t blStringGetSize(const BLStringCore* self) noexcept { |
127 | return self->impl->size; |
128 | } |
129 | |
130 | size_t blStringGetCapacity(const BLStringCore* self) BL_NOEXCEPT_C { |
131 | return self->impl->capacity; |
132 | } |
133 | |
134 | const char* blStringGetData(const BLStringCore* self) BL_NOEXCEPT_C { |
135 | return self->impl->data; |
136 | } |
137 | |
138 | BLResult blStringClear(BLStringCore* self) noexcept { |
139 | BLStringImpl* selfI = self->impl; |
140 | |
141 | if (!blImplIsMutable(selfI)) { |
142 | self->impl = &blNullStringImpl; |
143 | return blStringImplRelease(selfI); |
144 | } |
145 | else { |
146 | selfI->size = 0; |
147 | selfI->data[0] = '\0'; |
148 | return BL_SUCCESS; |
149 | } |
150 | } |
151 | |
152 | BLResult blStringShrink(BLStringCore* self) noexcept { |
153 | BLStringImpl* selfI = self->impl; |
154 | size_t size = selfI->size; |
155 | |
156 | if (!size) { |
157 | self->impl = &blNullStringImpl; |
158 | return blStringImplRelease(selfI); |
159 | } |
160 | |
161 | size_t capacity = blStringFittingCapacity(size); |
162 | if (capacity >= selfI->capacity) |
163 | return BL_SUCCESS; |
164 | |
165 | return blStringRealloc(self, capacity); |
166 | } |
167 | |
168 | BLResult blStringReserve(BLStringCore* self, size_t n) noexcept { |
169 | BLStringImpl* selfI = self->impl; |
170 | size_t immutableMsk = blBitMaskFromBool<size_t>(!blImplIsMutable(selfI)); |
171 | |
172 | if ((n | immutableMsk) > selfI->capacity) { |
173 | if (BL_UNLIKELY(n > blStringMaximumCapacity())) |
174 | return blTraceError(BL_ERROR_OUT_OF_MEMORY); |
175 | |
176 | size_t capacity = blStringFittingCapacity(blMax(n, selfI->size)); |
177 | return blStringRealloc(self, capacity); |
178 | } |
179 | |
180 | return BL_SUCCESS; |
181 | } |
182 | |
183 | BLResult blStringResize(BLStringCore* self, size_t n, char fill) noexcept { |
184 | BLStringImpl* selfI = self->impl; |
185 | size_t size = selfI->size; |
186 | |
187 | // If `n` is smaller than the current `size` then this is a truncation. |
188 | if (n <= size) { |
189 | if (!blImplIsMutable(selfI)) { |
190 | if (n == size) |
191 | return BL_SUCCESS; |
192 | |
193 | size_t capacity = blStringFittingCapacity(n); |
194 | BLStringImpl* newI = blStringImplNew(capacity); |
195 | |
196 | if (BL_UNLIKELY(!newI)) |
197 | return blTraceError(BL_ERROR_OUT_OF_MEMORY); |
198 | |
199 | newI->size = n; |
200 | self->impl = newI; |
201 | |
202 | char* dst = newI->data; |
203 | char* src = selfI->data; |
204 | |
205 | memcpy(dst, src, n); |
206 | dst[n] = '\0'; |
207 | |
208 | return blStringImplRelease(selfI); |
209 | } |
210 | else { |
211 | char* data = selfI->data; |
212 | |
213 | selfI->size = n; |
214 | data[n] = '\0'; |
215 | return BL_SUCCESS; |
216 | } |
217 | } |
218 | else { |
219 | n -= size; |
220 | char* dst; |
221 | BL_PROPAGATE(blStringModifyOp(self, BL_MODIFY_OP_APPEND_FIT, n, &dst)); |
222 | |
223 | memset(dst, int((unsigned char)fill), n); |
224 | return BL_SUCCESS; |
225 | } |
226 | } |
227 | |
228 | // ============================================================================ |
229 | // [BLString - Op] |
230 | // ============================================================================ |
231 | |
232 | BLResult blStringMakeMutable(BLStringCore* self, char** dataOut) noexcept { |
233 | BLStringImpl* selfI = self->impl; |
234 | |
235 | if (!blImplIsMutable(selfI)) { |
236 | size_t size = selfI->size; |
237 | size_t capacity = blMax(blStringFittingCapacity(size), |
238 | blStringCapacityOf(BL_ALLOC_HINT_ARRAY)); |
239 | |
240 | BL_PROPAGATE(blStringRealloc(self, capacity)); |
241 | selfI = self->impl; |
242 | } |
243 | |
244 | *dataOut = selfI->data; |
245 | return BL_SUCCESS; |
246 | } |
247 | |
248 | BLResult blStringModifyOp(BLStringCore* self, uint32_t op, size_t n, char** dataOut) noexcept { |
249 | BLStringImpl* selfI = self->impl; |
250 | |
251 | size_t size = selfI->size; |
252 | size_t index = (op >= BL_MODIFY_OP_APPEND_START) ? size : size_t(0); |
253 | size_t sizeAfter = blUAddSaturate(index, n); |
254 | size_t immutableMsk = blBitMaskFromBool<size_t>(!blImplIsMutable(selfI)); |
255 | |
256 | if ((sizeAfter | immutableMsk) > selfI->capacity) { |
257 | if (BL_UNLIKELY(sizeAfter > blStringMaximumCapacity())) |
258 | return blTraceError(BL_ERROR_OUT_OF_MEMORY); |
259 | |
260 | size_t capacity = |
261 | (op & BL_MODIFY_OP_GROW_MASK) |
262 | ? blStringGrowingCapacity(sizeAfter) |
263 | : blStringFittingCapacity(sizeAfter); |
264 | |
265 | BLStringImpl* newI = blStringImplNew(capacity); |
266 | if (BL_UNLIKELY(!newI)) { |
267 | *dataOut = nullptr; |
268 | return blTraceError(BL_ERROR_OUT_OF_MEMORY); |
269 | } |
270 | |
271 | self->impl = newI; |
272 | newI->size = sizeAfter; |
273 | |
274 | char* dst = newI->data; |
275 | char* src = selfI->data; |
276 | |
277 | *dataOut = dst + index; |
278 | memcpy(dst, src, index); |
279 | dst[sizeAfter] = '\0'; |
280 | |
281 | return blStringImplRelease(selfI); |
282 | } |
283 | else { |
284 | char* data = selfI->data; |
285 | |
286 | *dataOut = data + index; |
287 | selfI->size = sizeAfter; |
288 | |
289 | data[sizeAfter] = '\0'; |
290 | return BL_SUCCESS; |
291 | } |
292 | } |
293 | |
294 | static BLResult blStringModifyAndCopy(BLStringCore* self, uint32_t op, const char* str, size_t n) noexcept { |
295 | BLStringImpl* selfI = self->impl; |
296 | |
297 | size_t size = selfI->size; |
298 | size_t index = (op >= BL_MODIFY_OP_APPEND_START) ? size : size_t(0); |
299 | size_t sizeAfter = blUAddSaturate(index, n); |
300 | size_t immutableMsk = blBitMaskFromBool<size_t>(!blImplIsMutable(selfI)); |
301 | |
302 | if ((sizeAfter | immutableMsk) > selfI->capacity) { |
303 | if (BL_UNLIKELY(sizeAfter > blStringMaximumCapacity())) |
304 | return blTraceError(BL_ERROR_OUT_OF_MEMORY); |
305 | |
306 | size_t capacity = |
307 | (op & BL_MODIFY_OP_GROW_MASK) |
308 | ? blStringGrowingCapacity(sizeAfter) |
309 | : blStringFittingCapacity(sizeAfter); |
310 | |
311 | BLStringImpl* newI = blStringImplNew(capacity); |
312 | if (BL_UNLIKELY(!newI)) |
313 | return blTraceError(BL_ERROR_OUT_OF_MEMORY); |
314 | |
315 | self->impl = newI; |
316 | newI->size = sizeAfter; |
317 | |
318 | char* dst = newI->data; |
319 | char* src = selfI->data; |
320 | |
321 | memcpy(dst, src, index); |
322 | memcpy(dst + index, str, n); |
323 | dst[sizeAfter] = '\0'; |
324 | |
325 | return blStringImplRelease(selfI); |
326 | } |
327 | else { |
328 | char* data = selfI->data; |
329 | |
330 | selfI->size = sizeAfter; |
331 | memmove(data + index, str, n); |
332 | |
333 | data[sizeAfter] = '\0'; |
334 | return BL_SUCCESS; |
335 | } |
336 | } |
337 | |
338 | BLResult blStringInsertOp(BLStringCore* self, size_t index, size_t n, char** dataOut) noexcept { |
339 | BLStringImpl* selfI = self->impl; |
340 | |
341 | size_t size = selfI->size; |
342 | size_t sizeAfter = blUAddSaturate(size, n); |
343 | size_t immutableMsk = blBitMaskFromBool<size_t>(!blImplIsMutable(selfI)); |
344 | |
345 | if ((sizeAfter | immutableMsk) > selfI->capacity) { |
346 | if (BL_UNLIKELY(sizeAfter > blStringMaximumCapacity())) |
347 | return blTraceError(BL_ERROR_OUT_OF_MEMORY); |
348 | |
349 | size_t capacity = blStringGrowingCapacity(sizeAfter); |
350 | BLStringImpl* newI = blStringImplNew(capacity); |
351 | |
352 | if (BL_UNLIKELY(!newI)) { |
353 | *dataOut = nullptr; |
354 | return blTraceError(BL_ERROR_OUT_OF_MEMORY); |
355 | } |
356 | |
357 | self->impl = newI; |
358 | newI->size = sizeAfter; |
359 | |
360 | char* dst = newI->data; |
361 | char* src = selfI->data; |
362 | |
363 | *dataOut = dst + index; |
364 | memcpy(dst, src, index); |
365 | memcpy(dst + index + n, src+ index, size - index); |
366 | dst[sizeAfter] = '\0'; |
367 | |
368 | return blStringImplRelease(selfI); |
369 | } |
370 | else { |
371 | char* data = selfI->data; |
372 | |
373 | selfI->size = sizeAfter; |
374 | memmove(data + index + n, data + index, size - index); |
375 | |
376 | data[sizeAfter] = '\0'; |
377 | return BL_SUCCESS; |
378 | } |
379 | } |
380 | |
381 | static BLResult blStringInsertAndCopy(BLStringCore* self, size_t index, const char* str, size_t n) noexcept { |
382 | BLStringImpl* selfI = self->impl; |
383 | |
384 | size_t size = selfI->size; |
385 | size_t sizeAfter = blUAddSaturate(size, n); |
386 | |
387 | size_t endIndex = index + n; |
388 | size_t immutableMsk = blBitMaskFromBool<size_t>(!blImplIsMutable(selfI)); |
389 | |
390 | if ((sizeAfter | immutableMsk) > selfI->capacity) { |
391 | if (BL_UNLIKELY(sizeAfter > blStringMaximumCapacity())) |
392 | return blTraceError(BL_ERROR_OUT_OF_MEMORY); |
393 | |
394 | size_t capacity = blStringGrowingCapacity(sizeAfter); |
395 | BLStringImpl* newI = blStringImplNew(capacity); |
396 | |
397 | if (BL_UNLIKELY(!newI)) |
398 | return blTraceError(BL_ERROR_OUT_OF_MEMORY); |
399 | |
400 | char* dst = newI->data; |
401 | char* src = selfI->data; |
402 | |
403 | memcpy(dst, src, index); |
404 | memcpy(dst + endIndex, src + index, size - index); |
405 | |
406 | self->impl = newI; |
407 | newI->size = sizeAfter; |
408 | |
409 | memcpy(dst + index, str, n); |
410 | return blStringImplRelease(selfI); |
411 | } |
412 | else { |
413 | selfI->size = sizeAfter; |
414 | |
415 | char* data = selfI->data; |
416 | char* dataEnd = data + size; |
417 | |
418 | // The destination would point into the first byte that will be modified. |
419 | // So for example if the data is `[ABCDEF]` and we are inserting at index |
420 | // 1 then the `data` would point to `[BCDEF]`. |
421 | data += index; |
422 | dataEnd += n; |
423 | |
424 | // Move the memory in-place making space for items to insert. For example |
425 | // if the destination points to [ABCDEF] and we want to insert 4 items we |
426 | // would get [____ABCDEF]. |
427 | memmove(data + n, data, size - index); |
428 | |
429 | // Split the [str:strEnd] into LEAD and TRAIL slices and shift TRAIL slice |
430 | // in a way to cancel the `memmove()` if `str` overlaps `data`. In practice |
431 | // if there is an overlap the [str:strEnd] source should be within [data:dataEnd] |
432 | // as it doesn't make sense to insert something which is outside of the current |
433 | // valid area. |
434 | // |
435 | // This illustrates how the input is divided into leading and traling data. |
436 | // |
437 | // BCDEFGH <- Insert This |
438 | // [abcdefghi] |
439 | // ^ <- Here |
440 | // |
441 | // [abcd_______efgh] |
442 | // <- memmove() |
443 | // |
444 | // |-| <- Copy leading data |
445 | // [abcdBCD____efgh] |
446 | // |
447 | // |--| <- Copy shifted trailing data. |
448 | // [abcdBCDEFGHdefgh] |
449 | |
450 | // Leading area precedes `data` - nothing changed in here and if this is |
451 | // the whole ares then there was no overlap that we would have to deal with. |
452 | size_t nLeadBytes = 0; |
453 | if (str < data) { |
454 | nLeadBytes = blMin<size_t>((size_t)(data - str), n); |
455 | memcpy(data, str, nLeadBytes); |
456 | |
457 | data += nLeadBytes; |
458 | str += nLeadBytes; |
459 | } |
460 | |
461 | // Trailing area - we either shift none or all of it. |
462 | if (str < dataEnd) |
463 | str += n; // Shift source in case of overlap. |
464 | |
465 | memcpy(data, str, n - nLeadBytes); |
466 | return BL_SUCCESS; |
467 | } |
468 | } |
469 | |
470 | // ============================================================================ |
471 | // [BLString - Assign] |
472 | // ============================================================================ |
473 | |
474 | BLResult blStringAssignMove(BLStringCore* self, BLStringCore* other) noexcept { |
475 | BLStringImpl* selfI = self->impl; |
476 | BLStringImpl* otherI = other->impl; |
477 | |
478 | self->impl = otherI; |
479 | other->impl = &blNullStringImpl; |
480 | |
481 | return blStringImplRelease(selfI); |
482 | } |
483 | |
484 | BLResult blStringAssignWeak(BLStringCore* self, const BLStringCore* other) noexcept { |
485 | BLStringImpl* selfI = self->impl; |
486 | BLStringImpl* otherI = other->impl; |
487 | |
488 | self->impl = blImplIncRef(otherI); |
489 | return blStringImplRelease(selfI); |
490 | } |
491 | |
492 | BLResult blStringAssignDeep(BLStringCore* self, const BLStringCore* other) noexcept { |
493 | const BLStringImpl* otherI = other->impl; |
494 | return blStringModifyAndCopy(self, BL_MODIFY_OP_ASSIGN_FIT, otherI->data, otherI->size); |
495 | } |
496 | |
497 | BLResult blStringAssignData(BLStringCore* self, const char* str, size_t n) noexcept { |
498 | if (n == SIZE_MAX) |
499 | n = strlen(str); |
500 | return blStringModifyAndCopy(self, BL_MODIFY_OP_ASSIGN_FIT, str, n); |
501 | } |
502 | |
503 | // ============================================================================ |
504 | // [BLString - Apply] |
505 | // ============================================================================ |
506 | |
507 | BLResult blStringApplyOpChar(BLStringCore* self, uint32_t op, char c, size_t n) noexcept { |
508 | char* dst; |
509 | BL_PROPAGATE(blStringModifyOp(self, op, n, &dst)); |
510 | |
511 | memset(dst, int((unsigned char)c), n); |
512 | return BL_SUCCESS; |
513 | } |
514 | |
515 | BLResult blStringApplyOpData(BLStringCore* self, uint32_t op, const char* str, size_t n) noexcept { |
516 | if (n == SIZE_MAX) |
517 | n = strlen(str); |
518 | return blStringModifyAndCopy(self, op, str, n); |
519 | } |
520 | |
521 | BLResult blStringApplyOpString(BLStringCore* self, uint32_t op, const BLStringCore* other) noexcept { |
522 | BLStringImpl* otherI = other->impl; |
523 | return blStringModifyAndCopy(self, op, otherI->data, otherI->size); |
524 | } |
525 | |
526 | BLResult blStringApplyOpFormatV(BLStringCore* self, uint32_t op, const char* fmt, va_list ap) noexcept { |
527 | BLStringImpl* selfI = self->impl; |
528 | |
529 | size_t index = (op >= BL_MODIFY_OP_APPEND_START) ? selfI->size : size_t(0); |
530 | size_t remaining = selfI->capacity - index; |
531 | size_t mutableMsk = blBitMaskFromBool<size_t>(blImplIsMutable(selfI)); |
532 | |
533 | char buf[1024]; |
534 | int fmtResult; |
535 | size_t outputSize; |
536 | |
537 | if ((remaining & mutableMsk) >= 64) { |
538 | // We include null terminator in buffer size as this is what 'vsnprintf' expects. |
539 | // BLString always reserves one byte for null terminator so this is perfectly safe. |
540 | fmtResult = vsnprintf(selfI->data + index, remaining + 1, fmt, ap); |
541 | if (BL_UNLIKELY(fmtResult < 0)) |
542 | return blTraceError(BL_ERROR_INVALID_VALUE); |
543 | |
544 | outputSize = size_t(unsigned(fmtResult)); |
545 | if (BL_LIKELY(outputSize <= remaining)) { |
546 | // `vsnprintf` must write a null terminator, verify it's true. |
547 | BL_ASSERT(selfI->data[index + outputSize] == '\0'); |
548 | |
549 | selfI->size = index + outputSize; |
550 | return BL_SUCCESS; |
551 | } |
552 | } |
553 | else { |
554 | fmtResult = vsnprintf(buf, BL_ARRAY_SIZE(buf), fmt, ap); |
555 | if (BL_UNLIKELY(fmtResult < 0)) |
556 | return blTraceError(BL_ERROR_INVALID_VALUE); |
557 | |
558 | // If the `outputSize` is less than our buffer size then we are fine and |
559 | // the formatted text is already in the buffer. Since `vsnprintf` doesn't |
560 | // include null-terminator in the returned size we cannot use '<=' as that |
561 | // would mean that the last character written by `vsnprintf` was truncated. |
562 | outputSize = size_t(fmtResult); |
563 | if (BL_LIKELY(outputSize < BL_ARRAY_SIZE(buf))) |
564 | return blStringApplyOpData(self, op, buf, outputSize); |
565 | } |
566 | |
567 | // If we are here it means that the string is either not large enough to hold |
568 | // the formatted text or it's not mutable. In both cases we have to allocate |
569 | // a new buffer and call `vsnprintf` again. |
570 | size_t sizeAfter = blUAddSaturate(index, outputSize); |
571 | if (BL_UNLIKELY(sizeAfter > blStringMaximumCapacity())) |
572 | return blTraceError(BL_ERROR_OUT_OF_MEMORY); |
573 | |
574 | size_t capacity = |
575 | (op & BL_MODIFY_OP_GROW_MASK) |
576 | ? blStringGrowingCapacity(sizeAfter) |
577 | : blStringFittingCapacity(sizeAfter); |
578 | |
579 | BLStringImpl* newI = blStringImplNew(capacity); |
580 | if (BL_UNLIKELY(!newI)) |
581 | return blTraceError(BL_ERROR_OUT_OF_MEMORY); |
582 | |
583 | char* dst = newI->data; |
584 | fmtResult = vsnprintf(dst + index, remaining + 1, fmt, ap); |
585 | |
586 | // This should always match. If it doesn't then it means that some other thread |
587 | // must have changed some value where `ap` points and it caused `vsnprintf` to |
588 | // format a different string. If this happens we fail as there is no reason to |
589 | // try again... |
590 | if (BL_UNLIKELY(size_t(unsigned(fmtResult)) != outputSize)) { |
591 | blStringImplDelete(newI); |
592 | return blTraceError(BL_ERROR_INVALID_VALUE); |
593 | } |
594 | |
595 | self->impl = newI; |
596 | newI->size = sizeAfter; |
597 | |
598 | memcpy(dst, selfI->data, index); |
599 | BL_ASSERT(dst[sizeAfter] == '\0'); |
600 | |
601 | return blStringImplRelease(selfI); |
602 | } |
603 | |
604 | BLResult blStringApplyOpFormat(BLStringCore* self, uint32_t op, const char* fmt, ...) noexcept { |
605 | BLResult result; |
606 | va_list ap; |
607 | |
608 | va_start(ap, fmt); |
609 | result = blStringApplyOpFormatV(self, op, fmt, ap); |
610 | va_end(ap); |
611 | |
612 | return result; |
613 | } |
614 | |
615 | // ============================================================================ |
616 | // [BLString - Insert] |
617 | // ============================================================================ |
618 | |
619 | BLResult blStringInsertChar(BLStringCore* self, size_t index, char c, size_t n) noexcept { |
620 | char* dst; |
621 | BL_PROPAGATE(blStringInsertOp(self, index, n, &dst)); |
622 | |
623 | memset(dst, int((unsigned char)c), n); |
624 | return BL_SUCCESS; |
625 | } |
626 | |
627 | BLResult blStringInsertData(BLStringCore* self, size_t index, const char* str, size_t n) noexcept { |
628 | if (n == SIZE_MAX) |
629 | n = strlen(str); |
630 | return blStringInsertAndCopy(self, index, str, n); |
631 | } |
632 | |
633 | BLResult blStringInsertString(BLStringCore* self, size_t index, const BLStringCore* other) noexcept { |
634 | BLStringImpl* otherI = other->impl; |
635 | return blStringInsertAndCopy(self, index, otherI->data, otherI->size); |
636 | } |
637 | |
638 | // ============================================================================ |
639 | // [BLString - Remove] |
640 | // ============================================================================ |
641 | |
642 | BLResult blStringRemoveRange(BLStringCore* self, size_t rStart, size_t rEnd) noexcept { |
643 | BLStringImpl* selfI = self->impl; |
644 | |
645 | size_t size = selfI->size; |
646 | size_t end = blMin(rEnd, size); |
647 | size_t index = blMin(rStart, end); |
648 | |
649 | size_t n = end - index; |
650 | if (!n) |
651 | return BL_SUCCESS; |
652 | |
653 | if (!blImplIsMutable(selfI)) { |
654 | size_t capacity = blStringFittingCapacity(size - n); |
655 | BLStringImpl* newI = blStringImplNew(capacity); |
656 | |
657 | if (BL_UNLIKELY(!newI)) |
658 | return blTraceError(BL_ERROR_OUT_OF_MEMORY); |
659 | |
660 | newI->size = size - n; |
661 | self->impl = newI; |
662 | |
663 | char* dst = newI->data; |
664 | char* src = selfI->data; |
665 | |
666 | memcpy(dst, src, index); |
667 | memcpy(dst + index, src + end, size - end); |
668 | |
669 | return blStringImplRelease(selfI); |
670 | } |
671 | else { |
672 | char* data = selfI->data; |
673 | |
674 | // NOTE: We copy one more byte that acts as a null-terminator. |
675 | selfI->size = size - n; |
676 | memmove(data + index, data + index + n, size - end + 1); |
677 | |
678 | return BL_SUCCESS; |
679 | } |
680 | } |
681 | |
682 | // ============================================================================ |
683 | // [BLString - Equality / Comparison] |
684 | // ============================================================================ |
685 | |
686 | bool blStringEquals(const BLStringCore* self, const BLStringCore* other) noexcept { |
687 | const BLStringImpl* selfI = self->impl; |
688 | const BLStringImpl* otherI = other->impl; |
689 | |
690 | size_t size = selfI->size; |
691 | if (size != otherI->size) |
692 | return false; |
693 | |
694 | return memcmp(selfI->data, otherI->data, size) == 0; |
695 | } |
696 | |
697 | bool blStringEqualsData(const BLStringCore* self, const char* str, size_t n) noexcept { |
698 | BLStringImpl* selfI = self->impl; |
699 | size_t size = selfI->size; |
700 | |
701 | const char* aData = selfI->data; |
702 | const char* bData = str; |
703 | |
704 | if (n == SIZE_MAX) { |
705 | // Null terminated, we don't know the size of `str`. |
706 | size_t i; |
707 | for (i = 0; i < size; i++) |
708 | if ((aData[i] != bData[i]) | (bData[i] == 0)) |
709 | return false; |
710 | return bData[i] == 0; |
711 | } |
712 | else { |
713 | if (size != n) |
714 | return false; |
715 | |
716 | return memcmp(aData, bData, size) == 0; |
717 | } |
718 | } |
719 | |
720 | int blStringCompare(const BLStringCore* self, const BLStringCore* other) noexcept { |
721 | const BLStringImpl* selfI = self->impl; |
722 | const BLStringImpl* otherI = other->impl; |
723 | |
724 | size_t aSize = selfI->size; |
725 | size_t bSize = otherI->size; |
726 | size_t minSize = blMin(aSize, bSize); |
727 | |
728 | int c = memcmp(selfI->data, otherI->data, minSize); |
729 | if (c) |
730 | return c; |
731 | |
732 | return aSize < bSize ? -1 : int(aSize > bSize); |
733 | } |
734 | |
735 | int blStringCompareData(const BLStringCore* self, const char* str, size_t n) noexcept { |
736 | const BLStringImpl* selfI = self->impl; |
737 | size_t aSize = selfI->size; |
738 | |
739 | const char* aData = selfI->data; |
740 | const char* bData = str; |
741 | |
742 | if (n == SIZE_MAX) { |
743 | // Null terminated, we don't know the size of `str`. We cannot use strcmp as it's |
744 | // allowed to have zeros (or null terminators) in BLString data as it can act as |
745 | // a byte-vector and not string. |
746 | size_t i; |
747 | for (i = 0; i < aSize; i++) { |
748 | int a = uint8_t(aData[i]); |
749 | int b = uint8_t(bData[i]); |
750 | |
751 | int c = a - b; |
752 | |
753 | // If we found a null terminator in 'b' it means that so far the strings were |
754 | // equal, but now we are at the end of 'b', however, there is still some content |
755 | // in 'a'. This would mean that `a > b` like "abc?" > "abc". |
756 | if (b == 0) |
757 | c = 1; |
758 | |
759 | if (c) |
760 | return c; |
761 | } |
762 | |
763 | // We are at the end of 'a'. If this is also the end of 'b' then these strings are |
764 | // equal and we return zero. If 'b' doesn't point to a null terminator then `a < b`. |
765 | return -int(bData[i] != 0); |
766 | } |
767 | else { |
768 | size_t bSize = n; |
769 | size_t minSize = blMin(aSize, bSize); |
770 | |
771 | int c = memcmp(aData, bData, minSize); |
772 | if (c) |
773 | return c; |
774 | |
775 | return aSize < bSize ? -1 : int(aSize > bSize); |
776 | } |
777 | } |
778 | |
779 | // ============================================================================ |
780 | // [BLString - Runtime Init] |
781 | // ============================================================================ |
782 | |
783 | void blStringRtInit(BLRuntimeContext* rt) noexcept { |
784 | BL_UNUSED(rt); |
785 | |
786 | BLStringImpl* stringI = &blNullStringImpl; |
787 | stringI->implType = uint8_t(BL_IMPL_TYPE_STRING); |
788 | stringI->implTraits = uint8_t(BL_IMPL_TRAIT_NULL); |
789 | stringI->data = const_cast<char*>(blNullStringData); |
790 | blAssignBuiltInNull(stringI); |
791 | } |
792 | |
793 | // ============================================================================ |
794 | // [BLString - Unit Tests] |
795 | // ============================================================================ |
796 | |
797 | #if defined(BL_TEST) |
798 | UNIT(blend2d_string) { |
799 | BLString s; |
800 | |
801 | INFO("Assignment and comparison" ); |
802 | EXPECT(s.assign('b') == BL_SUCCESS); |
803 | EXPECT(s.size() == 1); |
804 | EXPECT(s.data()[0] == 'b'); |
805 | EXPECT(s.data()[1] == '\0'); |
806 | EXPECT(s.equals("b" ) == true); |
807 | EXPECT(s.equals("b" , 1) == true); |
808 | EXPECT(s.compare("a" ) > 0); |
809 | EXPECT(s.compare("a" , 1) > 0); |
810 | EXPECT(s.compare("a?" ) > 0); |
811 | EXPECT(s.compare("a?" , 2) > 0); |
812 | EXPECT(s.compare("b" ) == 0); |
813 | EXPECT(s.compare("b" , 1) == 0); |
814 | EXPECT(s.compare("b?" ) < 0); |
815 | EXPECT(s.compare("b?" , 2) < 0); |
816 | EXPECT(s.compare("c" ) < 0); |
817 | EXPECT(s.compare("c" , 1) < 0); |
818 | EXPECT(s.compare("c?" ) < 0); |
819 | EXPECT(s.compare("c?" , 2) < 0); |
820 | |
821 | EXPECT(s.assign('b', 4) == BL_SUCCESS); |
822 | EXPECT(s.size() == 4); |
823 | EXPECT(s.data()[0] == 'b'); |
824 | EXPECT(s.data()[1] == 'b'); |
825 | EXPECT(s.data()[2] == 'b'); |
826 | EXPECT(s.data()[3] == 'b'); |
827 | EXPECT(s.data()[4] == '\0'); |
828 | EXPECT(s.equals("bbbb" ) == true); |
829 | EXPECT(s.equals("bbbb" , 4) == true); |
830 | EXPECT(s.compare("bbbb" ) == 0); |
831 | EXPECT(s.compare("bbbb" , 4) == 0); |
832 | EXPECT(s.compare("bbba" ) > 0); |
833 | EXPECT(s.compare("bbba" , 4) > 0); |
834 | EXPECT(s.compare("bbbc" ) < 0); |
835 | EXPECT(s.compare("bbbc" , 4) < 0); |
836 | |
837 | EXPECT(s.assign("abc" ) == BL_SUCCESS); |
838 | EXPECT(s.size() == 3); |
839 | EXPECT(s.data()[0] == 'a'); |
840 | EXPECT(s.data()[1] == 'b'); |
841 | EXPECT(s.data()[2] == 'c'); |
842 | EXPECT(s.data()[3] == '\0'); |
843 | EXPECT(s.equals("abc" ) == true); |
844 | EXPECT(s.equals("abc" , 3) == true); |
845 | |
846 | INFO("String manipulation" ); |
847 | EXPECT(s.append("xyz" ) == BL_SUCCESS); |
848 | EXPECT(s.equals("abcxyz" ) == true); |
849 | |
850 | EXPECT(s.insert(2, s.view()) == BL_SUCCESS); |
851 | EXPECT(s.equals("ababcxyzcxyz" )); |
852 | |
853 | EXPECT(s.remove(BLRange(1, 11)) == BL_SUCCESS); |
854 | EXPECT(s.equals("az" )); |
855 | |
856 | EXPECT(s.insert(1, "xxx" ) == BL_SUCCESS); |
857 | EXPECT(s.equals("axxxz" )); |
858 | |
859 | BLString x(s); |
860 | EXPECT(s.insert(3, "INSERTED" ) == BL_SUCCESS); |
861 | EXPECT(s.equals("axxINSERTEDxz" )); |
862 | |
863 | x = s; |
864 | EXPECT(s.remove(BLRange(1, 11)) == BL_SUCCESS); |
865 | EXPECT(s.equals("axz" )); |
866 | |
867 | EXPECT(s.reserve(1024) == BL_SUCCESS); |
868 | EXPECT(s.capacity() >= 1024); |
869 | EXPECT(s.shrink() == BL_SUCCESS); |
870 | EXPECT(s.capacity() < 1024); |
871 | |
872 | INFO("String Formatting" ); |
873 | EXPECT(s.assignFormat("%d" , 1000) == BL_SUCCESS); |
874 | EXPECT(s.equals("1000" )); |
875 | |
876 | INFO("String Search" ); |
877 | EXPECT(s.assign("abcdefghijklmnop-ponmlkjihgfedcba" ) == BL_SUCCESS); |
878 | EXPECT(s.indexOf('a') == 0); |
879 | EXPECT(s.indexOf('a', 1) == 32); |
880 | EXPECT(s.indexOf('b') == 1); |
881 | EXPECT(s.indexOf('b', 1) == 1); |
882 | EXPECT(s.indexOf('b', 2) == 31); |
883 | EXPECT(s.lastIndexOf('b') == 31); |
884 | EXPECT(s.lastIndexOf('b', 30) == 1); |
885 | EXPECT(s.indexOf('z') == SIZE_MAX); |
886 | EXPECT(s.indexOf('z', SIZE_MAX) == SIZE_MAX); |
887 | EXPECT(s.lastIndexOf('z') == SIZE_MAX); |
888 | EXPECT(s.lastIndexOf('z', 0) == SIZE_MAX); |
889 | EXPECT(s.lastIndexOf('z', SIZE_MAX) == SIZE_MAX); |
890 | } |
891 | #endif |
892 | |