Files
OxideJS-New/crates/oxide_builtins/src/string.rs
T
2026-08-05 21:46:47 +08:00

974 lines
39 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::JsObject;
use oxide_types::value::JsValue;
use memchr::memchr;
use oxide_runtime_api::{NativeResult, VmHost};
use crate::builtins_debug;
use crate::builtins_error;
fn this_string<H: VmHost>(vm: &H, args: &[u8]) -> String {
let this_val = vm.reg(args[0]);
if this_val.is_null() || this_val.is_undefined() {
String::new() // caller should check and throw TypeError
} else {
oxide_runtime_api::to_string(this_val)
}
}
/// `String.fromCharCode(...codes)`:把各参数按低 16 位转成字符拼接为字符串。
pub fn string_from_char_code<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.fromCharCode called with {} args", args.len());
let mut out = String::new();
for &arg_reg in args.iter().skip(1) {
let code = oxide_runtime_api::to_uint32(vm.reg(arg_reg)) & 0xFFFF;
if let Some(ch) = char::from_u32(code) {
out.push(ch);
} else {
out.push('\u{FFFD}');
}
}
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());
let this_val = vm.reg(args[0]);
if this_val.is_string() {
return NativeResult::Ok(this_val);
}
if this_val.is_object() {
let ptr = this_val.as_js_object_ptr();
if !ptr.is_null() {
let obj = unsafe { &*ptr };
if obj.is_string_obj() {
return NativeResult::Ok(obj.get_prop_at(0));
}
}
}
builtins_error!("String.prototype.valueOf: invalid receiver");
NativeResult::Err(crate::error::create_type_error(
vm,
"String.prototype.valueOf called on non-String object",
))
}
/// JS `String()` 构造逻辑:把参数转成字符串;new 语义返回 `[[StringData]]` 包装对象。
pub fn string_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let s = if args.len() > 1 {
oxide_runtime_api::to_string(vm.reg(args[1]))
} else {
String::new()
};
let str_val = vm.new_string(&s);
let string_proto = vm.session().builtin_world().string_proto.as_ptr() as *mut JsObject;
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let is_ctor = if this_val.is_object() {
let ptr = this_val.as_js_object_ptr();
if ptr.is_null() {
false
} else {
let proto_ptr = unsafe { (*ptr).proto().as_js_object_ptr() };
!proto_ptr.is_null() && std::ptr::eq(proto_ptr, string_proto)
}
} else {
false
};
if !is_ctor {
return NativeResult::Ok(str_val);
}
let obj = unsafe { &mut *this_val.as_js_object_ptr() };
obj.type_tag = JsObject::OBJ_TYPE_STRING_OBJ;
obj.push_prop(str_val);
NativeResult::Ok(this_val)
}
/// `String.prototype.toString`:返回包装对象的原始字符串;其它 this 抛 TypeError。
pub fn string_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(args[0]);
if this_val.is_string() {
return NativeResult::Ok(this_val);
}
if this_val.is_object() {
let ptr = this_val.as_js_object_ptr();
if !ptr.is_null() {
let obj = unsafe { &*ptr };
if obj.is_string_obj() {
return NativeResult::Ok(obj.get_prop_at(0));
}
}
}
NativeResult::Err(crate::error::create_type_error(
vm,
"String.prototype.toString called on non-String object",
))
}
fn make_string_array<H: VmHost>(vm: &mut H, parts: &[String]) -> JsValue {
let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
let n = parts.len();
let arr =
vm.epoch()
.alloc(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(proto), n, vm.epoch().bump()));
unsafe {
for (i, s) in parts.iter().enumerate() {
let sv = vm.new_string(s);
(*arr).set_prop_at(i, sv);
}
(*arr).set_prop_count(n);
}
JsValue::from_js_object(arr)
}
fn as_string<H: VmHost>(vm: &mut H, val: JsValue) -> String {
oxide_runtime_api::to_string_full(val, vm).unwrap_or_else(|_| oxide_runtime_api::to_string(val))
}
fn char_len(s: &str) -> usize {
s.chars().count()
}
fn byte_index_at_char(s: &str, char_pos: usize) -> usize {
if char_pos == 0 {
return 0;
}
s.char_indices().nth(char_pos).map(|(idx, _)| idx).unwrap_or(s.len())
}
fn char_slice(s: &str, start: usize, end: usize) -> &str {
let start_byte = byte_index_at_char(s, start);
let end_byte = byte_index_at_char(s, end);
&s[start_byte..end_byte]
}
fn take_chars(s: &str, count: usize) -> String {
s.chars().take(count).collect()
}
fn is_regexp_obj<H: VmHost>(val: JsValue, vm: &H) -> bool {
if !val.is_object() {
return false;
}
let ptr = val.as_js_object_ptr();
if ptr.is_null() {
return false;
}
let obj = unsafe { &*ptr };
let proto = obj.proto();
if !proto.is_object() {
return false;
}
let proto_ptr = proto.as_js_object_ptr();
if proto_ptr.is_null() {
return false;
}
let rp = vm.session().builtin_world().regexp_proto.as_ptr() as *mut JsObject;
std::ptr::eq(proto_ptr, rp)
}
/// `String.prototype.indexOf(searchString, position)`:按字符索引查找首次出现位置。
pub fn string_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.indexOf called with {} args", args.len());
let s = this_string(vm, args);
let n = char_len(&s);
if args.len() < 2 {
return NativeResult::Ok(JsValue::int(-1));
}
let search = as_string(vm, vm.reg(args[1]));
let pos = if args.len() > 2 {
(vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN) as usize).min(n)
} else {
0
};
if search.is_empty() {
return NativeResult::Ok(JsValue::int(pos as i32));
}
let start_byte = byte_index_at_char(&s, pos);
let haystack = &s[start_byte..];
if search.len() == 1 {
if let Some(idx) = memchr(search.as_bytes()[0], haystack.as_bytes()) {
let matched_byte = start_byte + idx;
return NativeResult::Ok(JsValue::int(char_len(&s[..matched_byte]) as i32));
}
} else if let Some(idx) = haystack.find(&search) {
let matched_byte = start_byte + idx;
return NativeResult::Ok(JsValue::int(char_len(&s[..matched_byte]) as i32));
}
NativeResult::Ok(JsValue::int(-1))
}
/// `String.prototype.includes(searchString, position)`:是否包含子串。
pub fn string_includes<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.includes called with {} args", args.len());
let s = this_string(vm, args);
let n = char_len(&s);
if args.len() < 2 {
return NativeResult::Ok(JsValue::bool(false));
}
let search = as_string(vm, vm.reg(args[1]));
let pos = if args.len() > 2 {
(vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN) as usize).min(n)
} else {
0
};
if search.is_empty() {
return NativeResult::Ok(JsValue::bool(true));
}
let haystack = &s[byte_index_at_char(&s, pos)..];
if search.len() == 1 {
NativeResult::Ok(JsValue::bool(memchr(search.as_bytes()[0], haystack.as_bytes()).is_some()))
} else {
NativeResult::Ok(JsValue::bool(haystack.contains(&search)))
}
}
/// `String.prototype.charAt(index)`:返回指定位置的单字符;越界返回空串。
pub fn string_char_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.charAt called with {} args", args.len());
let s = this_string(vm, args);
if args.len() < 2 {
if s.is_empty() {
return NativeResult::Ok(vm.new_string(""));
}
return NativeResult::Ok(vm.new_string(&take_chars(&s, 1)));
}
let idx = vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as i32;
if idx < 0 || idx as usize >= char_len(&s) {
return NativeResult::Ok(vm.new_string(""));
}
let ch = s.chars().nth(idx as usize).map(|c| c.to_string()).unwrap_or_default();
NativeResult::Ok(vm.new_string(&ch))
}
/// `String.prototype.charCodeAt(index)`:返回指定位置字符的 UTF-16 code unit;越界返回 NaN。
pub fn string_char_code_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.charCodeAt called with {} args", args.len());
let s = this_string(vm, args);
if args.len() < 2 {
if s.is_empty() {
return NativeResult::Ok(JsValue::float(f64::NAN));
}
return NativeResult::Ok(JsValue::int(s.chars().next().unwrap() as i32));
}
let idx = vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as i32;
if idx < 0 || idx as usize >= char_len(&s) {
return NativeResult::Ok(JsValue::float(f64::NAN));
}
NativeResult::Ok(JsValue::int(s.chars().nth(idx as usize).unwrap() as i32))
}
/// `String.prototype.concat(...strings)`:拼接 this 与各参数返回新字符串。
pub fn string_concat<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.concat called with {} args", args.len());
let mut result = this_string(vm, args);
for &arg_reg in args.iter().skip(1) {
result.push_str(&oxide_runtime_api::to_string(vm.reg(arg_reg)));
}
NativeResult::Ok(vm.new_string(&result))
}
/// `String.prototype.slice(start, end)`:按字符区间(支持负索引)取子串。
pub fn string_slice<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.slice called with {} args", args.len());
let s = this_string(vm, args);
let n = char_len(&s) as i32;
let start = if args.len() > 1 {
let v = vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as i32;
if v < 0 {
(n + v).max(0)
} else {
v.min(n)
}
} else {
0
};
let end = if args.len() > 2 {
let v = vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN) as i32;
if v < 0 {
(n + v).max(0)
} else {
v.min(n)
}
} else {
n
};
let start = start as usize;
let end = end as usize;
let result = if start < end { char_slice(&s, start, end) } else { "" };
NativeResult::Ok(vm.new_string(result))
}
/// `String.prototype.substring(start, end)`:取子串,start/end 自动对调且取非负。
pub fn string_substring<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.substring called with {} args", args.len());
let s = this_string(vm, args);
let n = char_len(&s) as i32;
let mut start = if args.len() > 1 {
let v = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[1]));
if v.is_nan() || v < 0.0 {
0
} else {
(v as i32).min(n)
}
} else {
0
};
let mut end = if args.len() > 2 {
let v = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[2]));
if v.is_nan() || v < 0.0 {
0
} else {
(v as i32).min(n)
}
} else {
n
};
if start > end {
std::mem::swap(&mut start, &mut end);
}
let result = char_slice(&s, start as usize, end as usize);
NativeResult::Ok(vm.new_string(result))
}
/// `String.prototype.substr(start, length)`:从 start 取 length 个字符(Annex B,支持负 start)。
pub fn string_substr<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.substr called with {} args", args.len());
let s = this_string(vm, args);
let len = char_len(&s) as isize;
let start = if args.len() > 1 {
let n = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[1])) as isize;
if n < 0 {
(len + n).max(0)
} else {
n.min(len)
}
} else {
0
} as usize;
let length = if args.len() > 2 {
(oxide_runtime_api::to_integer_or_infinity(vm.reg(args[2])) as isize).max(0) as usize
} else {
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(&result))
}
/// `String.prototype.at(index)`:按字符索引取字符(支持负索引);越界返回 undefined。
pub fn string_at<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.at called with {} args", args.len());
let s = this_string(vm, args);
let len = char_len(&s) as i32;
let idx = if args.len() > 1 {
oxide_runtime_api::to_integer_or_infinity(vm.reg(args[1])) as i32
} else {
0
};
let idx = if idx < 0 { len + idx } else { idx };
if idx < 0 || idx >= len {
return NativeResult::Ok(JsValue::undefined());
}
let ch = s.chars().nth(idx as usize).unwrap().to_string();
NativeResult::Ok(vm.new_string(&ch))
}
/// `String.prototype.lastIndexOf(searchString, position)`:从后往前查找首次出现位置。
pub fn string_last_index_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.lastIndexOf called with {} args", args.len());
let s = this_string(vm, args);
let n = char_len(&s);
if args.len() < 2 {
return NativeResult::Ok(JsValue::int(-1));
}
let search = as_string(vm, vm.reg(args[1]));
let pos = if args.len() > 2 {
let p = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[2]));
if p.is_nan() {
n
} else {
(p as usize).min(n)
}
} else {
n
};
if search.is_empty() {
return NativeResult::Ok(JsValue::int(pos as i32));
}
let end_byte = byte_index_at_char(&s, (pos + 1).min(n));
let haystack = &s[..end_byte];
if search.len() == 1 {
if let Some(idx) = memchr::memrchr(search.as_bytes()[0], haystack.as_bytes()) {
let result = char_len(&s[..idx]);
return NativeResult::Ok(JsValue::int(result as i32));
}
} else if let Some(idx) = haystack.rfind(&search) {
let result = char_len(&s[..idx]);
return NativeResult::Ok(JsValue::int(result as i32));
}
NativeResult::Ok(JsValue::int(-1))
}
/// `String.prototype.toUpperCase`:全大写转换。
pub fn string_to_upper_case<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.toUpperCase called with {} args", args.len());
let s = this_string(vm, args);
NativeResult::Ok(vm.new_string(&s.to_uppercase()))
}
/// `String.prototype.toLowerCase`:全小写转换。
pub fn string_to_lower_case<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.toLowerCase called with {} args", args.len());
let s = this_string(vm, args);
NativeResult::Ok(vm.new_string(&s.to_lowercase()))
}
/// `String.prototype.trim`:去除两端空白。
pub fn string_trim<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.trim called with {} args", args.len());
let s = this_string(vm, args);
NativeResult::Ok(vm.new_string(s.trim()))
}
/// `String.prototype.repeat(count)`:重复字符串 count 次(当前上限 10000 防滥用)。
pub fn string_repeat<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.repeat called with {} args", args.len());
let s = this_string(vm, args);
let n = if args.len() > 1 {
(vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize).min(10000)
} else {
1
};
NativeResult::Ok(vm.new_string(&s.repeat(n)))
}
/// `String.prototype.padStart(targetLength, padString)`:在头部补足 padString 到目标长度。
pub fn string_pad_start<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.padStart called with {} args", args.len());
let s = this_string(vm, args);
let s_len = char_len(&s);
let target = if args.len() > 1 {
vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize
} else {
s_len
};
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 { as_string(vm, vm.reg(args[2])) } else { " ".to_string() };
if s_len >= target || pad.is_empty() {
return NativeResult::Ok(vm.new_string(&s));
}
let needed = target - s_len;
let pad_len = char_len(&pad).max(1);
let reps = needed.div_ceil(pad_len);
let mut out = take_chars(&pad.repeat(reps), needed);
out.push_str(&s);
NativeResult::Ok(vm.new_string(&out))
}
/// `String.prototype.padEnd(targetLength, padString)`:在尾部补足 padString 到目标长度。
pub fn string_pad_end<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.padEnd called with {} args", args.len());
let s = this_string(vm, args);
let s_len = char_len(&s);
let target = if args.len() > 1 {
vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize
} else {
s_len
};
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 { as_string(vm, vm.reg(args[2])) } else { " ".to_string() };
if s_len >= target || pad.is_empty() {
return NativeResult::Ok(vm.new_string(&s));
}
let needed = target - s_len;
let pad_len = char_len(&pad).max(1);
let reps = needed.div_ceil(pad_len);
let mut out = s;
out.push_str(&take_chars(&pad.repeat(reps), needed));
NativeResult::Ok(vm.new_string(&out))
}
/// `String.prototype.startsWith(searchString, position)`:是否以指定子串开头。
pub fn string_starts_with<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.startsWith called with {} args", args.len());
let s = this_string(vm, args);
let n = char_len(&s);
if args.len() < 2 {
return NativeResult::Ok(JsValue::bool(false));
}
let search = as_string(vm, vm.reg(args[1]));
let pos = if args.len() > 2 {
(vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN) as usize).min(n)
} else {
0
};
NativeResult::Ok(JsValue::bool(s[byte_index_at_char(&s, pos)..].starts_with(&search)))
}
/// `String.prototype.endsWith(searchString, endPosition)`:是否以指定子串结尾。
pub fn string_ends_with<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.endsWith called with {} args", args.len());
let s = this_string(vm, args);
let n = char_len(&s);
if args.len() < 2 {
return NativeResult::Ok(JsValue::bool(false));
}
let search = as_string(vm, vm.reg(args[1]));
let end_pos = if args.len() > 2 {
(vm.coerce_number_bounded(vm.reg(args[2])).unwrap_or(f64::NAN) as usize).min(n)
} else {
n
};
NativeResult::Ok(JsValue::bool(s[..byte_index_at_char(&s, end_pos)].ends_with(&search)))
}
/// `String.prototype.split(separator, limit)`:按分隔符拆分为字符串数组;
/// 分隔符可为 RegExp(含捕获组)或字符串。
pub fn string_split<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.split called with {} args", args.len());
let s = this_string(vm, args);
// Spec: if separator is undefined, return [string]
if args.len() < 2 || vm.reg(args[1]).is_undefined() {
let parts = vec![s.clone()];
return NativeResult::Ok(make_string_array(vm, &parts));
}
let sep_val = vm.reg(args[1]);
// ToUint32(limit), default 2^32-1
let limit = if args.len() > 2 {
let l = oxide_runtime_api::to_integer_or_infinity(vm.reg(args[2]));
if l.is_infinite() {
u32::MAX as usize
} else {
(l.max(0.0).trunc() as u64).min(u32::MAX as u64) as usize
}
} else {
u32::MAX as usize
};
if is_regexp_obj(sep_val, vm) {
let re_ptr = sep_val.as_js_object_ptr();
let re = unsafe { &*re_ptr };
if let Some(fn_ptr) = re.native_fn() {
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) };
let mut parts: Vec<String> = Vec::new();
let mut last_end = 0;
for caps in regex.captures_iter(&s) {
if parts.len() >= limit {
break;
}
let full = caps.get(0).unwrap();
parts.push(s[last_end..full.start()].to_string());
if parts.len() >= limit {
break;
}
for i in 1..caps.len() {
if parts.len() >= limit {
break;
}
parts.push(caps.get(i).map(|m| m.as_str()).unwrap_or("").to_string());
}
last_end = full.end();
}
if last_end <= s.len() && parts.len() < limit {
parts.push(s[last_end..].to_string());
}
return NativeResult::Ok(make_string_array(vm, &parts));
}
// Fall back to string path for regex-like objects without native engine regex
}
let sep = as_string(vm, sep_val);
let parts: Vec<String> = if sep.is_empty() {
s.chars().map(|c| c.to_string()).take(limit).collect()
} else {
s.split(&sep).map(|p| p.to_string()).take(limit).collect()
};
NativeResult::Ok(make_string_array(vm, &parts))
}
/// `String.prototype.replace(pattern, replacement)`:替换首个匹配;
/// 支持 RegExp(global 全替换)、字符串以及函数替换器。
pub fn string_replace<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.replace called with {} args", args.len());
let s = this_string(vm, args);
if args.len() < 2 {
return NativeResult::Ok(vm.new_string(&s));
}
let pattern_val = vm.reg(args[1]);
if is_regexp_obj(pattern_val, vm) {
let re_ptr = pattern_val.as_js_object_ptr();
let re = unsafe { &*re_ptr };
if let Some(fn_ptr) = re.native_fn() {
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) };
if args.len() > 2 {
let replacer_val = vm.reg(args[2]);
if replacer_val.is_object() {
let o = unsafe { &*replacer_val.as_js_object_ptr() };
if o.is_function() {
if let Some(captures) = regex.captures(&s) {
let full = captures.get(0).unwrap();
let mut cb_args: Vec<JsValue> = Vec::with_capacity(captures.len() + 2);
cb_args.push(vm.new_string(full.as_str()));
for i in 1..captures.len() {
cb_args.push(vm.new_string(captures.get(i).map(|m| m.as_str()).unwrap_or("")));
}
cb_args.push(JsValue::int(full.start() as i32));
cb_args.push(vm.new_string(&s));
match vm.call_function_sync(replacer_val, JsValue::undefined(), &cb_args) {
Ok(result) => {
let result_str = oxide_runtime_api::to_string(result);
let output = format!("{}{}{}", &s[..full.start()], &result_str, &s[full.end()..]);
return NativeResult::Ok(vm.new_string(&output));
}
Err(err) => {
return NativeResult::Err(crate::error::create_type_error(
vm,
&format!("replace replacer: {}", err),
))
}
}
}
return NativeResult::Ok(vm.new_string(&s));
}
}
}
let replacement = if args.len() > 2 { as_string(vm, vm.reg(args[2])) } else { String::new() };
let is_global = re.hash_props_vec().and_then(|v| v.get(3)).map(|v| v.as_bool()).unwrap_or(false);
let result = if is_global {
regex.replace_all(&s, replacement.as_str()).to_string()
} else {
regex.replacen(&s, 1, replacement.as_str()).to_string()
};
return NativeResult::Ok(vm.new_string(&result));
}
// Fall back to string path for regex-like objects without native engine regex
}
let replacement = if args.len() > 2 { as_string(vm, vm.reg(args[2])) } else { String::new() };
let pattern = as_string(vm, pattern_val);
let result = s.replacen(&pattern, &replacement, 1);
NativeResult::Ok(vm.new_string(&result))
}
/// `String.prototype.match(pattern)`:按 RegExp 匹配;global 返回全部匹配数组,
/// 否则返回首个匹配及捕获组,无匹配返回 null。
pub fn string_match_fn<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.match called with {} args", args.len());
let s = this_string(vm, args);
if args.len() < 2 {
return NativeResult::Ok(JsValue::null());
}
let pattern_val = vm.reg(args[1]);
if is_regexp_obj(pattern_val, vm) {
let re_ptr = pattern_val.as_js_object_ptr();
let re = unsafe { &*re_ptr };
let fn_ptr = match re.native_fn() {
Some(p) => p,
None => return NativeResult::Ok(JsValue::null()),
};
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) };
let is_global = re.hash_props_vec().and_then(|v| v.get(3)).map(|v| v.as_bool()).unwrap_or(false);
if is_global {
let matches: Vec<String> = regex.find_iter(&s).map(|m| m.as_str().to_string()).collect();
return NativeResult::Ok(make_string_array(vm, &matches));
}
if let Some(captures) = regex.captures(&s) {
let mut parts: Vec<String> = Vec::with_capacity(captures.len());
parts.push(captures.get(0).unwrap().as_str().to_string());
for i in 1..captures.len() {
parts.push(captures.get(i).map(|m| m.as_str()).unwrap_or("").to_string());
}
return NativeResult::Ok(make_string_array(vm, &parts));
}
return NativeResult::Ok(JsValue::null());
}
let pattern = as_string(vm, pattern_val);
if let Some(pos) = s.find(&pattern) {
return NativeResult::Ok(make_string_array(vm, &[s[pos..pos + pattern.len()].to_string()]));
}
NativeResult::Ok(JsValue::null())
}
/// `String.prototype.search(pattern)`:返回首个匹配位置,无匹配返回 -1。
pub fn string_search<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.search called with {} args", args.len());
let s = this_string(vm, args);
if args.len() < 2 {
return NativeResult::Ok(JsValue::int(-1));
}
let pattern_val = vm.reg(args[1]);
if is_regexp_obj(pattern_val, vm) {
let re_ptr = pattern_val.as_js_object_ptr();
let re = unsafe { &*re_ptr };
let fn_ptr = match re.native_fn() {
Some(p) => p,
None => return NativeResult::Ok(JsValue::int(-1)),
};
// SAFETY: fn_ptr holds a Box<regex::Regex> pointer stored by regexp_constructor.
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) };
if let Some(m) = regex.find(&s) {
return NativeResult::Ok(JsValue::int(m.start() as i32));
}
return NativeResult::Ok(JsValue::int(-1));
}
let pattern = as_string(vm, pattern_val);
if let Some(pos) = s.find(&pattern) {
return NativeResult::Ok(JsValue::int(pos as i32));
}
NativeResult::Ok(JsValue::int(-1))
}
/// `String.prototype.trimStart`:去除头部空白。
pub fn string_trim_start<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.trimStart called with {} args", args.len());
let s = this_string(vm, args);
NativeResult::Ok(vm.new_string(s.trim_start()))
}
/// `String.prototype.trimEnd`:去除尾部空白。
pub fn string_trim_end<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.trimEnd called with {} args", args.len());
let s = this_string(vm, args);
NativeResult::Ok(vm.new_string(s.trim_end()))
}
/// `String.prototype.codePointAt(pos)`:返回 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 = this_string(vm, args);
let pos = if args.len() > 1 {
vm.coerce_number_bounded(vm.reg(args[1])).unwrap_or(f64::NAN) as usize
} else {
0
};
let chars: Vec<char> = s.chars().collect();
if pos >= chars.len() {
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);
return NativeResult::Ok(JsValue::int(cp as i32));
}
}
NativeResult::Ok(JsValue::int(c as i32))
}
/// `String.prototype.normalize(form)`:按 NFC/NFD/NFKC/NFKD 规范化为 Unicode 规范形式。
pub fn string_normalize<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.normalize called with {} args", args.len());
use unicode_normalization::UnicodeNormalization;
let s = this_string(vm, args);
let form = if args.len() > 1 { as_string(vm, vm.reg(args[1])) } else { "NFC".to_string() };
let result: String = match form.as_str() {
"NFD" => s.nfd().collect(),
"NFKC" => s.nfkc().collect(),
"NFKD" => s.nfkd().collect(),
_ => s.nfc().collect(),
};
NativeResult::Ok(vm.new_string(&result))
}
const MALL_INPUT: &str = "__mal_input__";
const MALL_INDEX: &str = "__mal_index__";
const MALL_RE: &str = "__mal_re__";
/// `String.prototype.matchAll(pattern)`:返回带 `next` 的迭代器,逐步产出全部匹配
/// (要求 RegExp 带 global 标志;普通字符串会被转义成等效正则)。
pub fn string_match_all<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.matchAll called with {} args", args.len());
let s = this_string(vm, args);
if args.len() < 2 {
builtins_error!("String.prototype.matchAll: invalid receiver");
return NativeResult::Err(JsValue::undefined());
}
let pattern_val = vm.reg(args[1]);
let re_obj = if is_regexp_obj(pattern_val, vm) {
let re_ptr = pattern_val.as_js_object_ptr();
let re = unsafe { &*re_ptr };
let is_global = re.hash_props_vec().and_then(|v| v.get(3)).map(|v| v.as_bool()).unwrap_or(false);
if !is_global {
return NativeResult::Err(crate::error::create_type_error(
vm,
"String.prototype.matchAll: regex must have global flag",
));
}
pattern_val
} else {
let pattern_str = as_string(vm, pattern_val);
let escaped = regex::escape(&pattern_str);
let rx_str = if escaped.is_empty() { String::from("(?:)") } else { format!("({})", escaped) };
let compiled = match regex::Regex::new(&rx_str) {
Ok(rx) => rx,
Err(e) => {
return NativeResult::Err(crate::error::create_syntax_error(vm, &format!("Invalid regex: {}", e)));
}
};
let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
let mut stub = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto));
let boxed = Box::new(compiled);
let raw = Box::into_raw(boxed) as *const u8;
stub.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(raw as *const ()) }));
let stub_ptr = vm.alloc_object(stub);
JsValue::from_js_object(stub_ptr)
};
// Build wrapper object
builder_wrapper(vm, &s, re_obj)
}
fn builder_wrapper<H: VmHost>(vm: &mut H, input: &str, re_obj: JsValue) -> NativeResult {
let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
let wrapper = vm
.epoch()
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto)));
let wrapper_obj = unsafe { &mut *wrapper };
let input_si = vm.kernel_core().perm_interner().intern(MALL_INPUT).0;
let index_si = vm.kernel_core().perm_interner().intern(MALL_INDEX).0;
let re_si = vm.kernel_core().perm_interner().intern(MALL_RE).0;
let next_si = vm.kernel_core().perm_interner().intern("next").0;
let input_val = vm.new_string(input);
vm.set_or_create_prop_value(wrapper_obj, input_si, input_val);
vm.set_or_create_prop_value(wrapper_obj, index_si, JsValue::int(0));
vm.set_or_create_prop_value(wrapper_obj, re_si, re_obj);
let next_fn = make_public_native_fn(vm, "next", string_match_all_next::<H> as *const (), 0);
vm.set_or_create_prop_value(wrapper_obj, next_si, next_fn);
NativeResult::Ok(JsValue::from_js_object(wrapper))
}
fn make_public_native_fn<H: VmHost>(vm: &mut H, name: &str, native_fn: *const (), arg_count: u8) -> JsValue {
let function_proto = vm.session().builtin_world().function_proto.as_ptr() as *mut JsObject;
let mut func = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(function_proto));
func.set_function(true);
func.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(native_fn) }));
func.set_native_arg_count(arg_count);
let func = vm.alloc_object(func);
let name_si = vm.kernel_core().perm_interner().intern("name").0;
let value = vm.new_string(name);
let func_ref = unsafe { &mut *func };
vm.set_or_create_prop_value(func_ref, name_si, value);
JsValue::from_js_object(func)
}
/// `matchAll` 迭代器的 `next`:返回 `{value: 匹配数组, done}`,耗尽后 done 为 true。
pub fn string_match_all_next<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
if !this_val.is_object() {
return NativeResult::Err(crate::error::create_type_error(vm, "matchAll next called on non-object"));
}
let wrapper = unsafe { &mut *this_val.as_js_object_ptr() };
let input_si = vm.kernel_core().perm_interner().intern(MALL_INPUT).0;
let index_si = vm.kernel_core().perm_interner().intern(MALL_INDEX).0;
let re_si = vm.kernel_core().perm_interner().intern(MALL_RE).0;
let input_val = match vm.ordinary_get(wrapper, input_si, this_val) {
Ok(v) => v,
Err(_) => return make_match_done_result(vm, JsValue::undefined()),
};
let input_str = oxide_runtime_api::to_string(input_val);
let idx_val = match vm.ordinary_get(wrapper, index_si, this_val) {
Ok(v) => v,
Err(_) => return make_match_done_result(vm, JsValue::undefined()),
};
let mut idx = if idx_val.is_int() { idx_val.as_int().max(0) as usize } else { 0 };
let re_val = match vm.ordinary_get(wrapper, re_si, this_val) {
Ok(v) if v.is_object() => v,
_ => return make_match_done_result(vm, JsValue::undefined()),
};
let re_obj = unsafe { &*re_val.as_js_object_ptr() };
let fn_ptr = match re_obj.native_fn() {
Some(p) => p,
None => return make_match_done_result(vm, JsValue::undefined()),
};
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) };
if let Some(m) = regex.find_at(&input_str, idx) {
let mut parts: Vec<String> = Vec::new();
if let Some(caps) = regex.captures_at(&input_str, idx) {
parts.push(caps.get(0).unwrap().as_str().to_string());
for i in 1..caps.len() {
parts.push(caps.get(i).map(|c| c.as_str()).unwrap_or("").to_string());
}
}
idx = m.end();
vm.set_or_create_prop_value(wrapper, index_si, JsValue::int(idx as i32));
let arr_val = make_string_array(vm, &parts);
make_match_done_result(vm, arr_val)
} else {
make_match_done_result(vm, JsValue::undefined())
}
}
fn make_match_done_result<H: VmHost>(vm: &mut H, value: JsValue) -> NativeResult {
let done = value.is_undefined();
let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject;
let obj = vm
.epoch()
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto)));
let value_si = vm.kernel_core().perm_interner().intern("value").0;
let done_si = vm.kernel_core().perm_interner().intern("done").0;
let obj_ref = unsafe { &mut *obj };
vm.set_or_create_prop_value(obj_ref, value_si, value);
vm.set_or_create_prop_value(obj_ref, done_si, JsValue::bool(done));
NativeResult::Ok(JsValue::from_js_object(obj))
}
/// `String.prototype.replaceAll(pattern, replacement)`:替换全部匹配
/// (RegExp 或字符串模式)。
pub fn string_replace_all<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
builtins_debug!("String.prototype.replaceAll called with {} args", args.len());
let s = this_string(vm, args);
if args.len() < 2 {
return NativeResult::Ok(vm.new_string(&s));
}
let pattern_val = vm.reg(args[1]);
let replacement = if args.len() > 2 { as_string(vm, vm.reg(args[2])) } else { String::new() };
if is_regexp_obj(pattern_val, vm) {
let re_ptr = pattern_val.as_js_object_ptr();
let re = unsafe { &*re_ptr };
let fn_ptr = match re.native_fn() {
Some(p) => p,
None => return NativeResult::Ok(vm.new_string(&s)),
};
// SAFETY: fn_ptr holds a Box<regex::Regex> pointer stored by regexp_constructor.
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) };
let result = regex.replace_all(&s, replacement.as_str()).to_string();
return NativeResult::Ok(vm.new_string(&result));
}
let pattern = as_string(vm, pattern_val);
let result = s.replace(&pattern, &replacement);
NativeResult::Ok(vm.new_string(&result))
}