stage2 更多的工具库 stage1 修复问题
This commit is contained in:
1 parent
16a1f030d4
commit
b3e38ba7e4
28 files changed
+2274
-523
No files matched your search
+70
-35
@@ -1,9 +1,13 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 分块 bump 分配器:按块分配、块内顺序推进,只在 drop 时整体释放。
|
||||
// 适合 AST/IR 这类生命周期一致的批量小对象;不支持单独 free。
|
||||
// 失败(内存不足/参数非法)返回 null,不改变已有分配。
|
||||
// 分配失败通过 ArenaResult 区分原因:size==0/非法对齐/内存不足/地址溢出。
|
||||
// 失败的 alloc 不改变已有分配。
|
||||
|
||||
type .{ .Vec } = @import("vec.spl");
|
||||
type .{ .Error, .Kind, .Status } = @import("error.spl");
|
||||
type .{ .checked_add, .align_up } = @import("memory.spl");
|
||||
type Mem = @import("../sys/mem.spl");
|
||||
|
||||
type Chunk = struct {
|
||||
@@ -12,6 +16,11 @@ type Chunk = struct {
|
||||
used: usize,
|
||||
}
|
||||
|
||||
type ArenaResult = enum {
|
||||
Ok: *u8,
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
type Arena = @self;
|
||||
|
||||
chunk_size: usize,
|
||||
@@ -28,61 +37,68 @@ fn new(chunk_size: usize): Arena {
|
||||
};
|
||||
}
|
||||
|
||||
fn is_pow2(x: usize): bool {
|
||||
ret x != 0 && (x & (x - 1)) == 0;
|
||||
}
|
||||
|
||||
fn align_up(x: usize, align: usize): usize {
|
||||
var mask = align - 1;
|
||||
ret (x + mask) & ~mask;
|
||||
}
|
||||
|
||||
fn chunk_at(self: *Arena, index: usize): *Chunk {
|
||||
match Vec.get(&self.chunks, index) {
|
||||
.Ok(ptr) => { ret ptr as *Chunk; },
|
||||
// 越界/无效 Arena 统一返回 null;调用方负责判空,无资源需要释放。
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn push_chunk(self: *Arena, size: usize): bool {
|
||||
fn push_chunk(self: *Arena, size: usize): Status {
|
||||
var base = Mem.alloc(size) as *u8;
|
||||
if base == null { ret false; }
|
||||
if base == null { ret .Err(Error.make(.Overflow)); }
|
||||
var c = Chunk { .base = base, .size = size, .used = 0 };
|
||||
match Vec.push(&self.chunks, &c as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { Mem.free(base as *any); ret false; },
|
||||
.Err(error) => { Mem.free(base as *any); ret .Err(error); },
|
||||
}
|
||||
self.current = self.chunks.len - 1;
|
||||
ret true;
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// 分配 size 字节,地址按 align(非零二次幂)对齐。失败返回 null。
|
||||
fn alloc(self: *Arena, size: usize, align: usize): *u8 {
|
||||
if size == 0 { ret null; }
|
||||
// 分配 size 字节,地址按 align(非零二次幂)对齐。失败返回 ArenaResult.Err。
|
||||
fn alloc(self: *Arena, size: usize, align: usize): ArenaResult {
|
||||
if size == 0 { ret .Err(Error.make(.InvalidArgument)); }
|
||||
if align == 0 { align = 1; }
|
||||
if !is_pow2(align) { ret null; }
|
||||
|
||||
while true {
|
||||
if self.current >= self.chunks.len {
|
||||
var want = self.chunk_size;
|
||||
var need = size + align;
|
||||
var need: usize = 0;
|
||||
match checked_add(size, align) {
|
||||
.Ok(value) => { need = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
if need > want { want = need; }
|
||||
if !push_chunk(self, want) { ret null; }
|
||||
match push_chunk(self, want) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
}
|
||||
|
||||
var c = chunk_at(self, self.current);
|
||||
if c == null { ret null; }
|
||||
if c == null { ret .Err(Error.make(.InvalidArgument)); }
|
||||
var addr = c.base as usize;
|
||||
var aligned = align_up(addr + c.used, align);
|
||||
var used_addr: usize = 0;
|
||||
match checked_add(addr, c.used) {
|
||||
.Ok(value) => { used_addr = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
var aligned: usize = 0;
|
||||
match align_up(used_addr, align) {
|
||||
.Ok(value) => { aligned = value; },
|
||||
.Err(error) => { ret .Err(error); }, // 非二次幂对齐或地址溢出
|
||||
}
|
||||
var offset = aligned - addr;
|
||||
if offset <= c.size && size <= c.size - offset {
|
||||
c.used = offset + size;
|
||||
ret aligned as *u8;
|
||||
ret .Ok(aligned as *u8);
|
||||
}
|
||||
self.current = self.current + 1;
|
||||
}
|
||||
ret null;
|
||||
ret .Err(Error.make(.OutOfRange)); // 不可达:循环内所有路径均已返回
|
||||
}
|
||||
|
||||
fn chunk_count(self: Arena): usize {
|
||||
@@ -117,13 +133,31 @@ fn is_aligned(p: *u8, align: usize): bool {
|
||||
ret ((p as usize) & (align - 1)) == 0;
|
||||
}
|
||||
|
||||
fn alloc_ok(a: *Arena, size: usize, align: usize): *u8 {
|
||||
match alloc(a, size, align) {
|
||||
.Ok(ptr) => { ret ptr; },
|
||||
.Err(error) => { @assert(false, "unexpected arena alloc error"); },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn expect_alloc_error(result: ArenaResult, expected: Kind) {
|
||||
match result {
|
||||
.Ok(ptr) => { @assert(false, "expected arena alloc error"); },
|
||||
.Err(error) => {
|
||||
var want: Kind = expected;
|
||||
@assert(@enum2int(error.kind) == @enum2int(want), "arena alloc error kind");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn test_alloc_and_alignment() {
|
||||
var a = new(64);
|
||||
@assert(chunk_count(a) == 0, "lazy");
|
||||
|
||||
var p1 = alloc(&a, 1, 1);
|
||||
var p8 = alloc(&a, 16, 8);
|
||||
var p16 = alloc(&a, 1, 16);
|
||||
var p1 = alloc_ok(&a, 1, 1);
|
||||
var p8 = alloc_ok(&a, 16, 8);
|
||||
var p16 = alloc_ok(&a, 1, 16);
|
||||
@assert(p1 != null && p8 != null && p16 != null, "alloc");
|
||||
@assert(p1 != p8 && p8 != p16 && p1 != p16, "distinct");
|
||||
@assert(is_aligned(p8, 8), "align 8");
|
||||
@@ -136,18 +170,20 @@ fn test_alloc_and_alignment() {
|
||||
@assert(chunk_count(a) == 1, "one chunk");
|
||||
reset(&a);
|
||||
@assert(chunk_count(a) == 1, "reset keeps chunks");
|
||||
var q = alloc(&a, 1, 1);
|
||||
var q = alloc_ok(&a, 1, 1);
|
||||
@assert(q == p1, "reset reuses first chunk");
|
||||
|
||||
// drop 后还有断言,故保持手动释放,不改为 defer。
|
||||
drop(&a);
|
||||
@assert(chunk_count(a) == 0, "drop empties");
|
||||
}
|
||||
|
||||
fn test_growth() {
|
||||
var a = new(64);
|
||||
defer { drop(&a); }
|
||||
var i: i32 = 0;
|
||||
while i < 100 {
|
||||
var p = alloc(&a, 16, 8);
|
||||
var p = alloc_ok(&a, 16, 8);
|
||||
@assert(p != null, "grow alloc");
|
||||
@assert(is_aligned(p, 8), "grow align");
|
||||
p[0] = i as u8;
|
||||
@@ -156,18 +192,17 @@ fn test_growth() {
|
||||
@assert(chunk_count(a) > 1, "grew multiple chunks");
|
||||
|
||||
// 超过块大小的单次分配也能成功(新块按需放大)。
|
||||
var big = alloc(&a, 1024, 16);
|
||||
var big = alloc_ok(&a, 1024, 16);
|
||||
@assert(big != null && is_aligned(big, 16), "oversized alloc");
|
||||
drop(&a);
|
||||
}
|
||||
|
||||
fn test_failures() {
|
||||
var a = new(64);
|
||||
@assert(alloc(&a, 0, 1) == null, "zero size");
|
||||
var d = alloc(&a, 8, 0);
|
||||
defer { drop(&a); }
|
||||
expect_alloc_error(alloc(&a, 0, 1), .InvalidArgument);
|
||||
var d = alloc_ok(&a, 8, 0);
|
||||
@assert(d != null && is_aligned(d, 1), "align 0 defaults to 1");
|
||||
@assert(alloc(&a, 8, 3) == null, "non-pow2 align");
|
||||
drop(&a);
|
||||
expect_alloc_error(alloc(&a, 8, 3), .InvalidAlignment);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// ASCII 字符工具。输入和输出都是单个字节,不解释 UTF-8 或本地化规则。
|
||||
|
||||
type .{ .Error, .make } = @import("error.spl");
|
||||
|
||||
+46
-17
@@ -1,4 +1,5 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 任意精度有符号整数。limbs 为小端 u32;零规范为 len=0 且 negative=false。
|
||||
// 普通赋值只借用同一存储;深复制使用 clone,所有权转移使用 take。
|
||||
type .{ .Error, .Status } = @import("error.spl");
|
||||
@@ -64,6 +65,9 @@ fn reserve(self: *BigInt, need: usize): Status {
|
||||
.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));
|
||||
}
|
||||
@@ -203,6 +207,7 @@ fn sub_abs_into(out: *BigInt, a: BigInt, b: BigInt): Status {
|
||||
.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;
|
||||
@@ -210,7 +215,7 @@ fn sub_abs_into(out: *BigInt, a: BigInt, b: BigInt): Status {
|
||||
if i < b.len { bv = b.limbs[i] as u64; }
|
||||
var subtrahend = bv + borrow;
|
||||
if av < subtrahend {
|
||||
out.limbs[i] = (4294967296 + av - subtrahend) as u32;
|
||||
out.limbs[i] = (base + av - subtrahend) as u32;
|
||||
borrow = 1;
|
||||
} else {
|
||||
out.limbs[i] = (av - subtrahend) as u32;
|
||||
@@ -407,6 +412,7 @@ fn shift_one(self: *BigInt) {
|
||||
|
||||
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;
|
||||
@@ -414,7 +420,7 @@ fn sub_abs_in_place(a: *BigInt, b: BigInt) {
|
||||
if i < b.len { bv = b.limbs[i] as u64; }
|
||||
var subtrahend = bv + borrow;
|
||||
if av < subtrahend {
|
||||
a.limbs[i] = (4294967296 + av - subtrahend) as u32;
|
||||
a.limbs[i] = (base + av - subtrahend) as u32;
|
||||
borrow = 1;
|
||||
} else {
|
||||
a.limbs[i] = (av - subtrahend) as u32;
|
||||
@@ -639,13 +645,15 @@ fn to_i64(out: *i64, source: BigInt): Status {
|
||||
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 > 9223372036854775807 { ret .Err(Error.make(.Overflow)); }
|
||||
if magnitude > max_positive { ret .Err(Error.make(.Overflow)); }
|
||||
out.* = magnitude as i64;
|
||||
ret .Ok;
|
||||
}
|
||||
if magnitude > 9223372036854775808 { ret .Err(Error.make(.Overflow)); }
|
||||
if magnitude == 9223372036854775808 {
|
||||
if magnitude > min_magnitude { ret .Err(Error.make(.Overflow)); }
|
||||
if magnitude == min_magnitude {
|
||||
out.* = magnitude as i64;
|
||||
} else {
|
||||
out.* = 0 - magnitude as i64;
|
||||
@@ -693,12 +701,15 @@ fn parsed(text: []u8): BigInt {
|
||||
|
||||
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");
|
||||
@@ -706,43 +717,52 @@ fn test_arithmetic() {
|
||||
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));
|
||||
|
||||
drop(&shifted); drop(&difference); drop(&product); drop(&two_64);
|
||||
drop(&all_ones); drop(&one); drop(&a);
|
||||
}
|
||||
|
||||
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));
|
||||
@@ -750,37 +770,40 @@ fn test_division_and_sign() {
|
||||
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");
|
||||
|
||||
drop(&zero_divisor); drop(&saved); drop(&reconstructed); drop(&seven); drop(&negative);
|
||||
drop(&long_divisor); drop(&long_dividend);
|
||||
drop(&remainder); drop("ient); drop(&divisor); drop(÷nd);
|
||||
}
|
||||
|
||||
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) => {
|
||||
@@ -791,10 +814,12 @@ fn test_parse_format_and_conversion() {
|
||||
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); },
|
||||
@@ -803,10 +828,12 @@ fn test_parse_format_and_conversion() {
|
||||
@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); },
|
||||
@@ -815,17 +842,16 @@ fn test_parse_format_and_conversion() {
|
||||
@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");
|
||||
|
||||
drop(&moved); drop(&copied); drop(&one_more); drop(&signed_over); drop(&minimum);
|
||||
drop(&over); drop(&max); drop(&preserved); drop(&base36);
|
||||
ByteBuffer.drop(&output); drop(&value);
|
||||
}
|
||||
|
||||
fn expect_i64_value(value: BigInt, expected: i64) {
|
||||
@@ -836,9 +862,13 @@ fn expect_i64_value(value: BigInt, expected: i64) {
|
||||
|
||||
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;
|
||||
@@ -860,7 +890,6 @@ fn test_small_reference() {
|
||||
}
|
||||
a = a + 1;
|
||||
}
|
||||
drop(&remainder); drop(&result); drop(&right); drop(&left);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 拥有字节存储的动态缓冲区。普通赋值会复制所有权,转移必须使用 take。
|
||||
// 借用切片和 z_ptr 返回的指针在下一次可能扩容的操作或 drop 后失效。
|
||||
|
||||
@@ -205,6 +206,7 @@ fn expect_z(self: *ByteBuffer) {
|
||||
|
||||
fn test_append_and_resize() {
|
||||
var buffer = new();
|
||||
defer { drop(&buffer); }
|
||||
@assert(is_empty(buffer));
|
||||
@assert(size(buffer) == 0 && capacity(buffer) == 0);
|
||||
expect_z(&buffer);
|
||||
@@ -232,11 +234,11 @@ fn test_append_and_resize() {
|
||||
clear(&buffer);
|
||||
@assert(is_empty(buffer) && capacity(buffer) != 0, "clear retains allocation");
|
||||
expect_z(&buffer);
|
||||
drop(&buffer);
|
||||
}
|
||||
|
||||
fn test_growth_and_self_append() {
|
||||
var buffer = new();
|
||||
defer { drop(&buffer); }
|
||||
var i: usize = 0;
|
||||
while i < 8 {
|
||||
expect_ok(append_byte(&buffer, (65 + i) as u8));
|
||||
@@ -253,14 +255,15 @@ fn test_growth_and_self_append() {
|
||||
i = i + 1;
|
||||
}
|
||||
expect_z(&buffer);
|
||||
drop(&buffer);
|
||||
}
|
||||
|
||||
fn test_ownership_and_failures() {
|
||||
var source = new();
|
||||
defer { drop(&source); }
|
||||
expect_ok(append(&source, "owned"));
|
||||
|
||||
var copy = new();
|
||||
defer { drop(©); }
|
||||
expect_ok(clone(©, source));
|
||||
@assert(copy.storage.len == 5, "clone logical length");
|
||||
source.storage.data[0] = 79;
|
||||
@@ -293,6 +296,7 @@ fn test_ownership_and_failures() {
|
||||
expect_bytes(copy, "wne");
|
||||
|
||||
var moved = take(©);
|
||||
defer { drop(&moved); }
|
||||
@assert(copy.storage.raw == null && copy.storage.len == 0 && copy.storage.cap == 0);
|
||||
expect_bytes(moved, "wne");
|
||||
|
||||
@@ -304,10 +308,6 @@ fn test_ownership_and_failures() {
|
||||
moved.storage.cap == old_cap,
|
||||
"overflow preserves byte buffer");
|
||||
expect_bytes(moved, "wne");
|
||||
|
||||
drop(&moved);
|
||||
drop(©);
|
||||
drop(&source);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 借用的字节字符串视图;不拥有、不分配也不释放 ptr 指向的存储。
|
||||
// 非空视图要求 ptr 在整个借用期内可读。空视图统一表示为 null/0。
|
||||
|
||||
|
||||
@@ -1,243 +0,0 @@
|
||||
// Expect retcode = 0
|
||||
// 诊断收集与渲染:severity + 文件/行/列 + 消息;统计错误/警告数。
|
||||
// 消息与文件名按拥有型 Str 保存(深拷贝),Diag 拥有它们直到 clear/drop。
|
||||
// 渲染输出到 ByteBuffer;不直接写文件,便于 LSP/测试。
|
||||
|
||||
type .{ .Error, .Status, .make } = @import("error.spl");
|
||||
type .{ .Vec } = @import("vec.spl");
|
||||
type .{ .ByteBuffer } = @import("byte_buffer.spl");
|
||||
type .{ .Str } = @import("str.spl");
|
||||
type .{ .append_usize } = @import("fmt.spl");
|
||||
|
||||
type Severity = enum {
|
||||
Debug,
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
Fatal,
|
||||
}
|
||||
|
||||
type DiagItem = struct {
|
||||
severity: Severity,
|
||||
line: usize,
|
||||
col: usize,
|
||||
fname: Str,
|
||||
message: Str,
|
||||
}
|
||||
|
||||
type Diag = @self;
|
||||
|
||||
items: Vec, // 元素类型 DiagItem
|
||||
errors: usize,
|
||||
warnings: usize,
|
||||
|
||||
fn new(): Diag {
|
||||
ret Diag {
|
||||
.items = Vec.blank(@sizeof(DiagItem), 8),
|
||||
.errors = 0,
|
||||
.warnings = 0,
|
||||
};
|
||||
}
|
||||
|
||||
fn item_at(self: *Diag, index: usize): *DiagItem {
|
||||
match Vec.get(&self.items, index) {
|
||||
.Ok(ptr) => { ret ptr as *DiagItem; },
|
||||
.Err(error) => { ret null; },
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
|
||||
fn count(self: Diag): usize {
|
||||
ret self.items.len;
|
||||
}
|
||||
|
||||
fn error_count(self: Diag): usize {
|
||||
ret self.errors;
|
||||
}
|
||||
|
||||
fn warning_count(self: Diag): usize {
|
||||
ret self.warnings;
|
||||
}
|
||||
|
||||
fn has_error(self: Diag): bool {
|
||||
ret self.errors > 0;
|
||||
}
|
||||
|
||||
// 添加一条诊断;失败不改变已有内容。
|
||||
fn emit(self: *Diag, fname: []u8, line: usize, col: usize, severity: Severity,
|
||||
msg: []u8): Status {
|
||||
var item = DiagItem {
|
||||
.severity = severity,
|
||||
.line = line,
|
||||
.col = col,
|
||||
.fname = Str.new(),
|
||||
.message = Str.new(),
|
||||
};
|
||||
|
||||
match Str.from_slice(fname) {
|
||||
.Ok(v) => { item.fname = v; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
match Str.from_slice(msg) {
|
||||
.Ok(v) => { item.message = v; },
|
||||
.Err(error) => {
|
||||
Str.drop(&item.fname);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
match Vec.push(&self.items, &item as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => {
|
||||
Str.drop(&item.fname);
|
||||
Str.drop(&item.message);
|
||||
ret .Err(error);
|
||||
},
|
||||
}
|
||||
|
||||
match severity {
|
||||
.Error => { self.errors = self.errors + 1; },
|
||||
.Fatal => { self.errors = self.errors + 1; },
|
||||
.Warning => { self.warnings = self.warnings + 1; },
|
||||
_ => {},
|
||||
}
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
fn err(self: *Diag, fname: []u8, line: usize, col: usize, msg: []u8): Status {
|
||||
ret emit(self, fname, line, col, .Error, msg);
|
||||
}
|
||||
|
||||
fn warn(self: *Diag, fname: []u8, line: usize, col: usize, msg: []u8): Status {
|
||||
ret emit(self, fname, line, col, .Warning, msg);
|
||||
}
|
||||
|
||||
fn info(self: *Diag, fname: []u8, line: usize, col: usize, msg: []u8): Status {
|
||||
ret emit(self, fname, line, col, .Info, msg);
|
||||
}
|
||||
|
||||
fn level_name(severity: Severity): []u8 {
|
||||
match severity {
|
||||
.Debug => { ret "debug"; },
|
||||
.Info => { ret "info"; },
|
||||
.Warning => { ret "warning"; },
|
||||
.Error => { ret "error"; },
|
||||
.Fatal => { ret "fatal"; },
|
||||
}
|
||||
ret "?";
|
||||
}
|
||||
|
||||
fn append_str(out: *ByteBuffer, s: *Str): Status {
|
||||
ret ByteBuffer.append(out, Str.as_slice(s));
|
||||
}
|
||||
|
||||
// 渲染为 "file:line:col: level: message\n"(file 为空时省略前缀)。
|
||||
fn render(self: *Diag, out: *ByteBuffer): Status {
|
||||
var i: usize = 0;
|
||||
while i < self.items.len {
|
||||
var it = item_at(self, i);
|
||||
if it == null { ret .Err(make(.OutOfRange)); }
|
||||
|
||||
if Str.size(it.fname) != 0 {
|
||||
match append_str(out, &it.fname) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append_byte(out, ':') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
}
|
||||
match append_usize(out, it.line, 10, false) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append_byte(out, ':') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match append_usize(out, it.col, 10, false) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append(out, ": ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append(out, level_name(it.severity)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append(out, ": ") { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match append_str(out, &it.message) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append_byte(out, '\n') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
i = i + 1;
|
||||
}
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// 丢弃所有条目内容但保留缓冲区。
|
||||
fn clear(self: *Diag) {
|
||||
var i: usize = 0;
|
||||
while i < self.items.len {
|
||||
var it = item_at(self, i);
|
||||
if it != null {
|
||||
Str.drop(&it.fname);
|
||||
Str.drop(&it.message);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
Vec.clear(&self.items);
|
||||
self.errors = 0;
|
||||
self.warnings = 0;
|
||||
}
|
||||
|
||||
fn drop(self: *Diag) {
|
||||
var i: usize = 0;
|
||||
while i < self.items.len {
|
||||
var it = item_at(self, i);
|
||||
if it != null {
|
||||
Str.drop(&it.fname);
|
||||
Str.drop(&it.message);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
Vec.drop(&self.items);
|
||||
self.errors = 0;
|
||||
self.warnings = 0;
|
||||
}
|
||||
|
||||
// ---- 测试辅助 ----
|
||||
|
||||
fn expect_ok(result: Status) {
|
||||
match result {
|
||||
.Ok => {},
|
||||
.Err(error) => { @assert(false, "unexpected diag error"); },
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_bytes(out: *ByteBuffer, expected: []u8) {
|
||||
var actual = ByteBuffer.as_slice(out.*);
|
||||
@assert(actual.len == expected.len, "render length");
|
||||
var i: usize = 0;
|
||||
while i < expected.len {
|
||||
@assert(actual.ptr[i] == expected.ptr[i], "render byte");
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn test_collect_and_count() {
|
||||
var d = new();
|
||||
@assert(count(d) == 0 && !has_error(d), "empty");
|
||||
|
||||
expect_ok(err(&d, "a.spl", 3, 5, "undefined identifier `x`"));
|
||||
expect_ok(warn(&d, "a.spl", 7, 1, "unused variable"));
|
||||
expect_ok(info(&d, "a.spl", 9, 2, "note"));
|
||||
expect_ok(err(&d, "", 1, 1, "fatal-ish"));
|
||||
|
||||
@assert(count(d) == 4, "count");
|
||||
@assert(error_count(d) == 2, "errors");
|
||||
@assert(warning_count(d) == 1, "warnings");
|
||||
@assert(has_error(d), "has_error");
|
||||
|
||||
var out = ByteBuffer.new();
|
||||
expect_ok(render(&d, &out));
|
||||
expect_bytes(&out, "a.spl:3:5: error: undefined identifier `x`\na.spl:7:1: warning: unused variable\na.spl:9:2: info: note\n1:1: error: fatal-ish\n");
|
||||
ByteBuffer.drop(&out);
|
||||
drop(&d);
|
||||
}
|
||||
|
||||
fn test_clear_and_reuse() {
|
||||
var d = new();
|
||||
expect_ok(err(&d, "f", 1, 1, "one"));
|
||||
expect_ok(warn(&d, "f", 2, 2, "two"));
|
||||
clear(&d);
|
||||
@assert(count(d) == 0 && !has_error(d) && warning_count(d) == 0, "cleared");
|
||||
|
||||
expect_ok(err(&d, "f", 3, 3, "three"));
|
||||
@assert(count(d) == 1 && error_count(d) == 1, "reuse");
|
||||
drop(&d);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
test_collect_and_count();
|
||||
test_clear_and_reuse();
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 公共错误值不持有资源,可按值复制;不分配错误消息字符串。
|
||||
// position 是调用方输入中的从零开始的位置;仅在 has_position 时有效。
|
||||
// 未定位错误的 position 规范为 0,不能用 position == 0 判断是否有位置。
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 数值/布尔格式化,写入 ByteBuffer。radix 取值 2..36;uppercase 只影响 10..35 的字母形式。
|
||||
// 不分配额外内存(除目标 ByteBuffer 自身扩容),失败不产生部分输出。
|
||||
|
||||
@@ -107,6 +108,7 @@ fn expect_text(buffer: *ByteBuffer, expected: []u8) {
|
||||
|
||||
fn test_unsigned() {
|
||||
var b = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&b); }
|
||||
|
||||
expect_ok(append_u64(&b, 0, 10, false));
|
||||
expect_text(&b, "0");
|
||||
@@ -147,12 +149,11 @@ fn test_unsigned() {
|
||||
expect_ok(append_u64(&b, 18446744073709551615, 2, false));
|
||||
expect_text(&b, "1111111111111111111111111111111111111111111111111111111111111111");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
ByteBuffer.drop(&b);
|
||||
}
|
||||
|
||||
fn test_signed() {
|
||||
var b = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&b); }
|
||||
|
||||
expect_ok(append_i64(&b, 0, 10, false));
|
||||
expect_text(&b, "0");
|
||||
@@ -177,12 +178,11 @@ fn test_signed() {
|
||||
expect_ok(append_u64(&b, 48879, 16, true));
|
||||
expect_text(&b, "BEEF");
|
||||
ByteBuffer.clear(&b);
|
||||
|
||||
ByteBuffer.drop(&b);
|
||||
}
|
||||
|
||||
fn test_usize_bool_and_errors() {
|
||||
var b = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&b); }
|
||||
|
||||
expect_ok(append_usize(&b, 1000 as usize, 10, false));
|
||||
expect_text(&b, "1000");
|
||||
@@ -201,12 +201,11 @@ 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");
|
||||
|
||||
ByteBuffer.drop(&b);
|
||||
}
|
||||
|
||||
fn test_composition_and_growth() {
|
||||
var b = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&b); }
|
||||
|
||||
expect_ok(ByteBuffer.append(&b, "x="));
|
||||
expect_ok(append_i64(&b, -42, 10, false));
|
||||
@@ -229,8 +228,6 @@ fn test_composition_and_growth() {
|
||||
var s = ByteBuffer.as_slice(b);
|
||||
@assert(s.ptr[0] == '0', "starts with 0");
|
||||
@assert(s.ptr[1] == ',', "0 then comma");
|
||||
|
||||
ByteBuffer.drop(&b);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 哈希工具:FNV-1a 字节哈希 + 整型混合;并给出可直接交给 map 的回调
|
||||
// (map 的 hash/eq 回调接收指向键字节的指针,这里按 usize / ByteView 解释)。
|
||||
// 仅提供确定性哈希与等值,不承诺密码学性质。
|
||||
@@ -7,10 +8,11 @@ type .{ .ByteView } = @import("byte_view.spl");
|
||||
|
||||
// FNV-1a 64 位,逐字节;空输入返回 offset basis。
|
||||
fn hash_bytes(data: *u8, len: usize): usize {
|
||||
var h: usize = 14695981039346656037;
|
||||
var h: usize = 14695981039346656037; // FNV-1a 64 位 offset basis
|
||||
var prime: usize = 1099511628211; // FNV 质数
|
||||
var i: usize = 0;
|
||||
while i < len {
|
||||
h = (h ^ (data[i] as usize)) * 1099511628211;
|
||||
h = (h ^ (data[i] as usize)) * prime;
|
||||
i = i + 1;
|
||||
}
|
||||
ret h;
|
||||
@@ -22,12 +24,13 @@ fn hash_byte_view(view: ByteView): usize {
|
||||
|
||||
// 整型混合(splitmix 风格的移位-乘法),保证非零与良好扩散。
|
||||
fn hash_usize_value(value: usize): usize {
|
||||
var prime: usize = 1099511628211; // splitmix 风格的混合乘子
|
||||
var h = value;
|
||||
h = (h ^ (h >> 30)) * 1099511628211;
|
||||
h = (h ^ (h >> 30)) * prime;
|
||||
h = h ^ (h >> 27);
|
||||
h = h * 1099511628211;
|
||||
h = h * prime;
|
||||
h = h ^ (h >> 31);
|
||||
if h == 0 { ret 1099511628211; } // 0 映射到非零
|
||||
if h == 0 { ret prime; } // 0 映射到非零
|
||||
ret h;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// I/O 便利层(构建在 sys/file 之上):
|
||||
// - C 字符串路径/模式的 open 包装
|
||||
// - 整块读写(write_all / read_all)
|
||||
|
||||
+75
-23
@@ -4,13 +4,14 @@
|
||||
// - 输入 ByteView,字符串/数字以“源切片”借用,不做解码,round-trip 保真;
|
||||
// - 节点池 Vec[JsonNode];数组/对象子项存 Vec[JsonChild],保留插入顺序;
|
||||
// - 错误带字节位置(error.spl);输出写 ByteBuffer;缩进/数字用 ByteBuffer/fmt。
|
||||
// 限制:不做字符串转义解码、无递归深度保护、数字不做数值化(原样保存)。
|
||||
// 限制:不做字符串转义解码、数字不做数值化(原样保存);递归/输出深度上限见 max_depth()。
|
||||
|
||||
type .{ .Error, .Status, .make, .at } = @import("error.spl");
|
||||
type .{ .ByteView, .ViewResult, .FindResult } = @import("byte_view.spl");
|
||||
type .{ .Vec } = @import("vec.spl");
|
||||
type .{ .ByteBuffer } = @import("byte_buffer.spl");
|
||||
type .{ .is_digit, .is_hex_digit, .is_space } = @import("ascii.spl");
|
||||
type .{ .checked_mul } = @import("memory.spl");
|
||||
type .{ .append_i64, .append_u64 } = @import("fmt.spl");
|
||||
|
||||
type JsonKind = enum {
|
||||
@@ -29,6 +30,10 @@ type JsonChild = struct {
|
||||
val: usize,
|
||||
}
|
||||
|
||||
// 节点:kind 决定有效载荷字段,其余字段保持空。访问方必须按 kind 读取:
|
||||
// Null/True/False -> text 空、children 空;
|
||||
// Number/String -> text 为源切片、children 空;
|
||||
// Array/Object -> children 保存子项、text 空。
|
||||
type JsonNode = struct {
|
||||
kind: JsonKind,
|
||||
text: ByteView,
|
||||
@@ -57,6 +62,11 @@ type Parser = struct {
|
||||
nodes: Vec,
|
||||
}
|
||||
|
||||
// 递归/输出深度上限:防止病态输入耗尽调用栈。256 层足够覆盖正常 JSON。
|
||||
fn max_depth(): usize {
|
||||
ret 256;
|
||||
}
|
||||
|
||||
fn blank_node(kind: JsonKind): JsonNode {
|
||||
ret JsonNode {
|
||||
.kind = kind,
|
||||
@@ -78,7 +88,7 @@ fn intern_node(p: *Parser, node: JsonNode): NodeResult {
|
||||
fn node_at(doc: *Json, index: usize): *JsonNode {
|
||||
match Vec.get(&doc.nodes, index) {
|
||||
.Ok(ptr) => { ret ptr as *JsonNode; },
|
||||
.Err(error) => { ret null; },
|
||||
.Err(error) => { ret null; }, // 越界/无效池统一返回 null,调用方判空
|
||||
}
|
||||
ret null;
|
||||
}
|
||||
@@ -122,6 +132,7 @@ fn parse_string_raw(p: *Parser): ViewResult {
|
||||
}
|
||||
}
|
||||
} else if c < 32 {
|
||||
// JSON 只禁止未转义的 U+0000..U+001F;0x7F 合法,不能用 ascii.is_control。
|
||||
ret .Err(at(.InvalidFormat, p.pos));
|
||||
}
|
||||
p.pos = p.pos + 1;
|
||||
@@ -192,7 +203,8 @@ fn parse_number(p: *Parser): NodeResult {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_array(p: *Parser): NodeResult {
|
||||
fn parse_array(p: *Parser, depth: usize): NodeResult {
|
||||
if depth >= max_depth() { ret .Err(at(.OutOfRange, p.pos)); }
|
||||
p.pos = p.pos + 1; // '['
|
||||
var kids = Vec.blank(@sizeof(JsonChild), 8);
|
||||
|
||||
@@ -201,11 +213,14 @@ fn parse_array(p: *Parser): NodeResult {
|
||||
p.pos = p.pos + 1;
|
||||
var empty = blank_node(.Array);
|
||||
empty.children = kids;
|
||||
ret intern_node(p, empty);
|
||||
match intern_node(p, empty) {
|
||||
.Ok(index) => { ret .Ok(index); },
|
||||
.Err(error) => { Vec.drop(&empty.children); ret .Err(error); },
|
||||
}
|
||||
}
|
||||
|
||||
while true {
|
||||
var element = parse_value(p);
|
||||
var element = parse_value(p, depth + 1);
|
||||
var index: usize = 0;
|
||||
match element {
|
||||
.Ok(v) => { index = v; },
|
||||
@@ -232,10 +247,14 @@ fn parse_array(p: *Parser): NodeResult {
|
||||
|
||||
var node = blank_node(.Array);
|
||||
node.children = kids;
|
||||
ret intern_node(p, node);
|
||||
match intern_node(p, node) {
|
||||
.Ok(index) => { ret .Ok(index); },
|
||||
.Err(error) => { Vec.drop(&node.children); ret .Err(error); },
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_object(p: *Parser): NodeResult {
|
||||
fn parse_object(p: *Parser, depth: usize): NodeResult {
|
||||
if depth >= max_depth() { ret .Err(at(.OutOfRange, p.pos)); }
|
||||
p.pos = p.pos + 1; // '{'
|
||||
var kids = Vec.blank(@sizeof(JsonChild), 8);
|
||||
|
||||
@@ -244,7 +263,10 @@ fn parse_object(p: *Parser): NodeResult {
|
||||
p.pos = p.pos + 1;
|
||||
var empty = blank_node(.Object);
|
||||
empty.children = kids;
|
||||
ret intern_node(p, empty);
|
||||
match intern_node(p, empty) {
|
||||
.Ok(index) => { ret .Ok(index); },
|
||||
.Err(error) => { Vec.drop(&empty.children); ret .Err(error); },
|
||||
}
|
||||
}
|
||||
|
||||
while true {
|
||||
@@ -262,7 +284,7 @@ fn parse_object(p: *Parser): NodeResult {
|
||||
}
|
||||
p.pos = p.pos + 1;
|
||||
|
||||
var value = parse_value(p);
|
||||
var value = parse_value(p, depth + 1);
|
||||
var index: usize = 0;
|
||||
match value {
|
||||
.Ok(v) => { index = v; },
|
||||
@@ -290,10 +312,13 @@ fn parse_object(p: *Parser): NodeResult {
|
||||
|
||||
var node = blank_node(.Object);
|
||||
node.children = kids;
|
||||
ret intern_node(p, node);
|
||||
match intern_node(p, node) {
|
||||
.Ok(index) => { ret .Ok(index); },
|
||||
.Err(error) => { Vec.drop(&node.children); ret .Err(error); },
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_value(p: *Parser): NodeResult {
|
||||
fn parse_value(p: *Parser, depth: usize): NodeResult {
|
||||
skip_ws(p);
|
||||
var c = peek(p);
|
||||
if c < 0 { ret .Err(at(.UnexpectedEnd, p.pos)); }
|
||||
@@ -309,8 +334,8 @@ fn parse_value(p: *Parser): NodeResult {
|
||||
node.text = view;
|
||||
ret intern_node(p, node);
|
||||
}
|
||||
if ch == '{' { ret parse_object(p); } // '{'
|
||||
if ch == '[' { ret parse_array(p); } // '['
|
||||
if ch == '{' { ret parse_object(p, depth); } // '{'
|
||||
if ch == '[' { ret parse_array(p, depth); } // '['
|
||||
if ch == 't' { ret parse_literal(p, "true", .True); }
|
||||
if ch == 'f' { ret parse_literal(p, "false", .False); }
|
||||
if ch == 'n' { ret parse_literal(p, "null", .Null); }
|
||||
@@ -325,6 +350,7 @@ fn drop_node_pool(pool: *Vec) {
|
||||
var n = ptr as *JsonNode;
|
||||
Vec.drop(&n.children);
|
||||
},
|
||||
// i < pool.len 且池有效,get 不会失败;失败时无额外资源可释放。
|
||||
.Err(error) => {},
|
||||
}
|
||||
i = i + 1;
|
||||
@@ -341,7 +367,7 @@ fn parse(src: ByteView): JsonResult {
|
||||
|
||||
skip_ws(&p);
|
||||
var root: usize = 0;
|
||||
var result = parse_value(&p);
|
||||
var result = parse_value(&p, 0);
|
||||
match result {
|
||||
.Ok(index) => { root = index; },
|
||||
.Err(error) => {
|
||||
@@ -367,12 +393,6 @@ fn root_index(doc: *Json): usize {
|
||||
ret doc.root;
|
||||
}
|
||||
|
||||
fn kind_of(doc: *Json, index: usize): JsonKind {
|
||||
var n = node_at(doc, index);
|
||||
if n == null { ret .Null; }
|
||||
ret n.kind;
|
||||
}
|
||||
|
||||
// 枚举 tag 取整(stage1 枚举 == 有缺陷,用 @enum2int 比较更稳妥)。
|
||||
fn kind_code(kind: JsonKind): usize {
|
||||
ret @enum2int(kind) as usize;
|
||||
@@ -424,6 +444,7 @@ fn object_find(doc: *Json, index: usize, key: ByteView): FindResult {
|
||||
var child = ptr as *JsonChild;
|
||||
if ByteView.eq(child.key, key) { ret .Found(child.val); }
|
||||
},
|
||||
// i < count == children.len,get 不会失败。
|
||||
.Err(error) => {},
|
||||
}
|
||||
i = i + 1;
|
||||
@@ -452,10 +473,16 @@ fn emit_indent(out: *ByteBuffer, indent: usize, depth: usize): Status {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
ret ByteBuffer.append_repeat(out, 32, depth * indent);
|
||||
var spaces: usize = 0;
|
||||
match checked_mul(depth, indent) {
|
||||
.Ok(value) => { spaces = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
ret ByteBuffer.append_repeat(out, 32, spaces);
|
||||
}
|
||||
|
||||
fn emit(out: *ByteBuffer, doc: *Json, index: usize, indent: usize, depth: usize): Status {
|
||||
if depth >= max_depth() { ret .Err(make(.OutOfRange)); }
|
||||
var n = node_at(doc, index);
|
||||
if n == null { ret .Err(make(.OutOfRange)); }
|
||||
|
||||
@@ -738,19 +765,44 @@ fn test_errors() {
|
||||
expect_parse_error(parse(ByteView.from_slice("01")), 1);
|
||||
}
|
||||
|
||||
fn test_depth_limit() {
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
var i: usize = 0;
|
||||
while i < max_depth() + 8 {
|
||||
expect_ok(ByteBuffer.append_byte(&out, '['));
|
||||
i = i + 1;
|
||||
}
|
||||
i = 0;
|
||||
while i < max_depth() + 8 {
|
||||
expect_ok(ByteBuffer.append_byte(&out, ']'));
|
||||
i = i + 1;
|
||||
}
|
||||
match parse(ByteView.from_slice(ByteBuffer.as_slice(out))) {
|
||||
.Ok(doc) => { drop(&doc); @assert(false, "deep input should be rejected"); },
|
||||
.Err(error) => {
|
||||
match error.kind {
|
||||
.OutOfRange => {},
|
||||
_ => { @assert(false, "depth limit error kind"); },
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn test_construction_helpers() {
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
expect_ok(ByteBuffer.append(&out, "{\"id\":"));
|
||||
expect_ok(write_u64(&out, 18446744073709551615));
|
||||
expect_ok(ByteBuffer.append(&out, ",\"n\":"));
|
||||
expect_ok(write_int(&out, -7));
|
||||
expect_ok(ByteBuffer.append(&out, "}"));
|
||||
expect_bytes(&out, "{\"id\":18446744073709551615,\"n\":-7}");
|
||||
ByteBuffer.drop(&out);
|
||||
}
|
||||
|
||||
fn test_large_array() {
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
expect_ok(ByteBuffer.append_byte(&out, '[')); // '['
|
||||
var i: usize = 0;
|
||||
while i < 1000 {
|
||||
@@ -771,7 +823,6 @@ fn test_large_array() {
|
||||
},
|
||||
.Err(error) => { @assert(false, "parse large array"); },
|
||||
}
|
||||
ByteBuffer.drop(&out);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@@ -780,6 +831,7 @@ fn main() {
|
||||
test_roundtrip_compact();
|
||||
test_pretty();
|
||||
test_errors();
|
||||
test_depth_limit();
|
||||
test_construction_helpers();
|
||||
test_large_array();
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// LSP 传输帧:Content-Length 头 + 空行 + 正文。
|
||||
// 这里只做纯逻辑(写入/解析 ByteBuffer/ByteView),真正的 stdin/stdout I/O 由上层负责。
|
||||
|
||||
@@ -6,6 +7,8 @@ type .{ .Error, .Status, .make } = @import("error.spl");
|
||||
type .{ .ByteView } = @import("byte_view.spl");
|
||||
type .{ .ByteBuffer } = @import("byte_buffer.spl");
|
||||
type .{ .append_usize } = @import("fmt.spl");
|
||||
type .{ .checked_add, .checked_mul } = @import("memory.spl");
|
||||
type .{ .to_lower } = @import("ascii.spl");
|
||||
|
||||
type FrameResult = enum {
|
||||
Ok: usize, // 消耗的字节数
|
||||
@@ -13,17 +16,12 @@ type FrameResult = enum {
|
||||
Err: Error,
|
||||
}
|
||||
|
||||
fn ascii_lower(c: u8): u8 {
|
||||
if c >= 'A' && c <= 'Z' { ret c + ('a' - 'A'); }
|
||||
ret c;
|
||||
}
|
||||
|
||||
// data[j..] 是否以 word 开头(ASCII 大小写不敏感)。
|
||||
fn ci_prefix(data: *u8, len: usize, j: usize, word: []u8): bool {
|
||||
if j + word.len > len { ret false; }
|
||||
var i: usize = 0;
|
||||
while i < word.len {
|
||||
if ascii_lower(data[j + i]) != ascii_lower(word.ptr[i]) { ret false; }
|
||||
if to_lower(data[j + i]) != to_lower(word.ptr[i]) { ret false; }
|
||||
i = i + 1;
|
||||
}
|
||||
ret true;
|
||||
@@ -64,7 +62,16 @@ fn parse_frame(input: ByteView, body_out: *ByteBuffer): FrameResult {
|
||||
var n: usize = 0;
|
||||
var digits = 0;
|
||||
while k < term && data[k] >= '0' && data[k] <= '9' {
|
||||
n = n * 10 + (data[k] - '0') as usize;
|
||||
var digit = (data[k] - '0') as usize;
|
||||
var scaled: usize = 0;
|
||||
match checked_mul(n, 10) {
|
||||
.Ok(value) => { scaled = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
match checked_add(scaled, digit) {
|
||||
.Ok(value) => { n = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
digits = digits + 1;
|
||||
k = k + 1;
|
||||
}
|
||||
@@ -80,9 +87,15 @@ fn parse_frame(input: ByteView, body_out: *ByteBuffer): FrameResult {
|
||||
var body_start = term + 4;
|
||||
if len < body_start || content_len > len - body_start { ret .Incomplete; }
|
||||
|
||||
ByteBuffer.clear(body_out);
|
||||
// 先写入临时缓冲区,成功后再替换 body_out,失败不产生部分输出。
|
||||
var fresh = ByteBuffer.new();
|
||||
var body = data[body_start..body_start + content_len];
|
||||
match ByteBuffer.append(body_out, body) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append(&fresh, body) {
|
||||
.Ok => {},
|
||||
.Err(e) => { ByteBuffer.drop(&fresh); ret .Err(e); },
|
||||
}
|
||||
ByteBuffer.drop(body_out);
|
||||
body_out.* = ByteBuffer.take(&fresh);
|
||||
ret .Ok(body_start + content_len);
|
||||
}
|
||||
|
||||
@@ -107,11 +120,13 @@ fn expect_body(out: *ByteBuffer, expected: []u8) {
|
||||
|
||||
fn test_roundtrip() {
|
||||
var wire = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&wire); }
|
||||
expect_ok(write_frame(&wire, "{\"jsonrpc\":\"2.0\"}"));
|
||||
var bytes = ByteBuffer.as_slice(wire);
|
||||
@assert(bytes.len == 16 + 2 + 4 + 17, "wire length"); // "Content-Length: 17\r\n\r\n" + body
|
||||
|
||||
var body = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&body); }
|
||||
match parse_frame(ByteView.from_slice(bytes), &body) {
|
||||
.Ok(used) => {
|
||||
@assert(used == bytes.len, "consumed all");
|
||||
@@ -120,17 +135,17 @@ fn test_roundtrip() {
|
||||
.Incomplete => { @assert(false, "unexpected incomplete"); },
|
||||
.Err(error) => { @assert(false, "unexpected err"); },
|
||||
}
|
||||
ByteBuffer.drop(&body);
|
||||
ByteBuffer.drop(&wire);
|
||||
}
|
||||
|
||||
fn test_multiple_and_leftover() {
|
||||
var wire = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&wire); }
|
||||
expect_ok(write_frame(&wire, "abcd"));
|
||||
expect_ok(write_frame(&wire, "ef"));
|
||||
var bytes = ByteBuffer.as_slice(wire);
|
||||
|
||||
var body = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&body); }
|
||||
match parse_frame(ByteView.from_slice(bytes), &body) {
|
||||
.Ok(used) => {
|
||||
expect_body(&body, "abcd");
|
||||
@@ -142,12 +157,11 @@ fn test_multiple_and_leftover() {
|
||||
},
|
||||
_ => { @assert(false, "first frame"); },
|
||||
}
|
||||
ByteBuffer.drop(&body);
|
||||
ByteBuffer.drop(&wire);
|
||||
}
|
||||
|
||||
fn test_incomplete_and_errors() {
|
||||
var body = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&body); }
|
||||
|
||||
// 头未结束
|
||||
var h1 = "Content-Length: 3\r\n";
|
||||
@@ -174,7 +188,6 @@ fn test_incomplete_and_errors() {
|
||||
.Ok(used) => { expect_body(&body, "hi"); },
|
||||
_ => { @assert(false, "h4"); },
|
||||
}
|
||||
ByteBuffer.drop(&body);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
+124
-59
@@ -1,4 +1,5 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 开放寻址 + tombstone 哈希表。键和值都按字节复制到表自己的存储。
|
||||
// 键类型由 hash_fn / eq_fn 解释:回调收到指向键字节的指针(如 *usize / *ByteView)。
|
||||
// 普通赋值只复制句柄(与 Vec 一样),所有权转移必须使用 take。
|
||||
@@ -7,11 +8,29 @@ type .{ .Error, .Status, .make } = @import("error.spl");
|
||||
type .{ .Vec } = @import("vec.spl");
|
||||
type .{ .ByteView } = @import("byte_view.spl");
|
||||
type .{ .hash_usize, .eq_usize, .hash_view, .eq_view } = @import("hash.spl");
|
||||
type .{ .checked_add, .checked_mul, .grow_capacity } = @import("memory.spl");
|
||||
type .{ .copy } = @import("../sys/mem.spl");
|
||||
|
||||
// 槽位状态:0=EMPTY,1=OCCUPIED,2=DELETED(墓碑,仅在扩容重哈希时清除)。
|
||||
// 槽位状态:Empty/Occupied/Deleted(墓碑,仅在扩容重哈希时清除)。
|
||||
// 负载因子 70/128。
|
||||
|
||||
type SlotState = enum {
|
||||
Empty,
|
||||
Occupied,
|
||||
Deleted,
|
||||
}
|
||||
|
||||
fn state_code(state: SlotState): u8 {
|
||||
var s: SlotState = state;
|
||||
ret @enum2int(s) as u8;
|
||||
}
|
||||
|
||||
// 查找/删除结果:显式区分命中与缺失,替代 bool 哨兵。
|
||||
type LookupResult = enum {
|
||||
Found,
|
||||
NotFound,
|
||||
}
|
||||
|
||||
type Map = @self;
|
||||
|
||||
keys: Vec,
|
||||
@@ -64,12 +83,22 @@ fn slot_count(self: Map): usize {
|
||||
ret self.states.len;
|
||||
}
|
||||
|
||||
fn slot_state(self: Map, index: usize): u8 {
|
||||
ret self.states.data[index];
|
||||
fn slot_state(self: Map, index: usize): SlotState {
|
||||
var code = self.states.data[index];
|
||||
var occupied: SlotState = .Occupied;
|
||||
var deleted_state: SlotState = .Deleted;
|
||||
if code == state_code(occupied) { ret .Occupied; }
|
||||
if code == state_code(deleted_state) { ret .Deleted; }
|
||||
ret .Empty;
|
||||
}
|
||||
|
||||
fn slot_occupied(state: u8): bool {
|
||||
ret state == 1;
|
||||
fn slot_occupied(state: SlotState): bool {
|
||||
match state {
|
||||
.Occupied => { ret true; },
|
||||
.Empty => { ret false; },
|
||||
.Deleted => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn key_at(self: *Map, index: usize): *u8 {
|
||||
@@ -85,8 +114,11 @@ fn find_slot(self: *Map, key: *u8): usize {
|
||||
if self.states.cap == 0 { ret ~(0 as usize); }
|
||||
var mask = self.states.cap - 1;
|
||||
var i = self.hash_fn(key) & mask;
|
||||
while self.states.data[i] != 0 {
|
||||
if self.states.data[i] == 1 && self.eq_fn(self.keys.data + i * self.key_size, key) {
|
||||
var empty: SlotState = .Empty;
|
||||
var occupied: SlotState = .Occupied;
|
||||
while 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;
|
||||
@@ -98,12 +130,14 @@ fn find_slot(self: *Map, key: *u8): usize {
|
||||
fn place(self: *Map, key: *u8, val: *u8) {
|
||||
var mask = self.states.cap - 1;
|
||||
var i = self.hash_fn(key) & mask;
|
||||
while self.states.data[i] != 0 {
|
||||
var empty: SlotState = .Empty;
|
||||
var occupied: SlotState = .Occupied;
|
||||
while self.states.data[i] != state_code(empty) {
|
||||
i = (i + 1) & mask;
|
||||
}
|
||||
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);
|
||||
self.states.data[i] = 1;
|
||||
self.states.data[i] = state_code(occupied);
|
||||
self.size = self.size + 1;
|
||||
}
|
||||
|
||||
@@ -112,15 +146,7 @@ fn reserve(self: *Map, need: usize): Status {
|
||||
if !valid(self.*) { ret .Err(make(.InvalidArgument)); }
|
||||
if need <= self.states.cap { ret .Ok; }
|
||||
|
||||
var new_cap = self.states.cap;
|
||||
if new_cap < 8 { new_cap = 8; }
|
||||
while new_cap < need {
|
||||
if new_cap > (~(0 as usize)) / 2 {
|
||||
new_cap = need;
|
||||
break;
|
||||
}
|
||||
new_cap = new_cap * 2;
|
||||
}
|
||||
var new_cap = grow_capacity(self.states.cap, 8, need);
|
||||
|
||||
var fresh = self.*;
|
||||
fresh.keys = Vec.blank(self.key_size, self.keys.elem_align);
|
||||
@@ -145,14 +171,16 @@ fn reserve(self: *Map, need: usize): Status {
|
||||
fresh.states.len = new_cap;
|
||||
|
||||
var i: usize = 0;
|
||||
var empty: SlotState = .Empty;
|
||||
var occupied: SlotState = .Occupied;
|
||||
while i < new_cap {
|
||||
fresh.states.data[i] = 0;
|
||||
fresh.states.data[i] = state_code(empty);
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
while i < self.states.len {
|
||||
if self.states.data[i] == 1 {
|
||||
if self.states.data[i] == state_code(occupied) {
|
||||
place(&fresh, self.keys.data + i * self.key_size,
|
||||
self.vals.data + i * self.val_size);
|
||||
}
|
||||
@@ -181,42 +209,64 @@ fn put(self: *Map, key: *u8, val: *u8): Status {
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
if self.states.cap == 0 || (self.size + 1) * 128 / self.states.cap >= 70 {
|
||||
var need = self.states.cap * 2;
|
||||
if self.states.cap == 0 { need = 8; }
|
||||
match reserve(self, need) {
|
||||
if self.states.cap == 0 {
|
||||
match reserve(self, 8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
} else {
|
||||
var next_size: usize = 0;
|
||||
match checked_add(self.size, 1) {
|
||||
.Ok(value) => { next_size = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
var scaled: usize = 0;
|
||||
match checked_mul(next_size, 128) {
|
||||
.Ok(value) => { scaled = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
if scaled / self.states.cap >= 70 {
|
||||
var need: usize = 0;
|
||||
match checked_mul(self.states.cap, 2) {
|
||||
.Ok(value) => { need = value; },
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
match reserve(self, need) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ret .Err(error); },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
place(self, key, val);
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// 找到键则把值复制到 out 并返回 true;否则 out 不变并返回 false。
|
||||
fn get(self: *Map, key: *u8, out: *u8): bool {
|
||||
if !valid(self.*) || key == null || out == null { ret false; }
|
||||
// 找到键则把值复制到 out 并返回 Found;否则 out 不变并返回 NotFound。
|
||||
fn get(self: *Map, key: *u8, out: *u8): LookupResult {
|
||||
if !valid(self.*) || key == null || out == null { ret .NotFound; }
|
||||
var i = find_slot(self, key);
|
||||
if i == ~(0 as usize) { ret false; }
|
||||
if i == ~(0 as usize) { ret .NotFound; }
|
||||
copy(out as *any, (self.vals.data + i * self.val_size) as *any, self.val_size);
|
||||
ret true;
|
||||
ret .Found;
|
||||
}
|
||||
|
||||
fn del(self: *Map, key: *u8): bool {
|
||||
if !valid(self.*) || key == null { ret false; }
|
||||
fn del(self: *Map, key: *u8): LookupResult {
|
||||
if !valid(self.*) || key == null { ret .NotFound; }
|
||||
var i = find_slot(self, key);
|
||||
if i == ~(0 as usize) { ret false; }
|
||||
self.states.data[i] = 2;
|
||||
if i == ~(0 as usize) { ret .NotFound; }
|
||||
var deleted_state: SlotState = .Deleted;
|
||||
self.states.data[i] = state_code(deleted_state);
|
||||
self.size = self.size - 1;
|
||||
ret true;
|
||||
ret .Found;
|
||||
}
|
||||
|
||||
// 清空内容但保留容量。墓碑与负载计数一并复位。
|
||||
fn clear(self: *Map) {
|
||||
var i: usize = 0;
|
||||
var empty: SlotState = .Empty;
|
||||
while i < self.states.len {
|
||||
self.states.data[i] = 0;
|
||||
self.states.data[i] = state_code(empty);
|
||||
i = i + 1;
|
||||
}
|
||||
self.size = 0;
|
||||
@@ -269,6 +319,22 @@ fn count_occupied(m: *Map): usize {
|
||||
ret n;
|
||||
}
|
||||
|
||||
fn found(m: *Map, key: *u8, out: *u8): bool {
|
||||
match get(m, key, out) {
|
||||
.Found => { ret true; },
|
||||
.NotFound => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn deleted(m: *Map, key: *u8): bool {
|
||||
match del(m, key) {
|
||||
.Found => { ret true; },
|
||||
.NotFound => { ret false; },
|
||||
}
|
||||
ret false;
|
||||
}
|
||||
|
||||
fn make_int_map(): Map {
|
||||
ret new(@sizeof(usize), @alignof(usize), @sizeof(i32), @alignof(i32), hash_usize, eq_usize);
|
||||
}
|
||||
@@ -292,24 +358,26 @@ fn test_put_get_update() {
|
||||
@assert(size(m) == 1 && valid(m));
|
||||
|
||||
out = -1;
|
||||
@assert(get(&m, &key as *u8, &out as *u8) && out == 70, "insert then get");
|
||||
@assert(found(&m, &key as *u8, &out as *u8) && out == 70, "insert then get");
|
||||
|
||||
value = 71;
|
||||
expect_ok(put(&m, &key as *u8, &value as *u8));
|
||||
@assert(size(m) == 1, "update does not grow size");
|
||||
out = -1;
|
||||
@assert(get(&m, &key as *u8, &out as *u8) && out == 71, "update overwrites");
|
||||
@assert(found(&m, &key as *u8, &out as *u8) && out == 71, "update overwrites");
|
||||
|
||||
key = 8;
|
||||
out = 123;
|
||||
@assert(!get(&m, &key as *u8, &out as *u8) && out == 123, "missing key preserves out");
|
||||
@assert(!found(&m, &key as *u8, &out as *u8) && out == 123, "missing key preserves out");
|
||||
|
||||
// drop 后还有断言,故保持手动释放,不改为 defer。
|
||||
drop(&m);
|
||||
@assert(size(m) == 0 && capacity(m) == 0, "drop resets map");
|
||||
}
|
||||
|
||||
fn test_growth_and_delete() {
|
||||
var m = make_int_map();
|
||||
defer { drop(&m); }
|
||||
var i: i32 = 0;
|
||||
while i < 1000 {
|
||||
var key: usize = i as usize;
|
||||
@@ -324,7 +392,7 @@ fn test_growth_and_delete() {
|
||||
while i < 1000 {
|
||||
var key: usize = i as usize;
|
||||
var out: i32 = 0;
|
||||
@assert(get(&m, &key as *u8, &out as *u8), "get after growth");
|
||||
@assert(found(&m, &key as *u8, &out as *u8), "get after growth");
|
||||
@assert(out == i * 3 - 7, "value after growth");
|
||||
i = i + 1;
|
||||
}
|
||||
@@ -332,7 +400,7 @@ fn test_growth_and_delete() {
|
||||
i = 0;
|
||||
while i < 500 {
|
||||
var key: usize = i as usize;
|
||||
@assert(del(&m, &key as *u8));
|
||||
@assert(deleted(&m, &key as *u8));
|
||||
i = i + 1;
|
||||
}
|
||||
@assert(size(m) == 500 && count_occupied(&m) == 500, "deleted slots not occupied");
|
||||
@@ -341,7 +409,7 @@ fn test_growth_and_delete() {
|
||||
while i < 500 {
|
||||
var key: usize = i as usize;
|
||||
var out: i32 = 0;
|
||||
@assert(!get(&m, &key as *u8, &out as *u8), "deleted key missing");
|
||||
@assert(!found(&m, &key as *u8, &out as *u8), "deleted key missing");
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
@@ -351,41 +419,40 @@ fn test_growth_and_delete() {
|
||||
expect_ok(put(&m, &key as *u8, &value as *u8));
|
||||
@assert(size(m) == 501);
|
||||
var out: i32 = 0;
|
||||
@assert(get(&m, &key as *u8, &out as *u8) && out == 99, "reuse tombstone");
|
||||
@assert(found(&m, &key as *u8, &out as *u8) && out == 99, "reuse tombstone");
|
||||
|
||||
@assert(del(&m, &key as *u8), "delete key");
|
||||
@assert(deleted(&m, &key as *u8), "delete key");
|
||||
@assert(size(m) == 500);
|
||||
@assert(!get(&m, &key as *u8, &out as *u8));
|
||||
@assert(!found(&m, &key as *u8, &out as *u8));
|
||||
|
||||
clear(&m);
|
||||
@assert(size(m) == 0 && count_occupied(&m) == 0, "clear empties");
|
||||
|
||||
drop(&m);
|
||||
}
|
||||
|
||||
fn test_take_and_self_put() {
|
||||
var m = make_int_map();
|
||||
defer { drop(&m); }
|
||||
var key: usize = 5;
|
||||
var value: i32 = 55;
|
||||
expect_ok(put(&m, &key as *u8, &value as *u8));
|
||||
|
||||
var moved = take(&m);
|
||||
defer { drop(&moved); }
|
||||
@assert(size(m) == 0 && capacity(m) == 0, "take leaves source empty");
|
||||
var out: i32 = 0;
|
||||
@assert(get(&moved, &key as *u8, &out as *u8) && out == 55, "moved keeps content");
|
||||
@assert(found(&moved, &key as *u8, &out as *u8) && out == 55, "moved keeps content");
|
||||
|
||||
// 值指针可以指向表内已有的值(按字节移动,允许重叠)。
|
||||
var alias: i32 = 0;
|
||||
@assert(get(&moved, &key as *u8, &alias as *u8));
|
||||
@assert(found(&moved, &key as *u8, &alias as *u8));
|
||||
expect_ok(put(&moved, &key as *u8, &alias as *u8));
|
||||
@assert(get(&moved, &key as *u8, &out as *u8) && out == 55, "self value put");
|
||||
|
||||
drop(&moved);
|
||||
@assert(found(&moved, &key as *u8, &out as *u8) && out == 55, "self value put");
|
||||
}
|
||||
|
||||
fn test_byte_view_keys() {
|
||||
var m = new(@sizeof(ByteView), @alignof(ByteView), @sizeof(i32), @alignof(i32),
|
||||
hash_view, eq_view);
|
||||
defer { drop(&m); }
|
||||
|
||||
var alpha = ByteView.from_slice("alpha");
|
||||
var beta = ByteView.from_slice("beta");
|
||||
@@ -400,21 +467,19 @@ fn test_byte_view_keys() {
|
||||
@assert(size(m) == 3);
|
||||
|
||||
var out: i32 = 0;
|
||||
@assert(get(&m, &alpha as *u8, &out as *u8) && out == 1, "alpha");
|
||||
@assert(get(&m, &beta as *u8, &out as *u8) && out == 2, "beta");
|
||||
@assert(get(&m, &gamma as *u8, &out as *u8) && out == 3, "gamma");
|
||||
@assert(found(&m, &alpha as *u8, &out as *u8) && out == 1, "alpha");
|
||||
@assert(found(&m, &beta as *u8, &out as *u8) && out == 2, "beta");
|
||||
@assert(found(&m, &gamma as *u8, &out as *u8) && out == 3, "gamma");
|
||||
|
||||
// 内容相同的不同 ByteView 视为同一个键。
|
||||
var alpha2 = ByteView.from_slice("alpha");
|
||||
out = 0;
|
||||
@assert(get(&m, &alpha2 as *u8, &out as *u8) && out == 1, "equal content key");
|
||||
@assert(found(&m, &alpha2 as *u8, &out as *u8) && out == 1, "equal content key");
|
||||
@assert(size(m) == 3, "equal content does not insert");
|
||||
|
||||
@assert(del(&m, &beta as *u8), "delete beta");
|
||||
@assert(deleted(&m, &beta as *u8), "delete beta");
|
||||
@assert(size(m) == 2);
|
||||
@assert(!get(&m, &beta as *u8, &out as *u8));
|
||||
|
||||
drop(&m);
|
||||
@assert(!found(&m, &beta as *u8, &out as *u8));
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 尺寸计算和字节移动;不分配、不释放内存,不改变任何资源的所有权。
|
||||
|
||||
type .{ .Error, .Status, .make } = @import("error.spl");
|
||||
@@ -31,6 +32,22 @@ fn align_up(size: usize, alignment: usize): SizeResult {
|
||||
ret .Ok((size + mask) & ~mask);
|
||||
}
|
||||
|
||||
// 计算至少 need 的容量:从 current 起(不低于 min_cap)按倍增增长;
|
||||
// 无法再倍增(会溢出)时直接取 need。need 可表示时不溢出。
|
||||
fn grow_capacity(current: usize, min_cap: usize, need: usize): usize {
|
||||
var cap = current;
|
||||
if cap < min_cap { cap = min_cap; }
|
||||
var limit: usize = ~(0 as usize);
|
||||
while cap < need {
|
||||
if cap > limit / 2 {
|
||||
cap = need;
|
||||
break;
|
||||
}
|
||||
cap = cap * 2;
|
||||
}
|
||||
ret cap;
|
||||
}
|
||||
|
||||
// 将 src.len 个字节复制到 dst 的开头,允许两个切片任意重叠。
|
||||
// 零长度源不访问内存,允许空指针。所有可恢复错误均在首次写入前返回。
|
||||
// 非空源要求可读,目标前 src.len 字节要求可写,生命周期由调用方保证。
|
||||
@@ -164,6 +181,13 @@ fn test_sizes() {
|
||||
expect_size(align_up(1, highest_power), highest_power);
|
||||
expect_size(align_up(highest_power, highest_power), highest_power);
|
||||
expect_overflow(align_up(highest_power + 1, highest_power));
|
||||
|
||||
@assert(grow_capacity(0, 8, 0) == 8, "grow respects minimum");
|
||||
@assert(grow_capacity(0, 8, 5) == 8, "grow minimum covers need");
|
||||
@assert(grow_capacity(8, 8, 9) == 16, "grow doubles");
|
||||
@assert(grow_capacity(8, 8, 100) == 128, "grow repeats");
|
||||
@assert(grow_capacity(limit / 2, 8, limit) == limit, "grow exact to limit");
|
||||
@assert(grow_capacity(limit / 2 + 1, 8, limit) == limit, "grow clamps to need");
|
||||
}
|
||||
|
||||
// 穷举同一 8 字节数组内的所有合法源、目标和长度组合。
|
||||
|
||||
+63
-27
@@ -1,4 +1,5 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 路径工具(不访问文件系统):同时接受 '/' 与 '\\';支持 Windows 盘符 "C:"。
|
||||
// 输入为借用 ByteView,输出写入调用方的 ByteBuffer。
|
||||
// 注意:normalize 对相对路径的前导 ".." 采取丢弃(不保留),适用于导入路径场景。
|
||||
@@ -7,13 +8,14 @@ type .{ .Error, .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 == '\\'; // '/' '\\'
|
||||
}
|
||||
|
||||
fn is_drive(path: ByteView): bool {
|
||||
ret path.len >= 2 && path.ptr[1] == ':'; // ':'
|
||||
ret path.len >= 2 && is_alpha(path.ptr[0]) && path.ptr[1] == ':'; // 盘符首字符必须是字母
|
||||
}
|
||||
|
||||
fn is_absolute(path: ByteView): bool {
|
||||
@@ -33,6 +35,7 @@ fn last_sep(path: ByteView): usize {
|
||||
}
|
||||
|
||||
// 目录部分(不含末尾分隔符);根 "/" 返回 "/";无分隔符返回空。
|
||||
// 下标均由 last_sep 保证在界内,slice 不会失败;Err 分支仅作防御。
|
||||
fn dirname(path: ByteView): ByteView {
|
||||
var idx = last_sep(path);
|
||||
if idx == ~(0 as usize) { ret ByteView.empty(); }
|
||||
@@ -40,13 +43,13 @@ fn dirname(path: ByteView): ByteView {
|
||||
var r = ByteView.slice(path, 0, 1);
|
||||
match r {
|
||||
.Ok(v) => { ret v; },
|
||||
.Err(error) => { ret ByteView.empty(); },
|
||||
.Err(error) => { ret ByteView.empty(); }, // 防御:索引已界内
|
||||
}
|
||||
}
|
||||
var r = ByteView.slice(path, 0, idx);
|
||||
match r {
|
||||
.Ok(v) => { ret v; },
|
||||
.Err(error) => { ret ByteView.empty(); },
|
||||
.Err(error) => { ret ByteView.empty(); }, // 防御:索引已界内
|
||||
}
|
||||
ret ByteView.empty();
|
||||
}
|
||||
@@ -57,7 +60,7 @@ fn basename(path: ByteView): ByteView {
|
||||
var r = ByteView.slice(path, idx + 1, path.len);
|
||||
match r {
|
||||
.Ok(v) => { ret v; },
|
||||
.Err(error) => { ret ByteView.empty(); },
|
||||
.Err(error) => { ret ByteView.empty(); }, // 防御:idx+1 <= len
|
||||
}
|
||||
ret ByteView.empty();
|
||||
}
|
||||
@@ -73,39 +76,68 @@ fn extension(path: ByteView): ByteView {
|
||||
var r = ByteView.slice(base, i + 1, base.len);
|
||||
match r {
|
||||
.Ok(v) => { ret v; },
|
||||
.Err(error) => { ret ByteView.empty(); },
|
||||
.Err(error) => { ret ByteView.empty(); }, // 防御:i+1 <= base.len
|
||||
}
|
||||
}
|
||||
}
|
||||
ret ByteView.empty();
|
||||
}
|
||||
|
||||
// out = dir + sep + rel(rel 绝对时直接使用 rel);先清空 out。
|
||||
// out = dir + sep + rel(rel 绝对时直接使用 rel);成功后才替换 out,失败保持 out 不变。
|
||||
fn join_into(out: *ByteBuffer, dir: ByteView, rel: ByteView): Status {
|
||||
ByteBuffer.clear(out);
|
||||
var fresh = ByteBuffer.new();
|
||||
if is_absolute(rel) {
|
||||
ret ByteBuffer.append(out, ByteView.as_slice(rel));
|
||||
match ByteBuffer.append(&fresh, ByteView.as_slice(rel)) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); },
|
||||
}
|
||||
ByteBuffer.drop(out);
|
||||
out.* = ByteBuffer.take(&fresh);
|
||||
ret .Ok;
|
||||
}
|
||||
match ByteBuffer.append(&fresh, ByteView.as_slice(dir)) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); },
|
||||
}
|
||||
match ByteBuffer.append(out, ByteView.as_slice(dir)) { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
if dir.len != 0 && !is_sep(dir.ptr[dir.len - 1]) {
|
||||
match ByteBuffer.append_byte(out, '/') { .Ok => {}, .Err(e) => { ret .Err(e); } }
|
||||
match ByteBuffer.append_byte(&fresh, '/') {
|
||||
.Ok => {},
|
||||
.Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); },
|
||||
}
|
||||
}
|
||||
ret ByteBuffer.append(out, ByteView.as_slice(rel));
|
||||
match ByteBuffer.append(&fresh, ByteView.as_slice(rel)) {
|
||||
.Ok => {},
|
||||
.Err(error) => { ByteBuffer.drop(&fresh); ret .Err(error); },
|
||||
}
|
||||
ByteBuffer.drop(out);
|
||||
out.* = ByteBuffer.take(&fresh);
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
// 折叠重复分隔符、'.',及'..'(回退上一段;相对路径前导 '..' 丢弃)。
|
||||
// 成功后才替换 out,失败保持 out 不变。
|
||||
fn normalize_into(out: *ByteBuffer, path: ByteView): Status {
|
||||
ByteBuffer.clear(out);
|
||||
if path.len == 0 { ret .Ok; }
|
||||
var fresh = ByteBuffer.new();
|
||||
if path.len == 0 {
|
||||
ByteBuffer.drop(out);
|
||||
out.* = ByteBuffer.take(&fresh);
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
var starts = Vec.blank(@sizeof(usize), 8);
|
||||
var i: usize = 0;
|
||||
|
||||
if is_sep(path.ptr[0]) {
|
||||
match ByteBuffer.append_byte(out, '/') { .Ok => {}, .Err(e) => { Vec.drop(&starts); ret .Err(e); } }
|
||||
match ByteBuffer.append_byte(&fresh, '/') {
|
||||
.Ok => {}, .Err(e) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(e); },
|
||||
}
|
||||
} else if is_drive(path) {
|
||||
match ByteBuffer.append_byte(out, path.ptr[0]) { .Ok => {}, .Err(e) => { Vec.drop(&starts); ret .Err(e); } }
|
||||
match ByteBuffer.append_byte(out, ':') { .Ok => {}, .Err(e) => { Vec.drop(&starts); ret .Err(e); } }
|
||||
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; // 跳过盘符
|
||||
}
|
||||
|
||||
@@ -119,7 +151,7 @@ fn normalize_into(out: *ByteBuffer, path: ByteView): Status {
|
||||
var seg = ByteView.empty();
|
||||
match seg_result {
|
||||
.Ok(v) => { seg = v; },
|
||||
.Err(error) => { Vec.drop(&starts); ret .Err(error); },
|
||||
.Err(error) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(error); },
|
||||
}
|
||||
|
||||
if ByteView.eq(seg, ByteView.from_slice(".")) { continue; }
|
||||
@@ -127,30 +159,34 @@ fn normalize_into(out: *ByteBuffer, path: ByteView): Status {
|
||||
if starts.len > 0 {
|
||||
var idx = starts.len - 1;
|
||||
match Vec.get(&starts, idx) {
|
||||
.Ok(p) => { out.storage.len = (p as *usize).*; },
|
||||
.Err(error) => {},
|
||||
.Ok(p) => { fresh.storage.len = (p as *usize).*; },
|
||||
.Err(error) => {}, // starts.len 已保证 idx 在界内,get 不会失败
|
||||
}
|
||||
starts.len = starts.len - 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var comp_start = out.storage.len;
|
||||
if comp_start > 0 && !is_sep(out.storage.data[comp_start - 1]) {
|
||||
match ByteBuffer.append_byte(out, '/') { .Ok => {}, .Err(e) => { Vec.drop(&starts); ret .Err(e); } }
|
||||
var comp_start = fresh.storage.len;
|
||||
if comp_start > 0 && !is_sep(fresh.storage.data[comp_start - 1]) {
|
||||
match ByteBuffer.append_byte(&fresh, '/') {
|
||||
.Ok => {}, .Err(e) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(e); },
|
||||
}
|
||||
}
|
||||
var recorded = comp_start;
|
||||
match Vec.push(&starts, &recorded as *u8) {
|
||||
.Ok => {},
|
||||
.Err(error) => { Vec.drop(&starts); ret .Err(error); },
|
||||
.Err(error) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(error); },
|
||||
}
|
||||
match ByteBuffer.append(out, ByteView.as_slice(seg)) {
|
||||
match ByteBuffer.append(&fresh, ByteView.as_slice(seg)) {
|
||||
.Ok => {},
|
||||
.Err(error) => { Vec.drop(&starts); ret .Err(error); },
|
||||
.Err(error) => { Vec.drop(&starts); ByteBuffer.drop(&fresh); ret .Err(error); },
|
||||
}
|
||||
}
|
||||
|
||||
Vec.drop(&starts);
|
||||
ByteBuffer.drop(out);
|
||||
out.* = ByteBuffer.take(&fresh);
|
||||
ret .Ok;
|
||||
}
|
||||
|
||||
@@ -200,17 +236,18 @@ fn test_parts() {
|
||||
|
||||
fn test_join() {
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
expect_ok(join_into(&out, view("stage2/utils"), view("vec.spl")));
|
||||
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");
|
||||
ByteBuffer.drop(&out);
|
||||
}
|
||||
|
||||
fn test_normalize() {
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
expect_ok(normalize_into(&out, view("a/b/../c")));
|
||||
expect_out(&out, "a/c");
|
||||
expect_ok(normalize_into(&out, view("./a//b")));
|
||||
@@ -221,7 +258,6 @@ fn test_normalize() {
|
||||
expect_out(&out, "C:/b");
|
||||
expect_ok(normalize_into(&out, view("a/..")));
|
||||
expect_out(&out, "");
|
||||
ByteBuffer.drop(&out);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
@@ -173,7 +173,10 @@ fn set_at(set: *Sources, index: usize): *Source {
|
||||
|
||||
fn set_find(set: *Sources, fname: ByteView): FindResult {
|
||||
var out: usize = 0;
|
||||
if Map.get(&set.by_name, &fname as *u8, &out as *u8) { ret .Found(out); }
|
||||
match Map.get(&set.by_name, &fname as *u8, &out as *u8) {
|
||||
.Found => { ret .Found(out); },
|
||||
.NotFound => {},
|
||||
}
|
||||
ret .NotFound;
|
||||
}
|
||||
|
||||
|
||||
+8
-10
@@ -138,6 +138,7 @@ fn expect_text(s: *Str, expected: []u8) {
|
||||
|
||||
fn test_construction() {
|
||||
var s = new();
|
||||
defer { drop(&s); }
|
||||
@assert(is_empty(s) && size(s) == 0, "empty new");
|
||||
expect_ok(append(&s, "hello"));
|
||||
expect_ok(append_byte(&s, ' '));
|
||||
@@ -154,18 +155,19 @@ fn test_construction() {
|
||||
},
|
||||
.Err(error) => { @assert(false, "from_slice"); },
|
||||
}
|
||||
drop(&s);
|
||||
}
|
||||
|
||||
fn test_compare_and_search() {
|
||||
var hello_r = from_slice("hello");
|
||||
var hello: Str = new();
|
||||
defer { drop(&hello); }
|
||||
match hello_r {
|
||||
.Ok(v) => { hello = v; },
|
||||
.Err(error) => { @assert(false, "hello"); },
|
||||
}
|
||||
var help_r = from_slice("help");
|
||||
var help: Str = new();
|
||||
defer { drop(&help); }
|
||||
match help_r {
|
||||
.Ok(v) => { help = v; },
|
||||
.Err(error) => { @assert(false, "help"); },
|
||||
@@ -181,12 +183,14 @@ fn test_compare_and_search() {
|
||||
@assert(ends_with(hello, hello), "ends_with");
|
||||
var pre_r = from_slice("he");
|
||||
var pre: Str = new();
|
||||
defer { drop(&pre); }
|
||||
match pre_r { .Ok(v) => { pre = v; }, .Err(e) => { @assert(false, "pre"); } }
|
||||
@assert(starts_with(hello, pre), "starts he");
|
||||
@assert(!ends_with(hello, pre), "!ends he");
|
||||
|
||||
var needle_r = from_slice("ll");
|
||||
var needle: Str = new();
|
||||
defer { drop(&needle); }
|
||||
match needle_r { .Ok(v) => { needle = v; }, .Err(e) => { @assert(false, "needle"); } }
|
||||
match find(hello, needle) {
|
||||
.Found(index) => { @assert(index == 2, "find ll"); },
|
||||
@@ -196,18 +200,15 @@ fn test_compare_and_search() {
|
||||
.Found(index) => { @assert(index == 0, "find he"); },
|
||||
.NotFound => { @assert(false, "he not found"); },
|
||||
}
|
||||
|
||||
drop(&needle);
|
||||
drop(&pre);
|
||||
drop(&help);
|
||||
drop(&hello);
|
||||
}
|
||||
|
||||
fn test_clone_take_and_failures() {
|
||||
var source = new();
|
||||
defer { drop(&source); }
|
||||
expect_ok(append(&source, "owned"));
|
||||
|
||||
var copy = new();
|
||||
defer { drop(©); }
|
||||
expect_ok(clone(©, source));
|
||||
expect_text(©, "owned");
|
||||
expect_ok(append_byte(&source, '!')); // '!'
|
||||
@@ -219,16 +220,13 @@ fn test_clone_take_and_failures() {
|
||||
expect_text(©, "owned");
|
||||
|
||||
var moved = take(©);
|
||||
defer { drop(&moved); }
|
||||
@assert(ByteBuffer.size(copy.storage) == 0 && ByteBuffer.capacity(copy.storage) == 0,
|
||||
"take empties source");
|
||||
expect_text(&moved, "owned");
|
||||
|
||||
clear(&moved);
|
||||
@assert(is_empty(moved) && ByteBuffer.capacity(moved.storage) != 0, "clear keeps capacity");
|
||||
|
||||
drop(&moved);
|
||||
drop(©);
|
||||
drop(&source);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
+88
-49
@@ -1,60 +1,70 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// UTF-8 工具:解码/编码、校验、码点数、以及 UTF-8 字节偏移 <-> UTF-16 code unit 偏移
|
||||
// (LSP 位置基于 UTF-16)。严格校验过长编码与代理区。
|
||||
|
||||
type .{ .Error, .Status, .make } = @import("error.spl");
|
||||
type .{ .ByteBuffer } = @import("byte_buffer.spl");
|
||||
|
||||
// 由首字节推断序列长度;非法/续字节返回 0。
|
||||
fn seq_len(b: u8): usize {
|
||||
if b < 0x80 { ret 1; }
|
||||
if b < 0xC0 { ret 0; } // 续字节
|
||||
if b < 0xE0 { ret 2; }
|
||||
if b < 0xF0 { ret 3; }
|
||||
if b < 0xF8 { ret 4; }
|
||||
ret 0;
|
||||
// 解码结果:Ok 携带消耗字节数;明确区分越界/截断/非法,避免 0 哨兵歧义。
|
||||
type DecodeResult = enum {
|
||||
Ok: usize,
|
||||
Eof, // index 已越过输入
|
||||
Truncated, // 序列尾部字节不足
|
||||
Invalid, // 续字节/过长编码/代理区/超出码点范围
|
||||
}
|
||||
|
||||
// 从 index 解码一个码点到 out_cp,返回消耗字节数;非法返回 0。
|
||||
fn decode(data: *u8, len: usize, index: usize, out_cp: *u32): usize {
|
||||
if index >= len { ret 0; }
|
||||
// 从 index 解码一个码点到 out_cp;返回 DecodeResult。
|
||||
fn decode(data: *u8, len: usize, index: usize, out_cp: *u32): DecodeResult {
|
||||
if index >= len { ret .Eof; }
|
||||
var b0 = data[index];
|
||||
|
||||
if b0 < 0x80 {
|
||||
out_cp.* = b0 as u32;
|
||||
ret 1;
|
||||
ret .Ok(1);
|
||||
}
|
||||
if b0 < 0xC2 { ret 0; } // 续字节或过长编码的前导
|
||||
if b0 < 0xC2 { ret .Invalid; } // 续字节或过长编码的前导
|
||||
|
||||
if b0 < 0xE0 {
|
||||
if index + 1 >= len { ret 0; }
|
||||
if index + 1 >= len { ret .Truncated; }
|
||||
var b1 = data[index + 1];
|
||||
if (b1 & 0xC0) != 0x80 { ret 0; }
|
||||
if (b1 & 0xC0) != 0x80 { ret .Invalid; }
|
||||
out_cp.* = (((b0 & 0x1F) as u32) << 6) | ((b1 & 0x3F) as u32);
|
||||
ret 2;
|
||||
ret .Ok(2);
|
||||
}
|
||||
if b0 < 0xF0 {
|
||||
if index + 2 >= len { ret 0; }
|
||||
if index + 2 >= len { ret .Truncated; }
|
||||
var b1 = data[index + 1];
|
||||
var b2 = data[index + 2];
|
||||
if (b1 & 0xC0) != 0x80 || (b2 & 0xC0) != 0x80 { ret 0; }
|
||||
if (b1 & 0xC0) != 0x80 || (b2 & 0xC0) != 0x80 { ret .Invalid; }
|
||||
var cp = (((b0 & 0x0F) as u32) << 12) | (((b1 & 0x3F) as u32) << 6) | ((b2 & 0x3F) as u32);
|
||||
if cp < 0x800 { ret 0; } // 过长
|
||||
if cp >= 0xD800 && cp <= 0xDFFF { ret 0; } // 代理区
|
||||
if cp < 0x800 { ret .Invalid; } // 过长
|
||||
if cp >= 0xD800 && cp <= 0xDFFF { ret .Invalid; } // 代理区
|
||||
out_cp.* = cp;
|
||||
ret 3;
|
||||
ret .Ok(3);
|
||||
}
|
||||
if b0 < 0xF5 {
|
||||
if index + 3 >= len { ret 0; }
|
||||
if index + 3 >= len { ret .Truncated; }
|
||||
var b1 = data[index + 1];
|
||||
var b2 = data[index + 2];
|
||||
var b3 = data[index + 3];
|
||||
if (b1 & 0xC0) != 0x80 || (b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80 { ret 0; }
|
||||
if (b1 & 0xC0) != 0x80 || (b2 & 0xC0) != 0x80 || (b3 & 0xC0) != 0x80 { ret .Invalid; }
|
||||
var cp = (((b0 & 0x07) as u32) << 18) | (((b1 & 0x3F) as u32) << 12) |
|
||||
(((b2 & 0x3F) as u32) << 6) | ((b3 & 0x3F) as u32);
|
||||
if cp < 0x10000 || cp > 0x10FFFF { ret 0; }
|
||||
if cp < 0x10000 || cp > 0x10FFFF { ret .Invalid; }
|
||||
out_cp.* = cp;
|
||||
ret 4;
|
||||
ret .Ok(4);
|
||||
}
|
||||
ret .Invalid;
|
||||
}
|
||||
|
||||
// 测试辅助:把 DecodeResult 归约为“消耗字节数”,非 Ok 视为 0。
|
||||
fn decode_used(data: *u8, len: usize, index: usize, out_cp: *u32): usize {
|
||||
match decode(data, len, index, out_cp) {
|
||||
.Ok(used) => { ret used; },
|
||||
.Eof => { ret 0; },
|
||||
.Truncated => { ret 0; },
|
||||
.Invalid => { ret 0; },
|
||||
}
|
||||
ret 0;
|
||||
}
|
||||
@@ -63,9 +73,10 @@ fn valid(data: *u8, len: usize): bool {
|
||||
var i: usize = 0;
|
||||
var cp: u32 = 0;
|
||||
while i < len {
|
||||
var n = decode(data, len, i, &cp);
|
||||
if n == 0 { ret false; }
|
||||
i = i + n;
|
||||
match decode(data, len, i, &cp) {
|
||||
.Ok(used) => { i = i + used; },
|
||||
_ => { ret false; },
|
||||
}
|
||||
}
|
||||
ret true;
|
||||
}
|
||||
@@ -107,10 +118,10 @@ fn count_codepoints(data: *u8, len: usize): usize {
|
||||
var n: usize = 0;
|
||||
var cp: u32 = 0;
|
||||
while i < len {
|
||||
var used = decode(data, len, i, &cp);
|
||||
if used == 0 { ret n; }
|
||||
i = i + used;
|
||||
n = n + 1;
|
||||
match decode(data, len, i, &cp) {
|
||||
.Ok(used) => { i = i + used; n = n + 1; },
|
||||
_ => { ret n; },
|
||||
}
|
||||
}
|
||||
ret n;
|
||||
}
|
||||
@@ -121,10 +132,13 @@ fn utf16_units(data: *u8, len: usize): usize {
|
||||
var n: usize = 0;
|
||||
var cp: u32 = 0;
|
||||
while i < len {
|
||||
var used = decode(data, len, i, &cp);
|
||||
if used == 0 { ret n; }
|
||||
if cp >= 0x10000 { n = n + 2; } else { n = n + 1; }
|
||||
i = i + used;
|
||||
match decode(data, len, i, &cp) {
|
||||
.Ok(used) => {
|
||||
if cp >= 0x10000 { n = n + 2; } else { n = n + 1; }
|
||||
i = i + used;
|
||||
},
|
||||
_ => { ret n; },
|
||||
}
|
||||
}
|
||||
ret n;
|
||||
}
|
||||
@@ -135,10 +149,13 @@ fn byte_to_utf16(data: *u8, len: usize, byte_off: usize): usize {
|
||||
var n: usize = 0;
|
||||
var cp: u32 = 0;
|
||||
while i < len && i < byte_off {
|
||||
var used = decode(data, len, i, &cp);
|
||||
if used == 0 { ret n; }
|
||||
if cp >= 0x10000 { n = n + 2; } else { n = n + 1; }
|
||||
i = i + used;
|
||||
match decode(data, len, i, &cp) {
|
||||
.Ok(used) => {
|
||||
if cp >= 0x10000 { n = n + 2; } else { n = n + 1; }
|
||||
i = i + used;
|
||||
},
|
||||
_ => { ret n; },
|
||||
}
|
||||
}
|
||||
ret n;
|
||||
}
|
||||
@@ -150,8 +167,11 @@ fn utf16_to_byte(data: *u8, len: usize, utf16_off: usize): usize {
|
||||
var cp: u32 = 0;
|
||||
while i < len {
|
||||
if n >= utf16_off { ret i; }
|
||||
var used = decode(data, len, i, &cp);
|
||||
if used == 0 { ret i; }
|
||||
var used: usize = 0;
|
||||
match decode(data, len, i, &cp) {
|
||||
.Ok(value) => { used = value; },
|
||||
_ => { ret i; },
|
||||
}
|
||||
var units: usize = 1;
|
||||
if cp >= 0x10000 { units = 2; }
|
||||
if n + units > utf16_off { ret i; }
|
||||
@@ -172,6 +192,7 @@ fn expect_ok(result: Status) {
|
||||
|
||||
fn test_decode_encode_roundtrip() {
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
expect_ok(encode_into(&out, 0x41)); // 'A'
|
||||
expect_ok(encode_into(&out, 0xE9)); // é
|
||||
expect_ok(encode_into(&out, 0x20AC)); // €
|
||||
@@ -188,11 +209,10 @@ fn test_decode_encode_roundtrip() {
|
||||
@assert(utf16_units(bytes.ptr, bytes.len) == 1 + 1 + 1 + 2, "utf16 units");
|
||||
|
||||
var cp: u32 = 0;
|
||||
@assert(decode(bytes.ptr, bytes.len, 0, &cp) == 1 && cp == 0x41, "dec A");
|
||||
@assert(decode(bytes.ptr, bytes.len, 1, &cp) == 2 && cp == 0xE9, "dec e");
|
||||
@assert(decode(bytes.ptr, bytes.len, 3, &cp) == 3 && cp == 0x20AC, "dec euro");
|
||||
@assert(decode(bytes.ptr, bytes.len, 6, &cp) == 4 && cp == 0x1F600, "dec emoji");
|
||||
ByteBuffer.drop(&out);
|
||||
@assert(decode_used(bytes.ptr, bytes.len, 0, &cp) == 1 && cp == 0x41, "dec A");
|
||||
@assert(decode_used(bytes.ptr, bytes.len, 1, &cp) == 2 && cp == 0xE9, "dec e");
|
||||
@assert(decode_used(bytes.ptr, bytes.len, 3, &cp) == 3 && cp == 0x20AC, "dec euro");
|
||||
@assert(decode_used(bytes.ptr, bytes.len, 6, &cp) == 4 && cp == 0x1F600, "dec emoji");
|
||||
}
|
||||
|
||||
fn test_invalid() {
|
||||
@@ -212,7 +232,27 @@ fn test_invalid() {
|
||||
@assert(!valid(s4.ptr, s4.len), "lone cont");
|
||||
@assert(!valid(s5.ptr, s5.len), "truncated");
|
||||
|
||||
// 新 ADT:越界/截断/非法可区分。
|
||||
var cp: u32 = 0;
|
||||
match decode(s5.ptr, s5.len, 0, &cp) {
|
||||
.Truncated => {},
|
||||
_ => { @assert(false, "truncated sequence should be Truncated"); },
|
||||
}
|
||||
match decode(s5.ptr, s5.len, s5.len, &cp) {
|
||||
.Eof => {},
|
||||
_ => { @assert(false, "past end should be Eof"); },
|
||||
}
|
||||
match decode(s1.ptr, s1.len, 0, &cp) {
|
||||
.Invalid => {},
|
||||
_ => { @assert(false, "overlong should be Invalid"); },
|
||||
}
|
||||
match decode(s4.ptr, s4.len, 0, &cp) {
|
||||
.Invalid => {},
|
||||
_ => { @assert(false, "lone continuation should be Invalid"); },
|
||||
}
|
||||
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
match encode_into(&out, 0xD800) {
|
||||
.Ok => { @assert(false, "surrogate encode"); },
|
||||
.Err(error) => {},
|
||||
@@ -221,11 +261,11 @@ fn test_invalid() {
|
||||
.Ok => { @assert(false, "out of range encode"); },
|
||||
.Err(error) => {},
|
||||
}
|
||||
ByteBuffer.drop(&out);
|
||||
}
|
||||
|
||||
fn test_offsets() {
|
||||
var out = ByteBuffer.new();
|
||||
defer { ByteBuffer.drop(&out); }
|
||||
expect_ok(encode_into(&out, 0x41)); // byte 0, utf16 0
|
||||
expect_ok(encode_into(&out, 0x1F600)); // bytes 1..4, utf16 1..2
|
||||
expect_ok(encode_into(&out, 0x42)); // byte 5, utf16 3
|
||||
@@ -241,7 +281,6 @@ fn test_offsets() {
|
||||
@assert(utf16_to_byte(b.ptr, b.len, 2) == 1, "u2 stays in emoji");
|
||||
@assert(utf16_to_byte(b.ptr, b.len, 3) == 5, "u3");
|
||||
@assert(utf16_to_byte(b.ptr, b.len, 99) == 6, "clamp");
|
||||
ByteBuffer.drop(&out);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
+11
-16
@@ -1,9 +1,10 @@
|
||||
// Expect retcode = 0
|
||||
|
||||
// 类型擦除的动态数组。元素按值复制,数组只释放自身存储,不释放元素内部资源。
|
||||
// Vec 拥有 raw 指向的分配;普通赋值会复制所有权,所有权转移必须使用 take。
|
||||
|
||||
type .{ .Error, .Kind, .Status } = @import("error.spl");
|
||||
type .{ .SizeResult, .checked_add, .checked_mul, .move } = @import("memory.spl");
|
||||
type .{ .SizeResult, .checked_add, .checked_mul, .grow_capacity, .move } = @import("memory.spl");
|
||||
type .{ .alloc, .copy, .free } = @import("../sys/mem.spl");
|
||||
|
||||
type Vec = @self;
|
||||
@@ -87,6 +88,9 @@ fn allocate(capacity: usize, elem_size: usize, elem_align: usize): VecResult {
|
||||
}
|
||||
|
||||
var raw = alloc(total) as *u8;
|
||||
if raw == null {
|
||||
ret .Err(Error.make(.Overflow));
|
||||
}
|
||||
var address = raw as usize;
|
||||
var mask = elem_align - 1;
|
||||
if address > (~(0 as usize)) - mask {
|
||||
@@ -109,15 +113,7 @@ fn reserve(self: *Vec, need: usize): Status {
|
||||
if !valid(self.*) { ret status_error(.InvalidArgument); }
|
||||
if need <= self.cap { ret .Ok; }
|
||||
|
||||
var new_cap = self.cap;
|
||||
if new_cap < 8 { new_cap = 8; }
|
||||
while new_cap < need {
|
||||
if new_cap > (~(0 as usize)) / 2 {
|
||||
new_cap = need;
|
||||
break;
|
||||
}
|
||||
new_cap = new_cap * 2;
|
||||
}
|
||||
var new_cap = grow_capacity(self.cap, 8, need);
|
||||
|
||||
var fresh: Vec = undefined;
|
||||
match allocate(new_cap, self.elem_size, self.elem_align) {
|
||||
@@ -337,6 +333,7 @@ fn test_construction_and_alignment() {
|
||||
|
||||
fn test_growth_and_access() {
|
||||
var v = make_vec(@sizeof(i32), @alignof(i32));
|
||||
defer { drop(&v); }
|
||||
var i: i32 = 0;
|
||||
while i < 1000 {
|
||||
var value = i * 3 - 7;
|
||||
@@ -358,11 +355,11 @@ fn test_growth_and_access() {
|
||||
var popped: i32 = 0;
|
||||
expect_ok(pop(&v, &popped as *u8));
|
||||
@assert(popped == 2990 && v.len == 999, "pop copies then removes");
|
||||
drop(&v);
|
||||
}
|
||||
|
||||
fn test_self_reference() {
|
||||
var v = make_vec(@sizeof(i32), @alignof(i32));
|
||||
defer { drop(&v); }
|
||||
var i: i32 = 0;
|
||||
while i < 8 {
|
||||
expect_ok(push(&v, &i as *u8));
|
||||
@@ -391,17 +388,18 @@ fn test_self_reference() {
|
||||
}
|
||||
expect_ok(set(&v, 0, last_ptr));
|
||||
expect_i32(&v, 0, 7);
|
||||
drop(&v);
|
||||
}
|
||||
|
||||
fn test_clone_take_and_failures() {
|
||||
var source = make_vec(@sizeof(i32), @alignof(i32));
|
||||
defer { drop(&source); }
|
||||
var one: i32 = 1;
|
||||
var two: i32 = 2;
|
||||
expect_ok(push(&source, &one as *u8));
|
||||
expect_ok(push(&source, &two as *u8));
|
||||
|
||||
var copy = make_vec(@sizeof(i32), @alignof(i32));
|
||||
defer { drop(©); }
|
||||
expect_ok(clone(©, source));
|
||||
expect_i32(©, 0, 1);
|
||||
one = 99;
|
||||
@@ -415,6 +413,7 @@ fn test_clone_take_and_failures() {
|
||||
expect_i32(©, 1, 2);
|
||||
|
||||
var moved = take(©);
|
||||
defer { drop(&moved); }
|
||||
@assert(copy.raw == null && copy.data == null && copy.len == 0 && copy.cap == 0);
|
||||
expect_i32(&moved, 0, 1);
|
||||
|
||||
@@ -430,10 +429,6 @@ fn test_clone_take_and_failures() {
|
||||
var output: i32 = 55;
|
||||
expect_error(pop(&moved, &output as *u8), false);
|
||||
@assert(output == 55, "failed pop preserves output");
|
||||
|
||||
drop(&moved);
|
||||
drop(©);
|
||||
drop(&source);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
Reference in new issue
Block a user