Programming Language

Nocter

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

/development/std/string/storage.nct

storage.nct

//! Owned string storage and mutation.

include ./index.nct

use std/mem.{RawBuffer, TryAllocator, alloc, allocation_abort_raw, current_allocator}
use std/mem.{empty_page_buffer, try_alloc, try_grow_owned}
use std/mem.{raw_buffer_len, raw_buffer_ptr}
use std/ptr.copy_str_to_ptr
use std/ptr.str_from_raw_parts
use std/ptr.store_u8_to_ptr
use std/vec.Vec
include ./search.nct

struct String {
    storage: RawBuffer
    len: usize
}

construct String {
    default literal ""(text: &str): Self {
        return String.copy(text)
    }

    func empty(): Self {
        return String {
            storage: empty_page_buffer(1),
            len: 0,
        }
    }

    func with_capacity(requested_capacity: usize): Self {
        var allocator = current_allocator()
        return String {
            storage: alloc(&+allocator, requested_capacity, 1),
            len: 0,
        }
    }

    func try_with_capacity(
        allocator: &+TryAllocator,
        requested_capacity: usize,
    ): Self! {
        return String {
            storage: try_alloc(allocator, requested_capacity, 1)?,
            len: 0,
        }
    }

    func copy(value: &str): Self {
        let value_len: usize = bytes_from_str(value).len()
        var result = String.with_capacity(value_len)
        copy_str_to_ptr(raw_buffer_ptr(&result.storage), 0, value)
        result.len = value_len
        return move result
    }

    func concat(...parts: &str): Self {
        var result = String.empty()
        for part in parts {
            result.push_str(part)
        }
        return move result
    }

    func try_copy(allocator: &+TryAllocator, value: &str): Self! from allocator {
        let value_len: usize = bytes_from_str(value).len()
        var result = String.try_with_capacity(allocator, value_len)?
        copy_str_to_ptr(raw_buffer_ptr(&result.storage), 0, value)
        result.len = value_len
        return move result
    }

    func from_utf8(candidate: &[u8]): Self! {
        if !is_valid_utf8(candidate) { return invalid_utf8() }
        var result = String.with_capacity(candidate.len())
        var offset: usize = 0
        while offset < candidate.len() {
            store_u8_to_ptr(raw_buffer_ptr(&result.storage), offset, candidate[offset])
            offset = offset + 1
        }
        result.len = candidate.len()
        return move result
    }

    func try_from_utf8(
        allocator: &+TryAllocator,
        candidate: &[u8],
    ): Self! from allocator {
        if !is_valid_utf8(candidate) { return invalid_utf8() }
        var result = String.try_with_capacity(allocator, candidate.len())?
        var offset: usize = 0
        while offset < candidate.len() {
            store_u8_to_ptr(raw_buffer_ptr(&result.storage), offset, candidate[offset])
            offset = offset + 1
        }
        result.len = candidate.len()
        return move result
    }
}

func view(text: &String): &str {
    return str_from_raw_parts(raw_buffer_ptr(&text.storage), text.len)
}

func capacity(text: &String): usize {
    let text_capacity: usize = raw_buffer_len(&text.storage)
    return text_capacity
}

func try_reserve(text: &+String, additional: usize): void! {
    let text_len: usize = text.len
    if additional > 18446744073709551615 - text_len {
        return capacity_overflow()
    }

    let required_capacity: usize = text_len + additional
    if required_capacity <= raw_buffer_len(&text.storage) {
        return
    }

    try_grow_owned(&+text.storage, required_capacity)?
    return
}

func reserve(text: &+String, additional: usize): void {
    try_reserve(text, additional) catch allocation_error {
        return allocation_abort_raw()
    }
    return
}

func clear(text: &+String): void {
    text.len = 0
    return
}

func bytes(value: &str): &[u8] {
    return bytes_from_str(value)
}

