Programming Language

Nocter

A self-contained systems language built around simplicity, encapsulation, and foolproof design.

/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",
    )
}