stage2 更多的工具库 stage1 修复问题

This commit is contained in:
zzy committed 2026-09-18 10:10:53 +08:00
1 parent 16a1f030d4
commit b3e38ba7e4
28 files changed
+2274 -523

No files matched your search

+88 -49
View File
@@ -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() {