stage2 更多的工具库 stage1 修复问题
This commit is contained in:
1 parent
16a1f030d4
commit
b3e38ba7e4
28 files changed
+2274
-523
No files matched your search
+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() {
|
||||
|
||||
Reference in new issue
Block a user