types/kernel/vm/builtins: JsString UTF-16 载荷层(FlatU16 变体 + 单元入口)——原 UTF-8 载荷只存 well-formed 串、孤立 surrogate lossy 转 U+FFFD 回写,FromCodePoint/属性转义与字符串面码点语义皆破;现载荷直存 [u64;3](Flat well-formed / FlatU16 单元序列 / Cons 拼接),utf16_len 独立 32 位字,32B 布局锚保持;from_units 智能路由(含孤立→FlatU16),as_str 仅限 Flat,units/units_borrowed 单元借用,as_lossy_str 宽容读;string_forge 编解码 surrogate 感知(孤立 surrogate 逃逸 hex4 编码、解码展开 astral 代理对、string_ptr 由单元物化);VmHost 单元物化/分配/查询入口 + runtime_api 单元 API;字符串内置全法单元化(charAt/slice/substr/split/replace/search/match/matchAll 单元定位;空模式边界:search→0、match→[""]、replace 插 0 位、matchAll 游标耗尽守卫;接收者 ToString 错误原样传播);正则内置单元化(exec/test/search/symbol_* 单元文本,替换串 \u{...} 与孤立 surrogate 文本处理,lastIndex 与 matchAll 游标单元化);字符串迭代器码点化(高代理+后续低代理成对产出,astral 值回真实码点);String(Symbol) 抛 TypeError;回归钉更新五处(charAt/split/slice/substr 单元精确语义 + dispose 钉改 TypeError 断言);四 crate 全绿(oxide_vm 98 套件 2403 测)+ clippy/fmt 零新增

