mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: 补 String.fromCodePoint/codePointAt/isWellFormed/toWellFormed
This commit is contained in:
@@ -22,8 +22,21 @@ fn this_string<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<String, JsValue> {
|
||||
if this_val.is_null() || this_val.is_undefined() {
|
||||
return Err(crate::error::create_type_error(vm, "String.prototype method called on null or undefined"));
|
||||
}
|
||||
if this_val.is_symbol() {
|
||||
return Err(crate::error::create_type_error(vm, "Cannot convert a Symbol value to a string"));
|
||||
}
|
||||
// 对象 this 须经 ToString 完整转换(boxed Number/String 等取内部原始值)。
|
||||
oxide_runtime_api::to_string_full(this_val, vm).map_err(|e| crate::error::create_error(vm, &e))
|
||||
match oxide_runtime_api::to_string_full(this_val, vm) {
|
||||
Ok(s) => Ok(s),
|
||||
Err(_) => {
|
||||
// ToString 触发对象 toString/valueOf 抛出的原生异常须原样传播,
|
||||
// 否则会被展平为普通 Error 丢失原始异常对象。
|
||||
if let Some(exc) = vm.take_uncaught_value() {
|
||||
return Err(exc);
|
||||
}
|
||||
Err(crate::error::create_type_error(vm, "Cannot convert this value to a string"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `String.fromCharCode(...codes)`:把各参数按低 16 位转成字符拼接为字符串。
|
||||
@@ -41,6 +54,39 @@ pub fn string_from_char_code<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
|
||||
NativeResult::Ok(vm.new_string(&out))
|
||||
}
|
||||
|
||||
/// `String.fromCodePoint(...codes)`:把各参数按 ToNumber 语义转成 code point
|
||||
/// (0..0x10FFFF 的整数)拼接为字符串;非整数、NaN 或越界抛 RangeError,
|
||||
/// Symbol 抛 TypeError。surrogate 区间按现行规范接受(孤立 surrogate 无法在
|
||||
/// UTF-8 表示,与字面量编码一致输出 U+FFFD + 四位小写 hex 文本)。
|
||||
pub fn string_from_code_point<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_debug!("String.fromCodePoint called with {} args", args.len());
|
||||
let mut out = String::new();
|
||||
for &arg_reg in args.iter().skip(1) {
|
||||
let n = match oxide_runtime_api::to_number_full(vm.reg(arg_reg), vm) {
|
||||
Ok(n) => n,
|
||||
Err(_) => {
|
||||
// ToNumber 触发对象 valueOf/toString 抛出的原生异常须原样传播,
|
||||
// 否则会被展平为普通 Error 丢失原始异常对象。
|
||||
if let Some(exc) = vm.take_uncaught_value() {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "Cannot convert value to a number"));
|
||||
}
|
||||
};
|
||||
if !n.is_finite() || n.trunc() != n || n < 0.0 || n > 0x10FFFF as f64 {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "Invalid code point"));
|
||||
}
|
||||
let code = n as u32;
|
||||
if (0xD800..=0xDFFF).contains(&code) {
|
||||
out.push('\u{FFFD}');
|
||||
out.push_str(&format!("{code:04x}"));
|
||||
} else {
|
||||
out.push(char::from_u32(code).unwrap());
|
||||
}
|
||||
}
|
||||
NativeResult::Ok(vm.new_string(&out))
|
||||
}
|
||||
|
||||
/// `String.prototype.valueOf`:返回包装对象的原始字符串;其它 this 抛 TypeError。
|
||||
pub fn string_value_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_debug!("String.prototype.valueOf called with {} args", args.len());
|
||||
@@ -846,29 +892,92 @@ pub fn string_trim_end<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
NativeResult::Ok(vm.new_string(s.trim_end()))
|
||||
}
|
||||
|
||||
/// `String.prototype.codePointAt(pos)`:返回 code point(surrogate pair 会合并),
|
||||
/// 越界返回 undefined。
|
||||
/// `String.prototype.codePointAt(pos)`:按 UTF-16 code unit 位置取 code point
|
||||
/// (surrogate pair 合并);越界或孤立代理返回 undefined。
|
||||
pub fn string_code_point_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_debug!("String.prototype.codePointAt called with {} args", args.len());
|
||||
let s = try_string!(this_string(vm, args));
|
||||
let pos = if args.len() > 1 {
|
||||
vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize
|
||||
let pos_val = vm.reg(args[1]);
|
||||
if pos_val.is_symbol() {
|
||||
return NativeResult::Err(crate::error::create_type_error(
|
||||
vm,
|
||||
"Cannot convert a Symbol value to a number",
|
||||
));
|
||||
}
|
||||
match vm.coerce_number_bounded(pos_val) {
|
||||
Ok(n) => n,
|
||||
Err(_) => {
|
||||
// pos 对象 valueOf/toString 抛出的异常须原样传播。
|
||||
if let Some(exc) = vm.take_uncaught_value() {
|
||||
return NativeResult::Err(exc);
|
||||
}
|
||||
return NativeResult::Err(crate::error::create_type_error(
|
||||
vm,
|
||||
"Cannot convert argument to a number",
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
0
|
||||
0.0
|
||||
};
|
||||
let chars: Vec<char> = s.chars().collect();
|
||||
if pos >= chars.len() {
|
||||
let pos = if pos.is_nan() { 0.0 } else { pos.trunc() };
|
||||
if pos < 0.0 || pos > u32::MAX as f64 {
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
let c = chars[pos] as u32;
|
||||
if (0xD800..=0xDBFF).contains(&c) && pos + 1 < chars.len() {
|
||||
let next = chars[pos + 1] as u32;
|
||||
if (0xDC00..=0xDFFF).contains(&next) {
|
||||
let cp = 0x10000 + ((c - 0xD800) << 10) + (next - 0xDC00);
|
||||
let pos = pos as usize;
|
||||
// JS 规范索引是 UTF-16 code unit 位置,须按代理对展开定位(astral 字符占两单元)。
|
||||
let units: Vec<u16> = s.encode_utf16().collect();
|
||||
if pos >= units.len() {
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
let first = units[pos];
|
||||
if (0xD800..=0xDBFF).contains(&first) && pos + 1 < units.len() {
|
||||
let second = units[pos + 1];
|
||||
if (0xDC00..=0xDFFF).contains(&second) {
|
||||
let cp = 0x10000 + (((first - 0xD800) as u32) << 10) + (second - 0xDC00) as u32;
|
||||
return NativeResult::Ok(JsValue::int(cp as i32));
|
||||
}
|
||||
}
|
||||
NativeResult::Ok(JsValue::int(c as i32))
|
||||
NativeResult::Ok(JsValue::int(first as i32))
|
||||
}
|
||||
|
||||
/// `String.prototype.isWellFormed()`:字符串无孤立 surrogate(每个 UTF-16
|
||||
/// 码元要么是合法字符,要么与相邻码元成代理对)时返回 true。
|
||||
pub fn string_is_well_formed<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_debug!("String.prototype.isWellFormed called with {} args", args.len());
|
||||
let s = try_string!(this_string(vm, args));
|
||||
let mut iter = s.encode_utf16().peekable();
|
||||
while let Some(&first) = iter.peek() {
|
||||
if (0xDC00..=0xDFFF).contains(&first) {
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
if (0xD800..=0xDBFF).contains(&first) {
|
||||
if !matches!(iter.nth(1), Some(second) if (0xDC00..=0xDFFF).contains(&second)) {
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
} else {
|
||||
iter.next();
|
||||
}
|
||||
}
|
||||
NativeResult::Ok(JsValue::bool(true))
|
||||
}
|
||||
|
||||
/// `String.prototype.toWellFormed()`:把孤立 surrogate 替换为 U+FFFD 返回新字符串。
|
||||
pub fn string_to_well_formed<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_debug!("String.prototype.toWellFormed called with {} args", args.len());
|
||||
let s = try_string!(this_string(vm, args));
|
||||
// 引擎字符串为合法 UTF-8,Rust char 不可能落在 surrogate 区间;遍历保留
|
||||
// 通用来正确编码未来可能出现的替换路径。
|
||||
let mut out = String::with_capacity(s.len());
|
||||
for c in s.chars() {
|
||||
if (0xD800..=0xDFFF).contains(&(c as u32)) {
|
||||
out.push('\u{FFFD}');
|
||||
} else {
|
||||
out.push(c);
|
||||
}
|
||||
}
|
||||
NativeResult::Ok(vm.new_string(&out))
|
||||
}
|
||||
|
||||
/// `String.prototype.normalize(form)`:按 NFC/NFD/NFKC/NFKD 规范化为 Unicode 规范形式。
|
||||
|
||||
@@ -147,6 +147,9 @@ pub struct StringMethods {
|
||||
pub substr: *const (),
|
||||
pub at: *const (),
|
||||
pub last_index_of: *const (),
|
||||
pub from_code_point: *const (),
|
||||
pub is_well_formed: *const (),
|
||||
pub to_well_formed: *const (),
|
||||
}
|
||||
|
||||
/// RegExp 原型方法的 native 函数指针集合。
|
||||
@@ -1093,7 +1096,7 @@ impl BuiltinWorld {
|
||||
pub fn bind_string_methods(&self, methods: &StringMethods, string_forge: &PermInterner, shape_forge: &ShapeForge) {
|
||||
let ctor_ptr = P::as_ptr(&self.string_constructor) as *mut JsObject;
|
||||
let ctor = unsafe { &mut *ctor_ptr };
|
||||
bind_methods!(self, ctor, string_forge, shape_forge, ("fromCharCode", methods.from_char_code, 1),);
|
||||
bind_methods!(self, ctor, string_forge, shape_forge, ("fromCharCode", methods.from_char_code, 1), ("fromCodePoint", methods.from_code_point, 1),);
|
||||
|
||||
let proto_ptr = P::as_ptr(&self.string_proto) as *mut JsObject;
|
||||
let proto = unsafe { &mut *proto_ptr };
|
||||
@@ -1131,6 +1134,8 @@ impl BuiltinWorld {
|
||||
("substr", methods.substr, 2),
|
||||
("at", methods.at, 1),
|
||||
("lastIndexOf", methods.last_index_of, 1),
|
||||
("isWellFormed", methods.is_well_formed, 0),
|
||||
("toWellFormed", methods.to_well_formed, 0),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,9 @@ pub fn bind_string(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
substr: oxide_builtins::string::string_substr::<crate::vm::Vm> as *const (),
|
||||
at: oxide_builtins::string::string_at::<crate::vm::Vm> as *const (),
|
||||
last_index_of: oxide_builtins::string::string_last_index_of::<crate::vm::Vm> as *const (),
|
||||
from_code_point: oxide_builtins::string::string_from_code_point::<crate::vm::Vm> as *const (),
|
||||
is_well_formed: oxide_builtins::string::string_is_well_formed::<crate::vm::Vm> as *const (),
|
||||
to_well_formed: oxide_builtins::string::string_to_well_formed::<crate::vm::Vm> as *const (),
|
||||
};
|
||||
session.builtin_world().bind_string_methods(
|
||||
&string_methods,
|
||||
|
||||
Reference in New Issue
Block a user