boa_string/builder.rs
1use crate::r#type::{InternalStringType, Latin1, Utf16};
2use crate::{JsStr, JsStrVariant, JsString, SequenceString, alloc_overflow};
3use std::{
4 alloc::{Layout, alloc, dealloc, realloc},
5 marker::PhantomData,
6 ops::{Add, AddAssign},
7 ptr::{self, NonNull},
8 str::{self},
9};
10
11/// A mutable builder to create instances of `JsString`.
12#[derive(Debug)]
13#[allow(private_bounds)]
14pub struct JsStringBuilder<D: InternalStringType> {
15 cap: usize,
16 len: usize,
17 inner: NonNull<SequenceString<D>>,
18 phantom_data: PhantomData<D>,
19}
20
21impl<D: InternalStringType> Default for JsStringBuilder<D> {
22 fn default() -> Self {
23 Self::new()
24 }
25}
26
27#[allow(private_bounds)]
28impl<D: InternalStringType> JsStringBuilder<D> {
29 const DATA_SIZE: usize = size_of::<D::Byte>();
30 const MIN_NON_ZERO_CAP: usize = 8 / Self::DATA_SIZE;
31
32 /// Create a new `JsStringBuilder` with capacity of zero.
33 #[inline]
34 #[must_use]
35 pub const fn new() -> Self {
36 Self {
37 cap: 0,
38 len: 0,
39 inner: NonNull::dangling(),
40 phantom_data: PhantomData,
41 }
42 }
43
44 /// Returns the number of elements that inner `RawJsString` holds.
45 #[inline]
46 #[must_use]
47 pub const fn len(&self) -> usize {
48 self.len
49 }
50
51 /// Forces the length of the [`JsStringBuilder`] to `new_len`.
52 ///
53 /// # Safety
54 ///
55 /// - `new_len` must be less than or equal to `capacity()`.
56 /// - The elements at `old_len..new_len` must be initialized.
57 #[inline]
58 pub const unsafe fn set_len(&mut self, new_len: usize) {
59 debug_assert!(new_len <= self.capacity());
60
61 self.len = new_len;
62 }
63
64 /// Returns the total number of elements can hold without reallocating
65 #[inline]
66 #[must_use]
67 pub const fn capacity(&self) -> usize {
68 self.cap
69 }
70
71 /// Returns the capacity calculated from given layout.
72 #[must_use]
73 const fn capacity_from_layout(layout: Layout) -> usize {
74 (layout.size() - D::DATA_OFFSET) / Self::DATA_SIZE
75 }
76
77 /// Create a new `JsStringBuilder` with specific capacity
78 #[inline]
79 #[must_use]
80 pub fn with_capacity(cap: usize) -> Self {
81 if cap == 0 {
82 return Self::new();
83 }
84 let layout = Self::new_layout(cap);
85 #[allow(clippy::cast_ptr_alignment)]
86 // SAFETY:
87 // The layout size of `RawJsString` is never zero, since it has to store
88 // the length of the string and the reference count.
89 let ptr = unsafe { alloc(layout) };
90
91 let Some(ptr) = NonNull::new(ptr.cast()) else {
92 std::alloc::handle_alloc_error(layout)
93 };
94 Self {
95 cap: Self::capacity_from_layout(layout),
96 len: 0,
97 inner: ptr,
98 phantom_data: PhantomData,
99 }
100 }
101
102 /// Checks if the inner `RawJsString` is allocated.
103 #[must_use]
104 fn is_allocated(&self) -> bool {
105 self.inner != NonNull::dangling()
106 }
107
108 /// Returns the inner sequence string's layout.
109 ///
110 /// # Safety
111 ///
112 /// Caller should ensure that the inner is allocated.
113 #[must_use]
114 unsafe fn current_layout(&self) -> Layout {
115 // SAFETY:
116 // 1. Caller should ensure that the inner is allocated.
117 // 2. `unwrap_unchecked` is safe because this layout was successfully
118 // allocated previously with the same capacity, so it cannot overflow.
119 unsafe {
120 Layout::for_value(self.inner.as_ref())
121 .extend(Layout::array::<D::Byte>(self.capacity()).unwrap_unchecked())
122 .unwrap_unchecked()
123 .0
124 .pad_to_align()
125 }
126 }
127
128 /// Returns the pointer of `data` of inner.
129 ///
130 /// # Safety
131 ///
132 /// Caller should ensure that the inner is allocated.
133 #[must_use]
134 const unsafe fn data(&self) -> *mut D::Byte {
135 let seq_ptr: *mut D::Byte = self.inner.as_ptr().cast();
136 // SAFETY: Caller should ensure that the inner is allocated.
137 unsafe { seq_ptr.byte_add(D::DATA_OFFSET) }
138 }
139
140 /// Allocates when there is not sufficient capacity.
141 #[allow(clippy::inline_always)]
142 #[inline(always)]
143 fn allocate_if_needed(&mut self, required_cap: usize) {
144 if required_cap > self.capacity() {
145 self.allocate(required_cap);
146 }
147 }
148
149 /// Inner logic of `allocate`.
150 ///
151 /// Use `realloc` here because it has a better performance than using combination of `alloc`, `copy` and `dealloc`.
152 #[allow(clippy::cast_ptr_alignment)]
153 fn allocate_inner(&mut self, new_layout: Layout) {
154 let new_ptr = if self.is_allocated() {
155 let old_ptr = self.inner.as_ptr();
156 // SAFETY:
157 // Allocation check has been made above.
158 let old_layout = unsafe { self.current_layout() };
159 // SAFETY:
160 // Valid pointer is required by `realloc` and pointer is checked above to be valid.
161 // The layout size of the sequence string is never zero, since it has to store
162 // the length of the string and the reference count.
163 unsafe { realloc(old_ptr.cast(), old_layout, new_layout.size()) }
164 } else {
165 // SAFETY:
166 // The layout size of the sequence string is never zero, since it has to store
167 // the length of the string and the reference count.
168 unsafe { alloc(new_layout) }
169 };
170
171 let Some(new_ptr) = NonNull::new(new_ptr.cast::<SequenceString<D>>()) else {
172 std::alloc::handle_alloc_error(new_layout)
173 };
174 self.inner = new_ptr;
175 self.cap = Self::capacity_from_layout(new_layout);
176 }
177
178 /// Appends an element to the inner `RawJsString` of `JsStringBuilder`.
179 #[inline]
180 pub fn push(&mut self, v: D::Byte) {
181 let required_cap = self.len() + 1;
182 self.allocate_if_needed(required_cap);
183 // SAFETY:
184 // Capacity has been expanded to be large enough to hold elements.
185 unsafe {
186 self.push_unchecked(v);
187 }
188 }
189
190 /// Pushes elements from slice to `JsStringBuilder` without doing capacity check.
191 ///
192 /// Unlike the standard vector, our held element types are only `u8` and `u16`, which is [`Copy`] derived,
193 ///
194 /// so we only need to copy them instead of cloning.
195 ///
196 /// # Safety
197 ///
198 /// Caller should ensure the capacity is large enough to hold elements.
199 #[inline]
200 pub const unsafe fn extend_from_slice_unchecked(&mut self, v: &[D::Byte]) {
201 // SAFETY:
202 // 1. Caller must ensure `self.len() + v.len() <= self.capacity()` so the destination pointer is in-bounds.
203 // 2. Pointers are aligned: `v` is aligned by Rust's slice guarantee; `self.data()` is aligned because the allocation layout was padded to `D::Byte`'s alignment.
204 // 3. Regions do not overlap because `v` is an immutable reference and `self` is an exclusive mutable reference.
205 unsafe {
206 ptr::copy_nonoverlapping(v.as_ptr(), self.data().add(self.len()), v.len());
207 }
208 self.len += v.len();
209 }
210
211 /// Pushes elements from slice to `JsStringBuilder`.
212 #[inline]
213 pub fn extend_from_slice(&mut self, v: &[D::Byte]) {
214 let required_cap = self.len() + v.len();
215 self.allocate_if_needed(required_cap);
216 // SAFETY:
217 // Capacity has been expanded to be large enough to hold elements.
218 unsafe {
219 self.extend_from_slice_unchecked(v);
220 }
221 }
222
223 fn new_layout(cap: usize) -> Layout {
224 let new_layout = Layout::array::<D::Byte>(cap)
225 .and_then(|arr| Layout::new::<SequenceString<D>>().extend(arr))
226 .map(|(layout, offset)| (layout.pad_to_align(), offset))
227 .map_err(|_| None);
228 match new_layout {
229 Ok((new_layout, offset)) => {
230 debug_assert_eq!(offset, D::DATA_OFFSET);
231 new_layout
232 }
233 Err(None) => alloc_overflow(),
234 Err(Some(layout)) => std::alloc::handle_alloc_error(layout),
235 }
236 }
237
238 /// Similar to [`Vec::reserve`]
239 ///
240 /// Reserves capacity for at least `additional` more elements to be inserted
241 /// in the given `JsStringBuilder<D>`. The collection may reserve more space to
242 /// speculatively avoid frequent reallocations. After calling `reserve`,
243 /// capacity will be greater than or equal to `self.len() + additional`.
244 /// Does nothing if capacity is already sufficient.
245 #[inline]
246 pub fn reserve(&mut self, additional: usize) {
247 if additional > self.capacity().wrapping_sub(self.len) {
248 let Some(cap) = self.len().checked_add(additional) else {
249 alloc_overflow()
250 };
251 self.allocate(cap);
252 }
253 }
254
255 /// Similar to [`Vec::reserve_exact`]
256 ///
257 /// Reserves the minimum capacity for at least `additional` more elements to
258 /// be inserted in the given `JsStringBuilder<D>`. Unlike [`reserve`], this will not
259 /// deliberately over-allocate to speculatively avoid frequent allocations.
260 /// After calling `reserve_exact`, capacity will be greater than or equal to
261 /// `self.len() + additional`. Does nothing if the capacity is already
262 /// sufficient.
263 ///
264 /// Note that the allocator may give the collection more space than it
265 /// requests. Therefore, capacity can not be relied upon to be precisely
266 /// minimal. Prefer [`reserve`] if future insertions are expected.
267 ///
268 /// [`reserve`]: JsStringBuilder::reserve
269 #[inline]
270 pub fn reserve_exact(&mut self, additional: usize) {
271 if additional > self.capacity().wrapping_sub(self.len) {
272 let Some(cap) = self.len().checked_add(additional) else {
273 alloc_overflow()
274 };
275 self.allocate_inner(Self::new_layout(cap));
276 }
277 }
278
279 /// Allocates memory to the inner `RawJsString` by the given capacity.
280 /// Capacity calculation is from [`Vec::reserve`].
281 fn allocate(&mut self, cap: usize) {
282 let cap = std::cmp::max(self.capacity() * 2, cap);
283 let cap = std::cmp::max(Self::MIN_NON_ZERO_CAP, cap);
284 self.allocate_inner(Self::new_layout(cap));
285 }
286
287 /// Appends an element to the inner `RawJsString` of `JsStringBuilder` without doing bounds check.
288 /// # Safety
289 ///
290 /// Caller should ensure the capacity is large enough to hold elements.
291 #[inline]
292 pub const unsafe fn push_unchecked(&mut self, v: D::Byte) {
293 // SAFETY: Caller should ensure the capacity is large enough to hold elements.
294 unsafe {
295 self.data().add(self.len()).write(v);
296 self.len += 1;
297 }
298 }
299
300 /// Returns true if this `JsStringBuilder` has a length of zero, and false otherwise.
301 #[inline]
302 #[must_use]
303 pub fn is_empty(&self) -> bool {
304 self.len() == 0
305 }
306
307 /// Extracts a slice containing the elements in the inner `RawJsString`.
308 #[inline]
309 #[must_use]
310 pub fn as_slice(&self) -> &[D::Byte] {
311 if self.is_allocated() {
312 // SAFETY:
313 // The inner `RawJsString` is allocated which means it is not null.
314 unsafe { std::slice::from_raw_parts(self.data(), self.len()) }
315 } else {
316 &[]
317 }
318 }
319
320 /// Extracts a mutable slice containing the elements in the inner `RawJsString`.
321 ///
322 /// # Safety
323 /// The caller must ensure that the content of the slice is valid encoding before the borrow ends.
324 /// Use of a builder whose contents are not valid encoding is undefined behavior.
325 #[inline]
326 #[must_use]
327 pub unsafe fn as_mut_slice(&mut self) -> &mut [D::Byte] {
328 if self.is_allocated() {
329 // SAFETY:
330 // The inner `RawJsString` is allocated which means it is not null.
331 unsafe { std::slice::from_raw_parts_mut(self.data(), self.len()) }
332 } else {
333 &mut []
334 }
335 }
336
337 /// Builds `JsString` from `JsStringBuilder`
338 #[inline]
339 #[must_use]
340 fn build_inner(mut self) -> JsString {
341 if self.is_empty() {
342 return JsString::default();
343 }
344 let len = self.len();
345
346 // Shrink to fit the length.
347 if len != self.capacity() {
348 let layout = Self::new_layout(self.len());
349 self.allocate_inner(layout);
350 }
351
352 let inner = self.inner;
353
354 // SAFETY:
355 // `NonNull` verified for us that the pointer returned by `alloc` is valid,
356 // meaning we can write to its pointed memory.
357 unsafe {
358 inner.as_ptr().write(SequenceString::<D>::new(len));
359 }
360
361 // Tell the compiler not to call the destructor of `JsStringBuilder`,
362 // because we move inner sequence string to `JsString`.
363 std::mem::forget(self);
364
365 JsString { ptr: inner.cast() }
366 }
367}
368
369impl<D: InternalStringType> Drop for JsStringBuilder<D> {
370 /// Set cold since [`JsStringBuilder`] should be created to build `JsString`
371 #[cold]
372 #[inline]
373 fn drop(&mut self) {
374 if self.is_allocated() {
375 // SAFETY:
376 // Allocation check has been made above.
377 let layout = unsafe { self.current_layout() };
378 // SAFETY:
379 // layout: All the checks for the validity of the layout have already been made on `allocate_inner`.
380 // `NonNull` verified for us that the pointer returned by `alloc` is valid,
381 // meaning we can free its pointed memory.
382 unsafe {
383 dealloc(self.inner.as_ptr().cast(), layout);
384 }
385 }
386 }
387}
388
389impl<D: InternalStringType> AddAssign<&JsStringBuilder<D>> for JsStringBuilder<D> {
390 #[inline]
391 fn add_assign(&mut self, rhs: &JsStringBuilder<D>) {
392 self.extend_from_slice(rhs.as_slice());
393 }
394}
395
396impl<D: InternalStringType> AddAssign<&[D::Byte]> for JsStringBuilder<D> {
397 #[inline]
398 fn add_assign(&mut self, rhs: &[D::Byte]) {
399 self.extend_from_slice(rhs);
400 }
401}
402
403impl<D: InternalStringType> Add<&JsStringBuilder<D>> for JsStringBuilder<D> {
404 type Output = Self;
405
406 #[inline]
407 fn add(mut self, rhs: &JsStringBuilder<D>) -> Self::Output {
408 self.extend_from_slice(rhs.as_slice());
409 self
410 }
411}
412
413impl<D: InternalStringType> Add<&[D::Byte]> for JsStringBuilder<D> {
414 type Output = Self;
415
416 #[inline]
417 fn add(mut self, rhs: &[D::Byte]) -> Self::Output {
418 self.extend_from_slice(rhs);
419 self
420 }
421}
422
423impl<D: InternalStringType> Extend<D::Byte> for JsStringBuilder<D> {
424 #[inline]
425 fn extend<I: IntoIterator<Item = D::Byte>>(&mut self, iter: I) {
426 let iterator = iter.into_iter();
427 let (lower_bound, _) = iterator.size_hint();
428 let require_cap = self.len() + lower_bound;
429 self.allocate_if_needed(require_cap);
430 iterator.for_each(|c| self.push(c));
431 }
432}
433
434impl<D: InternalStringType> FromIterator<D::Byte> for JsStringBuilder<D> {
435 #[inline]
436 fn from_iter<T: IntoIterator<Item = D::Byte>>(iter: T) -> Self {
437 let mut builder = Self::new();
438 builder.extend(iter);
439 builder
440 }
441}
442
443impl<D: InternalStringType> From<&[D::Byte]> for JsStringBuilder<D> {
444 #[inline]
445 fn from(value: &[D::Byte]) -> Self {
446 let mut builder = Self::with_capacity(value.len());
447 // SAFETY: The capacity is large enough to hold elements.
448 unsafe { builder.extend_from_slice_unchecked(value) };
449 builder
450 }
451}
452
453impl<D: InternalStringType> PartialEq for JsStringBuilder<D>
454where
455 D::Byte: Eq + PartialEq,
456{
457 #[inline]
458 fn eq(&self, other: &Self) -> bool {
459 let slice: &[D::Byte] = self.as_slice();
460 let other_slice: &[D::Byte] = other.as_slice();
461 slice.eq(other_slice)
462 }
463}
464
465impl<D: InternalStringType> Clone for JsStringBuilder<D> {
466 #[inline]
467 fn clone(&self) -> Self {
468 if self.is_allocated() {
469 let mut builder = Self::with_capacity(self.capacity());
470 // SAFETY: The capacity is large enough to hold elements.
471 unsafe { builder.extend_from_slice_unchecked(self.as_slice()) };
472 builder
473 } else {
474 Self::new()
475 }
476 }
477
478 /// Performs copy-assignment from `source`.
479 ///
480 /// Rewritten to avoid unnecessary allocation.
481 #[inline]
482 fn clone_from(&mut self, source: &Self) {
483 let source_len = source.len();
484
485 if source_len > self.capacity() {
486 self.allocate(source_len);
487 } else {
488 // At this point, inner sequence string of self or source can be not allocated,
489 // returns earlier to avoid copying from/to `null`.
490 if source_len == 0 {
491 // SAFETY: 0 is always less or equal to self's capacity.
492 unsafe { self.set_len(0) };
493 return;
494 }
495 }
496
497 // SAFETY: self should be allocated after allocation.
498 let self_data = unsafe { self.data() };
499
500 // SAFETY: source_len is greater than 0 so source should be allocated.
501 let source_data = unsafe { source.data() };
502
503 // SAFETY: Borrow checker should not allow this to be overlapped and pointers are valid.
504 unsafe { ptr::copy_nonoverlapping(source_data, self_data, source_len) };
505
506 // SAFETY: source_len has checked to be less or equal to self's capacity.
507 unsafe { self.set_len(source_len) };
508 }
509}
510
511impl JsStringBuilder<Latin1> {
512 /// Checks if all bytes in inner `RawJsString`'s data are ascii.
513 #[inline]
514 #[must_use]
515 pub fn is_ascii(&self) -> bool {
516 self.as_slice().is_ascii()
517 }
518}
519
520impl JsStringBuilder<Utf16> {
521 /// Checks if all u16 in inner `RawJsString`'s data are ascii (<= 0x7F).
522 #[inline]
523 #[must_use]
524 pub fn is_ascii(&self) -> bool {
525 self.as_slice().iter().all(|&c| c <= 0x7F)
526 }
527}
528
529/// **`Latin1`** encoded `JsStringBuilder`
530/// # Warning
531/// If you are not sure the characters that will be added and don't want to preprocess them,
532/// use [`CommonJsStringBuilder`] instead.
533/// ## Examples
534///
535/// ```rust
536/// use boa_string::Latin1JsStringBuilder;
537/// let mut s = Latin1JsStringBuilder::new();
538/// s.push(b'x');
539/// s.extend_from_slice(&[b'1', b'2', b'3']);
540/// s.extend([b'1', b'2', b'3']);
541/// let js_string = s.build();
542/// ```
543pub type Latin1JsStringBuilder = JsStringBuilder<Latin1>;
544
545impl Latin1JsStringBuilder {
546 /// Builds a `JsString` if the current instance is strictly `ASCII`.
547 ///
548 /// When the string contains characters outside the `ASCII` range, it cannot be determined
549 /// whether the encoding is `Latin1` or others. Therefore, this method only returns a
550 /// valid `JsString` when the instance is entirely `ASCII`. If any non-`ASCII` characters
551 /// are present, it returns `None` to avoid ambiguity in encoding.
552 ///
553 /// If the caller is certain that the string is encoded in `Latin1`,
554 /// [`build_as_latin1`](Self::build_as_latin1) can be used to avoid the `ASCII` check.
555 #[inline]
556 #[must_use]
557 pub fn build(self) -> Option<JsString> {
558 if self.is_ascii() {
559 Some(self.build_inner())
560 } else {
561 None
562 }
563 }
564
565 /// Builds `JsString` from `Latin1JsStringBuilder`, assume that the inner data is `Latin1` encoded
566 ///
567 /// # Safety
568 /// Caller must ensure that the string is encoded in `Latin1`.
569 ///
570 /// If the string contains characters outside the `Latin1` range, it may lead to encoding errors,
571 /// resulting in an incorrect or malformed `JsString`. This could cause undefined behavior
572 /// when the resulting string is used in further operations or when interfacing with other
573 /// parts of the system that expect valid `Latin1` encoded string.
574 #[inline]
575 #[must_use]
576 pub unsafe fn build_as_latin1(self) -> JsString {
577 self.build_inner()
578 }
579}
580
581/// **`UTF-16`** encoded `JsStringBuilder`
582/// ## Examples
583///
584/// ```rust
585/// use boa_string::Utf16JsStringBuilder;
586/// let mut s = Utf16JsStringBuilder::new();
587/// s.push(b'x' as u16);
588/// s.extend_from_slice(&[b'1', b'2', b'3'].map(u16::from));
589/// s.extend([0xD83C, 0xDFB9, 0xD83C, 0xDFB6, 0xD83C, 0xDFB5]); // πΉπΆπ΅
590/// let js_string = s.build();
591/// ```
592pub type Utf16JsStringBuilder = JsStringBuilder<Utf16>;
593
594impl Utf16JsStringBuilder {
595 /// Builds `JsString` from `Utf16JsStringBuilder`
596 #[inline]
597 #[must_use]
598 pub fn build(self) -> JsString {
599 self.build_inner()
600 }
601}
602
603/// Represents a segment of a string used to construct a [`JsString`].
604#[derive(Clone, Debug)]
605pub enum Segment<'a> {
606 /// A string segment represented as a `JsString`.
607 String(JsString),
608
609 /// A string segment represented as a `JsStr`.
610 Str(JsStr<'a>),
611
612 /// A string segment represented as a byte.
613 Latin1(u8),
614
615 /// A Unicode code point segment represented as a character.
616 CodePoint(char),
617}
618
619impl Segment<'_> {
620 /// Checks if the segment can be represented as `Latin1` characters.
621 #[inline]
622 #[must_use]
623 fn can_be_latin1(&self) -> bool {
624 match self {
625 Segment::String(s) => s.as_str().is_latin1(),
626 Segment::Str(s) => s.is_latin1(),
627 Segment::Latin1(_) => true,
628 Segment::CodePoint(ch) => *ch as u32 <= 0xFF,
629 }
630 }
631}
632
633impl From<JsString> for Segment<'_> {
634 #[inline]
635 fn from(value: JsString) -> Self {
636 Self::String(value)
637 }
638}
639
640impl From<String> for Segment<'_> {
641 #[inline]
642 fn from(value: String) -> Self {
643 Self::String(value.into())
644 }
645}
646
647impl From<&[u16]> for Segment<'_> {
648 #[inline]
649 fn from(value: &[u16]) -> Self {
650 Self::String(value.into())
651 }
652}
653
654impl From<&str> for Segment<'_> {
655 #[inline]
656 fn from(value: &str) -> Self {
657 Self::String(value.into())
658 }
659}
660
661impl<'seg, 'ref_str: 'seg> From<JsStr<'ref_str>> for Segment<'seg> {
662 #[inline]
663 fn from(value: JsStr<'ref_str>) -> Self {
664 Self::Str(value)
665 }
666}
667
668impl From<u8> for Segment<'_> {
669 #[inline]
670 fn from(value: u8) -> Self {
671 Self::Latin1(value)
672 }
673}
674
675impl From<char> for Segment<'_> {
676 #[inline]
677 fn from(value: char) -> Self {
678 Self::CodePoint(value)
679 }
680}
681
682/// Common `JsString` builder that accepts multiple variant of string or character.
683///
684/// Originally based on [kiesel-js](https://codeberg.org/kiesel-js/kiesel/src/branch/main/src/types/language/String/Builder.zig)
685#[derive(Clone, Debug, Default)]
686pub struct CommonJsStringBuilder<'a> {
687 segments: Vec<Segment<'a>>,
688}
689
690impl<'seg, 'ref_str: 'seg> CommonJsStringBuilder<'seg> {
691 /// Creates a new `CommonJsStringBuilder` with capacity of zero.
692 #[inline]
693 #[must_use]
694 pub const fn new() -> Self {
695 Self {
696 segments: Vec::new(),
697 }
698 }
699
700 /// Similar to `Vec::with_capacity`.
701 ///
702 /// Creates a new `CommonJsStringBuilder` with given capacity.
703 #[inline]
704 #[must_use]
705 pub fn with_capacity(capacity: usize) -> Self {
706 Self {
707 segments: Vec::with_capacity(capacity),
708 }
709 }
710
711 /// Similar to `Vec::reserve`.
712 ///
713 /// Reserves additional capacity for the inner vector.
714 #[inline]
715 pub fn reserve(&mut self, additional: usize) {
716 self.segments.reserve(additional);
717 }
718
719 /// Similar to `Vec::reserve_exact`.
720 ///
721 /// Reserves the minimum capacity for the inner vector.
722 #[inline]
723 pub fn reserve_exact(&mut self, additional: usize) {
724 self.segments.reserve_exact(additional);
725 }
726
727 /// Appends string segments to the back of the inner vector.
728 #[inline]
729 pub fn push<T: Into<Segment<'ref_str>>>(&mut self, seg: T) {
730 self.segments.push(seg.into());
731 }
732
733 /// Checks if all string segments can be represented as `Latin1` characters.
734 #[inline]
735 #[must_use]
736 pub fn can_be_latin1(&self) -> bool {
737 self.segments.iter().all(Segment::can_be_latin1)
738 }
739
740 /// Returns the number of string segment in inner vector.
741 #[inline]
742 #[must_use]
743 pub fn len(&self) -> usize {
744 self.segments.len()
745 }
746
747 /// Returns true if this `CommonJsStringBuilder` has a length of zero, and false otherwise.
748 #[inline]
749 #[must_use]
750 pub fn is_empty(&self) -> bool {
751 self.len() == 0
752 }
753
754 /// Builds `Latin1` encoded `JsString` from string segments.
755 ///
756 /// This doesn't consume the builder itself because it may fails to build
757 /// and the caller may wants to keep the builder for further operations.
758 ///
759 /// This processes the following types of segments:
760 ///
761 /// - `Segment::String(s)`: Encodes the string if it can be represented in `Latin1`.
762 /// - `Segment::Str(s)`: Encodes the string slice if it can be represented in `Latin1`.
763 /// - `Segment::Latin1(b)`: Encodes the byte if it's within the `ASCII` range.
764 /// - `Segment::CodePoint(ch)`: Encodes the code point by converting it to a byte if it's within the `ASCII` range.
765 ///
766 /// Return `None` if any segment fails to encode.
767 #[inline]
768 #[must_use]
769 #[allow(clippy::cast_lossless)]
770 pub fn build_from_latin1(&self) -> Option<JsString> {
771 let mut builder = Latin1JsStringBuilder::new();
772 for seg in &self.segments {
773 match seg {
774 Segment::String(s) => {
775 builder.extend_from_slice(s.as_str().as_latin1()?);
776 }
777 Segment::Str(s) => {
778 builder.extend_from_slice(s.as_latin1()?);
779 }
780 Segment::Latin1(b) => {
781 if *b <= 0x7f {
782 builder.push(*b);
783 } else {
784 return None;
785 }
786 }
787 Segment::CodePoint(ch) => {
788 if let Ok(b) = u8::try_from(*ch as u32) {
789 builder.push(b);
790 } else {
791 return None;
792 }
793 }
794 }
795 }
796 builder.build()
797 }
798
799 /// Builds `Utf-16` encoded `JsString` from string segments.
800 #[inline]
801 #[must_use]
802 #[allow(clippy::cast_possible_truncation)]
803 pub fn build_from_utf16(self) -> JsString {
804 let mut builder = Utf16JsStringBuilder::new();
805 for seg in self.segments {
806 match seg {
807 Segment::String(s) => {
808 let js_str = s.as_str();
809 match js_str.variant() {
810 JsStrVariant::Latin1(s) => builder.extend(s.iter().copied().map(u16::from)),
811 JsStrVariant::Utf16(s) => builder.extend_from_slice(s),
812 }
813 }
814 Segment::Str(s) => match s.variant() {
815 JsStrVariant::Latin1(s) => builder.extend(s.iter().copied().map(u16::from)),
816 JsStrVariant::Utf16(s) => builder.extend_from_slice(s),
817 },
818 Segment::Latin1(latin1) => builder.push(u16::from(latin1)),
819 Segment::CodePoint(code_point) => {
820 builder.extend_from_slice(code_point.encode_utf16(&mut [0_u16; 2]));
821 }
822 }
823 }
824 builder.build()
825 }
826
827 /// Builds `JsString` from `CommonJsStringBuilder`,
828 ///
829 /// This function first checks if the instance is empty:
830 /// - If it is empty, it returns the default `JsString`.
831 /// - If it can be represented as Latin1 characters, it safely encodes it as `Latin1`.
832 /// - Otherwise, it falls back to encoding using `UTF-16`.
833 #[inline]
834 #[must_use]
835 pub fn build(self) -> JsString {
836 if self.is_empty() {
837 JsString::default()
838 } else if self.can_be_latin1() {
839 // SAFETY:
840 // All string segments can be represented as Latin1, so this can be encoded as `Latin1`.
841 unsafe { self.build_as_latin1() }
842 } else {
843 self.build_from_utf16()
844 }
845 }
846
847 /// Builds `Latin1` encoded `JsString` from `CommonJsStringBuilder`, return `None` if segments can't be encoded as `Latin1`
848 ///
849 /// # Safety
850 /// Caller must ensure that the string segments can be `Latin1` encoded.
851 ///
852 /// If string segments can't be `Latin1` encoded, it may lead to encoding errors,
853 /// resulting in an incorrect or malformed `JsString`. This could cause undefined behavior
854 /// when the resulting string is used in further operations or when interfacing with other
855 /// parts of the system that expect valid `Latin1` encoded string.
856 #[inline]
857 #[must_use]
858 pub unsafe fn build_as_latin1(self) -> JsString {
859 let mut builder = Latin1JsStringBuilder::new();
860 for seg in self.segments {
861 match seg {
862 Segment::String(s) => {
863 let js_str = s.as_str();
864 let Some(s) = js_str.as_latin1() else {
865 unreachable!("string segment should be latin1")
866 };
867 builder.extend_from_slice(s);
868 }
869 Segment::Str(s) => {
870 let Some(s) = s.as_latin1() else {
871 unreachable!("string segment should be latin1")
872 };
873 builder.extend_from_slice(s);
874 }
875 Segment::Latin1(latin1) => builder.push(latin1),
876 Segment::CodePoint(code_point) => builder.push(code_point as u8),
877 }
878 }
879 // SAFETY: All string segments can be encoded as `Latin1` string.
880 unsafe { builder.build_as_latin1() }
881 }
882}
883
884impl<'ref_str, T: Into<Segment<'ref_str>>> AddAssign<T> for CommonJsStringBuilder<'ref_str> {
885 #[inline]
886 fn add_assign(&mut self, rhs: T) {
887 self.push(rhs);
888 }
889}
890
891impl<'ref_str, T: Into<Segment<'ref_str>>> Add<T> for CommonJsStringBuilder<'ref_str> {
892 type Output = Self;
893
894 #[inline]
895 fn add(mut self, rhs: T) -> Self::Output {
896 self.push(rhs);
897 self
898 }
899}