// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
struct StringBuilder {
mut data : FixedArray[UInt16]
mut len : Int
}
///|
/// Creates a new string builder with an optional initial capacity hint.
///
/// Parameters:
///
/// * `size_hint` : An optional initial capacity hint for the internal buffer. If
/// less than 1, a minimum capacity of 1 is used. Defaults to 0. It is the size of bytes,
/// not the size of characters. `size_hint` may be ignored on some platforms, JS for example.
///
/// Returns a new `StringBuilder` instance with the specified initial capacity.
///
#alias(new)
pub fn StringBuilder::StringBuilder(size_hint? : Int = 0) -> StringBuilder {
let initial = if size_hint < 1 { 1 } else { (size_hint + 1) / 2 }
let data : FixedArray[UInt16] = FixedArray::make(initial, 0)
{ data, len: 0 }
}
///|
/// Return whether the given buffer is empty.
pub fn StringBuilder::is_empty(self : StringBuilder) -> Bool {
self.len == 0
}
///|
/// Compute the next capacity without allocating. Since appends never shrink the
/// builder, `required < len` means the required-size calculation overflowed.
#inline
fn stringbuilder_growth_capacity(
current : Int,
len : Int,
required : Int,
) -> Int {
if required < len {
abort("StringBuilder capacity overflow")
}
let enough_space = for space = current; space < required; {
let next = space * 2
if next <= space {
break required
}
continue next
} nobreak {
space
}
enough_space
}
///|
/// Grow the builder to at least `required`. Callers keep the capacity check on
/// their fast path and enter here only when growth or overflow handling is
/// needed. The builder invariant `0 <= len <= data.length()` lets fixed-size
/// appends compare against the remaining capacity without overflowing.
fn StringBuilder::grow(self : StringBuilder, required : Int) -> Unit {
let new_capacity = stringbuilder_growth_capacity(
self.data.length(),
self.len,
required,
)
let new_data = FixedArray::make_and_blit(
self.data,
allocate_len=new_capacity,
init=(Default::default() : UInt16),
len=self.len,
)
self.data = new_data
}
///|
fn FixedArray::unsafe_blit_from_string(
self : FixedArray[UInt16],
dst_offset : Int,
str : String,
str_offset : Int,
len : Int,
) -> Unit {
let end_str_offset = str_offset + len
for i = str_offset, j = dst_offset; i < end_str_offset; i = i + 1, j = j + 1 {
self.unsafe_set(j, str.unsafe_get(i))
}
}
///|
/// Writes a string to the StringBuilder.
pub impl Logger for StringBuilder with fn write_string(self, str) {
let str_len = str.length()
if str_len == 0 {
return
}
let required = self.len + str_len
if required > self.data.length() || required < self.len {
self.grow(required)
}
self.data.unsafe_blit_from_string(self.len, str, 0, str_len)
self.len += str_len
}
///|
/// Writes a character to the StringBuilder.
pub impl Logger for StringBuilder with fn write_char(self, ch) {
let code = ch.to_uint()
if code <= 0xFFFFU {
if self.len >= self.data.length() {
self.grow(self.len + 1)
}
self.data[self.len] = code.to_uint16()
self.len += 1
} else if code <= 0x10FFFFU {
if self.data.length() - self.len < 2 {
self.grow(self.len + 2)
}
let code = code - 0x10000U
self.data[self.len] = (0xD800U + (code >> 10)).to_uint16()
self.data[self.len + 1] = (0xDC00U + code.land(0x3FFU)).to_uint16()
self.len += 2
} else {
abort("invalid code point")
}
}
///|
/// Writes a part of the given string to the StringBuilder.
///
/// Parameters:
///
/// * `self` : The StringBuilder to write to.
/// * `str` : The given string.
/// * `start` : The start index of the substring to write.
/// * `len` : The length of the substring to write.
///
/// Example:
///
/// ```mbt check
/// test {
/// let sb = StringBuilder()
/// sb.write_view("Hello, world!"[:5])
/// @test.assert_eq(sb.to_string(), "Hello")
/// }
/// ```
pub impl Logger for StringBuilder with fn write_view(
self : StringBuilder,
str : StringView,
) -> Unit {
let str_len = str.length()
if str_len == 0 {
return
}
let required = self.len + str_len
if required > self.data.length() || required < self.len {
self.grow(required)
}
self.data.unsafe_blit_from_string(
self.len,
str.data(),
str.start_offset(),
str_len,
)
self.len += str_len
}
///|
/// Returns the current content of the StringBuilder as a string.
pub fn StringBuilder::to_string(self : StringBuilder) -> String {
if self.len == 0 {
""
} else if self.len == self.data.length() {
unsafe_fixedarray_uint16_to_string(self.data)
} else {
let data = FixedArray::make_and_blit(
self.data,
allocate_len=self.len,
init=(Default::default() : UInt16),
len=self.len,
)
unsafe_fixedarray_uint16_to_string(data)
}
}
///|
#doc(hidden)
pub fn StringBuilder::write_end(self : StringBuilder) -> String {
self.to_string()
}
///|
pub impl Show for StringBuilder with fn to_string(self) {
StringBuilder::to_string(self)
}
///|
/// Resets the string builder to an empty state.
pub fn StringBuilder::reset(self : StringBuilder) -> Unit {
self.data = FixedArray::make(
self.data.length(),
(Default::default() : UInt16),
)
self.len = 0
}