stage2 更多的工具库

This commit is contained in:
zzy committed 2026-09-18 11:17:33 +08:00
1 parent b3e38ba7e4
commit c30143609a
8 files changed
+2706 -1133

No files matched your search

+326
View File
@@ -0,0 +1,326 @@
// Expect retcode = 0
// 类型擦除的搜索与稳定归并排序。元素由定长字节表示,比较回调负责解释元素和键;
// context 只借用,模块不持有元素、键或上下文。所有尺寸错误都在写入前返回。
type .{ .Error, .Status, .make } = @import("error.spl");
type .{ .checked_mul } = @import("memory.spl");
type .{ .alloc, .copy, .free } = @import("../sys/mem.spl");
type SearchResult = enum {
Found: usize,
NotFound,
Err: Error,
}
fn validate_range(data: *u8, len: usize, elem_size: usize): Status {
if elem_size == 0 { ret .Err(make(.InvalidArgument)); }
match checked_mul(len, elem_size) {
.Ok(bytes) => {},
.Err(error) => { ret .Err(error); },
}
if len != 0 && data == null { ret .Err(make(.InvalidArgument)); }
ret .Ok;
}
// 返回首个相等元素。空范围不读取 data、key 或调用 compare。
fn linear_search(data: *u8, len: usize, elem_size: usize, key: *u8,
context: *u8, compare: fn(*u8, *u8, *u8): i32): SearchResult {
match validate_range(data, len, elem_size) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
if len == 0 { ret .NotFound; }
if key == null { ret .Err(make(.InvalidArgument)); }
var i: usize = 0;
while i < len {
if compare(data + i * elem_size, key, context) == 0 {
ret .Found(i);
}
i = i + 1;
}
ret .NotFound;
}
// data 必须按 compare 升序排列。存在重复值时返回首个相等元素。
fn binary_search(data: *u8, len: usize, elem_size: usize, key: *u8,
context: *u8, compare: fn(*u8, *u8, *u8): i32): SearchResult {
match validate_range(data, len, elem_size) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
if len == 0 { ret .NotFound; }
if key == null { ret .Err(make(.InvalidArgument)); }
var low: usize = 0;
var high = len;
while low < high {
var mid = low + (high - low) / 2;
if compare(data + mid * elem_size, key, context) < 0 {
low = mid + 1;
} else {
high = mid;
}
}
if low < len && compare(data + low * elem_size, key, context) == 0 {
ret .Found(low);
}
ret .NotFound;
}
fn min_size(a: usize, b: usize): usize {
if a < b { ret a; }
ret b;
}
fn merge_run(dst: *u8, src: *u8, elem_size: usize,
begin: usize, middle: usize, end: usize,
context: *u8, compare: fn(*u8, *u8, *u8): i32) {
var left = begin;
var right = middle;
var output = begin;
while left < middle && right < end {
var left_ptr = src + left * elem_size;
var right_ptr = src + right * elem_size;
// 相等时先取左侧元素,由此保证稳定性。
if compare(left_ptr, right_ptr, context) <= 0 {
copy((dst + output * elem_size) as *any, left_ptr as *any, elem_size);
left = left + 1;
} else {
copy((dst + output * elem_size) as *any, right_ptr as *any, elem_size);
right = right + 1;
}
output = output + 1;
}
while left < middle {
copy((dst + output * elem_size) as *any,
(src + left * elem_size) as *any, elem_size);
left = left + 1;
output = output + 1;
}
while right < end {
copy((dst + output * elem_size) as *any,
(src + right * elem_size) as *any, elem_size);
right = right + 1;
output = output + 1;
}
}
// 自底向上的稳定归并排序。分配失败及参数错误不改变输入。
fn stable_sort(data: *u8, len: usize, elem_size: usize,
context: *u8, compare: fn(*u8, *u8, *u8): i32): Status {
var bytes: usize = 0;
match validate_range(data, len, elem_size) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
match checked_mul(len, elem_size) {
.Ok(value) => { bytes = value; },
.Err(error) => { ret .Err(error); },
}
if len < 2 { ret .Ok; }
var scratch = alloc(bytes) as *u8;
if scratch == null { ret .Err(make(.Overflow)); }
defer { free(scratch as *any); }
var source = data;
var destination = scratch;
var width: usize = 1;
while width < len {
var begin: usize = 0;
while begin < len {
var left_width = min_size(width, len - begin);
var middle = begin + left_width;
var remaining = len - middle;
var right_width = min_size(width, remaining);
var end = middle + right_width;
merge_run(destination, source, elem_size, begin, middle, end,
context, compare);
begin = end;
}
var swap = source;
source = destination;
destination = swap;
if width > len / 2 {
width = len;
} else {
width = width * 2;
}
}
if source != data {
copy(data as *any, source as *any, bytes);
}
ret .Ok;
}
// ---- 测试 ----
type Item = struct {
key: i32,
sequence: i32,
}
fn compare_i32(a: *u8, b: *u8, context: *u8): i32 {
var av = (a as *i32).*;
var bv = (b as *i32).*;
if av < bv { ret -1; }
if av > bv { ret 1; }
ret 0;
}
fn compare_item(a: *u8, b: *u8, context: *u8): i32 {
var av = (a as *Item).*.key;
var bv = (b as *Item).*.key;
var direction: i32 = 1;
if context != null { direction = (context as *i32).*; }
if av < bv { ret -direction; }
if av > bv { ret direction; }
ret 0;
}
fn expect_ok(result: Status) {
match result {
.Ok => {},
.Err(error) => { @assert(false, "unexpected algorithm error"); },
}
}
fn expect_found(result: SearchResult, expected: usize) {
match result {
.Found(index) => { @assert(index == expected, "search index"); },
.NotFound => { @assert(false, "expected search match"); },
.Err(error) => { @assert(false, "unexpected search error"); },
}
}
fn expect_missing(result: SearchResult) {
match result {
.Found(index) => { @assert(false, "unexpected search match"); },
.NotFound => {},
.Err(error) => { @assert(false, "unexpected search error"); },
}
}
fn expect_search_error(result: SearchResult, overflow: bool) {
match result {
.Found(index) => { @assert(false, "expected search error"); },
.NotFound => { @assert(false, "expected search error"); },
.Err(error) => {
match error.kind {
.Overflow => { @assert(overflow); },
.InvalidArgument => { @assert(!overflow); },
_ => { @assert(false, "unexpected search error kind"); },
}
},
}
}
fn test_search() {
var values: [8]i32 = .[1, 2, 2, 2, 5, 8, 13, 21];
var two: i32 = 2;
var seven: i32 = 7;
expect_found(linear_search(&values[0] as *u8, 8, @sizeof(i32),
&two as *u8, null, compare_i32), 1);
expect_found(binary_search(&values[0] as *u8, 8, @sizeof(i32),
&two as *u8, null, compare_i32), 1);
expect_missing(linear_search(&values[0] as *u8, 8, @sizeof(i32),
&seven as *u8, null, compare_i32));
expect_missing(binary_search(&values[0] as *u8, 8, @sizeof(i32),
&seven as *u8, null, compare_i32));
expect_missing(linear_search(null, 0, @sizeof(i32), null, null, compare_i32));
expect_missing(binary_search(null, 0, @sizeof(i32), null, null, compare_i32));
expect_search_error(linear_search(&values[0] as *u8, 8, 0,
&two as *u8, null, compare_i32), false);
expect_search_error(binary_search(null, 1, @sizeof(i32),
&two as *u8, null, compare_i32), false);
var huge = (~(0 as usize)) / @sizeof(i32) + 1;
expect_search_error(linear_search(&values[0] as *u8, huge, @sizeof(i32),
&two as *u8, null, compare_i32), true);
}
fn test_stable_sort() {
var items: [8]Item = .[
.{ .key = 3, .sequence = 0 },
.{ .key = 1, .sequence = 1 },
.{ .key = 2, .sequence = 2 },
.{ .key = 1, .sequence = 3 },
.{ .key = 3, .sequence = 4 },
.{ .key = 2, .sequence = 5 },
.{ .key = 1, .sequence = 6 },
.{ .key = 2, .sequence = 7 },
];
var ascending: i32 = 1;
expect_ok(stable_sort(&items[0] as *u8, 8, @sizeof(Item),
&ascending as *u8, compare_item));
var expected_keys: [8]i32 = .[1, 1, 1, 2, 2, 2, 3, 3];
var expected_sequence: [8]i32 = .[1, 3, 6, 2, 5, 7, 0, 4];
var i: usize = 0;
while i < 8 {
@assert(items[i].key == expected_keys[i], "sorted key");
@assert(items[i].sequence == expected_sequence[i], "stable equal order");
i = i + 1;
}
var descending: i32 = -1;
expect_ok(stable_sort(&items[0] as *u8, 8, @sizeof(Item),
&descending as *u8, compare_item));
i = 1;
while i < 8 {
@assert(items[i - 1].key >= items[i].key, "context controls order");
if items[i - 1].key == items[i].key {
@assert(items[i - 1].sequence < items[i].sequence,
"second stable sort preserves group order");
}
i = i + 1;
}
expect_ok(stable_sort(null, 0, @sizeof(Item), null, compare_item));
expect_ok(stable_sort(&items[0] as *u8, 1, @sizeof(Item), null, compare_item));
// 非二次幂长度覆盖末尾不足一个完整归并段的路径。
var odd: [9]i32 = .[9, -1, 4, 4, 0, 8, 2, 7, 3];
var odd_expected: [9]i32 = .[-1, 0, 2, 3, 4, 4, 7, 8, 9];
expect_ok(stable_sort(&odd[0] as *u8, 9, @sizeof(i32), null, compare_i32));
i = 0;
while i < 9 {
@assert(odd[i] == odd_expected[i], "non-power-of-two length");
i = i + 1;
}
}
fn test_sort_failures_preserve_input() {
var values: [3]i32 = .[3, 1, 2];
match stable_sort(&values[0] as *u8, 3, 0, null, compare_i32) {
.Ok => { @assert(false, "zero element size"); },
.Err(error) => {
match error.kind {
.InvalidArgument => {},
_ => { @assert(false); },
}
},
}
@assert(values[0] == 3 && values[1] == 1 && values[2] == 2,
"validation failure preserves input");
var huge = (~(0 as usize)) / @sizeof(i32) + 1;
match stable_sort(&values[0] as *u8, huge, @sizeof(i32), null, compare_i32) {
.Ok => { @assert(false, "size overflow"); },
.Err(error) => {
match error.kind {
.Overflow => {},
_ => { @assert(false); },
}
},
}
@assert(values[0] == 3 && values[1] == 1 && values[2] == 2,
"overflow preserves input");
}
fn main() {
test_search();
test_stable_sort();
test_sort_failures_preserve_input();
}
-900
View File
@@ -1,900 +0,0 @@
// Expect retcode = 0
// 任意精度有符号整数。limbs 为小端 u32;零规范为 len=0 且 negative=false。
// 普通赋值只借用同一存储;深复制使用 clone,所有权转移使用 take。
type .{ .Error, .Status } = @import("error.spl");
type .{ .checked_mul } = @import("memory.spl");
type .{ .digit_char, .digit_value } = @import("ascii.spl");
type .{ .ByteBuffer } = @import("byte_buffer.spl");
type .{ .alloc, .copy, .free } = @import("../sys/mem.spl");
type BigInt = @self;
limbs: *u32,
len: usize,
cap: usize,
negative: bool,
fn zero(): BigInt {
ret BigInt { .limbs = null, .len = 0, .cap = 0, .negative = false };
}
fn valid(self: BigInt): bool {
if self.len > self.cap { ret false; }
if self.cap == 0 {
ret self.limbs == null && self.len == 0 && !self.negative;
}
if self.limbs == null { ret false; }
if self.len == 0 { ret !self.negative; }
ret self.limbs[self.len - 1] != 0;
}
fn is_zero(self: BigInt): bool {
ret self.len == 0;
}
fn is_negative(self: BigInt): bool {
ret self.negative;
}
fn normalize(self: *BigInt) {
while self.len > 0 {
if self.limbs[self.len - 1] != 0 { break; }
self.len = self.len - 1;
}
if self.len == 0 { self.negative = false; }
}
fn reserve(self: *BigInt, need: usize): Status {
if !valid(self.*) { ret .Err(Error.make(.InvalidArgument)); }
if need <= self.cap { ret .Ok; }
var new_cap = self.cap;
if new_cap < 2 { new_cap = 2; }
while new_cap < need {
if new_cap > (~(0 as usize)) / 2 {
new_cap = need;
break;
}
new_cap = new_cap * 2;
}
var byte_count: usize = 0;
match checked_mul(new_cap, @sizeof(u32)) {
.Ok(value) => { byte_count = value; },
.Err(error) => { ret .Err(error); },
}
var fresh = alloc(byte_count) as *u32;
if fresh == null {
ret .Err(Error.make(.Overflow));
}
if self.len != 0 {
copy(fresh as *any, self.limbs as *any, self.len * @sizeof(u32));
}
free(self.limbs as *any);
self.limbs = fresh;
self.cap = new_cap;
ret .Ok;
}
fn drop(self: *BigInt) {
free(self.limbs as *any);
self.* = zero();
}
fn take(source: *BigInt): BigInt {
var result = source.*;
source.* = zero();
ret result;
}
fn replace(out: *BigInt, fresh: *BigInt) {
drop(out);
out.* = take(fresh);
}
fn clone(out: *BigInt, source: BigInt): Status {
if !valid(out.*) || !valid(source) { ret .Err(Error.make(.InvalidArgument)); }
var fresh = zero();
match reserve(&fresh, source.len) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
if source.len != 0 {
copy(fresh.limbs as *any, source.limbs as *any, source.len * @sizeof(u32));
}
fresh.len = source.len;
fresh.negative = source.negative;
replace(out, &fresh);
ret .Ok;
}
fn from_u64(out: *BigInt, value: u64): Status {
if !valid(out.*) { ret .Err(Error.make(.InvalidArgument)); }
var fresh = zero();
if value != 0 {
match reserve(&fresh, 2) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
fresh.limbs[0] = value as u32;
fresh.limbs[1] = (value >> 32) as u32;
fresh.len = 1;
if fresh.limbs[1] != 0 { fresh.len = 2; }
}
replace(out, &fresh);
ret .Ok;
}
fn from_i64(out: *BigInt, value: i64): Status {
var magnitude = value as u64;
var negative = value < 0;
if negative { magnitude = 0 - magnitude; }
match from_u64(out, magnitude) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
if out.len != 0 { out.negative = negative; }
ret .Ok;
}
fn cmp_abs(a: BigInt, b: BigInt): i32 {
if a.len < b.len { ret -1; }
if a.len > b.len { ret 1; }
var i = a.len;
while i > 0 {
i = i - 1;
if a.limbs[i] < b.limbs[i] { ret -1; }
if a.limbs[i] > b.limbs[i] { ret 1; }
}
ret 0;
}
fn cmp(a: BigInt, b: BigInt): i32 {
if a.negative != b.negative {
if a.negative { ret -1; }
ret 1;
}
var order = cmp_abs(a, b);
if a.negative { ret 0 - order; }
ret order;
}
fn eq(a: BigInt, b: BigInt): bool {
if a.negative != b.negative || a.len != b.len { ret false; }
var i: usize = 0;
while i < a.len {
if a.limbs[i] != b.limbs[i] { ret false; }
i = i + 1;
}
ret true;
}
fn add_abs_into(out: *BigInt, a: BigInt, b: BigInt): Status {
var count = a.len;
if b.len > count { count = b.len; }
if count == ~(0 as usize) { ret .Err(Error.make(.Overflow)); }
match reserve(out, count + 1) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
var carry: u64 = 0;
var i: usize = 0;
while i < count {
var av: u64 = 0;
var bv: u64 = 0;
if i < a.len { av = a.limbs[i] as u64; }
if i < b.len { bv = b.limbs[i] as u64; }
var sum = av + bv + carry;
out.limbs[i] = sum as u32;
carry = sum >> 32;
i = i + 1;
}
if carry != 0 {
out.limbs[count] = carry as u32;
count = count + 1;
}
out.len = count;
out.negative = false;
normalize(out);
ret .Ok;
}
// 要求 |a| >= |b|。
fn sub_abs_into(out: *BigInt, a: BigInt, b: BigInt): Status {
match reserve(out, a.len) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
var borrow: u64 = 0;
var base: u64 = 4294967296; // 2^32
var i: usize = 0;
while i < a.len {
var av = a.limbs[i] as u64;
var bv: u64 = 0;
if i < b.len { bv = b.limbs[i] as u64; }
var subtrahend = bv + borrow;
if av < subtrahend {
out.limbs[i] = (base + av - subtrahend) as u32;
borrow = 1;
} else {
out.limbs[i] = (av - subtrahend) as u32;
borrow = 0;
}
i = i + 1;
}
out.len = a.len;
out.negative = false;
normalize(out);
ret .Ok;
}
fn add(out: *BigInt, a: BigInt, b: BigInt): Status {
if !valid(out.*) || !valid(a) || !valid(b) {
ret .Err(Error.make(.InvalidArgument));
}
var fresh = zero();
if a.negative == b.negative {
match add_abs_into(&fresh, a, b) {
.Ok => {}, .Err(error) => { drop(&fresh); ret .Err(error); },
}
if fresh.len != 0 { fresh.negative = a.negative; }
} else {
var order = cmp_abs(a, b);
if order >= 0 {
match sub_abs_into(&fresh, a, b) {
.Ok => {}, .Err(error) => { drop(&fresh); ret .Err(error); },
}
if fresh.len != 0 { fresh.negative = a.negative; }
} else {
match sub_abs_into(&fresh, b, a) {
.Ok => {}, .Err(error) => { drop(&fresh); ret .Err(error); },
}
if fresh.len != 0 { fresh.negative = b.negative; }
}
}
replace(out, &fresh);
ret .Ok;
}
fn sub(out: *BigInt, a: BigInt, b: BigInt): Status {
var neg_b = b;
if neg_b.len != 0 { neg_b.negative = !neg_b.negative; }
ret add(out, a, neg_b);
}
fn neg(out: *BigInt, source: BigInt): Status {
match clone(out, source) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
if out.len != 0 { out.negative = !out.negative; }
ret .Ok;
}
fn abs(out: *BigInt, source: BigInt): Status {
match clone(out, source) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
out.negative = false;
ret .Ok;
}
fn mul(out: *BigInt, a: BigInt, b: BigInt): Status {
if !valid(out.*) || !valid(a) || !valid(b) {
ret .Err(Error.make(.InvalidArgument));
}
var fresh = zero();
if a.len == 0 || b.len == 0 {
replace(out, &fresh);
ret .Ok;
}
if b.len > (~(0 as usize)) - a.len {
ret .Err(Error.make(.Overflow));
}
var count = a.len + b.len;
match reserve(&fresh, count) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
var z: usize = 0;
while z < count { fresh.limbs[z] = 0; z = z + 1; }
var i: usize = 0;
while i < a.len {
var carry: u64 = 0;
var j: usize = 0;
while j < b.len {
var at = i + j;
var product = (a.limbs[i] as u64) * (b.limbs[j] as u64) +
(fresh.limbs[at] as u64) + carry;
fresh.limbs[at] = product as u32;
carry = product >> 32;
j = j + 1;
}
fresh.limbs[i + b.len] = carry as u32;
i = i + 1;
}
fresh.len = count;
fresh.negative = a.negative != b.negative;
normalize(&fresh);
replace(out, &fresh);
ret .Ok;
}
fn bit_length(out: *usize, self: BigInt): Status {
if out == null || !valid(self) { ret .Err(Error.make(.InvalidArgument)); }
if self.len == 0 { out.* = 0; ret .Ok; }
if self.len - 1 > (~(0 as usize)) / 32 { ret .Err(Error.make(.Overflow)); }
var top = self.limbs[self.len - 1];
var bits = (self.len - 1) * 32;
while top != 0 {
bits = bits + 1;
top = top >> 1;
}
out.* = bits;
ret .Ok;
}
fn mul_pow2(out: *BigInt, source: BigInt, amount: usize): Status {
if !valid(out.*) || !valid(source) { ret .Err(Error.make(.InvalidArgument)); }
if source.len == 0 { ret from_u64(out, 0); }
var words = amount / 32;
var shift = amount % 32;
if source.len == ~(0 as usize) || words > (~(0 as usize)) - source.len - 1 {
ret .Err(Error.make(.Overflow));
}
var fresh = zero();
var count = source.len + words + 1;
match reserve(&fresh, count) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
var i: usize = 0;
while i < words { fresh.limbs[i] = 0; i = i + 1; }
var carry: u64 = 0;
i = 0;
while i < source.len {
var value = ((source.limbs[i] as u64) << shift as u32) | carry;
fresh.limbs[i + words] = value as u32;
carry = value >> 32;
i = i + 1;
}
fresh.len = source.len + words;
if carry != 0 {
fresh.limbs[fresh.len] = carry as u32;
fresh.len = fresh.len + 1;
}
fresh.negative = source.negative;
replace(out, &fresh);
ret .Ok;
}
// 按绝对值右移,等价于向零除以 2^amount。
fn div_pow2(out: *BigInt, source: BigInt, amount: usize): Status {
if !valid(out.*) || !valid(source) { ret .Err(Error.make(.InvalidArgument)); }
var words = amount / 32;
var shift = amount % 32;
if words >= source.len { ret from_u64(out, 0); }
var count = source.len - words;
var fresh = zero();
match reserve(&fresh, count) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
var i: usize = 0;
while i < count {
var src = i + words;
var value = (source.limbs[src] as u64) >> shift as u32;
if shift != 0 && src + 1 < source.len {
value = value | ((source.limbs[src + 1] << (32 - shift) as u32) as u64);
}
fresh.limbs[i] = value as u32;
i = i + 1;
}
fresh.len = count;
fresh.negative = source.negative;
normalize(&fresh);
replace(out, &fresh);
ret .Ok;
}
fn shift_one(self: *BigInt) {
var carry: u64 = 0;
var i: usize = 0;
while i < self.len {
var value = ((self.limbs[i] as u64) << 1) | carry;
self.limbs[i] = value as u32;
carry = value >> 32;
i = i + 1;
}
if carry != 0 {
self.limbs[self.len] = carry as u32;
self.len = self.len + 1;
}
}
fn sub_abs_in_place(a: *BigInt, b: BigInt) {
var borrow: u64 = 0;
var base: u64 = 4294967296; // 2^32
var i: usize = 0;
while i < a.len {
var av = a.limbs[i] as u64;
var bv: u64 = 0;
if i < b.len { bv = b.limbs[i] as u64; }
var subtrahend = bv + borrow;
if av < subtrahend {
a.limbs[i] = (base + av - subtrahend) as u32;
borrow = 1;
} else {
a.limbs[i] = (av - subtrahend) as u32;
borrow = 0;
}
i = i + 1;
}
normalize(a);
}
fn div_rem(quotient: *BigInt, remainder: *BigInt, dividend: BigInt,
divisor: BigInt): Status {
if (quotient as usize) == (remainder as usize) {
ret .Err(Error.make(.InvalidArgument));
}
if !valid(quotient.*) || !valid(remainder.*) || !valid(dividend) || !valid(divisor) {
ret .Err(Error.make(.InvalidArgument));
}
if divisor.len == 0 { ret .Err(Error.make(.DivisionByZero)); }
var q = zero();
var r = zero();
match reserve(&q, dividend.len) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
if divisor.len == ~(0 as usize) { drop(&q); ret .Err(Error.make(.Overflow)); }
match reserve(&r, divisor.len + 1) {
.Ok => {}, .Err(error) => { drop(&q); ret .Err(error); },
}
var i: usize = 0;
while i < dividend.len { q.limbs[i] = 0; i = i + 1; }
q.len = dividend.len;
var bit: usize = 0;
match bit_length(&bit, dividend) {
.Ok => {},
.Err(error) => { drop(&q); drop(&r); ret .Err(error); },
}
while bit > 0 {
bit = bit - 1;
shift_one(&r);
var word = bit / 32;
var offset = bit % 32;
var incoming = (dividend.limbs[word] >> offset as u32) & 1;
if incoming != 0 {
if r.len == 0 { r.len = 1; r.limbs[0] = 0; }
r.limbs[0] = r.limbs[0] | 1;
}
if cmp_abs(r, divisor) >= 0 {
sub_abs_in_place(&r, divisor);
q.limbs[word] = q.limbs[word] | ((1 as u32) << offset as u32);
}
}
q.negative = q.len != 0 && dividend.negative != divisor.negative;
r.negative = r.len != 0 && dividend.negative;
normalize(&q);
normalize(&r);
replace(quotient, &q);
replace(remainder, &r);
ret .Ok;
}
fn mul_small(self: *BigInt, value: u32): Status {
if self.len == 0 || value == 1 { ret .Ok; }
if value == 0 { self.len = 0; self.negative = false; ret .Ok; }
if self.len == ~(0 as usize) { ret .Err(Error.make(.Overflow)); }
match reserve(self, self.len + 1) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
var carry: u64 = 0;
var i: usize = 0;
while i < self.len {
var product = (self.limbs[i] as u64) * (value as u64) + carry;
self.limbs[i] = product as u32;
carry = product >> 32;
i = i + 1;
}
if carry != 0 {
self.limbs[self.len] = carry as u32;
self.len = self.len + 1;
}
ret .Ok;
}
fn add_small(self: *BigInt, value: u32): Status {
if value == 0 { ret .Ok; }
if self.len == ~(0 as usize) { ret .Err(Error.make(.Overflow)); }
match reserve(self, self.len + 1) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
var carry = value as u64;
var i: usize = 0;
while carry != 0 && i < self.len {
var sum = (self.limbs[i] as u64) + carry;
self.limbs[i] = sum as u32;
carry = sum >> 32;
i = i + 1;
}
if carry != 0 {
self.limbs[self.len] = carry as u32;
self.len = self.len + 1;
}
ret .Ok;
}
fn parse(out: *BigInt, bytes: []u8, radix: u8): Status {
if !valid(out.*) { ret .Err(Error.make(.InvalidArgument)); }
if radix < 2 || radix > 36 { ret .Err(Error.make(.InvalidArgument)); }
if bytes.len == 0 { ret .Err(Error.at(.UnexpectedEnd, 0)); }
if bytes.ptr == null { ret .Err(Error.make(.InvalidArgument)); }
var i: usize = 0;
var negative = false;
if bytes.ptr[0] == '-' || bytes.ptr[0] == '+' {
negative = bytes.ptr[0] == '-';
i = 1;
}
if i == bytes.len { ret .Err(Error.at(.UnexpectedEnd, i)); }
var fresh = zero();
while i < bytes.len {
var digit: u8 = 0;
match digit_value(bytes.ptr[i], radix) {
.Ok(value) => { digit = value; },
.Err(error) => { drop(&fresh); ret .Err(Error.at(.InvalidDigit, i)); },
}
match mul_small(&fresh, radix as u32) {
.Ok => {}, .Err(error) => { drop(&fresh); ret .Err(error); },
}
match add_small(&fresh, digit as u32) {
.Ok => {}, .Err(error) => { drop(&fresh); ret .Err(error); },
}
i = i + 1;
}
fresh.negative = fresh.len != 0 && negative;
replace(out, &fresh);
ret .Ok;
}
fn div_small(self: *BigInt, radix: u32): u32 {
var remainder: u64 = 0;
var i = self.len;
while i > 0 {
i = i - 1;
var current = (remainder << 32) | (self.limbs[i] as u64);
self.limbs[i] = (current / radix as u64) as u32;
remainder = current % radix as u64;
}
normalize(self);
ret remainder as u32;
}
fn format(out: *ByteBuffer, source: BigInt, radix: u8, uppercase: bool): Status {
if !valid(source) { ret .Err(Error.make(.InvalidArgument)); }
if radix < 2 || radix > 36 { ret .Err(Error.make(.InvalidArgument)); }
var reverse = ByteBuffer.new();
var fresh = ByteBuffer.new();
var magnitude = zero();
match clone(&magnitude, source) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
magnitude.negative = false;
if magnitude.len == 0 {
match ByteBuffer.append_byte(&reverse, '0') {
.Ok => {}, .Err(error) => { drop(&magnitude); ret .Err(error); },
}
}
while magnitude.len != 0 {
var remainder = div_small(&magnitude, radix as u32);
var byte: u8 = 0;
match digit_char(remainder as u8, uppercase) {
.Ok(value) => { byte = value; },
.Err(error) => { drop(&magnitude); ByteBuffer.drop(&reverse); ret .Err(error); },
}
match ByteBuffer.append_byte(&reverse, byte) {
.Ok => {},
.Err(error) => {
drop(&magnitude); ByteBuffer.drop(&reverse); ret .Err(error);
},
}
}
if source.negative {
match ByteBuffer.append_byte(&fresh, '-') {
.Ok => {},
.Err(error) => {
drop(&magnitude); ByteBuffer.drop(&reverse); ret .Err(error);
},
}
}
var i = ByteBuffer.size(reverse);
var digits = ByteBuffer.as_slice(reverse);
while i > 0 {
i = i - 1;
match ByteBuffer.append_byte(&fresh, digits.ptr[i]) {
.Ok => {},
.Err(error) => {
drop(&magnitude); ByteBuffer.drop(&reverse); ByteBuffer.drop(&fresh);
ret .Err(error);
},
}
}
drop(&magnitude);
ByteBuffer.drop(&reverse);
ByteBuffer.drop(out);
out.* = ByteBuffer.take(&fresh);
ret .Ok;
}
fn to_u64(out: *u64, source: BigInt): Status {
if out == null || !valid(source) { ret .Err(Error.make(.InvalidArgument)); }
if source.negative || source.len > 2 { ret .Err(Error.make(.Overflow)); }
var value: u64 = 0;
if source.len > 0 { value = source.limbs[0] as u64; }
if source.len > 1 { value = value | ((source.limbs[1] as u64) << 32); }
out.* = value;
ret .Ok;
}
fn to_i64(out: *i64, source: BigInt): Status {
if out == null || !valid(source) { ret .Err(Error.make(.InvalidArgument)); }
if source.len > 2 { ret .Err(Error.make(.Overflow)); }
var magnitude: u64 = 0;
if source.len > 0 { magnitude = source.limbs[0] as u64; }
if source.len > 1 { magnitude = magnitude | ((source.limbs[1] as u64) << 32); }
var max_positive: u64 = 9223372036854775807; // i64::MAX
var min_magnitude: u64 = 9223372036854775808; // |i64::MIN|
if !source.negative {
if magnitude > max_positive { ret .Err(Error.make(.Overflow)); }
out.* = magnitude as i64;
ret .Ok;
}
if magnitude > min_magnitude { ret .Err(Error.make(.Overflow)); }
if magnitude == min_magnitude {
out.* = magnitude as i64;
} else {
out.* = 0 - magnitude as i64;
}
ret .Ok;
}
fn to_usize(out: *usize, source: BigInt): Status {
if out == null { ret .Err(Error.make(.InvalidArgument)); }
var value: u64 = 0;
match to_u64(&value, source) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
if value > (~(0 as usize)) as u64 { ret .Err(Error.make(.Overflow)); }
out.* = value as usize;
ret .Ok;
}
fn expect_ok(result: Status) {
match result { .Ok => {}, .Err(error) => { @assert(false, "unexpected BigInt error"); }, }
}
fn expect_text_radix(value: BigInt, radix: u8, uppercase: bool, expected: []u8) {
var buffer = ByteBuffer.new();
expect_ok(format(&buffer, value, radix, uppercase));
var actual = ByteBuffer.as_slice(buffer);
@assert(actual.len == expected.len, "formatted length");
var i: usize = 0;
while i < expected.len {
@assert(actual.ptr[i] == expected.ptr[i], "formatted byte");
i = i + 1;
}
ByteBuffer.drop(&buffer);
}
fn expect_text(value: BigInt, expected: []u8) {
expect_text_radix(value, 10, false, expected);
}
fn parsed(text: []u8): BigInt {
var value = zero();
expect_ok(parse(&value, text, 10));
ret value;
}
fn test_arithmetic() {
var a = parsed("4294967295");
defer { drop(&a); }
var one = zero();
defer { drop(&one); }
expect_ok(from_u64(&one, 1));
expect_ok(add(&a, a, one));
expect_text(a, "4294967296");
var all_ones = zero();
defer { drop(&all_ones); }
expect_ok(parse(&all_ones, "ffffffffffffffffffffffffffffffff", 16));
expect_ok(add(&all_ones, all_ones, one));
expect_text(all_ones, "340282366920938463463374607431768211456");
expect_ok(sub(&all_ones, all_ones, one));
expect_text_radix(all_ones, 16, false, "ffffffffffffffffffffffffffffffff");
var two_64 = parsed("18446744073709551616");
defer { drop(&two_64); }
var product = zero();
defer { drop(&product); }
expect_ok(mul(&product, two_64, two_64));
expect_text(product, "340282366920938463463374607431768211456");
var difference = zero();
defer { drop(&difference); }
expect_ok(sub(&difference, product, two_64));
expect_text(difference, "340282366920938463444927863358058659840");
var shifted = zero();
defer { drop(&shifted); }
expect_ok(mul_pow2(&shifted, one, 257));
var bits: usize = 0;
expect_ok(bit_length(&bits, shifted));
@assert(bits == 258);
expect_ok(div_pow2(&shifted, shifted, 257));
@assert(eq(shifted, one));
}
fn test_division_and_sign() {
var dividend = parsed("340282366920938463463374607431768211456");
defer { drop(&dividend); }
var divisor = parsed("18446744073709551616");
defer { drop(&divisor); }
var quotient = zero();
defer { drop(&quotient); }
var remainder = zero();
defer { drop(&remainder); }
expect_ok(div_rem(&quotient, &remainder, dividend, divisor));
@assert(eq(quotient, divisor));
@assert(is_zero(remainder));
var long_dividend = parsed("123456789012345678901234567890");
defer { drop(&long_dividend); }
var long_divisor = parsed("9876543210");
defer { drop(&long_divisor); }
expect_ok(div_rem(&quotient, &remainder, long_dividend, long_divisor));
expect_text(quotient, "12499999887343749990");
expect_text(remainder, "1562499990");
var negative = zero();
defer { drop(&negative); }
var seven = zero();
defer { drop(&seven); }
expect_ok(from_i64(&negative, -100));
expect_ok(from_u64(&seven, 7));
expect_ok(div_rem(&negative, &remainder, negative, seven));
expect_text(negative, "-14");
expect_text(remainder, "-2");
var reconstructed = zero();
defer { drop(&reconstructed); }
expect_ok(mul(&reconstructed, negative, seven));
expect_ok(add(&reconstructed, reconstructed, remainder));
expect_text(reconstructed, "-100");
var saved = parsed("123");
defer { drop(&saved); }
var zero_divisor = zero();
defer { drop(&zero_divisor); }
match div_rem(&saved, &remainder, dividend, zero_divisor) {
.Ok => { @assert(false, "division by zero"); },
.Err(error) => { match error.kind { .DivisionByZero => {}, _ => { @assert(false); }, } },
}
expect_text(saved, "123");
}
fn test_parse_format_and_conversion() {
var value = zero();
defer { drop(&value); }
expect_ok(parse(&value, "-deadBEEF", 16));
var output = ByteBuffer.new();
defer { ByteBuffer.drop(&output); }
expect_ok(format(&output, value, 16, false));
var text = ByteBuffer.as_slice(output);
@assert(text.len == 9 && text.ptr[0] == '-' && text.ptr[1] == 'd' && text.ptr[8] == 'f');
expect_text_radix(value, 16, true, "-DEADBEEF");
var base36 = zero();
defer { drop(&base36); }
expect_ok(parse(&base36, "zzzzzzzzzz", 36));
expect_text_radix(base36, 36, false, "zzzzzzzzzz");
var preserved = parsed("77");
defer { drop(&preserved); }
match parse(&preserved, "12x", 10) {
.Ok => { @assert(false, "invalid digit"); },
.Err(error) => {
@assert(error.has_position && error.position == 2);
match error.kind { .InvalidDigit => {}, _ => { @assert(false); }, }
},
}
expect_text(preserved, "77");
var max = parsed("18446744073709551615");
defer { drop(&max); }
var converted: u64 = 0;
expect_ok(to_u64(&converted, max));
@assert(converted == 18446744073709551615);
var over = parsed("18446744073709551616");
defer { drop(&over); }
converted = 17;
match to_u64(&converted, over) {
.Ok => { @assert(false); },
.Err(error) => { match error.kind { .Overflow => {}, _ => { @assert(false); }, } },
}
@assert(converted == 17, "failed conversion preserves output");
var minimum = parsed("-9223372036854775808");
defer { drop(&minimum); }
var signed_value: i64 = 0;
expect_ok(to_i64(&signed_value, minimum));
@assert(signed_value == (9223372036854775808 as u64) as i64);
var signed_over = parsed("9223372036854775808");
defer { drop(&signed_over); }
signed_value = 23;
match to_i64(&signed_value, signed_over) {
.Ok => { @assert(false); },
.Err(error) => { match error.kind { .Overflow => {}, _ => { @assert(false); }, } },
}
@assert(signed_value == 23, "failed signed conversion preserves output");
var copied = zero();
defer { drop(&copied); }
expect_ok(clone(&copied, max));
var one_more = parsed("1");
defer { drop(&one_more); }
expect_ok(add(&max, max, one_more));
expect_text(copied, "18446744073709551615");
var moved = take(&copied);
defer { drop(&moved); }
@assert(copied.limbs == null && copied.len == 0 && copied.cap == 0);
expect_text(moved, "18446744073709551615");
}
fn expect_i64_value(value: BigInt, expected: i64) {
var actual: i64 = 0;
expect_ok(to_i64(&actual, value));
@assert(actual == expected, "small integer reference result");
}
fn test_small_reference() {
var left = zero();
defer { drop(&left); }
var right = zero();
defer { drop(&right); }
var result = zero();
defer { drop(&result); }
var remainder = zero();
defer { drop(&remainder); }
var a: i64 = 0;
while a <= 0 {
var b: i64 = 2;
while b <= 2 {
expect_ok(from_i64(&left, a));
expect_ok(from_i64(&right, b));
expect_ok(add(&result, left, right));
expect_i64_value(result, a + b);
expect_ok(sub(&result, left, right));
expect_i64_value(result, a - b);
expect_ok(mul(&result, left, right));
expect_i64_value(result, a * b);
if b != 0 {
expect_ok(div_rem(&result, &remainder, left, right));
expect_i64_value(result, a / b);
expect_i64_value(remainder, a % b);
}
b = b + 1;
}
a = a + 1;
}
}
fn main() {
test_arithmetic();
test_division_and_sign();
test_parse_format_and_conversion();
test_small_reference();
}
+71 -38
View File
@@ -1,11 +1,11 @@
// Expect retcode = 0
// 数值/布尔格式化,写入 ByteBuffer。radix 取值 2..36;uppercase 只影响 10..35 的字母形式。
// 不分配额外内存(除目标 ByteBuffer 自身扩容),失败不产生部分输出。
// 整数转换统一复用 MPA。先在临时缓冲区完成转换,再一次性追加,失败不产生部分输出。
type .{ .Error, .Status, .make } = @import("error.spl");
type .{ .ByteBuffer } = @import("byte_buffer.spl");
type .{ .digit_char } = @import("ascii.spl");
type .{ .BigInt, .Bits } = @import("mp.spl");
// 注意:radix 边界直接用字面量。stage1 刻意不做常量求值/折叠,
// 顶层 const 不应用于运行时表达式。
@@ -13,53 +13,47 @@ fn valid_radix(radix: u8): bool {
ret radix >= 2 && radix <= 36;
}
fn append_u64(out: *ByteBuffer, value: u64, radix: u8, uppercase: bool): Status {
fn append_big_int(out: *ByteBuffer, value: BigInt,
radix: u8, uppercase: bool): Status {
if !valid_radix(radix) { ret .Err(make(.InvalidArgument)); }
var digits: [64]u8 = undefined;
var count: usize = 0;
if value == 0 {
digits[0] = '0';
count = 1;
} else {
var v = value;
var base = radix as u64;
while v != 0 {
var d = (v % base) as u8;
match digit_char(d, uppercase) {
.Ok(c) => { digits[count] = c; },
.Err(error) => { ret .Err(error); },
}
count = count + 1;
v = v / base;
}
}
// digits 是低位在前,反向写入得到正常顺序。
var i = count;
while i > 0 {
i = i - 1;
match ByteBuffer.append_byte(out, digits[i]) {
var text = ByteBuffer.new();
defer { ByteBuffer.drop(&text); }
match BigInt.format(&text, value, radix, uppercase) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
ret ByteBuffer.append(out, ByteBuffer.as_slice(text));
}
ret .Ok;
fn append_bits(out: *ByteBuffer, value: Bits, signed: bool,
radix: u8, uppercase: bool): Status {
var integer = BigInt.zero();
defer { BigInt.drop(&integer); }
match Bits.to_big(&integer, value, signed) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
ret append_big_int(out, integer, radix, uppercase);
}
fn append_u64(out: *ByteBuffer, value: u64, radix: u8, uppercase: bool): Status {
var integer = BigInt.zero();
defer { BigInt.drop(&integer); }
match BigInt.from_u64(&integer, value) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
ret append_big_int(out, integer, radix, uppercase);
}
fn append_i64(out: *ByteBuffer, value: i64, radix: u8, uppercase: bool): Status {
if !valid_radix(radix) { ret .Err(make(.InvalidArgument)); }
if value < 0 {
match ByteBuffer.append_byte(out, '-') {
var integer = BigInt.zero();
defer { BigInt.drop(&integer); }
match BigInt.from_i64(&integer, value) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
// value 为补码,(0 - (value as u64)) 得到绝对值(含 i64 最小值)。
var magnitude = 0 - (value as u64);
ret append_u64(out, magnitude, radix, uppercase);
}
ret append_u64(out, value as u64, radix, uppercase);
ret append_big_int(out, integer, radix, uppercase);
}
fn append_usize(out: *ByteBuffer, value: usize, radix: u8, uppercase: bool): Status {
@@ -201,6 +195,44 @@ fn test_usize_bool_and_errors() {
expect_invalid(append_u64(&b, 1, 37, false));
expect_invalid(append_i64(&b, -1, 0, false));
@assert(ByteBuffer.size(b) == 0, "invalid radix writes nothing");
expect_ok(ByteBuffer.append(&b, "prefix"));
expect_invalid(append_i64(&b, -123, 1, false));
expect_text(&b, "prefix");
}
fn test_mpa_integer_formatting() {
var b = ByteBuffer.new();
defer { ByteBuffer.drop(&b); }
var huge = BigInt.zero();
defer { BigInt.drop(&huge); }
expect_ok(BigInt.parse(&huge, "1234567890123456789012345678901234567890", 10));
expect_ok(append_big_int(&b, huge, 10, false));
expect_text(&b, "1234567890123456789012345678901234567890");
ByteBuffer.clear(&b);
expect_ok(BigInt.parse(&huge, "-ffffffffffffffffffffffffffffffff", 16));
expect_ok(append_big_int(&b, huge, 16, true));
expect_text(&b, "-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
ByteBuffer.clear(&b);
var raw = BigInt.zero();
defer { BigInt.drop(&raw); }
expect_ok(BigInt.from_u64(&raw, 255));
var bits: Bits = undefined;
match Bits.new(8) {
.Ok(value) => { bits = value; },
.Err(error) => { @assert(false, "Bits construction"); },
}
defer { Bits.drop(&bits); }
expect_ok(Bits.from_big_checked(&bits, raw, 8, false));
expect_ok(append_bits(&b, bits, false, 10, false));
expect_text(&b, "255");
ByteBuffer.clear(&b);
expect_ok(append_bits(&b, bits, true, 10, false));
expect_text(&b, "-1");
}
fn test_composition_and_growth() {
@@ -234,5 +266,6 @@ fn main() {
test_unsigned();
test_signed();
test_usize_bool_and_errors();
test_mpa_integer_formatting();
test_composition_and_growth();
}
+129 -38
View File
@@ -2,14 +2,16 @@
// I/O 便利层(构建在 sys/file 之上):
// - C 字符串路径/模式的 open 包装
// - 整块读写(write_all / read_all)
// - 精确读取、完整写入和普通文件整读
// - stdout/stderr 打印
// 说明:read_all 会按文件大小一次性读入 ByteBuffer;不处理增长中的文件。
// 短读、短写后的零进展及关闭失败返回 Err(.Io);文件写入不承诺回滚。
type .{ .Error, .Status, .make } = @import("error.spl");
type .{ .ByteView } = @import("byte_view.spl");
type .{ .ByteBuffer } = @import("byte_buffer.spl");
type File = @import("../sys/file.spl");
type Mem = @import("../sys/mem.spl");
// 把 view 复制到以 NUL 结尾的临时缓冲,返回其 data(归属 tmp,调用后勿再用)。
fn cstr(tmp: *ByteBuffer, view: ByteView): *u8 {
@@ -32,59 +34,99 @@ fn open_cstr(path: ByteView, mode: ByteView): File {
var mbuf = ByteBuffer.new();
var p = cstr(&pbuf, path);
var m = cstr(&mbuf, mode);
var f = File.open(p, m);
var f = File { .handle = null };
if p != null && m != null { f = File.open(p, m); }
ByteBuffer.drop(&pbuf);
ByteBuffer.drop(&mbuf);
ret f;
}
// 反复写入直到写完;返回实际写入字节数(!= bytes.len 表示中途失败)。
fn write_all(f: *File, bytes: []u8): u64 {
var total: u64 = 0;
var want = bytes.len as u64;
while total < want {
var offset = total as usize;
var w = File.write(f, bytes.ptr + offset, want - total);
if w == 0 { break; }
total = total + w;
// 反复写入直到完成。发生错误时文件可能已经包含前缀,不承诺回滚。
fn write_all(f: *File, bytes: []u8): Status {
if f == null || !File.valid(f) { ret .Err(make(.Io)); }
if bytes.len != 0 && bytes.ptr == null { ret .Err(make(.InvalidArgument)); }
var total: usize = 0;
while total < bytes.len {
var written = File.write(f, bytes.ptr + total, (bytes.len - total) as u64);
if written == 0 || written > (bytes.len - total) as u64 {
ret .Err(make(.Io));
}
ret total;
total = total + written as usize;
}
ret .Ok;
}
fn print(bytes: []u8) {
fn print(bytes: []u8): Status {
var out = File.stdout();
write_all(&out, bytes);
ret write_all(&out, bytes);
}
fn println(bytes: []u8) {
print(bytes);
print("\n");
fn println(bytes: []u8): Status {
match print(bytes) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
ret print("\n");
}
fn eprint(bytes: []u8) {
fn eprint(bytes: []u8): Status {
var err = File.stderr();
write_all(&err, bytes);
ret write_all(&err, bytes);
}
// 读取整个文件到 out(先清空)。打开失败或不完整返回 Err(.Io)。
// 仅供已经隔离的临时存储使用;失败时可能写入目标前缀。
fn read_fully(f: *File, data: *u8, len: usize): Status {
var total: usize = 0;
while total < len {
var got = File.read(f, data + total, len - total);
if got == 0 || got > len - total { ret .Err(make(.Io)); }
total = total + got;
}
ret .Ok;
}
// 精确读取 bytes.len 字节。短读返回 Err(.Io),失败保持 bytes 原内容不变。
fn read_exact(f: *File, bytes: []u8): Status {
if f == null || !File.valid(f) { ret .Err(make(.Io)); }
if bytes.len == 0 { ret .Ok; }
if bytes.ptr == null { ret .Err(make(.InvalidArgument)); }
var temporary = ByteBuffer.new();
defer { ByteBuffer.drop(&temporary); }
match ByteBuffer.reserve(&temporary, bytes.len) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
match read_fully(f, temporary.storage.data, bytes.len) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
Mem.copy(bytes.ptr as *any, temporary.storage.data as *any, bytes.len);
ret .Ok;
}
// 读取整个普通文件到 out。打开、短读或关闭失败时保持 out 不变。
fn read_all(path: ByteView, out: *ByteBuffer): Status {
if out == null { ret .Err(make(.InvalidArgument)); }
var f = open_cstr(path, ByteView.from_slice("rb"));
if !File.valid(&f) { ret .Err(make(.Io)); }
var sz = File.size(&f);
ByteBuffer.clear(out);
var fresh = ByteBuffer.new();
if sz != 0 {
match ByteBuffer.reserve(out, sz as usize) {
match ByteBuffer.reserve(&fresh, sz) {
.Ok => {},
.Err(error) => { File.close(&f); ret .Err(error); },
.Err(error) => { File.close(&f); ByteBuffer.drop(&fresh); ret .Err(error); },
}
var got = File.read(&f, out.storage.data, sz);
out.storage.len = got as usize;
File.close(&f);
if got != sz { ret .Err(make(.Io)); }
ret .Ok;
match read_fully(&f, fresh.storage.data, sz) {
.Ok => {},
.Err(error) => { File.close(&f); ByteBuffer.drop(&fresh); ret .Err(error); },
}
File.close(&f);
fresh.storage.len = sz;
}
if File.close(&f) != 0 { ByteBuffer.drop(&fresh); ret .Err(make(.Io)); }
ByteBuffer.drop(out);
out.* = ByteBuffer.take(&fresh);
ret .Ok;
}
@@ -94,14 +136,32 @@ fn view(text: []u8): ByteView {
ret ByteView.from_slice(text);
}
fn expect_ok(result: Status) {
match result {
.Ok => {},
.Err(error) => { @assert(false, "unexpected io error"); },
}
}
fn expect_io(result: Status) {
match result {
.Ok => { @assert(false, "expected io error"); },
.Err(error) => {
match error.kind {
.Io => {},
_ => { @assert(false, "expected Io kind"); },
}
},
}
}
fn test_roundtrip() {
var path = view("build/_io_roundtrip.tmp");
var f = open_cstr(path, view("wb"));
@assert(File.valid(&f), "open wb");
var w = write_all(&f, "hello\nworld");
@assert(w == 11, "write_all length");
File.close(&f);
expect_ok(write_all(&f, "hello\nworld"));
@assert(File.close(&f) == 0, "close writer");
var out = ByteBuffer.new();
match read_all(path, &out) {
@@ -119,14 +179,13 @@ fn test_append_and_missing() {
var f = open_cstr(path, view("wb"));
@assert(File.valid(&f), "open wb");
write_all(&f, "ab");
File.close(&f);
expect_ok(write_all(&f, "ab"));
@assert(File.close(&f) == 0, "close initial writer");
f = open_cstr(path, view("ab"));
@assert(File.valid(&f), "open ab");
var w2 = write_all(&f, "cd");
File.close(&f);
@assert(w2 == 2, "append length");
expect_ok(write_all(&f, "cd"));
@assert(File.close(&f) == 0, "close append writer");
var out = ByteBuffer.new();
match read_all(path, &out) {
@@ -143,11 +202,43 @@ fn test_append_and_missing() {
match error.kind { .Io => {}, _ => { @assert(false, "error kind"); } }
},
}
got = ByteBuffer.as_slice(out);
@assert(got.len == 4 && got.ptr[0] == 'a' && got.ptr[3] == 'd',
"failed read_all preserves output");
ByteBuffer.drop(&out);
}
fn test_read_exact_and_errors() {
var path = view("build/_io_exact.tmp");
var f = open_cstr(path, view("wb"));
@assert(File.valid(&f));
expect_ok(write_all(&f, "xyz"));
@assert(File.close(&f) == 0);
f = open_cstr(path, view("rb"));
@assert(File.valid(&f));
var too_long: [4]u8 = .[9, 8, 7, 6];
expect_io(read_exact(&f, too_long[0..4]));
@assert(too_long[0] == 9 && too_long[1] == 8 &&
too_long[2] == 7 && too_long[3] == 6,
"short read preserves destination");
@assert(File.close(&f) == 0);
f = open_cstr(path, view("rb"));
@assert(File.valid(&f));
var exact: [3]u8 = .[0, 0, 0];
expect_ok(read_exact(&f, exact[0..3]));
@assert(exact[0] == 'x' && exact[1] == 'y' && exact[2] == 'z');
@assert(File.close(&f) == 0);
var invalid = File { .handle = null };
expect_io(write_all(&invalid, "x"));
expect_io(read_exact(&invalid, exact[0..3]));
}
fn main() {
test_roundtrip();
test_append_and_missing();
println("io: ok");
test_read_exact_and_errors();
expect_ok(println("io: ok"));
}
+15 -3
View File
@@ -116,24 +116,36 @@ fn find_slot(self: *Map, key: *u8): usize {
var i = self.hash_fn(key) & mask;
var empty: SlotState = .Empty;
var occupied: SlotState = .Occupied;
while self.states.data[i] != state_code(empty) {
var probes: usize = 0;
while probes < self.states.cap && self.states.data[i] != state_code(empty) {
if self.states.data[i] == state_code(occupied) &&
self.eq_fn(self.keys.data + i * self.key_size, key) {
ret i;
}
i = (i + 1) & mask;
probes = probes + 1;
}
ret ~(0 as usize);
}
// 要求容量非零且键不存在,直接写入第一个空槽(重哈希专用)。
// 要求容量非零且键不存在。优先复用探测链中的第一个墓碑。
fn place(self: *Map, key: *u8, val: *u8) {
var mask = self.states.cap - 1;
var i = self.hash_fn(key) & mask;
var empty: SlotState = .Empty;
var deleted: SlotState = .Deleted;
var occupied: SlotState = .Occupied;
while self.states.data[i] != state_code(empty) {
var first_deleted = ~(0 as usize);
var probes: usize = 0;
while probes < self.states.cap && self.states.data[i] != state_code(empty) {
if self.states.data[i] == state_code(deleted) && first_deleted == ~(0 as usize) {
first_deleted = i;
}
i = (i + 1) & mask;
probes = probes + 1;
}
if first_deleted != ~(0 as usize) {
i = first_deleted;
}
copy((self.keys.data + i * self.key_size) as *any, key as *any, self.key_size);
copy((self.vals.data + i * self.val_size) as *any, val as *any, self.val_size);
+1643
View File
File diff suppressed because it is too large. Load diff
+287 -154
View File
@@ -1,267 +1,400 @@
// Expect retcode = 0
// 路径工具(不访问文件系统):同时接受 '/' 与 '\\';支持 Windows 盘符 "C:"。
// 输入为借用 ByteView,输出写入调用方的 ByteBuffer。
// 注意:normalize 对相对路径的前导 ".." 采取丢弃(不保留),适用于导入路径场景。
type .{ .Error, .Status, .make } = @import("error.spl");
// 纯词法路径工具,不访问文件系统。调用方必须显式选择 POSIX 或 Windows 规则;
// 输入视图均为借用,拥有型结果写入 ByteBuffer,失败保持原输出不变。
type .{ .Status, .make } = @import("error.spl");
type .{ .ByteView } = @import("byte_view.spl");
type .{ .ByteBuffer } = @import("byte_buffer.spl");
type .{ .Vec } = @import("vec.spl");
type .{ .is_alpha } = @import("ascii.spl");
fn is_sep(c: u8): bool {
ret c == '/' || c == '\\'; // '/' '\\'
type PathStyle = enum { Posix, Windows }
fn is_windows(style: PathStyle): bool {
match style { .Posix => { ret false; }, .Windows => { ret true; }, }
ret false;
}
fn is_drive(path: ByteView): bool {
ret path.len >= 2 && is_alpha(path.ptr[0]) && path.ptr[1] == ':'; // 盘符首字符必须是字母
fn separator(style: PathStyle): u8 {
if is_windows(style) { ret '\\'; }
ret '/';
}
fn is_absolute(path: ByteView): bool {
fn is_sep(style: PathStyle, byte: u8): bool {
if is_windows(style) { ret byte == '/' || byte == '\\'; }
ret byte == '/';
}
fn is_drive_prefix(path: ByteView): bool {
ret path.len >= 2 && is_alpha(path.ptr[0]) && path.ptr[1] == ':';
}
fn is_unc(path: ByteView, style: PathStyle): bool {
ret is_windows(style) && path.len >= 2 &&
is_sep(style, path.ptr[0]) && is_sep(style, path.ptr[1]);
}
fn is_absolute(path: ByteView, style: PathStyle): bool {
if path.len == 0 { ret false; }
if is_sep(path.ptr[0]) { ret true; }
ret is_drive(path);
if !is_windows(style) { ret path.ptr[0] == '/'; }
if is_unc(path, style) { ret true; }
if is_drive_prefix(path) {
ret path.len >= 3 && is_sep(style, path.ptr[2]);
}
// 根相对路径以当前盘符为根,本模块将其视为绝对。
ret is_sep(style, path.ptr[0]);
}
// 最后一个分隔符下标;无返回 ~0。
fn last_sep(path: ByteView): usize {
// UNC 根长度不含共享名后的分隔符;不完整时返回 0。
fn unc_root_len(path: ByteView, style: PathStyle): usize {
if !is_unc(path, style) { ret 0; }
var i: usize = 2;
while i < path.len && is_sep(style, path.ptr[i]) { i = i + 1; }
var server = i;
while i < path.len && !is_sep(style, path.ptr[i]) { i = i + 1; }
if i == server { ret 0; }
while i < path.len && is_sep(style, path.ptr[i]) { i = i + 1; }
var share = i;
while i < path.len && !is_sep(style, path.ptr[i]) { i = i + 1; }
if i == share { ret 0; }
ret i;
}
fn root_len(path: ByteView, style: PathStyle): usize {
if path.len == 0 { ret 0; }
if !is_windows(style) {
if path.ptr[0] == '/' { ret 1; }
ret 0;
}
var unc = unc_root_len(path, style);
if unc != 0 { ret unc; }
if is_drive_prefix(path) {
if path.len >= 3 && is_sep(style, path.ptr[2]) { ret 3; }
ret 2;
}
if is_sep(style, path.ptr[0]) { ret 1; }
ret 0;
}
fn last_sep(path: ByteView, style: PathStyle): usize {
var i = path.len;
while i > 0 {
i = i - 1;
if is_sep(path.ptr[i]) { ret i; }
if is_sep(style, path.ptr[i]) { ret i; }
}
ret ~(0 as usize);
}
// 目录部分(不含末尾分隔符);根 "/" 返回 "/";无分隔符返回空。
// 下标均由 last_sep 保证在界内,slice 不会失败;Err 分支仅作防御。
fn dirname(path: ByteView): ByteView {
var idx = last_sep(path);
if idx == ~(0 as usize) { ret ByteView.empty(); }
if idx == 0 {
var r = ByteView.slice(path, 0, 1);
match r {
.Ok(v) => { ret v; },
.Err(error) => { ret ByteView.empty(); }, // 防御:索引已界内
}
}
var r = ByteView.slice(path, 0, idx);
match r {
.Ok(v) => { ret v; },
.Err(error) => { ret ByteView.empty(); }, // 防御:索引已界内
fn slice_or_empty(path: ByteView, start: usize, end: usize): ByteView {
match ByteView.slice(path, start, end) {
.Ok(value) => { ret value; },
.Err(error) => { ret ByteView.empty(); },
}
ret ByteView.empty();
}
fn basename(path: ByteView): ByteView {
var idx = last_sep(path);
if idx == ~(0 as usize) { ret path; }
var r = ByteView.slice(path, idx + 1, path.len);
match r {
.Ok(v) => { ret v; },
.Err(error) => { ret ByteView.empty(); }, // 防御:idx+1 <= len
fn dirname(path: ByteView, style: PathStyle): ByteView {
var root = root_len(path, style);
if root != 0 && path.len == root { ret path; }
var index = last_sep(path, style);
if index == ~(0 as usize) {
if is_windows(style) && is_drive_prefix(path) {
ret slice_or_empty(path, 0, 2);
}
ret ByteView.empty();
}
if root != 0 && index <= root { ret slice_or_empty(path, 0, root); }
if index == 0 { ret slice_or_empty(path, 0, 1); }
ret slice_or_empty(path, 0, index);
}
// 扩展名(不含 '.');无扩展名返回空。
fn extension(path: ByteView): ByteView {
var base = basename(path);
fn basename(path: ByteView, style: PathStyle): ByteView {
var root = root_len(path, style);
if root != 0 && path.len == root { ret ByteView.empty(); }
var index = last_sep(path, style);
if index == ~(0 as usize) {
if is_windows(style) && is_drive_prefix(path) {
ret slice_or_empty(path, 2, path.len);
}
ret path;
}
ret slice_or_empty(path, index + 1, path.len);
}
fn extension(path: ByteView, style: PathStyle): ByteView {
var base = basename(path, style);
var i = base.len;
while i > 0 {
i = i - 1;
if base.ptr[i] == '.' { // '.'
if base.ptr[i] == '.' {
if i == 0 { ret ByteView.empty(); }
var r = ByteView.slice(base, i + 1, base.len);
match r {
.Ok(v) => { ret v; },
.Err(error) => { ret ByteView.empty(); }, // 防御:i+1 <= base.len
}
ret slice_or_empty(base, i + 1, base.len);
}
}
ret ByteView.empty();
}
// out = dir + sep + rel(rel 绝对时直接使用 rel);成功后才替换 out,失败保持 out 不变。
fn join_into(out: *ByteBuffer, dir: ByteView, rel: ByteView): Status {
// Windows 盘符相对路径依赖进程的盘符工作目录,join 明确拒绝该歧义。
fn join_into(out: *ByteBuffer, dir: ByteView,
rel: ByteView, style: PathStyle): Status {
if out == null { ret .Err(make(.InvalidArgument)); }
var fresh = ByteBuffer.new();
if is_absolute(rel) {
if is_absolute(rel, style) {
match ByteBuffer.append(&fresh, ByteView.as_slice(rel)) {
.Ok => {},
.Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); },
.Ok => {}, .Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); },
}
ByteBuffer.drop(out);
out.* = ByteBuffer.take(&fresh);
ret .Ok;
}
if is_windows(style) && is_drive_prefix(rel) {
ByteBuffer.drop(&fresh);
ret .Err(make(.InvalidArgument));
}
match ByteBuffer.append(&fresh, ByteView.as_slice(dir)) {
.Ok => {},
.Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); },
.Ok => {}, .Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); },
}
if dir.len != 0 && !is_sep(dir.ptr[dir.len - 1]) {
match ByteBuffer.append_byte(&fresh, '/') {
.Ok => {},
.Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); },
var drive_relative_base = is_windows(style) && dir.len == 2 && is_drive_prefix(dir);
if dir.len != 0 && !is_sep(style, dir.ptr[dir.len - 1]) && rel.len != 0 &&
!drive_relative_base {
match ByteBuffer.append_byte(&fresh, separator(style)) {
.Ok => {}, .Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); },
}
}
match ByteBuffer.append(&fresh, ByteView.as_slice(rel)) {
.Ok => {}, .Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); },
}
ByteBuffer.drop(out);
out.* = ByteBuffer.take(&fresh);
ret .Ok;
}
fn segment_is_parent(buffer: ByteBuffer, rollback: usize, style: PathStyle): bool {
var start = rollback;
if start < buffer.storage.len && is_sep(style, buffer.storage.data[start]) {
start = start + 1;
}
ret buffer.storage.len - start == 2 &&
buffer.storage.data[start] == '.' && buffer.storage.data[start + 1] == '.';
}
fn append_segment(out: *ByteBuffer, starts: *Vec,
segment: ByteView, style: PathStyle): Status {
var rollback = out.storage.len;
if rollback != 0 && !is_sep(style, out.storage.data[rollback - 1]) &&
!(is_windows(style) && out.storage.data[rollback - 1] == ':') {
match ByteBuffer.append_byte(out, separator(style)) {
.Ok => {}, .Err(error) => { ret .Err(error); },
}
}
match Vec.push(starts, &rollback as *u8) {
.Ok => {},
.Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); },
.Err(error) => { out.storage.len = rollback; ret .Err(error); },
}
match ByteBuffer.append(out, ByteView.as_slice(segment)) {
.Ok => {},
.Err(error) => {
starts.len = starts.len - 1;
out.storage.len = rollback;
ret .Err(error);
},
}
ByteBuffer.drop(out);
out.* = ByteBuffer.take(&fresh);
ret .Ok;
}
// 折叠重复分隔符、'.',及'..'(回退上一段;相对路径前导 '..' 丢弃)。
// 成功后才替换 out,失败保持 out 不变。
fn normalize_into(out: *ByteBuffer, path: ByteView): Status {
// 规范化分隔符并折叠 '.'/'..'。相对路径无法消去的前导 '..' 会被保留;
// 绝对路径的 '..' 不会越过根,UNC 的 server/share 也不可被消去。
fn normalize_into(out: *ByteBuffer, path: ByteView, style: PathStyle): Status {
if out == null { ret .Err(make(.InvalidArgument)); }
var fresh = ByteBuffer.new();
if path.len == 0 {
ByteBuffer.drop(out);
out.* = ByteBuffer.take(&fresh);
ret .Ok;
}
var starts = Vec.blank(@sizeof(usize), 8);
var starts = Vec.blank(@sizeof(usize), @alignof(usize));
var i: usize = 0;
var absolute = false;
var protected: usize = 0;
if is_sep(path.ptr[0]) {
if path.len != 0 {
if !is_windows(style) && path.ptr[0] == '/' {
absolute = true;
match ByteBuffer.append_byte(&fresh, '/') {
.Ok => {}, .Err(e) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(e); },
.Ok => {}, .Err(error) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(error); },
}
} else if is_drive(path) {
match ByteBuffer.append_byte(&fresh, path.ptr[0]) {
.Ok => {}, .Err(e) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(e); },
while i < path.len && is_sep(style, path.ptr[i]) { i = i + 1; }
} else if is_unc(path, style) {
absolute = true;
protected = 2;
match ByteBuffer.append(&fresh, "\\\\") {
.Ok => {}, .Err(error) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(error); },
}
match ByteBuffer.append_byte(&fresh, ':') {
.Ok => {}, .Err(e) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(e); },
i = 2;
while i < path.len && is_sep(style, path.ptr[i]) { i = i + 1; }
} else if is_windows(style) && is_drive_prefix(path) {
match ByteBuffer.append(&fresh, path.ptr[0..2]) {
.Ok => {}, .Err(error) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(error); },
}
i = 2;
if i < path.len && is_sep(style, path.ptr[i]) {
absolute = true;
match ByteBuffer.append_byte(&fresh, '\\') {
.Ok => {}, .Err(error) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(error); },
}
while i < path.len && is_sep(style, path.ptr[i]) { i = i + 1; }
}
} else if is_windows(style) && is_sep(style, path.ptr[0]) {
absolute = true;
match ByteBuffer.append_byte(&fresh, '\\') {
.Ok => {}, .Err(error) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(error); },
}
while i < path.len && is_sep(style, path.ptr[i]) { i = i + 1; }
}
i = 2; // 跳过盘符
}
while i < path.len {
while i < path.len && is_sep(path.ptr[i]) { i = i + 1; }
while i < path.len && is_sep(style, path.ptr[i]) { i = i + 1; }
if i >= path.len { break; }
var start = i;
while i < path.len && !is_sep(path.ptr[i]) { i = i + 1; }
var begin = i;
while i < path.len && !is_sep(style, path.ptr[i]) { i = i + 1; }
var segment = slice_or_empty(path, begin, i);
var seg_result = ByteView.slice(path, start, i);
var seg = ByteView.empty();
match seg_result {
.Ok(v) => { seg = v; },
.Err(error) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(error); },
}
if ByteView.eq(seg, ByteView.from_slice(".")) { continue; }
if ByteView.eq(seg, ByteView.from_slice("..")) {
if starts.len > 0 {
var idx = starts.len - 1;
match Vec.get(&starts, idx) {
.Ok(p) => { fresh.storage.len = (p as *usize).*; },
.Err(error) => {}, // starts.len 已保证 idx 在界内,get 不会失败
}
starts.len = starts.len - 1;
if ByteView.eq(segment, ByteView.from_slice(".")) {
if protected != 0 && starts.len < protected {
Vec.drop(&starts); ByteBuffer.drop(&fresh);
ret .Err(make(.InvalidArgument));
}
continue;
}
if ByteView.eq(segment, ByteView.from_slice("..")) {
if protected != 0 && starts.len < protected {
Vec.drop(&starts); ByteBuffer.drop(&fresh);
ret .Err(make(.InvalidArgument));
}
if starts.len > protected {
var last: usize = 0;
match Vec.get(&starts, starts.len - 1) {
.Ok(ptr) => { last = (ptr as *usize).*; },
.Err(error) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(error); },
}
if !segment_is_parent(fresh, last, style) {
fresh.storage.len = last;
starts.len = starts.len - 1;
continue;
}
}
if absolute { continue; }
}
var comp_start = fresh.storage.len;
if comp_start > 0 && !is_sep(fresh.storage.data[comp_start - 1]) {
match ByteBuffer.append_byte(&fresh, '/') {
.Ok => {}, .Err(e) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(e); },
}
}
var recorded = comp_start;
match Vec.push(&starts, &recorded as *u8) {
.Ok => {},
.Err(error) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(error); },
}
match ByteBuffer.append(&fresh, ByteView.as_slice(seg)) {
.Ok => {},
.Err(error) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(error); },
match append_segment(&fresh, &starts, segment, style) {
.Ok => {}, .Err(error) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(error); },
}
}
if protected != 0 && starts.len < protected {
Vec.drop(&starts); ByteBuffer.drop(&fresh);
ret .Err(make(.InvalidArgument));
}
Vec.drop(&starts);
ByteBuffer.drop(out);
out.* = ByteBuffer.take(&fresh);
ret .Ok;
}
// ---- 测试辅助 ----
// ---- 测试 ----
fn expect_ok(result: Status) {
match result { .Ok => {}, .Err(error) => { @assert(false, "unexpected path error"); }, }
}
fn expect_invalid(result: Status) {
match result {
.Ok => {},
.Err(error) => { @assert(false, "unexpected path error"); },
.Ok => { @assert(false, "expected invalid path"); },
.Err(error) => {
match error.kind { .InvalidArgument => {}, _ => { @assert(false); }, }
},
}
}
fn view(text: []u8): ByteView {
ret ByteView.from_slice(text);
}
fn view(text: []u8): ByteView { ret ByteView.from_slice(text); }
fn expect_view(got: ByteView, expected: []u8) {
@assert(ByteView.eq(got, view(expected)), "view value");
@assert(ByteView.eq(got, view(expected)), "path view");
}
fn expect_out(out: *ByteBuffer, expected: []u8) {
var actual = ByteBuffer.as_slice(out.*);
@assert(actual.len == expected.len, "out length");
@assert(actual.len == expected.len, "path output length");
var i: usize = 0;
while i < expected.len {
@assert(actual.ptr[i] == expected.ptr[i], "out byte");
@assert(actual.ptr[i] == expected.ptr[i], "path output byte");
i = i + 1;
}
}
fn test_parts() {
@assert(is_sep('/') && is_sep('\\'), "seps");
@assert(is_absolute(view("/a/b")), "posix abs");
@assert(is_absolute(view("\\a")), "win abs");
@assert(is_absolute(view("C:/a")), "drive abs");
@assert(!is_absolute(view("a/b")), "relative");
expect_view(dirname(view("/a/b/c")), "/a/b");
expect_view(dirname(view("/a")), "/");
expect_view(dirname(view("abc")), "");
expect_view(basename(view("/a/b/c")), "c");
expect_view(basename(view("abc")), "abc");
expect_view(extension(view("a/b.spl")), "spl");
expect_view(extension(view("a/b")), "");
expect_view(extension(view("a/.hidden")), "");
fn test_style_and_parts() {
@assert(is_absolute(view("/a"), .Posix));
@assert(!is_absolute(view("\\a"), .Posix));
@assert(is_absolute(view("C:\\a"), .Windows));
@assert(!is_absolute(view("C:a"), .Windows));
@assert(is_absolute(view("\\\\server\\share\\a"), .Windows));
@assert(!is_absolute(view("C:/a"), .Posix));
expect_view(dirname(view("/a/b"), .Posix), "/a");
expect_view(dirname(view("/a"), .Posix), "/");
expect_view(basename(view("/a/b"), .Posix), "b");
expect_view(extension(view("a/b.spl"), .Posix), "spl");
expect_view(extension(view("a/.hidden"), .Posix), "");
expect_view(dirname(view("C:\\a"), .Windows), "C:\\");
expect_view(basename(view("C:\\a"), .Windows), "a");
expect_view(dirname(view("C:relative"), .Windows), "C:");
expect_view(basename(view("C:relative"), .Windows), "relative");
expect_view(dirname(view("\\\\server\\share\\file"), .Windows),
"\\\\server\\share");
}
fn test_join() {
var out = ByteBuffer.new();
defer { ByteBuffer.drop(&out); }
expect_ok(join_into(&out, view("stage2/utils"), view("vec.spl")));
expect_ok(join_into(&out, view("stage2/utils"), view("vec.spl"), .Posix));
expect_out(&out, "stage2/utils/vec.spl");
expect_ok(join_into(&out, view("a/"), view("b.spl")));
expect_out(&out, "a/b.spl");
expect_ok(join_into(&out, view("ignored"), view("/abs/x")));
expect_out(&out, "/abs/x");
expect_ok(join_into(&out, view("C:\\base"), view("child\\x"), .Windows));
expect_out(&out, "C:\\base\\child\\x");
expect_ok(join_into(&out, view("C:"), view("child"), .Windows));
expect_out(&out, "C:child");
expect_ok(join_into(&out, view("ignored"), view("D:/abs"), .Windows));
expect_out(&out, "D:/abs");
expect_invalid(join_into(&out, view("C:\\base"), view("D:relative"), .Windows));
expect_out(&out, "D:/abs");
}
fn test_normalize() {
fn test_normalize_posix() {
var out = ByteBuffer.new();
defer { ByteBuffer.drop(&out); }
expect_ok(normalize_into(&out, view("a/b/../c")));
expect_ok(normalize_into(&out, view("a/b/../c"), .Posix));
expect_out(&out, "a/c");
expect_ok(normalize_into(&out, view("./a//b")));
expect_out(&out, "a/b");
expect_ok(normalize_into(&out, view("/a/./b/../c")));
expect_out(&out, "/a/c");
expect_ok(normalize_into(&out, view("C:/a/../b")));
expect_out(&out, "C:/b");
expect_ok(normalize_into(&out, view("a/..")));
expect_out(&out, "");
expect_ok(normalize_into(&out, view("../../a/./b"), .Posix));
expect_out(&out, "../../a/b");
expect_ok(normalize_into(&out, view("/../../a//b"), .Posix));
expect_out(&out, "/a/b");
expect_ok(normalize_into(&out, view("a\\b/../c"), .Posix));
expect_out(&out, "c");
}
fn test_normalize_windows() {
var out = ByteBuffer.new();
defer { ByteBuffer.drop(&out); }
expect_ok(normalize_into(&out, view("C:/a\\b/../c"), .Windows));
expect_out(&out, "C:\\a\\c");
expect_ok(normalize_into(&out, view("C:foo\\..\\bar"), .Windows));
expect_out(&out, "C:bar");
expect_ok(normalize_into(&out, view("\\root\\..\\file"), .Windows));
expect_out(&out, "\\file");
expect_ok(normalize_into(&out,
view("\\\\server\\share\\a\\..\\file"), .Windows));
expect_out(&out, "\\\\server\\share\\file");
expect_ok(ByteBuffer.assign(&out, "preserved"));
expect_invalid(normalize_into(&out, view("\\\\server\\..\\file"), .Windows));
expect_out(&out, "preserved");
}
fn main() {
test_parts();
test_style_and_parts();
test_join();
test_normalize();
test_normalize_posix();
test_normalize_windows();
}
+235
View File
@@ -0,0 +1,235 @@
// Expect retcode = 0
// 开放寻址集合。键按字节复制到集合自己的存储中;集合只释放自身存储,
// 不释放键值内部引用的资源。哈希与相等回调的约定和 Map 一致。
type .{ .Status } = @import("error.spl");
type .{ .Map, .LookupResult } = @import("map.spl");
type .{ .ByteView } = @import("byte_view.spl");
type .{ .hash_usize, .eq_usize, .hash_view, .eq_view } = @import("hash.spl");
type Set = @self;
map: Map,
fn new(key_size: usize, key_align: usize,
hash_fn: fn(*u8): usize, eq_fn: fn(*u8, *u8): bool): Set {
ret Set {
.map = Map.new(key_size, key_align, 1, 1, hash_fn, eq_fn),
};
}
fn valid(self: Set): bool {
ret Map.valid(self.map) && self.map.val_size == 1;
}
fn size(self: Set): usize {
ret self.map.size;
}
fn capacity(self: Set): usize {
ret self.map.states.cap;
}
fn reserve(self: *Set, need: usize): Status {
ret Map.reserve(&self.map, need);
}
// 重复插入保持集合大小不变。
fn insert(self: *Set, key: *u8): Status {
var marker: u8 = 1;
ret Map.put(&self.map, key, &marker);
}
fn find(self: *Set, key: *u8): LookupResult {
var marker: u8 = 0;
ret Map.get(&self.map, key, &marker);
}
fn remove(self: *Set, key: *u8): LookupResult {
ret Map.del(&self.map, key);
}
// 遍历 [0, slot_count);仅 slot_occupied 为真时 key_at 有效。
fn slot_count(self: Set): usize {
ret Map.slot_count(self.map);
}
fn slot_occupied(self: Set, index: usize): bool {
ret Map.slot_occupied(Map.slot_state(self.map, index));
}
fn key_at(self: *Set, index: usize): *u8 {
ret Map.key_at(&self.map, index);
}
fn clear(self: *Set) {
Map.clear(&self.map);
}
fn take(source: *Set): Set {
ret Set { .map = Map.take(&source.map) };
}
fn drop(self: *Set) {
Map.drop(&self.map);
}
// ---- 测试 ----
fn expect_ok(result: Status) {
match result {
.Ok => {},
.Err(error) => { @assert(false, "unexpected Set error"); },
}
}
fn found(set: *Set, key: *u8): bool {
match find(set, key) {
.Found => { ret true; },
.NotFound => { ret false; },
}
ret false;
}
fn removed(set: *Set, key: *u8): bool {
match remove(set, key) {
.Found => { ret true; },
.NotFound => { ret false; },
}
ret false;
}
fn constant_hash(key: *u8): usize {
ret 3;
}
fn test_insert_find_remove() {
var set = new(@sizeof(usize), @alignof(usize), hash_usize, eq_usize);
defer { drop(&set); }
@assert(valid(set));
@assert(size(set) == 0 && capacity(set) == 0);
var i: usize = 0;
while i < 1000 {
expect_ok(insert(&set, &i as *u8));
i = i + 1;
}
@assert(size(set) == 1000, "all unique keys inserted");
i = 0;
while i < 1000 {
@assert(found(&set, &i as *u8), "key survives growth");
i = i + 1;
}
// 重复键不增加 size。
i = 0;
while i < 1000 {
expect_ok(insert(&set, &i as *u8));
i = i + 1;
}
@assert(size(set) == 1000, "duplicate insertion is idempotent");
i = 0;
while i < 1000 {
if i % 2 == 0 { @assert(removed(&set, &i as *u8)); }
i = i + 1;
}
@assert(size(set) == 500);
i = 0;
while i < 1000 {
@assert(found(&set, &i as *u8) == (i % 2 != 0), "removed membership");
i = i + 1;
}
i = 0;
while i < 1000 {
if i % 2 == 0 { expect_ok(insert(&set, &i as *u8)); }
i = i + 1;
}
@assert(size(set) == 1000, "removed keys can be reinserted");
var occupied: usize = 0;
i = 0;
while i < slot_count(set) {
if slot_occupied(set, i) {
var key = (key_at(&set, i) as *usize).*;
@assert(key < 1000, "iteration yields stored key");
occupied = occupied + 1;
}
i = i + 1;
}
@assert(occupied == size(set), "iteration visits each member once");
var missing: usize = 2000;
@assert(!removed(&set, &missing as *u8));
clear(&set);
@assert(size(set) == 0 && capacity(set) != 0, "clear retains allocation");
}
fn test_collisions_and_take() {
var set = new(@sizeof(usize), @alignof(usize), constant_hash, eq_usize);
defer { drop(&set); }
var i: usize = 0;
while i < 64 {
expect_ok(insert(&set, &i as *u8));
i = i + 1;
}
i = 0;
while i < 64 {
@assert(found(&set, &i as *u8), "collision chain lookup");
i = i + 1;
}
// 删除整条碰撞链后重新插入,验证墓碑会被复用且探测会终止。
i = 0;
while i < 64 {
@assert(removed(&set, &i as *u8));
i = i + 1;
}
@assert(size(set) == 0);
i = 64;
while i < 128 {
expect_ok(insert(&set, &i as *u8));
i = i + 1;
}
i = 64;
while i < 128 {
@assert(found(&set, &i as *u8), "tombstone collision chain reuse");
i = i + 1;
}
var moved = take(&set);
defer { drop(&moved); }
@assert(size(set) == 0 && capacity(set) == 0, "take clears source");
@assert(size(moved) == 64, "take preserves members");
i = 64;
while i < 128 {
@assert(found(&moved, &i as *u8));
i = i + 1;
}
}
fn test_byte_view_keys() {
var set = new(@sizeof(ByteView), @alignof(ByteView), hash_view, eq_view);
defer { drop(&set); }
var alpha = ByteView.from_slice("alpha");
var alpha_copy = ByteView.from_slice("alpha");
var beta = ByteView.from_slice("beta");
expect_ok(insert(&set, &alpha as *u8));
expect_ok(insert(&set, &alpha_copy as *u8));
expect_ok(insert(&set, &beta as *u8));
@assert(size(set) == 2, "ByteView keys compare by content");
@assert(found(&set, &alpha_copy as *u8));
@assert(removed(&set, &alpha_copy as *u8));
@assert(!found(&set, &alpha as *u8));
}
fn main() {
test_insert_find_remove();
test_collisions_and_take();
test_byte_view_keys();
}