stage2 更多的工具库
This commit is contained in:
1 parent
b3e38ba7e4
commit
c30143609a
8 files changed
+2705
-1132
No files matched your search
@@ -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();
|
||||
}
|
||||
@@ -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(÷nd); }
|
||||
var divisor = parsed("18446744073709551616");
|
||||
defer { drop(&divisor); }
|
||||
var quotient = zero();
|
||||
defer { drop("ient); }
|
||||
var remainder = zero();
|
||||
defer { drop(&remainder); }
|
||||
expect_ok(div_rem("ient, &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("ient, &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
@@ -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));
|
||||
}
|
||||
|
||||
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 .Ok;
|
||||
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
@@ -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
@@ -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
File diff suppressed because it is too large.
Load diff
+286
-153
@@ -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;
|
||||
}
|
||||
match ByteBuffer.append(&fresh, ByteView.as_slice(dir)) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); },
|
||||
if is_windows(style) && is_drive_prefix(rel) {
|
||||
ByteBuffer.drop(&fresh);
|
||||
ret .Err(make(.InvalidArgument));
|
||||
}
|
||||
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); },
|
||||
match ByteBuffer.append(&fresh, ByteView.as_slice(dir)) {
|
||||
.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); },
|
||||
.Ok => {}, .Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); },
|
||||
}
|
||||
ByteBuffer.drop(out);
|
||||
out.* = ByteBuffer.take(&fresh);
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// 折叠重复分隔符、'.',及'..'(回退上一段;相对路径前导 '..' 丢弃)。
|
||||
// 成功后才替换 out,失败保持 out 不变。
|
||||
fn normalize_into(out: *ByteBuffer, path: ByteView): Status {
|
||||
var fresh = ByteBuffer.new();
|
||||
if path.len == 0 {
|
||||
ByteBuffer.drop(out);
|
||||
out.* = ByteBuffer.take(&fresh);
|
||||
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) => { 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);
|
||||
},
|
||||
}
|
||||
ret .Ok;
|
||||
}
|
||||
}
|
||||
|
||||
var starts = Vec.blank(@sizeof(usize), 8);
|
||||
// 规范化分隔符并折叠 '.'/'..'。相对路径无法消去的前导 '..' 会被保留;
|
||||
// 绝对路径的 '..' 不会越过根,UNC 的 server/share 也不可被消去。
|
||||
fn normalize_into(out: *ByteBuffer, path: ByteView, style: PathStyle): Status {
|
||||
if out == null { ret .Err(make(.InvalidArgument)); }
|
||||
var fresh = ByteBuffer.new();
|
||||
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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
Reference in new issue
Block a user