diff --git a/stage2/utils/algorithm.spl b/stage2/utils/algorithm.spl new file mode 100644 index 0000000..d99b194 --- /dev/null +++ b/stage2/utils/algorithm.spl @@ -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(); +} diff --git a/stage2/utils/big_int.spl b/stage2/utils/big_int.spl deleted file mode 100644 index e6d5627..0000000 --- a/stage2/utils/big_int.spl +++ /dev/null @@ -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(); -} diff --git a/stage2/utils/fmt.spl b/stage2/utils/fmt.spl index 6a5e082..33a5250 100644 --- a/stage2/utils/fmt.spl +++ b/stage2/utils/fmt.spl @@ -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; - } + 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)); +} - // digits 是低位在前,反向写入得到正常顺序。 - var i = count; - while i > 0 { - i = i - 1; - match ByteBuffer.append_byte(out, digits[i]) { - .Ok => {}, - .Err(error) => { ret .Err(error); }, - } +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, '-') { - .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); + var integer = BigInt.zero(); + defer { BigInt.drop(&integer); } + match BigInt.from_i64(&integer, value) { + .Ok => {}, + .Err(error) => { ret .Err(error); }, } - 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(); } diff --git a/stage2/utils/io.spl b/stage2/utils/io.spl index d322188..b8ac6a4 100644 --- a/stage2/utils/io.spl +++ b/stage2/utils/io.spl @@ -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)); + } + total = total + written as usize; } - ret total; + 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); }, + } + fresh.storage.len = sz; } - File.close(&f); + 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")); } diff --git a/stage2/utils/map.spl b/stage2/utils/map.spl index 90c165e..eac66f1 100644 --- a/stage2/utils/map.spl +++ b/stage2/utils/map.spl @@ -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); diff --git a/stage2/utils/mp.spl b/stage2/utils/mp.spl new file mode 100644 index 0000000..c73dde7 --- /dev/null +++ b/stage2/utils/mp.spl @@ -0,0 +1,1643 @@ +// Expect retcode = 0 + +// 多精度整数模块:BigInt 表示数学整数,Bits 表示任意正位宽的位串。 +// limbs 为小端 u32;BigInt 的零规范为 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; +} + +// 任意正位宽的位串。value 始终按无符号值规范化,符号解释由具体操作显式指定。 +type Bits = struct { + value: BigInt, + bit_width: usize, + + type Result = enum { + Ok: Bits, + Err: Error, + } + + type Endian = enum { + Little, + Big, + } + + fn limb_count(width: usize): usize { + var count = width / 32; + if width % 32 != 0 { count = count + 1; } + ret count; + } + + fn blank(): Bits { + ret Bits { .value = BigInt.zero(), .bit_width = 0 }; + } + + fn new(width: usize): Bits.Result { + if width == 0 { ret .Err(Error.make(.InvalidArgument)); } + ret .Ok(Bits { .value = BigInt.zero(), .bit_width = width }); + } + + fn valid(self: Bits): bool { + if self.bit_width == 0 || !BigInt.valid(self.value) || self.value.negative { + ret false; + } + var bits: usize = 0; + match BigInt.bit_length(&bits, self.value) { + .Ok => {}, + .Err(error) => { ret false; }, + } + ret bits <= self.bit_width; + } + + fn drop(self: *Bits) { + BigInt.drop(&self.value); + self.bit_width = 0; + } + + fn take(source: *Bits): Bits { + var result = source.*; + source.* = Bits.blank(); + ret result; + } + + fn replace(out: *Bits, fresh: *Bits) { + Bits.drop(out); + out.* = Bits.take(fresh); + } + + fn clone(out: *Bits, source: Bits): Status { + if !Bits.valid(out.*) || !Bits.valid(source) { ret .Err(Error.make(.InvalidArgument)); } + var fresh_r = Bits.new(source.bit_width); + var fresh = Bits.blank(); + match fresh_r { + .Ok(value) => { fresh = value; }, + .Err(error) => { ret .Err(error); }, + } + match BigInt.clone(&fresh.value, source.value) { + .Ok => {}, + .Err(error) => { Bits.drop(&fresh); ret .Err(error); }, + } + Bits.replace(out, &fresh); + ret .Ok; + } + + fn same_width(a: Bits, b: Bits): bool { + ret Bits.valid(a) && Bits.valid(b) && a.bit_width == b.bit_width; + } + + fn sign_bit(self: Bits): bool { + var bit = self.bit_width - 1; + var word = bit / 32; + var offset = bit % 32; + if word >= self.value.len { ret false; } + ret ((self.value.limbs[word] >> offset as u32) & 1) != 0; + } + + fn mask_top(self: *Bits) { + var excess = self.bit_width % 32; + if excess != 0 && self.value.len != 0 { + var top = Bits.limb_count(self.bit_width) - 1; + if top < self.value.len { + var mask = ((1 as u32) << excess as u32) - 1; + self.value.limbs[top] = self.value.limbs[top] & mask; + } + } + if self.value.len > Bits.limb_count(self.bit_width) { + self.value.len = Bits.limb_count(self.bit_width); + } + BigInt.normalize(&self.value); + self.value.negative = false; + } + + // 显式模 2^width 转换,接受任意正负 BigInt。 + fn from_big_truncate(out: *Bits, source: BigInt, width: usize): Status { + if !Bits.valid(out.*) || !BigInt.valid(source) || width == 0 { + ret .Err(Error.make(.InvalidArgument)); + } + var fresh_r = Bits.new(width); + var fresh = Bits.blank(); + match fresh_r { + .Ok(value) => { fresh = value; }, + .Err(error) => { ret .Err(error); }, + } + var count = Bits.limb_count(width); + match BigInt.reserve(&fresh.value, count) { + .Ok => {}, + .Err(error) => { Bits.drop(&fresh); ret .Err(error); }, + } + var i: usize = 0; + while i < count { + var limb: u32 = 0; + if i < source.len { limb = source.limbs[i]; } + if source.negative { limb = ~limb; } + fresh.value.limbs[i] = limb; + i = i + 1; + } + fresh.value.len = count; + if source.negative { + var carry: u64 = 1; + i = 0; + while i < count { + var sum = (fresh.value.limbs[i] as u64) + carry; + fresh.value.limbs[i] = sum as u32; + carry = sum >> 32; + i = i + 1; + } + } + Bits.mask_top(&fresh); + Bits.replace(out, &fresh); + ret .Ok; + } + + fn fits_big(source: BigInt, width: usize, signed: bool): bool { + if width == 0 || !BigInt.valid(source) { ret false; } + var bits: usize = 0; + match BigInt.bit_length(&bits, source) { + .Ok => {}, .Err(error) => { ret false; }, + } + if !signed { ret !source.negative && bits <= width; } + if !source.negative { ret bits < width; } + if bits < width { ret true; } + if bits > width { ret false; } + var power = BigInt.zero(); + var one = BigInt.zero(); + defer { BigInt.drop(&power); } + defer { BigInt.drop(&one); } + match BigInt.from_u64(&one, 1) { .Ok => {}, .Err(error) => { ret false; }, } + match BigInt.mul_pow2(&power, one, width - 1) { + .Ok => {}, .Err(error) => { ret false; }, + } + ret BigInt.cmp_abs(source, power) == 0; + } + + fn from_big_checked(out: *Bits, source: BigInt, width: usize, signed: bool): Status { + if !Bits.valid(out.*) || !BigInt.valid(source) || width == 0 { + ret .Err(Error.make(.InvalidArgument)); + } + if !Bits.fits_big(source, width, signed) { ret .Err(Error.make(.Overflow)); } + ret Bits.from_big_truncate(out, source, width); + } + + fn to_big(out: *BigInt, source: Bits, signed: bool): Status { + if !BigInt.valid(out.*) || !Bits.valid(source) { + ret .Err(Error.make(.InvalidArgument)); + } + if !signed || !Bits.sign_bit(source) { ret BigInt.clone(out, source.value); } + + var modulus = BigInt.zero(); + var one = BigInt.zero(); + defer { BigInt.drop(&modulus); } + defer { BigInt.drop(&one); } + match BigInt.from_u64(&one, 1) { .Ok => {}, .Err(error) => { ret .Err(error); }, } + match BigInt.mul_pow2(&modulus, one, source.bit_width) { + .Ok => {}, .Err(error) => { ret .Err(error); }, + } + ret BigInt.sub(out, source.value, modulus); + } + + fn cmp_unsigned(out: *i32, a: Bits, b: Bits): Status { + if out == null { ret .Err(Error.make(.InvalidArgument)); } + if !Bits.same_width(a, b) { ret .Err(Error.make(.WidthMismatch)); } + out.* = BigInt.cmp_abs(a.value, b.value); + ret .Ok; + } + + fn cmp_signed(out: *i32, a: Bits, b: Bits): Status { + if out == null { ret .Err(Error.make(.InvalidArgument)); } + if !Bits.same_width(a, b) { ret .Err(Error.make(.WidthMismatch)); } + var an = Bits.sign_bit(a); + var bn = Bits.sign_bit(b); + if an != bn { + if an { out.* = -1; } else { out.* = 1; } + } else { + out.* = BigInt.cmp_abs(a.value, b.value); + } + ret .Ok; + } + + fn bitwise(out: *Bits, a: Bits, b: Bits, operation: u8): Status { + if !Bits.valid(out.*) || !Bits.same_width(a, b) || out.bit_width != a.bit_width { + ret .Err(Error.make(.WidthMismatch)); + } + var fresh_r = Bits.new(a.bit_width); + var fresh = Bits.blank(); + match fresh_r { .Ok(value) => { fresh = value; }, .Err(error) => { ret .Err(error); }, } + var count = Bits.limb_count(a.bit_width); + match BigInt.reserve(&fresh.value, count) { + .Ok => {}, .Err(error) => { Bits.drop(&fresh); ret .Err(error); }, + } + var i: usize = 0; + while i < count { + var av: u32 = 0; + var bv: u32 = 0; + if i < a.value.len { av = a.value.limbs[i]; } + if i < b.value.len { bv = b.value.limbs[i]; } + if operation == 0 { fresh.value.limbs[i] = av & bv; } + else if operation == 1 { fresh.value.limbs[i] = av | bv; } + else { fresh.value.limbs[i] = av ^ bv; } + i = i + 1; + } + fresh.value.len = count; + Bits.mask_top(&fresh); + Bits.replace(out, &fresh); + ret .Ok; + } + + fn bit_and(out: *Bits, a: Bits, b: Bits): Status { ret Bits.bitwise(out, a, b, 0); } + fn bit_or(out: *Bits, a: Bits, b: Bits): Status { ret Bits.bitwise(out, a, b, 1); } + fn bit_xor(out: *Bits, a: Bits, b: Bits): Status { ret Bits.bitwise(out, a, b, 2); } + + fn bit_not(out: *Bits, source: Bits): Status { + if !Bits.valid(out.*) || !Bits.valid(source) || out.bit_width != source.bit_width { + ret .Err(Error.make(.WidthMismatch)); + } + var fresh_r = Bits.new(source.bit_width); + var fresh = Bits.blank(); + match fresh_r { + .Ok(value) => { fresh = value; }, .Err(error) => { ret .Err(error); }, + } + var count = Bits.limb_count(source.bit_width); + match BigInt.reserve(&fresh.value, count) { + .Ok => {}, .Err(error) => { Bits.drop(&fresh); ret .Err(error); }, + } + var i: usize = 0; + while i < count { + var limb: u32 = 0; + if i < source.value.len { limb = source.value.limbs[i]; } + fresh.value.limbs[i] = ~limb; + i = i + 1; + } + fresh.value.len = count; + Bits.mask_top(&fresh); + Bits.replace(out, &fresh); + ret .Ok; + } + + fn shift_left(out: *Bits, source: Bits, amount: usize): Status { + if !Bits.valid(out.*) || !Bits.valid(source) { + ret .Err(Error.make(.InvalidArgument)); + } + if out.bit_width != source.bit_width { + ret .Err(Error.make(.WidthMismatch)); + } + if amount >= source.bit_width { ret .Err(Error.make(.OutOfRange)); } + var shifted = BigInt.zero(); + defer { BigInt.drop(&shifted); } + match BigInt.mul_pow2(&shifted, source.value, amount) { + .Ok => {}, .Err(error) => { ret .Err(error); }, + } + ret Bits.from_big_truncate(out, shifted, source.bit_width); + } + + fn shift_right(out: *Bits, source: Bits, amount: usize): Status { + if !Bits.valid(out.*) || !Bits.valid(source) { + ret .Err(Error.make(.InvalidArgument)); + } + if out.bit_width != source.bit_width { + ret .Err(Error.make(.WidthMismatch)); + } + if amount >= source.bit_width { ret .Err(Error.make(.OutOfRange)); } + var shifted = BigInt.zero(); + defer { BigInt.drop(&shifted); } + match BigInt.div_pow2(&shifted, source.value, amount) { + .Ok => {}, .Err(error) => { ret .Err(error); }, + } + ret Bits.from_big_truncate(out, shifted, source.bit_width); + } + + fn shift_right_arithmetic(out: *Bits, source: Bits, amount: usize): Status { + if !Bits.valid(source) { ret .Err(Error.make(.InvalidArgument)); } + var negative = Bits.sign_bit(source); + match Bits.shift_right(out, source, amount) { + .Ok => {}, .Err(error) => { ret .Err(error); }, + } + if !negative || amount == 0 { ret .Ok; } + var bit = source.bit_width - amount; + while bit < source.bit_width { + var word = bit / 32; + var offset = bit % 32; + match BigInt.reserve(&out.value, word + 1) { + .Ok => {}, .Err(error) => { ret .Err(error); }, + } + while out.value.len <= word { + out.value.limbs[out.value.len] = 0; + out.value.len = out.value.len + 1; + } + out.value.limbs[word] = out.value.limbs[word] | ((1 as u32) << offset as u32); + bit = bit + 1; + } + Bits.mask_top(out); + ret .Ok; + } + + fn arithmetic(out: *Bits, a: Bits, b: Bits, signed: bool, operation: u8, + wrapping: bool): Status { + if !Bits.valid(out.*) || !Bits.same_width(a, b) || out.bit_width != a.bit_width { + ret .Err(Error.make(.WidthMismatch)); + } + var av = BigInt.zero(); + var bv = BigInt.zero(); + var result = BigInt.zero(); + defer { BigInt.drop(&result); } + defer { BigInt.drop(&bv); } + defer { BigInt.drop(&av); } + match Bits.to_big(&av, a, signed) { .Ok => {}, .Err(error) => { ret .Err(error); }, } + match Bits.to_big(&bv, b, signed) { .Ok => {}, .Err(error) => { ret .Err(error); }, } + if operation == 0 { + match BigInt.add(&result, av, bv) { .Ok => {}, .Err(error) => { ret .Err(error); }, } + } else if operation == 1 { + match BigInt.sub(&result, av, bv) { .Ok => {}, .Err(error) => { ret .Err(error); }, } + } else { + match BigInt.mul(&result, av, bv) { .Ok => {}, .Err(error) => { ret .Err(error); }, } + } + if wrapping { ret Bits.from_big_truncate(out, result, a.bit_width); } + ret Bits.from_big_checked(out, result, a.bit_width, signed); + } + + fn add_checked(out: *Bits, a: Bits, b: Bits, signed: bool): Status { + ret Bits.arithmetic(out, a, b, signed, 0, false); + } + fn sub_checked(out: *Bits, a: Bits, b: Bits, signed: bool): Status { + ret Bits.arithmetic(out, a, b, signed, 1, false); + } + fn mul_checked(out: *Bits, a: Bits, b: Bits, signed: bool): Status { + ret Bits.arithmetic(out, a, b, signed, 2, false); + } + fn add_wrapping(out: *Bits, a: Bits, b: Bits): Status { + ret Bits.arithmetic(out, a, b, false, 0, true); + } + fn sub_wrapping(out: *Bits, a: Bits, b: Bits): Status { + ret Bits.arithmetic(out, a, b, false, 1, true); + } + fn mul_wrapping(out: *Bits, a: Bits, b: Bits): Status { + ret Bits.arithmetic(out, a, b, false, 2, true); + } + + fn truncate(out: *Bits, source: Bits, width: usize): Status { + if width == 0 || width > source.bit_width { ret .Err(Error.make(.InvalidArgument)); } + if !Bits.valid(out.*) || !Bits.valid(source) { + ret .Err(Error.make(.InvalidArgument)); + } + ret Bits.from_big_truncate(out, source.value, width); + } + + fn zero_extend(out: *Bits, source: Bits, width: usize): Status { + if width < source.bit_width { ret .Err(Error.make(.InvalidArgument)); } + if !Bits.valid(out.*) || !Bits.valid(source) { + ret .Err(Error.make(.InvalidArgument)); + } + ret Bits.from_big_checked(out, source.value, width, false); + } + + fn sign_extend(out: *Bits, source: Bits, width: usize): Status { + if width < source.bit_width { ret .Err(Error.make(.InvalidArgument)); } + if !Bits.valid(out.*) || !Bits.valid(source) { + ret .Err(Error.make(.InvalidArgument)); + } + var signed_value = BigInt.zero(); + defer { BigInt.drop(&signed_value); } + match Bits.to_big(&signed_value, source, true) { + .Ok => {}, .Err(error) => { ret .Err(error); }, + } + ret Bits.from_big_truncate(out, signed_value, width); + } + + fn div_rem(quotient: *Bits, remainder: *Bits, dividend: Bits, divisor: Bits, + signed: bool): Status { + if (quotient as usize) == (remainder as usize) { + ret .Err(Error.make(.InvalidArgument)); + } + if !Bits.valid(quotient.*) || !Bits.valid(remainder.*) || + !Bits.same_width(dividend, divisor) || + quotient.bit_width != dividend.bit_width || + remainder.bit_width != dividend.bit_width { + ret .Err(Error.make(.WidthMismatch)); + } + var a = BigInt.zero(); + var b = BigInt.zero(); + var q = BigInt.zero(); + var r = BigInt.zero(); + defer { BigInt.drop(&r); } + defer { BigInt.drop(&q); } + defer { BigInt.drop(&b); } + defer { BigInt.drop(&a); } + match Bits.to_big(&a, dividend, signed) { + .Ok => {}, .Err(error) => { ret .Err(error); }, + } + match Bits.to_big(&b, divisor, signed) { + .Ok => {}, .Err(error) => { ret .Err(error); }, + } + match BigInt.div_rem(&q, &r, a, b) { + .Ok => {}, .Err(error) => { ret .Err(error); }, + } + var q_r = Bits.new(dividend.bit_width); + var r_r = Bits.new(dividend.bit_width); + var q_bits = Bits.blank(); + var r_bits = Bits.blank(); + match q_r { .Ok(value) => { q_bits = value; }, .Err(error) => { ret .Err(error); }, } + match r_r { + .Ok(value) => { r_bits = value; }, + .Err(error) => { Bits.drop(&q_bits); ret .Err(error); }, + } + match Bits.from_big_checked(&q_bits, q, dividend.bit_width, signed) { + .Ok => {}, + .Err(error) => { Bits.drop(&r_bits); Bits.drop(&q_bits); ret .Err(error); }, + } + match Bits.from_big_checked(&r_bits, r, dividend.bit_width, signed) { + .Ok => {}, + .Err(error) => { Bits.drop(&r_bits); Bits.drop(&q_bits); ret .Err(error); }, + } + Bits.replace(quotient, &q_bits); + Bits.replace(remainder, &r_bits); + ret .Ok; + } + + fn byte_len(self: Bits): usize { + ret self.bit_width / 8 + (self.bit_width % 8 != 0) as usize; + } + + fn little(endian: Bits.Endian): bool { + match endian { .Little => { ret true; }, .Big => { ret false; }, } + } + + fn import_bytes(out: *Bits, bytes: []u8, width: usize, endian: Bits.Endian): Status { + if !Bits.valid(out.*) || width == 0 { ret .Err(Error.make(.InvalidArgument)); } + var expected = width / 8 + (width % 8 != 0) as usize; + if bytes.len != expected { ret .Err(Error.make(.OutOfRange)); } + if bytes.len != 0 && bytes.ptr == null { ret .Err(Error.make(.InvalidArgument)); } + + var value = BigInt.zero(); + defer { BigInt.drop(&value); } + var i: usize = 0; + while i < bytes.len { + var index = i; + if Bits.little(endian) { index = bytes.len - 1 - i; } + match BigInt.mul_small(&value, 256) { + .Ok => {}, .Err(error) => { ret .Err(error); }, + } + match BigInt.add_small(&value, bytes.ptr[index] as u32) { + .Ok => {}, .Err(error) => { ret .Err(error); }, + } + i = i + 1; + } + ret Bits.from_big_checked(out, value, width, false); + } + + fn export_bytes(out: *ByteBuffer, source: Bits, endian: Bits.Endian): Status { + if !Bits.valid(source) { ret .Err(Error.make(.InvalidArgument)); } + var fresh = ByteBuffer.new(); + var count = Bits.byte_len(source); + var i: usize = 0; + while i < count { + var logical = i; + if !Bits.little(endian) { logical = count - 1 - i; } + var word = logical / 4; + var offset = (logical % 4) * 8; + var byte: u8 = 0; + if word < source.value.len { + byte = (source.value.limbs[word] >> offset as u32) as u8; + } + match ByteBuffer.append_byte(&fresh, byte) { + .Ok => {}, .Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); }, + } + i = i + 1; + } + ByteBuffer.drop(out); + out.* = ByteBuffer.take(&fresh); + 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 = -32; + while a <= 32 { + var b: i64 = -32; + while b <= 32 { + 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 bits_new(width: usize): Bits { + match Bits.new(width) { + .Ok(value) => { ret value; }, + .Err(error) => { @assert(false, "Bits construction"); }, + } + ret Bits.blank(); +} + +fn bits_i64(value: i64, width: usize, signed: bool): Bits { + var integer = BigInt.zero(); + defer { BigInt.drop(&integer); } + expect_ok(BigInt.from_i64(&integer, value)); + var result = bits_new(width); + expect_ok(Bits.from_big_checked(&result, integer, width, signed)); + ret result; +} + +fn bits_u64(value: u64, width: usize): Bits { + var integer = BigInt.zero(); + defer { BigInt.drop(&integer); } + expect_ok(BigInt.from_u64(&integer, value)); + var result = bits_new(width); + expect_ok(Bits.from_big_checked(&result, integer, width, false)); + ret result; +} + +fn bits_as_i64(value: Bits, signed: bool): i64 { + var integer = BigInt.zero(); + defer { BigInt.drop(&integer); } + expect_ok(Bits.to_big(&integer, value, signed)); + var result: i64 = 0; + expect_ok(BigInt.to_i64(&result, integer)); + ret result; +} + +fn expect_overflow_status(result: Status) { + match result { + .Ok => { @assert(false, "expected overflow"); }, + .Err(error) => { match error.kind { .Overflow => {}, _ => { @assert(false); }, } }, + } +} + +fn expect_range_status(result: Status) { + match result { + .Ok => { @assert(false, "expected range error"); }, + .Err(error) => { match error.kind { .OutOfRange => {}, _ => { @assert(false); }, } }, + } +} + +fn expect_width_status(result: Status) { + match result { + .Ok => { @assert(false, "expected width mismatch"); }, + .Err(error) => { match error.kind { .WidthMismatch => {}, _ => { @assert(false); }, } }, + } +} + +fn test_bits_widths_and_conversion() { + match Bits.new(0) { + .Ok(value) => { @assert(false, "zero width"); }, + .Err(error) => { match error.kind { .InvalidArgument => {}, _ => { @assert(false); }, } }, + } + var widths: [6]usize = .[1, 7, 24, 65, 128, 257]; + var i: usize = 0; + while i < 6 { + var value = bits_new(widths[i]); + @assert(Bits.valid(value) && value.bit_width == widths[i]); + Bits.drop(&value); + i = i + 1; + } + + var minus_one = bits_i64(-1, 7, true); + defer { Bits.drop(&minus_one); } + @assert(bits_as_i64(minus_one, true) == -1); + @assert(bits_as_i64(minus_one, false) == 127); + + var one_bit = bits_u64(1, 1); + defer { Bits.drop(&one_bit); } + @assert(bits_as_i64(one_bit, true) == -1 && bits_as_i64(one_bit, false) == 1); + + var all_128_int = parsed("340282366920938463463374607431768211455"); + defer { BigInt.drop(&all_128_int); } + var all_128 = bits_new(128); + defer { Bits.drop(&all_128); } + expect_ok(Bits.from_big_checked(&all_128, all_128_int, 128, false)); + @assert(all_128.value.len == 4 && all_128.value.limbs[3] == 0xffffffff); + + var all_257 = bits_i64(-1, 257, true); + defer { Bits.drop(&all_257); } + @assert(all_257.value.len == 9 && all_257.value.limbs[8] == 1, + "257-bit top mask"); + @assert(bits_as_i64(all_257, true) == -1); + + var wide_text = parsed("36893488147419103231"); + defer { BigInt.drop(&wide_text); } + var wide = bits_new(65); + defer { Bits.drop(&wide); } + expect_ok(Bits.from_big_checked(&wide, wide_text, 65, false)); + var bit_count: usize = 0; + expect_ok(BigInt.bit_length(&bit_count, wide.value)); + @assert(bit_count == 65 && wide.value.limbs[2] == 1, "65-bit top mask"); +} + +fn test_bits_arithmetic() { + var one = bits_u64(1, 7); + defer { Bits.drop(&one); } + var max = bits_u64(127, 7); + defer { Bits.drop(&max); } + var out = bits_u64(5, 7); + defer { Bits.drop(&out); } + + expect_overflow_status(Bits.add_checked(&out, max, one, false)); + @assert(bits_as_i64(out, false) == 5, "overflow preserves output"); + expect_ok(Bits.add_wrapping(&out, max, one)); + @assert(bits_as_i64(out, false) == 0, "u7 wrapping add"); + + var signed_max = bits_i64(63, 7, true); + defer { Bits.drop(&signed_max); } + expect_overflow_status(Bits.add_checked(&out, signed_max, one, true)); + var signed_min = bits_i64(-64, 7, true); + defer { Bits.drop(&signed_min); } + expect_overflow_status(Bits.sub_checked(&out, signed_min, one, true)); + + var twenty = bits_u64(20, 7); + defer { Bits.drop(&twenty); } + var ten = bits_u64(10, 7); + defer { Bits.drop(&ten); } + expect_ok(Bits.mul_wrapping(&out, twenty, ten)); + @assert(bits_as_i64(out, false) == 72, "u7 wrapping multiply"); +} + +fn test_bits_logic_and_shift() { + var a = bits_u64(85, 7); + defer { Bits.drop(&a); } + var b = bits_u64(15, 7); + defer { Bits.drop(&b); } + var out = bits_new(7); + defer { Bits.drop(&out); } + expect_ok(Bits.bit_and(&out, a, b)); + @assert(bits_as_i64(out, false) == 5); + expect_ok(Bits.bit_or(&out, a, b)); + @assert(bits_as_i64(out, false) == 95); + expect_ok(Bits.bit_xor(&out, a, b)); + @assert(bits_as_i64(out, false) == 90); + expect_ok(Bits.bit_not(&out, a)); + @assert(bits_as_i64(out, false) == 42); + + var signed = bits_u64(65, 7); + defer { Bits.drop(&signed); } + expect_ok(Bits.shift_left(&out, signed, 1)); + @assert(bits_as_i64(out, false) == 2); + expect_ok(Bits.shift_right(&out, signed, 1)); + @assert(bits_as_i64(out, false) == 32); + expect_ok(Bits.shift_right_arithmetic(&out, signed, 1)); + @assert(bits_as_i64(out, false) == 96); + expect_range_status(Bits.shift_left(&out, signed, 7)); + @assert(bits_as_i64(out, false) == 96, "bad shift preserves output"); + + var order: i32 = 0; + expect_ok(Bits.cmp_unsigned(&order, signed, a)); + @assert(order < 0); + expect_ok(Bits.cmp_signed(&order, signed, a)); + @assert(order < 0); + + var other_width = bits_u64(1, 24); + defer { Bits.drop(&other_width); } + order = 17; + expect_width_status(Bits.cmp_unsigned(&order, a, other_width)); + @assert(order == 17, "width mismatch preserves comparison output"); + expect_width_status(Bits.bit_and(&out, a, other_width)); + expect_ok(Bits.shift_right_arithmetic(&signed, signed, 1)); + @assert(bits_as_i64(signed, false) == 96, "aliased arithmetic shift"); +} + +fn test_bits_resize_division_and_bytes() { + var source = bits_u64(85, 7); + defer { Bits.drop(&source); } + var resized = bits_new(7); + defer { Bits.drop(&resized); } + expect_ok(Bits.truncate(&resized, source, 4)); + @assert(bits_as_i64(resized, false) == 5); + expect_ok(Bits.zero_extend(&resized, source, 24)); + @assert(bits_as_i64(resized, false) == 85 && resized.bit_width == 24); + + var negative = bits_u64(65, 7); + defer { Bits.drop(&negative); } + expect_ok(Bits.sign_extend(&resized, negative, 24)); + @assert(bits_as_i64(resized, true) == -63); + + var hundred = bits_u64(100, 8); + defer { Bits.drop(&hundred); } + var seven = bits_u64(7, 8); + defer { Bits.drop(&seven); } + var quotient = bits_new(8); + defer { Bits.drop("ient); } + var remainder = bits_new(8); + defer { Bits.drop(&remainder); } + expect_ok(Bits.div_rem("ient, &remainder, hundred, seven, false)); + @assert(bits_as_i64(quotient, false) == 14 && bits_as_i64(remainder, false) == 2); + + var neg_hundred = bits_i64(-100, 8, true); + defer { Bits.drop(&neg_hundred); } + expect_ok(Bits.div_rem("ient, &remainder, neg_hundred, seven, true)); + @assert(bits_as_i64(quotient, true) == -14 && bits_as_i64(remainder, true) == -2); + + var min = bits_i64(-128, 8, true); + defer { Bits.drop(&min); } + var minus_one = bits_i64(-1, 8, true); + defer { Bits.drop(&minus_one); } + expect_overflow_status(Bits.div_rem("ient, &remainder, min, minus_one, true)); + + var bytes: [3]u8 = .[0x12, 0x34, 0x56]; + expect_ok(Bits.import_bytes(&resized, bytes[0..3], 24, .Big)); + @assert(bits_as_i64(resized, false) == 0x123456); + var output = ByteBuffer.new(); + defer { ByteBuffer.drop(&output); } + expect_ok(Bits.export_bytes(&output, resized, .Little)); + var exported = ByteBuffer.as_slice(output); + @assert(exported.len == 3 && exported.ptr[0] == 0x56 && + exported.ptr[1] == 0x34 && exported.ptr[2] == 0x12); + + var bad: [1]u8 = .[0xff]; + var seven_bits = bits_u64(3, 7); + defer { Bits.drop(&seven_bits); } + expect_overflow_status(Bits.import_bytes(&seven_bits, bad[0..1], 7, .Big)); + @assert(bits_as_i64(seven_bits, false) == 3, "bad import preserves output"); + + var copied = bits_new(7); + defer { Bits.drop(&copied); } + expect_ok(Bits.clone(&copied, seven_bits)); + expect_ok(Bits.bit_not(&seven_bits, seven_bits)); + @assert(bits_as_i64(copied, false) == 3, "Bits clone is deep"); + var moved = Bits.take(&copied); + defer { Bits.drop(&moved); } + @assert(copied.bit_width == 0 && copied.value.limbs == null, "Bits take empties source"); + @assert(bits_as_i64(moved, false) == 3); +} + +fn main() { + test_arithmetic(); + test_division_and_sign(); + test_parse_format_and_conversion(); + test_small_reference(); + test_bits_widths_and_conversion(); + test_bits_arithmetic(); + test_bits_logic_and_shift(); + test_bits_resize_division_and_bytes(); +} diff --git a/stage2/utils/path.spl b/stage2/utils/path.spl index be857f4..6b17629 100644 --- a/stage2/utils/path.spl +++ b/stage2/utils/path.spl @@ -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(); }, // 防御:索引已界内 +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 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(); } - var r = ByteView.slice(path, 0, idx); - match r { - .Ok(v) => { ret v; }, - .Err(error) => { ret ByteView.empty(); }, // 防御:索引已界内 - } - 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 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 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 ByteView.empty(); + ret slice_or_empty(path, index + 1, path.len); } -// 扩展名(不含 '.');无扩展名返回空。 -fn extension(path: ByteView): ByteView { - var base = basename(path); +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); - 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] == '.'; +} - var starts = Vec.blank(@sizeof(usize), 8); +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; +} + +// 规范化分隔符并折叠 '.'/'..'。相对路径无法消去的前导 '..' 会被保留; +// 绝对路径的 '..' 不会越过根,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]) { - match ByteBuffer.append_byte(&fresh, '/') { - .Ok => {}, .Err(e) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(e); }, + if path.len != 0 { + if !is_windows(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; } + } 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); }, + } + 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; } } - } 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); }, - } - match ByteBuffer.append_byte(&fresh, ':') { - .Ok => {}, .Err(e) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(e); }, - } - 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; } - - 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); }, + 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 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(); } diff --git a/stage2/utils/set.spl b/stage2/utils/set.spl new file mode 100644 index 0000000..bb7e114 --- /dev/null +++ b/stage2/utils/set.spl @@ -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(); +}