/development/std/str/views.nct
views.nct
//! Borrowed UTF-8 ranges, byte projection, and text iterators.
see ./index.nct
see ./search.nct
use /iter.Iterator
primitive bytes_from_str(value: &str): &[u8] from value
primitive str_subview_unchecked(
text: &str,
start: usize,
len: usize,
): &str from text
struct SplitIter {
text: &str
separator: &str
next_start: usize
finished: bool
}
struct LinesIter {
text: &str
next_start: usize
finished: bool
}
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
}
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)
}
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 += 1
}
return str_subview_unchecked(text, prefix.len(), text.len() - prefix.len())
}
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 += 1
}
return str_subview_unchecked(text, 0, start)
}
func find_from(text: &str, needle: &str, start: usize): usize? {
return find_from_bytes(text, needle, start)?
}
func find(text: &str, needle: &str): usize? {
return find_from(text, needle, 0)?
}
func contains(text: &str, needle: &str): bool {
let offset: usize = find(text, needle) otherwise { return false }
return offset <= text.len()
}
func starts_with(text: &str, prefix: &str): bool {
let offset: usize = find_from(text, prefix, 0) otherwise { return false }
return offset == 0
}
func ends_with(text: &str, suffix: &str): bool {
if suffix.len() > text.len() { return false }
let start: usize = text.len() - suffix.len()
let offset: usize = find_from(text, suffix, start) otherwise { return false }
return offset == start
}
construct SplitIter {
default 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,
}
}
}
func split_views(
text: &str,
separator: &str,
): SplitIter! from text | separator {
return SplitIter.new(text, separator)?
}
conform Iterator for SplitIter {
method &+self.next(): &str? {
if self.finished { return none }
let part_start: usize = self.next_start
let separator_start: usize = find_from(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 {
default func new(text: &str): Self {
return LinesIter {
text: text,
next_start: 0,
finished: text.len() == 0,
}
}
}
func lines(text: &str): LinesIter {
return LinesIter.new(text)
}
conform Iterator 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 -= 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 += 1
}
return none
}
func empty_separator_error(): error {
return error.new(
"std.str.empty_separator",
"string separator must not be empty",
)
}