236 lines
6.1 KiB
Plaintext
236 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();
|
|
defer { drop(&s); }
|
|
@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"); },
|
|
}
|
|
}
|
|
|
|
fn test_compare_and_search() {
|
|
var hello_r = from_slice("hello");
|
|
var hello: Str = new();
|
|
defer { drop(&hello); }
|
|
match hello_r {
|
|
.Ok(v) => { hello = v; },
|
|
.Err(error) => { @assert(false, "hello"); },
|
|
}
|
|
var help_r = from_slice("help");
|
|
var help: Str = new();
|
|
defer { drop(&help); }
|
|
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();
|
|
defer { drop(&pre); }
|
|
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();
|
|
defer { drop(&needle); }
|
|
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"); },
|
|
}
|
|
}
|
|
|
|
fn test_clone_take_and_failures() {
|
|
var source = new();
|
|
defer { drop(&source); }
|
|
expect_ok(append(&source, "owned"));
|
|
|
|
var copy = new();
|
|
defer { drop(©); }
|
|
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(©);
|
|
defer { drop(&moved); }
|
|
@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");
|
|
}
|
|
|
|
fn main() {
|
|
test_construction();
|
|
test_compare_and_search();
|
|
test_clone_take_and_failures();
|
|
}
|