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
16static BLWrap<BLStringImpl> blNullStringImpl;
17static const char blNullStringData[1] = "";
18
19// ============================================================================
20// [BLString - Internal]
21// ============================================================================
22
23static constexpr size_t blStringImplSizeOf(size_t n = 0) noexcept {
24 return blContainerSizeOf(sizeof(BLStringImpl) - 4 + 1, 1, n);
25}
26
27static constexpr size_t blStringCapacityOf(size_t implSize) noexcept {
28 return blContainerCapacityOf(blStringImplSizeOf(), 1, implSize);
29}
30
31static constexpr size_t blStringMaximumCapacity() noexcept {
32 return blStringCapacityOf(SIZE_MAX);
33}
34
35static BL_INLINE size_t blStringFittingCapacity(size_t n) noexcept {
36 return blContainerFittingCapacity(blStringImplSizeOf(), 1, n);
37}
38
39static BL_INLINE size_t blStringGrowingCapacity(size_t n) noexcept {
40 return blContainerGrowingCapacity(blStringImplSizeOf(), 1, n, BL_ALLOC_HINT_STRING);
41}
42
43static 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.
63BLResult 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
81static BL_INLINE BLResult blStringImplRelease(BLStringImpl* impl) noexcept {
82 if (blImplDecRefAndTest(impl))
83 return blStringImplDelete(impl);
84 return BL_SUCCESS;
85}
86
87static 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
111BLResult blStringInit(BLStringCore* self) noexcept {
112 self->impl = &blNullStringImpl;
113 return BL_SUCCESS;
114}
115
116BLResult 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
126size_t blStringGetSize(const BLStringCore* self) noexcept {
127 return self->impl->size;
128}
129
130size_t blStringGetCapacity(const BLStringCore* self) BL_NOEXCEPT_C {
131 return self->impl->capacity;
132}
133
134const char* blStringGetData(const BLStringCore* self) BL_NOEXCEPT_C {
135 return self->impl->data;
136}
137
138BLResult 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
152BLResult 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
168BLResult 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
183BLResult 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
232BLResult 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
248BLResult 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
294static 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
338BLResult 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
381static 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
474BLResult 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
484BLResult 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
492BLResult 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
497BLResult 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
507BLResult 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
515BLResult 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
521BLResult 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
526BLResult 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
604BLResult 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
619BLResult 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
627BLResult 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
633BLResult 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
642BLResult 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
686bool 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
697bool 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
720int 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
735int 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
783void 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)
798UNIT(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