Files
spl/stage2/utils/str.spl
T
2026-09-17 22:32:36 +08:00

239 lines
6.1 KiB
Plaintext

// Expect retcode = 0
// 拥有型字节字符串:在 ByteBuffer 之上提供字符串常用操作。
// 普通赋值复制句柄(共享存储),所有权转移用 take;失败不产生部分状态。
type .{ .Error, .Status, .make } = @import("error.spl");
type .{ .ByteView, .FindResult } = @import("byte_view.spl");
type .{ .ByteBuffer } = @import("byte_buffer.spl");
type Str = @self;
storage: ByteBuffer,
type StrResult = enum {
Ok: Str,
Err: Error,
}
fn new(): Str {
ret Str { .storage = ByteBuffer.new() };
}
// 复制 view 指向的字节。
fn from_view(view: ByteView): StrResult {
var s = new();
match append_view(&s, view) {
.Ok => {},
.Err(error) => { drop(&s); ret .Err(error); },
}
ret .Ok(s);
}
fn from_slice(bytes: []u8): StrResult {
ret from_view(ByteView.from_slice(bytes));
}
fn size(self: Str): usize {
ret ByteBuffer.size(self.storage);
}
fn is_empty(self: Str): bool {
ret ByteBuffer.is_empty(self.storage);
}
fn as_view(self: *Str): ByteView {
ret ByteView.from_slice(ByteBuffer.as_slice(self.storage));
}
fn as_slice(self: *Str): []u8 {
ret ByteBuffer.as_slice(self.storage);
}
fn reserve(self: *Str, capacity: usize): Status {
ret ByteBuffer.reserve(&self.storage, capacity);
}
fn append_view(self: *Str, view: ByteView): Status {
ret ByteBuffer.append(&self.storage, ByteView.as_slice(view));
}
fn append(self: *Str, bytes: []u8): Status {
ret ByteBuffer.append(&self.storage, bytes);
}
fn append_str(self: *Str, other: Str): Status {
var o = other;
ret append_view(self, as_view(&o));
}
fn append_byte(self: *Str, byte: u8): Status {
ret ByteBuffer.append_byte(&self.storage, byte);
}
fn clear(self: *Str) {
ByteBuffer.clear(&self.storage);
}
fn eq(a: Str, b: Str): bool {
var x = a;
var y = b;
ret ByteView.eq(as_view(&x), as_view(&y));
}
fn eq_slice(a: Str, bytes: []u8): bool {
var x = a;
ret ByteView.eq(as_view(&x), ByteView.from_slice(bytes));
}
fn cmp(a: Str, b: Str): i32 {
var x = a;
var y = b;
ret ByteView.cmp(as_view(&x), as_view(&y));
}
fn starts_with(self: Str, prefix: Str): bool {
var x = self;
var p = prefix;
ret ByteView.starts_with(as_view(&x), as_view(&p));
}
fn ends_with(self: Str, suffix: Str): bool {
var x = self;
var s = suffix;
ret ByteView.ends_with(as_view(&x), as_view(&s));
}
fn find(self: Str, needle: Str): FindResult {
var x = self;
var n = needle;
ret ByteView.find(as_view(&x), as_view(&n));
}
// 深复制;允许 out 与 source 为同一对象。
fn clone(out: *Str, source: Str): Status {
ret ByteBuffer.clone(&out.storage, source.storage);
}
fn take(source: *Str): Str {
ret Str { .storage = ByteBuffer.take(&source.storage) };
}
fn drop(self: *Str) {
ByteBuffer.drop(&self.storage);
}
// ---- 测试辅助 ----
fn expect_ok(result: Status) {
match result {
.Ok => {},
.Err(error) => { @assert(false, "unexpected str error"); },
}
}
fn expect_text(s: *Str, expected: []u8) {
var view = as_view(s);
@assert(ByteView.eq(view, ByteView.from_slice(expected)), "str text");
}
fn test_construction() {
var s = new();
@assert(is_empty(s) && size(s) == 0, "empty new");
expect_ok(append(&s, "hello"));
expect_ok(append_byte(&s, ' '));
expect_ok(append(&s, "world"));
expect_text(&s, "hello world");
@assert(size(s) == 11, "size");
var r = from_slice("copy");
match r {
.Ok(copy) => {
expect_text(&copy, "copy");
@assert(eq(copy, copy), "self eq");
drop(&copy);
},
.Err(error) => { @assert(false, "from_slice"); },
}
drop(&s);
}
fn test_compare_and_search() {
var hello_r = from_slice("hello");
var hello: Str = new();
match hello_r {
.Ok(v) => { hello = v; },
.Err(error) => { @assert(false, "hello"); },
}
var help_r = from_slice("help");
var help: Str = new();
match help_r {
.Ok(v) => { help = v; },
.Err(error) => { @assert(false, "help"); },
}
@assert(eq(hello, hello), "eq");
@assert(!eq(hello, help), "ne");
@assert(eq_slice(hello, "hello"), "eq_slice");
@assert(cmp(hello, help) < 0, "cmp lt");
@assert(cmp(help, hello) > 0, "cmp gt");
@assert(starts_with(hello, hello), "starts_with");
@assert(ends_with(hello, hello), "ends_with");
var pre_r = from_slice("he");
var pre: Str = new();
match pre_r { .Ok(v) => { pre = v; }, .Err(e) => { @assert(false, "pre"); } }
@assert(starts_with(hello, pre), "starts he");
@assert(!ends_with(hello, pre), "!ends he");
var needle_r = from_slice("ll");
var needle: Str = new();
match needle_r { .Ok(v) => { needle = v; }, .Err(e) => { @assert(false, "needle"); } }
match find(hello, needle) {
.Found(index) => { @assert(index == 2, "find ll"); },
.NotFound => { @assert(false, "ll not found"); },
}
match find(hello, pre) {
.Found(index) => { @assert(index == 0, "find he"); },
.NotFound => { @assert(false, "he not found"); },
}
drop(&needle);
drop(&pre);
drop(&help);
drop(&hello);
}
fn test_clone_take_and_failures() {
var source = new();
expect_ok(append(&source, "owned"));
var copy = new();
expect_ok(clone(&copy, source));
expect_text(&copy, "owned");
expect_ok(append_byte(&source, '!')); // '!'
expect_text(&copy, "owned"); // 独立
var before = ByteBuffer.as_slice(copy.storage).ptr;
expect_ok(clone(&copy, copy));
@assert(ByteBuffer.as_slice(copy.storage).ptr != before, "self clone replaces allocation");
expect_text(&copy, "owned");
var moved = take(&copy);
@assert(ByteBuffer.size(copy.storage) == 0 && ByteBuffer.capacity(copy.storage) == 0,
"take empties source");
expect_text(&moved, "owned");
clear(&moved);
@assert(is_empty(moved) && ByteBuffer.capacity(moved.storage) != 0, "clear keeps capacity");
drop(&moved);
drop(&copy);
drop(&source);
}
fn main() {
test_construction();
test_compare_and_search();
test_clone_take_and_failures();
}