perf: 字符串构造类方法改借用免整串拷贝

This commit is contained in:
2026-08-13 23:13:34 +08:00
parent 33a8539680
commit 781d0c832f
2 changed files with 165 additions and 118 deletions
+2 -2
View File
@@ -355,7 +355,7 @@ pub fn regexp_symbol_match<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if matches.is_empty() { if matches.is_empty() {
return NativeResult::Ok(JsValue::null()); return NativeResult::Ok(JsValue::null());
} }
NativeResult::Ok(crate::string::make_string_array(vm, &matches)) NativeResult::Ok(crate::string::make_string_array(vm, matches))
} }
/// `RegExp.prototype[Symbol.replace](string, replacement)`:按匹配替换。 /// `RegExp.prototype[Symbol.replace](string, replacement)`:按匹配替换。
@@ -473,7 +473,7 @@ pub fn regexp_symbol_split<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if last_end <= haystack.len() && parts.len() < limit { if last_end <= haystack.len() && parts.len() < limit {
parts.push(haystack[last_end..].to_string()); parts.push(haystack[last_end..].to_string());
} }
NativeResult::Ok(crate::string::make_string_array(vm, &parts)) NativeResult::Ok(crate::string::make_string_array(vm, parts))
} }
/// `RegExp.prototype[Symbol.matchAll](string)`:返回按 global 语义逐个产出 /// `RegExp.prototype[Symbol.matchAll](string)`:返回按 global 语义逐个产出
+163 -116
View File
@@ -185,15 +185,15 @@ pub fn string_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
)) ))
} }
pub(crate) fn make_string_array<H: VmHost>(vm: &mut H, parts: &[String]) -> JsValue { pub(crate) fn make_string_array<H: VmHost>(vm: &mut H, parts: Vec<String>) -> JsValue {
let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject; let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let n = parts.len(); let n = parts.len();
let arr = let arr =
vm.epoch() vm.epoch()
.alloc(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(proto), n, vm.epoch().bump())); .alloc(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(proto), n, vm.epoch().bump()));
unsafe { unsafe {
for (i, s) in parts.iter().enumerate() { for (i, s) in parts.into_iter().enumerate() {
let sv = vm.new_string(s); let sv = vm.new_string_owned(s);
(*arr).set_prop_at(i, sv); (*arr).set_prop_at(i, sv);
} }
(*arr).set_prop_count(n); (*arr).set_prop_count(n);
@@ -554,85 +554,104 @@ pub fn string_concat<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// `String.prototype.slice(start, end)`:按字符区间(支持负索引)取子串。 /// `String.prototype.slice(start, end)`:按字符区间(支持负索引)取子串。
pub fn string_slice<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn string_slice<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.slice called with {} args", args.len()); builtins_debug!("String.prototype.slice called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned(); // 位置参数先行(&mut 转换),后借 this 取子串。
let start_raw = if args.len() > 1 {
Some(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as i32)
} else {
None
};
let end_raw = if args.len() > 2 {
Some(vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN) as i32)
} else {
None
};
let s = try_string!(this_string(vm, args));
let n = char_len(&s) as i32; let n = char_len(&s) as i32;
let start = if args.len() > 1 { let start = match start_raw {
let v = vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as i32; Some(v) => {
if v < 0 { if v < 0 {
(n + v).max(0) (n + v).max(0)
} else { } else {
v.min(n) v.min(n)
}
} }
} else { None => 0,
0
}; };
let end = if args.len() > 2 { let end = match end_raw {
let v = vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN) as i32; Some(v) => {
if v < 0 { if v < 0 {
(n + v).max(0) (n + v).max(0)
} else { } else {
v.min(n) v.min(n)
}
} }
} else { None => n,
n
}; };
let start = start as usize; let result = if start < end { char_slice(&s, start as usize, end as usize) } else { "" };
let end = end as usize; let owned = result.to_string();
let result = if start < end { char_slice(&s, start, end) } else { "" }; NativeResult::Ok(vm.new_string_owned(owned))
NativeResult::Ok(vm.new_string(result))
} }
/// `String.prototype.substring(start, end)`:取子串,start/end 自动对调且取非负。 /// `String.prototype.substring(start, end)`:取子串,start/end 自动对调且取非负。
pub fn string_substring<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn string_substring<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.substring called with {} args", args.len()); builtins_debug!("String.prototype.substring called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned(); // 寄存器取值先行(纯函数),后借 this 取子串。
let start_arg = if args.len() > 1 { Some(vm.reg(args[1])) } else { None };
let end_arg = if args.len() > 2 { Some(vm.reg(args[2])) } else { None };
let s = try_string!(this_string(vm, args));
let n = char_len(&s) as i32; let n = char_len(&s) as i32;
let mut start = if args.len() > 1 { let mut start = match start_arg {
let v = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[1])); Some(v) => {
if v.is_nan() || v < 0.0 { let v = oxide_runtime_api::to_integer_or_infinity(v);
0 if v.is_nan() || v < 0.0 {
} else { 0
(v as i32).min(n) } else {
(v as i32).min(n)
}
} }
} else { None => 0,
0
}; };
let mut end = if args.len() > 2 { let mut end = match end_arg {
let v = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[2])); Some(v) => {
if v.is_nan() || v < 0.0 { let v = oxide_runtime_api::to_integer_or_infinity(v);
0 if v.is_nan() || v < 0.0 {
} else { 0
(v as i32).min(n) } else {
(v as i32).min(n)
}
} }
} else { None => n,
n
}; };
if start > end { if start > end {
std::mem::swap(&mut start, &mut end); std::mem::swap(&mut start, &mut end);
} }
let result = char_slice(&s, start as usize, end as usize); let result = char_slice(&s, start as usize, end as usize);
NativeResult::Ok(vm.new_string(result)) let owned = result.to_string();
NativeResult::Ok(vm.new_string_owned(owned))
} }
/// `String.prototype.substr(start, length)`:从 start 取 length 个字符(Annex B,支持负 start)。 /// `String.prototype.substr(start, length)`:从 start 取 length 个字符(Annex B,支持负 start)。
pub fn string_substr<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn string_substr<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.substr called with {} args", args.len()); builtins_debug!("String.prototype.substr called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned(); // 寄存器取值先行(纯函数),后借 this 截取。
let start_arg = if args.len() > 1 { Some(vm.reg(args[1])) } else { None };
let length_arg = if args.len() > 2 { Some(vm.reg(args[2])) } else { None };
let s = try_string!(this_string(vm, args));
let len = char_len(&s) as isize; let len = char_len(&s) as isize;
let start = if args.len() > 1 { let start = match start_arg {
let n = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[1])) as isize; Some(v) => {
if n < 0 { let n = oxide_runtime_api::to_integer_or_infinity(v) as isize;
(len + n).max(0) if n < 0 {
} else { (len + n).max(0)
n.min(len) } else {
n.min(len)
}
} }
} else { None => 0,
0
} as usize; } as usize;
let length = if args.len() > 2 { let length = match length_arg {
(oxide_runtime_api::to_integer_or_infinity(vm.reg(args[2])) as isize).max(0) as usize Some(v) => (oxide_runtime_api::to_integer_or_infinity(v) as isize).max(0) as usize,
} else { None => len as usize - start,
len as usize - start
}; };
let result = take_chars(&s[byte_index_at_char(&s, start)..], length.min(len as usize - start)); let result = take_chars(&s[byte_index_at_char(&s, start)..], length.min(len as usize - start));
NativeResult::Ok(vm.new_string_owned(result)) NativeResult::Ok(vm.new_string_owned(result))
@@ -707,57 +726,65 @@ pub fn string_last_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
/// `String.prototype.toUpperCase`:全大写转换。 /// `String.prototype.toUpperCase`:全大写转换。
pub fn string_to_upper_case<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn string_to_upper_case<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.toUpperCase called with {} args", args.len()); builtins_debug!("String.prototype.toUpperCase called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned(); let s = try_string!(this_string(vm, args));
NativeResult::Ok(vm.new_string_owned(s.to_uppercase())) let upper = s.to_uppercase();
NativeResult::Ok(vm.new_string_owned(upper))
} }
/// `String.prototype.toLowerCase`:全小写转换。 /// `String.prototype.toLowerCase`:全小写转换。
pub fn string_to_lower_case<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn string_to_lower_case<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.toLowerCase called with {} args", args.len()); builtins_debug!("String.prototype.toLowerCase called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned(); let s = try_string!(this_string(vm, args));
NativeResult::Ok(vm.new_string_owned(s.to_lowercase())) let lower = s.to_lowercase();
NativeResult::Ok(vm.new_string_owned(lower))
} }
/// `String.prototype.trim`:去除两端空白。 /// `String.prototype.trim`:去除两端空白。
pub fn string_trim<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn string_trim<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.trim called with {} args", args.len()); builtins_debug!("String.prototype.trim called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned(); let s = try_string!(this_string(vm, args));
NativeResult::Ok(vm.new_string(s.trim())) let trimmed = s.trim().to_string();
NativeResult::Ok(vm.new_string_owned(trimmed))
} }
/// `String.prototype.repeat(count)`:重复字符串 count 次(当前上限 10000 防滥用)。 /// `String.prototype.repeat(count)`:重复字符串 count 次(当前上限 10000 防滥用)。
pub fn string_repeat<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn string_repeat<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.repeat called with {} args", args.len()); builtins_debug!("String.prototype.repeat called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned(); // count 转换先行(&mut 路径),后借 this 重复。
let n = if args.len() > 1 { let n = if args.len() > 1 {
(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize).min(10000) (vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize).min(10000)
} else { } else {
1 1
}; };
NativeResult::Ok(vm.new_string_owned(s.repeat(n))) let s = try_string!(this_string(vm, args));
let repeated = s.repeat(n);
NativeResult::Ok(vm.new_string_owned(repeated))
} }
/// `String.prototype.padStart(targetLength, padString)`:在头部补足 padString 到目标长度。 /// `String.prototype.padStart(targetLength, padString)`:在头部补足 padString 到目标长度。
pub fn string_pad_start<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn string_pad_start<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.padStart called with {} args", args.len()); builtins_debug!("String.prototype.padStart called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned(); // 参数转换先行(&mut 路径):targetLength 与 padString 均可能触发对象转换。
let s_len = char_len(&s); let target_arg = if args.len() > 1 {
let target = if args.len() > 1 { Some(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize)
vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize
} else { } else {
s_len None
}; };
if target > 10000 {
builtins_error!("String.prototype.padStart: invalid receiver");
return NativeResult::Err(crate::error::create_range_error(vm, "Invalid string length"));
}
let pad = if args.len() > 2 { let pad = if args.len() > 2 {
as_string(vm, vm.reg(args[2])).into_owned() as_string(vm, vm.reg(args[2])).into_owned()
} else { } else {
" ".to_string() " ".to_string()
}; };
let s = try_string!(this_string(vm, args));
let s_len = char_len(&s);
let target = target_arg.unwrap_or(s_len);
if target > 10000 {
builtins_error!("String.prototype.padStart: invalid receiver");
return NativeResult::Err(crate::error::create_range_error(vm, "Invalid string length"));
}
if s_len >= target || pad.is_empty() { if s_len >= target || pad.is_empty() {
return NativeResult::Ok(vm.new_string_owned(s)); let owned = s.into_owned();
return NativeResult::Ok(vm.new_string_owned(owned));
} }
let needed = target - s_len; let needed = target - s_len;
let pad_len = char_len(&pad).max(1); let pad_len = char_len(&pad).max(1);
@@ -770,29 +797,32 @@ pub fn string_pad_start<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// `String.prototype.padEnd(targetLength, padString)`:在尾部补足 padString 到目标长度。 /// `String.prototype.padEnd(targetLength, padString)`:在尾部补足 padString 到目标长度。
pub fn string_pad_end<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn string_pad_end<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.padEnd called with {} args", args.len()); builtins_debug!("String.prototype.padEnd called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned(); // 参数转换先行(&mut 路径):targetLength 与 padString 均可能触发对象转换。
let s_len = char_len(&s); let target_arg = if args.len() > 1 {
let target = if args.len() > 1 { Some(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize)
vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize
} else { } else {
s_len None
}; };
if target > 10000 {
builtins_error!("String.prototype.padEnd: invalid receiver");
return NativeResult::Err(crate::error::create_range_error(vm, "Invalid string length"));
}
let pad = if args.len() > 2 { let pad = if args.len() > 2 {
as_string(vm, vm.reg(args[2])).into_owned() as_string(vm, vm.reg(args[2])).into_owned()
} else { } else {
" ".to_string() " ".to_string()
}; };
let s = try_string!(this_string(vm, args));
let s_len = char_len(&s);
let target = target_arg.unwrap_or(s_len);
if target > 10000 {
builtins_error!("String.prototype.padEnd: invalid receiver");
return NativeResult::Err(crate::error::create_range_error(vm, "Invalid string length"));
}
if s_len >= target || pad.is_empty() { if s_len >= target || pad.is_empty() {
return NativeResult::Ok(vm.new_string_owned(s)); let owned = s.into_owned();
return NativeResult::Ok(vm.new_string_owned(owned));
} }
let needed = target - s_len; let needed = target - s_len;
let pad_len = char_len(&pad).max(1); let pad_len = char_len(&pad).max(1);
let reps = needed.div_ceil(pad_len); let reps = needed.div_ceil(pad_len);
let mut out = s; let mut out = s.into_owned();
out.push_str(&take_chars(&pad.repeat(reps), needed)); out.push_str(&take_chars(&pad.repeat(reps), needed));
NativeResult::Ok(vm.new_string_owned(out)) NativeResult::Ok(vm.new_string_owned(out))
} }
@@ -847,13 +877,19 @@ pub fn string_ends_with<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// 分隔符可为 RegExp(含捕获组)或字符串。 /// 分隔符可为 RegExp(含捕获组)或字符串。
pub fn string_split<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn string_split<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.split called with {} args", args.len()); builtins_debug!("String.prototype.split called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned(); // 参数转换先行:separator 为对象时 ToString 可能触发用户代码(&mut 路径),
// 规范:separator 为 undefined 时返回 [string]。 // limit 仅纯函数计算;之后借 this 源串扫描切分。
if args.len() < 2 || vm.reg(args[1]).is_undefined() { let sep_val = if args.len() >= 2 { Some(vm.reg(args[1])) } else { None };
let parts = vec![s.clone()]; let is_undefined_sep = sep_val.map(|v| v.is_undefined()).unwrap_or(true);
return NativeResult::Ok(make_string_array(vm, &parts)); let is_re = match sep_val {
} Some(v) if !v.is_undefined() => is_regexp_obj(v, vm),
let sep_val = vm.reg(args[1]); _ => false,
};
let sep = if is_undefined_sep || is_re {
String::new()
} else {
as_string(vm, sep_val.unwrap()).into_owned()
};
// ToUint32(limit),缺省为 2^32-1。 // ToUint32(limit),缺省为 2^32-1。
let limit = if args.len() > 2 { let limit = if args.len() > 2 {
let l = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[2])); let l = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[2]));
@@ -865,9 +901,14 @@ pub fn string_split<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
} else { } else {
u32::MAX as usize u32::MAX as usize
}; };
let s = try_string!(this_string(vm, args));
if is_regexp_obj(sep_val, vm) { // 规范:separator 为 undefined 时返回 [this]。
let re_ptr = sep_val.as_js_object_ptr(); if is_undefined_sep {
let owned = s.into_owned();
return NativeResult::Ok(make_string_array(vm, vec![owned]));
}
if is_re {
let re_ptr = sep_val.unwrap().as_js_object_ptr();
let re = unsafe { &*re_ptr }; let re = unsafe { &*re_ptr };
if let Some(fn_ptr) = re.native_fn() { if let Some(fn_ptr) = re.native_fn() {
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) }; let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
@@ -896,18 +937,16 @@ pub fn string_split<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if last_end <= s.len() && parts.len() < limit { if last_end <= s.len() && parts.len() < limit {
parts.push(s[last_end..].to_string()); parts.push(s[last_end..].to_string());
} }
return NativeResult::Ok(make_string_array(vm, &parts)); return NativeResult::Ok(make_string_array(vm, parts));
} }
// 无原生正则的类正则对象回退到字符串路径。 // 无原生正则的类正则对象回退到字符串路径。
} }
let sep = as_string(vm, sep_val).into_owned();
let parts: Vec<String> = if sep.is_empty() { let parts: Vec<String> = if sep.is_empty() {
s.chars().map(|c| c.to_string()).take(limit).collect() s.chars().map(|c| c.to_string()).take(limit).collect()
} else { } else {
s.split(&sep).map(|p| p.to_string()).take(limit).collect() s.split(&sep).map(|p| p.to_string()).take(limit).collect()
}; };
NativeResult::Ok(make_string_array(vm, &parts)) NativeResult::Ok(make_string_array(vm, parts))
} }
/// `String.prototype.replace(pattern, replacement)`:替换首个匹配; /// `String.prototype.replace(pattern, replacement)`:替换首个匹配;
@@ -972,24 +1011,31 @@ pub fn string_replace<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// 否则返回首个匹配及捕获组,无匹配返回 null。 /// 否则返回首个匹配及捕获组,无匹配返回 null。
pub fn string_match_fn<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn string_match_fn<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.match called with {} args", args.len()); builtins_debug!("String.prototype.match called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned(); // 正则判定与参数字符串转换先行(&mut 路径),后借 this 匹配扫描。
let pattern_val = if args.len() >= 2 { Some(vm.reg(args[1])) } else { None };
let is_re = pattern_val.map(|v| is_regexp_obj(v, vm)).unwrap_or(false);
let pattern = match pattern_val {
Some(v) if !is_re => as_string(vm, v).into_owned(),
_ => String::new(),
};
let s = try_string!(this_string(vm, args));
if args.len() < 2 { if args.len() < 2 {
return NativeResult::Ok(JsValue::null()); return NativeResult::Ok(JsValue::null());
} }
let pattern_val = vm.reg(args[1]); let pattern_val = pattern_val.unwrap();
if is_re {
if is_regexp_obj(pattern_val, vm) {
let re_ptr = pattern_val.as_js_object_ptr(); let re_ptr = pattern_val.as_js_object_ptr();
let re = unsafe { &*re_ptr }; let re = unsafe { &*re_ptr };
let fn_ptr = match re.native_fn() { let fn_ptr = match re.native_fn() {
Some(p) => p, Some(p) => p,
None => return NativeResult::Ok(JsValue::null()), None => return NativeResult::Ok(JsValue::null()),
}; };
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regress::Regex>` 指针。
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) }; let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
let is_global = re.hash_props_vec().and_then(|v| v.get(3)).map(|v| v.as_bool()).unwrap_or(false); let is_global = re.hash_props_vec().and_then(|v| v.get(3)).map(|v| v.as_bool()).unwrap_or(false);
if is_global { if is_global {
let matches: Vec<String> = regex.find_iter(&s).map(|m| s[m.range()].to_string()).collect(); let matches: Vec<String> = regex.find_iter(&s).map(|m| s[m.range()].to_string()).collect();
return NativeResult::Ok(make_string_array(vm, &matches)); return NativeResult::Ok(make_string_array(vm, matches));
} }
if let Some(m) = regex.find(&s) { if let Some(m) = regex.find(&s) {
let range = m.range(); let range = m.range();
@@ -1001,15 +1047,13 @@ pub fn string_match_fn<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
None => parts.push(String::new()), None => parts.push(String::new()),
} }
} }
return NativeResult::Ok(make_string_array(vm, &parts)); return NativeResult::Ok(make_string_array(vm, parts));
} }
return NativeResult::Ok(JsValue::null()); return NativeResult::Ok(JsValue::null());
} }
if let Some(pos) = s.find(&pattern) {
let pattern = as_string(vm, pattern_val);
if let Some(pos) = s.find(pattern.as_ref()) {
let matched = s[pos..pos + pattern.len()].to_string(); let matched = s[pos..pos + pattern.len()].to_string();
return NativeResult::Ok(make_string_array(vm, &[matched])); return NativeResult::Ok(make_string_array(vm, vec![matched]));
} }
NativeResult::Ok(JsValue::null()) NativeResult::Ok(JsValue::null())
} }
@@ -1052,22 +1096,24 @@ pub fn string_search<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
/// `String.prototype.trimStart`:去除头部空白。 /// `String.prototype.trimStart`:去除头部空白。
pub fn string_trim_start<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn string_trim_start<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.trimStart called with {} args", args.len()); builtins_debug!("String.prototype.trimStart called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned(); let s = try_string!(this_string(vm, args));
NativeResult::Ok(vm.new_string(s.trim_start())) let trimmed = s.trim_start().to_string();
NativeResult::Ok(vm.new_string_owned(trimmed))
} }
/// `String.prototype.trimEnd`:去除尾部空白。 /// `String.prototype.trimEnd`:去除尾部空白。
pub fn string_trim_end<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn string_trim_end<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.trimEnd called with {} args", args.len()); builtins_debug!("String.prototype.trimEnd called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned(); let s = try_string!(this_string(vm, args));
NativeResult::Ok(vm.new_string(s.trim_end())) let trimmed = s.trim_end().to_string();
NativeResult::Ok(vm.new_string_owned(trimmed))
} }
/// `String.prototype.codePointAt(pos)`:按 UTF-16 code unit 位置取 code point /// `String.prototype.codePointAt(pos)`:按 UTF-16 code unit 位置取 code point
/// (surrogate pair 合并);越界或孤立代理返回 undefined。 /// (surrogate pair 合并);越界或孤立代理返回 undefined。
pub fn string_code_point_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn string_code_point_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.codePointAt called with {} args", args.len()); builtins_debug!("String.prototype.codePointAt called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned(); // pos 转换先行(&mut 路径),后借 this 按 code unit 定位。
let pos = if args.len() > 1 { let pos = if args.len() > 1 {
let pos_val = vm.reg(args[1]); let pos_val = vm.reg(args[1]);
if pos_val.is_symbol() { if pos_val.is_symbol() {
@@ -1091,6 +1137,7 @@ pub fn string_code_point_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
return NativeResult::Ok(JsValue::undefined()); return NativeResult::Ok(JsValue::undefined());
} }
let pos = pos as usize; let pos = pos as usize;
let s = try_string!(this_string(vm, args));
// JS 规范索引是 UTF-16 code unit 位置,须按代理对展开定位(astral 字符占两单元)。 // JS 规范索引是 UTF-16 code unit 位置,须按代理对展开定位(astral 字符占两单元)。
let units: Vec<u16> = s.encode_utf16().collect(); let units: Vec<u16> = s.encode_utf16().collect();
if pos >= units.len() { if pos >= units.len() {
@@ -1131,7 +1178,7 @@ pub fn string_is_well_formed<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
/// `String.prototype.toWellFormed()`:把孤立 surrogate 替换为 U+FFFD 返回新字符串。 /// `String.prototype.toWellFormed()`:把孤立 surrogate 替换为 U+FFFD 返回新字符串。
pub fn string_to_well_formed<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn string_to_well_formed<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.toWellFormed called with {} args", args.len()); builtins_debug!("String.prototype.toWellFormed called with {} args", args.len());
let s = try_string!(this_string(vm, args)).into_owned(); let s = try_string!(this_string(vm, args));
// 引擎字符串为合法 UTF-8,Rust char 不可能落在 surrogate 区间;遍历保留 // 引擎字符串为合法 UTF-8,Rust char 不可能落在 surrogate 区间;遍历保留
// 通用来正确编码未来可能出现的替换路径。 // 通用来正确编码未来可能出现的替换路径。
let mut out = String::with_capacity(s.len()); let mut out = String::with_capacity(s.len());
@@ -1149,12 +1196,12 @@ pub fn string_to_well_formed<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
pub fn string_normalize<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult { pub fn string_normalize<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.normalize called with {} args", args.len()); builtins_debug!("String.prototype.normalize called with {} args", args.len());
use unicode_normalization::UnicodeNormalization; use unicode_normalization::UnicodeNormalization;
let s = try_string!(this_string(vm, args)).into_owned();
let form = if args.len() > 1 { let form = if args.len() > 1 {
as_string(vm, vm.reg(args[1])).into_owned() as_string(vm, vm.reg(args[1])).into_owned()
} else { } else {
"NFC".to_string() "NFC".to_string()
}; };
let s = try_string!(this_string(vm, args));
let result: String = match form.as_str() { let result: String = match form.as_str() {
"NFD" => s.nfd().collect(), "NFD" => s.nfd().collect(),
"NFKC" => s.nfkc().collect(), "NFKC" => s.nfkc().collect(),
@@ -1294,7 +1341,7 @@ pub fn string_match_all_next<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
} }
idx = range.end; idx = range.end;
vm.set_or_create_prop_value(wrapper, index_si, JsValue::int(idx as i32)); vm.set_or_create_prop_value(wrapper, index_si, JsValue::int(idx as i32));
let arr_val = make_string_array(vm, &parts); let arr_val = make_string_array(vm, parts);
make_match_done_result(vm, arr_val) make_match_done_result(vm, arr_val)
} else { } else {
make_match_done_result(vm, JsValue::undefined()) make_match_done_result(vm, JsValue::undefined())