/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
}
}