func is_valid_utf8(candidate: &[u8]): bool {
    var offset: usize = 0
    while offset < candidate.len() {
        let leading: u8 = candidate[offset]
        if leading < 128 {
            offset = offset + 1
        } else if leading >= 194 && leading <= 223 {
            if offset + 1 >= candidate.len() || !is_continuation(candidate[offset + 1]) {
                return false
            }
            offset = offset + 2
        } else if leading >= 224 && leading <= 239 {
            if offset + 2 >= candidate.len() {
                return false
            }
            let second: u8 = candidate[offset + 1]
            if !is_continuation(second) || !is_continuation(candidate[offset + 2]) {
                return false
            }
            if leading == 224 && second < 160 { return false }
            if leading == 237 && second >= 160 { return false }
            offset = offset + 3
        } else if leading >= 240 && leading <= 244 {
            if offset + 3 >= candidate.len() {
                return false
            }
            let second: u8 = candidate[offset + 1]
            if !is_continuation(second) || !is_continuation(candidate[offset + 2]) || !is_continuation(candidate[offset + 3]) {
                return false
            }
            if leading == 240 && second < 144 { return false }
            if leading == 244 && second >= 144 { return false }
            offset = offset + 4
        } else {
            return false
        }
    }
    return true
}

func is_continuation(byte: u8): bool {
    return byte >= 128 && byte <= 191
}

func find_from(value: &str, needle: &str, start: usize): usize? {
    return find_from_bytes(value, needle, start)?
}

func find(value: &str, needle: &str): usize? {
    return find_from(value, needle, 0)?
}

func contains(value: &str, needle: &str): bool {
    let offset: usize = find(value, needle) otherwise { return false }
    return offset <= value.len()
}

func starts_with(value: &str, prefix: &str): bool {
    let offset: usize = find_from(value, prefix, 0) otherwise { return false }
    return offset == 0
}

func ends_with(value: &str, suffix: &str): bool {
    if suffix.len() > value.len() { return false }
    let offset: usize = find_from(value, suffix, value.len() - suffix.len()) otherwise { return false }
    return offset == value.len() - suffix.len()
}

func split(value: &str, separator: &str): Vec<String>! {
    if separator.len() == 0 {
        return empty_separator()
    }
    var result: Vec<String> = Vec.empty()
    var part_start: usize = 0
    while part_start <= value.len() {
        let separator_offset: usize = find_from(value, separator, part_start) otherwise {
            result.push(copy_range(value, part_start, value.len()))
            break
        }
        result.push(copy_range(value, part_start, separator_offset))
        part_start = separator_offset + separator.len()
    }
    return move result
}

func copy_range(value: &str, start: usize, end: usize): String {
    let source: &[u8] = bytes_from_str(value)
    var result = String.with_capacity(end - start)
    var offset: usize = start
    while offset < end {
        store_u8_to_ptr(raw_buffer_ptr(&result.storage), result.len, source[offset])
        result.len = result.len + 1
        offset = offset + 1
    }
    return move result
}

func invalid_utf8(): error {
    return error.new("std.string.invalid_utf8", "invalid UTF-8")
}

func empty_separator(): error {
    return error.new("std.string.empty_separator", "string separator must not be empty")
}

func try_push_str(text: &+String, value: &str): void! {
    let value_len: usize = value.len()
    let old_len: usize = text.len
    try_reserve(text, value_len)?
    copy_str_to_ptr(raw_buffer_ptr(&text.storage), old_len, value)
    text.len = old_len + value_len
    return
}

func push_str(text: &+String, value: &str): void {
    try_push_str(text, value) catch allocation_error {
        return allocation_abort_raw()
    }
    return
}

func capacity_overflow(): error {
    return error.new("std.string.capacity_overflow", "string capacity overflow")
}

instance String {
    coerce &self as &str {
        return view(self)
    }

    method &self.capacity(): usize {
        return capacity(self)
    }

    method &+self.reserve(additional: usize): void {
        reserve(self, additional)
        return
    }

    method &+self.try_reserve(additional: usize): void! {
        try_reserve(self, additional)?
        return
    }

    method &+self.clear(): void {
        clear(self)
        return
    }

    method &+self.push_str(value: &str): void {
        push_str(self, value)
        return
    }

    method &+self.try_push_str(value: &str): void! {
        try_push_str(self, value)?
        return
    }
}