/development/std/string/views.nct
views.nct
//! Allocation-free borrowed UTF-8 ranges and text iterators.
//!
//! Public operations validate byte ranges before crossing the restricted
//! projection boundary. Returned views retain their source text storage.
use std/iter.Iterator
/// Returns whether `index` is a valid UTF-8 byte boundary in `text`.
func is_char_boundary(text: &str, index: usize): bool {
if index > text.len() { return false }
if index == 0 || index == text.len() { return true }
let byte: u8 = text[index]
return byte < 128 || byte >= 192
}
/// Returns a borrowed UTF-8 range, or `none` for invalid endpoints.
func get_range(text: &str, start: usize, end: usize): &str? {
if start > end { return none }
if !is_char_boundary(text, start) || !is_char_boundary(text, end) {
return none
}
return str_subview_unchecked(text, start, end - start)
}
/// Removes `prefix` without allocation when it exactly matches.
func strip_prefix(text: &str, prefix: &str): &str? from text {
if prefix.len() > text.len() { return none }
var index: usize = 0
while index < prefix.len() {
if text[index] != prefix[index] { return none }
index = index + 1
}
return str_subview_unchecked(text, prefix.len(), text.len() - prefix.len())
}
/// Removes `suffix` without allocation when it exactly matches.
func strip_suffix(text: &str, suffix: &str): &str? from text {
if suffix.len() > text.len() { return none }
let start: usize = text.len() - suffix.len()
var index: usize = 0
while index < suffix.len() {
if text[start + index] != suffix[index] { return none }
index = index + 1
}
return str_subview_unchecked(text, 0, start)
}
construct SplitIter {
func new(
text: &str,
separator: &str,
): Self! from text | separator {
if separator.len() == 0 { return empty_separator_error() }
return SplitIter {
text: text,
separator: separator,
next_start: 0,
finished: false,
}
}
}
/// Creates an allocation-free separator iterator.
func split_views(
text: &str,
separator: &str,
): SplitIter! from text | separator {
return SplitIter.new(text, separator)?
}
impl Iterator<&str> for SplitIter {
method &+self.next(): &str? {
if self.finished { return none }
let part_start: usize = self.next_start
let separator_start: usize = find_from_bytes(self.text, self.separator, part_start) otherwise {
self.finished = true
return str_subview_unchecked(
self.text,
part_start,
self.text.len() - part_start,
)
}
self.next_start = separator_start + self.separator.len()
return str_subview_unchecked(
self.text,
part_start,
separator_start - part_start,
)
}
}
construct LinesIter {
func new(text: &str): Self {
return LinesIter {
text: text,
next_start: 0,
finished: text.len() == 0,
}
}
}
/// Creates an allocation-free iterator over borrowed lines.
func lines(text: &str): LinesIter {
return LinesIter.new(text)
}
impl Iterator<&str> for LinesIter {
method &+self.next(): &str? {
if self.finished { return none }
let line_start: usize = self.next_start
let newline: usize = find_byte(self.text, 10, line_start) otherwise {
self.finished = true
return str_subview_unchecked(
self.text,
line_start,
self.text.len() - line_start,
)
}
var line_end: usize = newline
if line_end > line_start && self.text[line_end - 1] == 13 {
line_end = line_end - 1
}
self.next_start = newline + 1
if self.next_start == self.text.len() {
self.finished = true
}
return str_subview_unchecked(
self.text,
line_start,
line_end - line_start,
)
}
}
func find_byte(text: &str, needle: u8, start: usize): usize? {
var index: usize = start
while index < text.len() {
if text[index] == needle { return index }
index = index + 1
}
return none
}
func empty_separator_error(): error {
return Error.new(
"std.string.empty_separator",
"string separator must not be empty",
)
}