// 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.
///|
/// Returns the source string being viewed.
fn StringView::str(self : StringView) -> String = "%stringview.str"
///|
/// Returns the starting UTF-16 code unit index into the string.
fn StringView::start(self : StringView) -> Int = "%stringview.start"
///|
/// Returns the ending UTF-16 code unit index into the string (not included).
fn StringView::end(self : StringView) -> Int = "%stringview.end"
///|
fn StringView::make_view(str : String, start : Int, end : Int) -> StringView = "%stringview.make"
///|
/// Returns the UTF-16 code unit at the given index.
///
/// This method has O(1) complexity.
/// Panics if the index is out of bounds.
#intrinsic("%stringview.get")
#alias("_[_]")
#alias(code_unit_at)
pub fn StringView::at(self : StringView, index : Int) -> UInt16 {
guard index >= 0 && index < self.length() else {
index_out_of_bounds(self.length(), index)
}
self.unsafe_get(index)
}
///|
/// Returns the length of the view.
///
/// This method counts the charcodes(code unit) in the view and has O(1) complexity.
#intrinsic("%stringview.length")
pub fn StringView::length(self : StringView) -> Int {
self.end() - self.start()
}
///|
/// Iterates over all suffixes of the view, advancing by a Unicode character at
/// a time. Each yielded suffix is itself a view into the original string.
pub fn StringView::suffixes(
self : StringView,
include_empty? : Bool = false,
) -> Iter[StringView] {
let str = self.str()
let end = self.end()
let mut next_start = self.start()
let mut finished = false
Iter::new(fn() -> StringView? {
if finished {
None
} else if next_start == end {
finished = true
if include_empty {
Some(StringView::make_view(str, next_start, end))
} else {
None
}
} else {
let suffix = StringView::make_view(str, next_start, end)
let code = str.unsafe_get(next_start)
if code.is_leading_surrogate() &&
next_start + 1 < end &&
str.unsafe_get(next_start + 1).is_trailing_surrogate() {
next_start += 2
} else {
next_start += 1
}
Some(suffix)
}
})
}
///|
/// Returns the original string that is being viewed.
pub fn StringView::data(self : StringView) -> String {
self.str()
}
///|
/// Returns the starting offset (in UTF-16 code units) of this view into its
/// underlying string.
pub fn StringView::start_offset(self : StringView) -> Int {
self.start()
}
///|
/// Returns a new view of the view with the given start and end offsets.
pub fn StringView::view(
self : StringView,
start_offset? : Int = 0,
end_offset? : Int,
) -> StringView {
let end_offset = if end_offset is Some(o) { o } else { self.length() }
guard start_offset >= 0 &&
start_offset <= end_offset &&
end_offset <= self.length() else {
abort("Invalid index for View")
}
StringView::make_view(
self.str(),
self.start() + start_offset,
self.start() + end_offset,
)
}
///|
/// Returns the UTF-16 code unit at the given index without checking if the
/// index is within bounds.
///
/// This method has O(1) complexity.
#intrinsic("%stringview.unsafe_get")
#internal(unsafe, "Undefined behavior if index is out of bounds.")
pub fn StringView::unsafe_get(self : StringView, index : Int) -> UInt16 {
self.str().unsafe_get(self.start() + index)
}
///|
/// Returns an `ArrayView` containing the UTF-16 code units of the string view.
///
/// This method yields code units, not Unicode characters. Surrogate pairs are
/// represented as two `UInt16` values.
///
/// Note: the default implementation copies the entire underlying string into a
/// fresh `FixedArray` and then slices it, so the returned view does not alias
/// the original string's storage. Some backends lower the intrinsic to a
/// zero-copy view instead.
#intrinsic("%stringview.code_units")
pub fn StringView::code_units(self : StringView) -> ArrayView[UInt16] {
FixedArray::makei(self.str().length(), i => self.str().unsafe_get(i))[self.start():self.end()]
}
///|
/// Returns the charcode(code unit) at the given index without checking if the
/// index is within bounds.
///
/// This method has O(1) complexity.
/// #Example
///
/// ```mbt check
/// test {
/// let str = "B🤣🤣C"
/// let view = str[:]
/// inspect(view.unsafe_get(0), content="66")
/// inspect(view.unsafe_get(1), content="55358")
/// inspect(view.unsafe_get(2), content="56611")
/// inspect(view.unsafe_get(3), content="55358")
/// inspect(view.unsafe_get(4), content="56611")
/// inspect(view.unsafe_get(5), content="67")
/// }
/// ```
#deprecated("Use `StringView::unsafe_get` instead")
pub fn StringView::unsafe_charcode_at(self : StringView, index : Int) -> Int {
self.str().unsafe_get(self.start() + index).to_int()
}
///|
/// Returns the number of Unicode characters in this view.
///
/// Note this has O(n) complexity where n is the length of the code points in
/// the view.
pub fn StringView::char_length(self : StringView) -> Int {
self.str().char_length(start_offset=self.start(), end_offset=self.end())
}
///|
/// Materialize this view into an owned `String`.
///
/// This crosses an ownership boundary and generally allocates. Use format
/// strings (`"\{view}"`) or `Show::to_string(view)` when you only need a
/// display representation — those paths go through the `Show` trait and are
/// not flagged.
///
/// # Examples
///
/// ```mbt check
/// test {
/// let str = "Hello World"
/// let view = str.view(
/// start_offset=str.offset_of_nth_char(0).unwrap(),
/// end_offset=str.offset_of_nth_char(5).unwrap(),
/// ) // "Hello"
/// inspect(view.to_owned(), content="Hello")
/// }
/// ```
#alias(to_string, deprecated="Use `to_owned` to allocate an owned String from a StringView; use `Show::to_string` or format strings for display")
pub fn StringView::to_owned(self : StringView) -> String {
// when `self == self.str()`, `String::unsafe_substring` would return original string, which doesn't create a new copy.
self.str().unsafe_substring(start=self.start(), end=self.end())
}
///|
pub impl Show for StringView with fn output(self, logger) {
logger.write_view(self)
}
///|
pub impl Show for StringView with fn to_string(self) {
self.to_owned()
}
///|
/// Returns an iterator over the Unicode characters in the string view.
#alias(iterator, deprecated)
pub fn StringView::iter(self : StringView) -> Iter[Char] {
let start = self.start()
let end = self.end()
let mut index = start
Iter::new(fn() {
guard index < end else { None }
let c1 = self.str().unsafe_get(index)
if c1.is_leading_surrogate() && index + 1 < self.end() {
let c2 = self.str().unsafe_get(index + 1)
if c2.is_trailing_surrogate() {
index += 2
return Some(code_point_of_surrogate_pair(c1.to_int(), c2.to_int()))
}
}
index += 1
Some(c1.unsafe_to_char())
})
}
///|
/// Returns an iterator over the Unicode characters in the string view,
/// yielding pairs of (character index, character).
#alias(iterator2, deprecated)
pub fn StringView::iter2(self : StringView) -> Iter2[Int, Char] {
let start = self.start()
let end = self.end()
let mut index = start
let mut char_index = 0
Iter2::new(fn() {
guard index < end else { None }
let c1 = self.str().unsafe_get(index)
if c1.is_leading_surrogate() && index + 1 < self.end() {
let c2 = self.str().unsafe_get(index + 1)
if c2.is_trailing_surrogate() {
let result = (
char_index,
code_point_of_surrogate_pair(c1.to_int(), c2.to_int()),
)
index += 2
char_index += 1
return Some(result)
}
}
let result = (char_index, c1.unsafe_to_char())
index += 1
char_index += 1
Some(result)
})
}
///|
/// Returns an iterator over the Unicode characters in the string view in reverse order.
#alias(rev_iterator, deprecated)
pub fn StringView::rev_iter(self : StringView) -> Iter[Char] {
let start = self.start()
let end = self.end()
let mut index = end
Iter::new(fn() {
guard index > start else { None }
index -= 1
let c1 = self.str().unsafe_get(index)
if c1.is_trailing_surrogate() && index - 1 >= 0 {
let c2 = self.str().unsafe_get(index - 1)
if c2.is_leading_surrogate() {
index -= 1
return Some(code_point_of_surrogate_pair(c2.to_int(), c1.to_int()))
}
}
Some(c1.unsafe_to_char())
})
}
///|
/// Checks if all characters in the string view match the condition.
///
/// # Example
///
/// ```mbt check
/// test {
/// let view = "zabc!"[1:4]
/// assert_true(view.all(c => c.is_ascii_lowercase()))
/// assert_false(view.all(c => c == 'a'))
/// }
/// ```
#locals(f)
#alias(every)
pub fn StringView::all(
self : StringView,
f : (Char) -> Bool raise?,
) -> Bool raise? {
for c in self {
if !f(c) {
return false
}
}
true
}
///|
/// Checks if any character in the string view matches the condition.
///
/// # Example
///
/// ```mbt check
/// test {
/// let view = "zabc!"[1:4]
/// assert_true(view.any(c => c == 'b'))
/// assert_false(view.any(c => c.is_ascii_digit()))
/// }
/// ```
#locals(f)
#alias(exists)
pub fn StringView::any(
self : StringView,
f : (Char) -> Bool raise?,
) -> Bool raise? {
for c in self {
if f(c) {
return true
}
}
false
}
///|
/// Compares two views for equality. Returns true only if both views
/// have the same length and contain identical characters in the same order.
pub impl Eq for StringView with fn equal(self, other) {
let len = self.length()
guard len == other.length() else { return false }
if physical_equal(self.str(), other.str()) && self.start() == other.start() {
return true
}
self
.str()
.unsafe_range_equal(
other.str(),
self_off=self.start(),
other_off=other.start(),
len~,
)
}
///|
/// Compares a `StringView` to a `String` code-unit-for-code-unit.
///
/// This is the cross-type equivalent of `==` and avoids materializing a fresh
/// `String` (or a wrapping `StringView`) when probing an owned-`String`-keyed
/// container with a view-shaped key. When the view spans an entire backing
/// string that is physically the same as `other`, this short-circuits.
///
/// Returns `true` if the lengths match and every UTF-16 code unit in `self`
/// equals the code unit at the same index in `other`.
///
/// Example:
/// ```mbt check
/// test {
/// let s = "say hello to everyone"
/// inspect(
/// s.view(start_offset=4, end_offset=9).equal_to_string("hello"),
/// content="true",
/// )
/// inspect(
/// s.view(start_offset=4, end_offset=9).equal_to_string("world"),
/// content="false",
/// )
/// }
/// ```
pub fn StringView::equal_to_string(self : StringView, other : String) -> Bool {
let len = self.length()
guard len == other.length() else { return false }
if physical_equal(self.str(), other) && self.start() == 0 {
return true
}
self.str().unsafe_range_equal(other, self_off=self.start(), other_off=0, len~)
}
///|
/// Views are ordered based on shortlex order by their charcodes (code units). This
/// orders Unicode characters based on their positions in the code charts. This is
/// not necessarily the same as "alphabetical" order, which varies by language
/// and locale.
pub impl Compare for StringView with fn compare(self, other) {
let self_len = self.length()
let other_len = other.length()
let cmp = self_len.compare(other_len)
guard cmp == 0 else { return cmp }
if physical_equal(self.str(), other.str()) && self.start() == other.start() {
return 0
}
for i in 0.. Int {
let self_len = self.length()
let other_len = other.length()
let min_len = if self_len < other_len { self_len } else { other_len }
// Compare character by character up to the minimum length
for i in 0.. UInt16 {
if c >= ('A' : UInt16) && c <= ('Z' : UInt16) {
c + (32 : UInt16)
} else {
c
}
}
///|
/// Performs a lexicographical comparison of two string views, treating ASCII
/// letters as case-insensitive.
///
/// Each pair of UTF-16 code units is compared after folding ASCII `'A'..'Z'`
/// onto `'a'..'z'`. Non-ASCII code units are compared by their raw value, so
/// this function does **not** perform Unicode case folding (e.g. `'Ä'` and
/// `'ä'` are still considered different). Use this when you only need to
/// match ASCII identifiers, headers, file extensions, or similar protocol
/// text — not for human-language text where locale-dependent folding matters.
///
/// Aside from case folding, the semantics match `lexical_compare`: characters
/// are compared one by one (UTF-16 code unit by code unit) and, when one view
/// is a prefix of the other, the shorter view is considered less. The result
/// is independent of which view is `self`.
///
/// # Returns
///
/// - A negative integer if `self` is less than `other`
/// - Zero if `self` is equal to `other` under ASCII case folding
/// - A positive integer if `self` is greater than `other`
///
/// # Example
///
/// ```mbt check
/// test {
/// inspect("Hello".view().compare_ignore_ascii_case("hello".view()), content="0")
/// inspect("ABC".view().compare_ignore_ascii_case("abd".view()), content="-1")
/// inspect("abc".view().compare_ignore_ascii_case("AB".view()), content="1")
/// }
/// ```
pub fn StringView::compare_ignore_ascii_case(
self : StringView,
other : StringView,
) -> Int {
let self_len = self.length()
let other_len = other.length()
let min_len = if self_len < other_len { self_len } else { other_len }
for i in 0.. Bool {
let len = self.length()
if len != other.length() {
return false
}
for i in 0.. StringView {
let end_offset = if end_offset is Some(o) { o } else { self.length() }
guard start_offset >= 0 &&
start_offset <= end_offset &&
end_offset <= self.length() else {
abort("Invalid index for View")
}
StringView::make_view(self, start_offset, end_offset)
}
///|
/// Convert char array to string view.
pub fn StringView::from_array(chars : ArrayView[Char]) -> StringView {
String::from_array(chars)
}
///|
/// Convert char iterator to string view.
#alias(from_iterator, deprecated)
pub fn StringView::from_iter(iter : Iter[Char]) -> StringView {
String::from_iter(iter)
}
///|
/// Returns a view of the string between `start` and `end`, or `None` if the
/// range is invalid — either out of bounds or splitting a UTF-16 surrogate
/// pair. Unlike `String::sub` (a.k.a. `s[start:end]`), this variant does not
/// abort, making it suitable for composition with pattern matching:
///
/// ```mbt check
/// test {
/// let s = "Hello🤣World"
/// debug_inspect(
/// s.get_view(end=5).map(v => v.to_owned()),
/// content="Some(\"Hello\")",
/// )
/// // Splitting a surrogate pair is rejected rather than panicking.
/// debug_inspect(s.get_view(end=6), content="None")
/// debug_inspect(s.get_view(start=100), content="None")
/// }
/// ```
pub fn String::get_view(
self : String,
start? : Int = 0,
end? : Int,
) -> StringView? {
let len = self.length()
let end = match end {
Some(end) | (None with end = len) => end
}
guard start >= 0 && start <= end && end <= len else { None }
if start < len && self.unsafe_get(start).is_trailing_surrogate() {
return None
}
if end < len && self.unsafe_get(end).is_trailing_surrogate() {
return None
}
Some(StringView::make_view(self, start, end))
}
///|
/// Returns a sub-view of the view between `start` and `end`, or `None` if the
/// range is invalid — either out of bounds or splitting a UTF-16 surrogate
/// pair. The optional variant of `StringView::sub` (a.k.a. `sv[start:end]`).
pub fn StringView::get_view(
self : StringView,
start? : Int = 0,
end? : Int,
) -> StringView? {
let str_len = self.str().length()
let abs_end = match end {
None => self.end()
Some(end) => self.start() + end
}
let abs_start = self.start() + start
guard abs_start >= self.start() &&
abs_start <= abs_end &&
abs_end <= self.end() else {
None
}
if abs_start < str_len &&
self.str().unsafe_get(abs_start).is_trailing_surrogate() {
return None
}
if abs_end < str_len && self.str().unsafe_get(abs_end).is_trailing_surrogate() {
return None
}
Some(StringView::make_view(self.str(), abs_start, abs_end))
}
///|
/// Returns the largest well-formed view contained in the requested range.
/// Total: out-of-range offsets are clamped to the string, an offset that
/// would split a surrogate pair is snapped inward (`start` forward, `end`
/// backward), and an inverted range yields an empty view — this function
/// never aborts and never cuts a surrogate pair in half.
///
/// Intended for truncation where the exact cut point does not matter, such
/// as short summaries: the result is at most one character shorter than the
/// requested range on each side. Unpaired surrogates already present in the
/// input are treated as boundaries and pass through unchanged.
///
/// See `s[start:end]` for the aborting variant and `String::get_view` for
/// the exact `Option`-returning variant.
///
/// # Example
///
/// ```mbt check
/// test {
/// let s = "ab😀cd"
/// inspect(s.clamped_view(end=3), content="ab") // 3 splits 😀: snapped to 2
/// inspect(s.clamped_view(start=3), content="cd") // snapped to 4
/// inspect(s.clamped_view(end=100), content="ab😀cd") // clamped
/// inspect(s.clamped_view(start=3, end=3), content="") // inside the pair
/// }
/// ```
pub fn String::clamped_view(
self : String,
start? : Int = 0,
end? : Int,
) -> StringView {
let len = self.length()
let mut lo = if start < 0 { 0 } else if start > len { len } else { start }
let mut hi = match end {
None => len
Some(e) => if e < 0 { 0 } else if e > len { len } else { e }
}
if lo > 0 &&
lo < len &&
self.unsafe_get(lo).is_trailing_surrogate() &&
self.unsafe_get(lo - 1).is_leading_surrogate() {
lo += 1
}
if hi > 0 &&
hi < len &&
self.unsafe_get(hi).is_trailing_surrogate() &&
self.unsafe_get(hi - 1).is_leading_surrogate() {
hi -= 1
}
if lo >= hi {
StringView::make_view(self, lo, lo)
} else {
StringView::make_view(self, lo, hi)
}
}
///|
/// Returns the largest well-formed sub-view contained in the requested range
/// of this view; offsets are relative to the view. Total like
/// `String::clamped_view`: clamps out-of-range offsets, snaps
/// surrogate-splitting offsets inward, and yields an empty view for an
/// inverted range.
///
/// # Example
///
/// ```mbt check
/// test {
/// let v = "xx ab😀cd".view(start_offset=3)
/// inspect(v.clamped_view(end=3), content="ab")
/// inspect(v.clamped_view(start=3), content="cd")
/// }
/// ```
pub fn StringView::clamped_view(
self : StringView,
start? : Int = 0,
end? : Int,
) -> StringView {
let len = self.length()
let mut lo = if start < 0 { 0 } else if start > len { len } else { start }
let mut hi = match end {
None => len
Some(e) => if e < 0 { 0 } else if e > len { len } else { e }
}
let str = self.str()
let base = self.start()
if lo > 0 &&
lo < len &&
str.unsafe_get(base + lo).is_trailing_surrogate() &&
str.unsafe_get(base + lo - 1).is_leading_surrogate() {
lo += 1
}
if hi > 0 &&
hi < len &&
str.unsafe_get(base + hi).is_trailing_surrogate() &&
str.unsafe_get(base + hi - 1).is_leading_surrogate() {
hi -= 1
}
if lo >= hi {
StringView::make_view(str, base + lo, base + lo)
} else {
StringView::make_view(str, base + lo, base + hi)
}
}
///|
/// Splits the string into a well-formed `(prefix, suffix)` pair at the given
/// code-unit offset: the cut point is `at` clamped to the string and snapped
/// down to the nearest character boundary, so the prefix never exceeds `at`
/// units and a character straddling the cut goes to the suffix.
///
/// Total, O(1), and lossless: `prefix + suffix == self` always holds —
/// unlike composing two `clamped_view` calls, which would drop a character
/// straddling the cut from both halves. Unpaired surrogates are treated as
/// boundaries and pass through unchanged.
///
/// # Example
///
/// ```mbt check
/// test {
/// let s = "ab😀cd"
/// let (p, q) = s.split_at(3) // 3 splits 😀: the cut snaps down to 2
/// inspect(p, content="ab")
/// inspect(q, content="😀cd")
/// let (p2, q2) = s.split_at(100) // clamped
/// inspect(p2, content="ab😀cd")
/// inspect(q2, content="")
/// }
/// ```
pub fn String::split_at(self : String, at : Int) -> (StringView, StringView) {
let len = self.length()
let mut cut = if at < 0 { 0 } else if at > len { len } else { at }
if cut > 0 &&
cut < len &&
self.unsafe_get(cut).is_trailing_surrogate() &&
self.unsafe_get(cut - 1).is_leading_surrogate() {
cut -= 1
}
(StringView::make_view(self, 0, cut), StringView::make_view(self, cut, len))
}
///|
/// Splits the view into a well-formed `(prefix, suffix)` pair at the given
/// view-relative code-unit offset; same contract as `String::split_at`.
///
/// # Example
///
/// ```mbt check
/// test {
/// let v = "xx ab😀cd".view(start_offset=3)
/// let (p, q) = v.split_at(3)
/// inspect(p, content="ab")
/// inspect(q, content="😀cd")
/// }
/// ```
pub fn StringView::split_at(
self : StringView,
at : Int,
) -> (StringView, StringView) {
let len = self.length()
let mut cut = if at < 0 { 0 } else if at > len { len } else { at }
let str = self.str()
let base = self.start()
if cut > 0 &&
cut < len &&
str.unsafe_get(base + cut).is_trailing_surrogate() &&
str.unsafe_get(base + cut - 1).is_leading_surrogate() {
cut -= 1
}
(
StringView::make_view(str, base, base + cut),
StringView::make_view(str, base + cut, self.end()),
)
}
///|
/// Creates a view of a string with proper UTF-16 boundary validation.
///
/// # Parameters
///
/// - `start` : Starting UTF-16 code unit index (default: 0), counting from the
/// beginning of the string
/// - `end` : Ending UTF-16 code unit index (optional)
/// - If `None`: extends to the end of the string
/// - Otherwise: counts from the beginning of the string
///
/// # Returns
///
/// - A `View` representing the specified substring range
///
/// # Panics
///
/// - If start or end indices are out of valid range
/// - If start or end position would split a UTF-16 surrogate pair
///
/// This prevents creating views that would split surrogate pairs, which would
/// result in invalid Unicode characters.
///
/// # Performance
///
/// This function has O(1) complexity as it only performs boundary checks
/// without scanning the string content.
///
/// # Examples
///
/// ```mbt check
/// test {
/// let str = "Hello🤣World"
/// let view1 = str[0:5]
/// inspect(view1, content="Hello")
/// let view2 = str[7:]
/// inspect(view2, content="World")
/// }
/// ```
#alias("_[_:_]")
pub fn String::sub(self : String, start? : Int = 0, end? : Int) -> StringView {
let len = self.length()
let end = match end {
Some(end) | (None with end = len) => end
}
guard! start >= 0 && start <= end && end <= len
if start < len {
guard! !self.unsafe_get(start).is_trailing_surrogate()
}
if end < len {
guard! !self.unsafe_get(end).is_trailing_surrogate()
}
StringView::make_view(self, start, end)
}
///|
/// Creates a subview of an existing view with proper UTF-16 boundary validation.
///
/// # Parameters
///
/// - `start` : Starting UTF-16 code unit index relative to this view (default: 0),
/// counting from the beginning of this view
/// - `end` : Ending UTF-16 code unit index relative to this view (optional)
/// - If `None`: extends to the end of this view
/// - Otherwise: counts from the beginning of this view
///
/// # Returns
///
/// - A `View` representing the specified subrange of this view
///
/// # Panics
///
/// - If start or end indices are out of this view's range
/// - If start or end position would split a UTF-16 surrogate pair
///
/// This prevents creating views that would split surrogate pairs, which would
/// result in invalid Unicode characters.
///
/// # Performance
///
/// This function has O(1) complexity as it only performs boundary checks
/// without scanning the string content.
///
/// # Examples
///
/// ```mbt check
/// test {
/// let str = "Hello🤣World"[1:11] // "ello🤣Worl"
/// let view1 = str[0:6]
/// inspect(view1, content="ello🤣")
/// let view2 = str[8:]
/// inspect(view2, content="rl")
/// }
/// ```
#alias("_[_:_]")
pub fn StringView::sub(
self : StringView,
start? : Int = 0,
end? : Int,
) -> StringView {
let str_len = self.str().length()
// Calculate absolute positions in the original string
let abs_end = match end {
None => self.end()
Some(end) => self.start() + end
}
let abs_start = self.start() + start
// Validate bounds against the original string
guard! abs_start >= self.start() &&
abs_start <= abs_end &&
abs_end <= self.end()
// Check for surrogate pair boundaries
if abs_start < str_len {
guard! !self.str().unsafe_get(abs_start).is_trailing_surrogate()
}
if abs_end < str_len {
guard! !self.str().unsafe_get(abs_end).is_trailing_surrogate()
}
StringView::make_view(self.str(), abs_start, abs_end)
}
///|
/// Test if the length of the view is equal to the given length.
///
/// This has O(n) complexity where n is the length in the parameter.
pub fn StringView::char_length_eq(self : StringView, len : Int) -> Bool {
self
.str()
.char_length_eq(len, start_offset=self.start(), end_offset=self.end())
}
///|
/// Test if the length of the view is greater than or equal to the given length.
///
/// This has O(n) complexity where n is the length in the parameter.
pub fn StringView::char_length_ge(self : StringView, len : Int) -> Bool {
self
.str()
.char_length_ge(len, start_offset=self.start(), end_offset=self.end())
}
///|
/// Returns the UTF-16 index of the i-th (zero-indexed) Unicode character of
/// the view. If i is negative, it returns the index of the (n + i)-th character
/// where n is the total number of Unicode characters in the view.
pub fn StringView::offset_of_nth_char(self : StringView, i : Int) -> Int? {
if self
.str()
.offset_of_nth_char(i, start_offset=self.start(), end_offset=self.end())
is Some(index) {
Some(index - self.start())
} else {
None
}
}
///|
/// The empty view of a string
pub impl Default for StringView with fn default() {
""
}
///|
/// Create a new string by repeating the given character `value` `length` times.
pub fn StringView::make(length : Int, value : Char) -> StringView {
String::make(length, value)
}
///|
pub impl ToJson for StringView with fn to_json(self) {
String::to_json(self.to_owned())
}
///|
pub impl Add for StringView with fn add(self, other) {
[..self, ..other]
}