// 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"); @assert(eq(copy, copy), "self eq"); drop(©); }, .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(©, source)); expect_text(©, "owned"); expect_ok(append_byte(&source, '!')); // '!' expect_text(©, "owned"); // 独立 var before = ByteBuffer.as_slice(copy.storage).ptr; expect_ok(clone(©, copy)); @assert(ByteBuffer.as_slice(copy.storage).ptr != before, "self clone replaces allocation"); expect_text(©, "owned"); var moved = take(©); @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(©); drop(&source); } fn main() { test_construction(); test_compare_and_search(); test_clone_take_and_failures(); }