This commit is contained in:
2026-09-13 22:42:10 +00:00
parent 8a35dcd06e
commit 3acac721b0
17 changed files with 2177 additions and 1186 deletions
@@ -528,9 +528,9 @@ fn symbol_dispose_and_async_dispose_registered() {
let result = eval(&mut vm, src).unwrap();
assert_eq!(result.as_bool(), expected, "for {}", src);
}
// 描述性名称经 String 构造器反射(well_known_symbol_name 表)。
let desc = eval(&mut vm, "String(Symbol.dispose)").unwrap();
assert_eq!(to_str(&vm, desc), "Symbol(Symbol.dispose)");
// ToString(Symbol) 按规格抛 TypeError(String 构造器无描述性名称通道)。
let err = eval(&mut vm, "try { String(Symbol.dispose); 'no-throw' } catch (e) { e.name }").unwrap();
assert_eq!(to_str(&vm, err), "TypeError");
}
#[test]
+54 -19
View File
@@ -940,25 +940,45 @@ fn string_methods_null_receiver_throw_type_error() {
#[test]
fn string_slice_astral_character_indices() {
// UTF-16 单元索引语义:astral 字符占 2 单元,切片边界落在代理对中间时
// 保留所在 astral 字符(孤立代理无法在 UTF-8 表示,见架构限制)。
// 单元原样保留(单元载荷可承载孤立 surrogate,规格口径)。
let mut vm = Vm::new();
let s = eval(&mut vm, "'\\u{1F600}ab'.slice(1, 3)").unwrap();
assert_eq!(to_str(&vm, s), "\u{1F600}a");
let s = eval(&mut vm, "'\\u{1F600}ab'.slice(0, 1)").unwrap();
assert_eq!(to_str(&vm, s), "\u{1F600}");
let r = eval(
&mut vm,
"var s = '\\u{1F600}ab'.slice(1, 3); s.length === 2 && s.charCodeAt(0) === 0xDE00 && s.charCodeAt(1) === 0x61",
)
.unwrap();
assert!(r.as_bool());
let r = eval(
&mut vm,
"var s = '\\u{1F600}ab'.slice(0, 1); s.length === 1 && s.charCodeAt(0) === 0xD83D",
)
.unwrap();
assert!(r.as_bool());
let s = eval(&mut vm, "'\\u{1F600}'.padStart(3, 'x')").unwrap();
assert_eq!(to_str(&vm, s), "xx\u{1F600}");
}
#[test]
fn string_char_at_ascii_and_astral() {
// charAt 产出:ASCII 走单字符缓存,astral 按 UTF-16 单元定位(落在代理对
// 中间时返回所在 astral 字符,架构限制),越界空串,缺省参数取首字符。
// charAt 按码元定位:单单元子串(代理对各出 1 单元,规格口径——
// 规格返回单元子串而非 Unicode 标量),越界空串,缺省参数取首字符。
let mut vm = Vm::new();
let s = eval(&mut vm, "'abc'.charAt(1)").unwrap();
assert_eq!(to_str(&vm, s), "b");
let s = eval(&mut vm, "'\\u{1F600}ab'.charAt(0)").unwrap();
assert_eq!(to_str(&vm, s), "\u{1F600}");
let r = eval(
&mut vm,
"'\\u{1F600}ab'.charAt(0).length === 1 && '\\u{1F600}ab'.charAt(0).charCodeAt(0) === 0xD83D",
)
.unwrap();
assert!(r.as_bool());
let r = eval(
&mut vm,
"'\\u{1F600}ab'.charAt(1).length === 1 && '\\u{1F600}ab'.charAt(1).charCodeAt(0) === 0xDE00",
)
.unwrap();
assert!(r.as_bool());
let r = eval(&mut vm, "'\\u{1F600}ab'.charAt(2) === 'a'").unwrap();
assert!(r.as_bool());
let s = eval(&mut vm, "'ab'.charAt(5)").unwrap();
assert_eq!(to_str(&vm, s), "");
let s = eval(&mut vm, "'xy'.charAt()").unwrap();
@@ -975,13 +995,16 @@ fn string_char_at_eq_perm_string() {
#[test]
fn string_split_empty_separator_chars() {
// 空分隔 split 逐字符产出:ASCII 走单字符缓存,混合 astral 按标量切分。
// 空分隔 split 逐码元产出:ASCII 走单字符缓存,代理对各出 1 单元元素
// (规格口径:空分隔按单元切分,孤立 surrogate 各为独立元素)。
let mut vm = Vm::new();
let s = eval(&mut vm, "'abc'.split('').join('-')").unwrap();
assert_eq!(to_str(&vm, s), "a-b-c");
let result = eval(&mut vm, "'\\u{1F600}a'.split('').length").unwrap();
assert_eq!(result.as_int(), 2);
let result = eval(&mut vm, "'\\u{1F600}a'.split('')[1]").unwrap();
assert_eq!(result.as_int(), 3);
let r = eval(&mut vm, "'\\u{1F600}a'.split('')[1].charCodeAt(0) === 0xDE00").unwrap();
assert!(r.as_bool());
let result = eval(&mut vm, "'\\u{1F600}a'.split('')[2]").unwrap();
assert_eq!(to_str(&vm, result), "a");
}
@@ -1016,10 +1039,17 @@ fn string_utf16_length_char_at_code_at() {
assert_eq!(r.as_int(), 0xDE00);
let r = eval(&mut vm, "'\\u{1F600}a'.charCodeAt(2)").unwrap();
assert_eq!(r.as_int(), 0x61);
let r = eval(&mut vm, "'\\u{1F600}ab'.charAt(2)").unwrap();
assert_eq!(to_str(&vm, r), "a");
let r = eval(&mut vm, "'\\u{1F600}ab'.charAt(1)").unwrap();
assert_eq!(to_str(&vm, r), "\u{1F600}");
let r = eval(&mut vm, "'\\u{1F600}ab'.charAt(2) === 'a'").unwrap();
assert!(r.as_bool());
// charAt 出单单元子串:代理对位置各出 1 单元(高/低 surrogate)。
let r = eval(
&mut vm,
"'\\u{1F600}ab'.charAt(0).length === 1 && '\\u{1F600}ab'.charAt(0).charCodeAt(0) === 0xD83D",
)
.unwrap();
assert!(r.as_bool());
let r = eval(&mut vm, "'\\u{1F600}ab'.charAt(1).charCodeAt(0) === 0xDE00").unwrap();
assert!(r.as_bool());
let r = eval(&mut vm, "'\\u{1F600}ab'.charAt(4)").unwrap();
assert_eq!(to_str(&vm, r), "");
}
@@ -1040,7 +1070,8 @@ fn string_utf16_at() {
#[test]
fn string_utf16_slice_substring_substr() {
// 切片族按 UTF-16 单元计数:边界落在代理对中间时保留所在 astral 字符。
// 切片族按 UTF-16 单元计数:边界落在代理对中间时单元原样保留(孤立
// surrogate 由单元载荷承载,规格口径)。
let mut vm = Vm::new();
let s = eval(&mut vm, "'\\u{1F600}ab'.slice(2)").unwrap();
assert_eq!(to_str(&vm, s), "ab");
@@ -1054,8 +1085,12 @@ fn string_utf16_slice_substring_substr() {
assert_eq!(to_str(&vm, s), "\u{1F600}");
let s = eval(&mut vm, "'\\u{1F600}ab'.substr(2, 2)").unwrap();
assert_eq!(to_str(&vm, s), "ab");
let s = eval(&mut vm, "'\\u{1F600}ab'.substr(1, 2)").unwrap();
assert_eq!(to_str(&vm, s), "\u{1F600}a");
let r = eval(
&mut vm,
"var s = '\\u{1F600}ab'.substr(1, 2); s.length === 2 && s.charCodeAt(0) === 0xDE00 && s.charCodeAt(1) === 0x61",
)
.unwrap();
assert!(r.as_bool());
}
#[test]