This repository has been archived on 2026-09-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
OxideJS/crates/oxide_builtins/src/regexp.rs
T
2026-08-06 23:10:27 +08:00

260 lines
9.2 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, NativeFnPtr};
use oxide_types::value::JsValue;
use oxide_runtime_api::{NativeResult, VmHost};
fn get_regexp_ptr<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<*mut JsObject, JsValue> {
let this_val = vm.reg(args[0]);
if !this_val.is_object() {
return Err(crate::error::create_type_error(vm, "RegExp.prototype method called on non-object"));
}
let ptr = this_val.as_js_object_ptr();
if ptr.is_null() {
return Err(crate::error::create_type_error(vm, "null object"));
}
if !unsafe { &*ptr }.is_regexp_obj() {
return Err(crate::error::create_type_error(vm, "RegExp.prototype method called on non-RegExp object"));
}
Ok(ptr)
}
fn parse_flags(flags: &str) -> (bool, bool, bool) {
let mut global = false;
let mut ignore_case = false;
let mut multi_line = false;
for c in flags.chars() {
match c {
'g' => global = true,
'i' => ignore_case = true,
'm' => multi_line = true,
_ => {}
}
}
(global, ignore_case, multi_line)
}
fn set_prop<H: VmHost>(obj: &mut JsObject, name: &str, val: JsValue, vm: &H) {
let si = vm.kernel_core().perm_interner().intern(name).0;
let shape_id = vm.kernel_core().shape_forge().make_shape(obj.shape_id(), si);
obj.set_shape_id(shape_id);
obj.ensure_hash_props().push(val);
}
fn get_prop(obj: &JsObject, idx: usize) -> JsValue {
obj.hash_props_vec()
.and_then(|v| v.get(idx))
.copied()
.unwrap_or(JsValue::undefined())
}
fn set_prop_at(obj: *mut JsObject, idx: usize, val: JsValue) {
unsafe {
let re = &mut *obj;
if let Some(vec) = re.hash_props_vec() {
let ptr = vec.as_ptr() as *mut JsValue;
let v = &mut *ptr.add(idx);
*v = val;
}
}
}
/// `RegExp(pattern, flags)` 构造逻辑:用 regex crate 编译模式,
/// 支持 g/i/m 标志;非法模式抛 SyntaxError。编译结果存于对象的 native_fn 槽。
pub fn regexp_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let (pattern, flags) = if args.len() < 2 {
(String::new(), String::new())
} else if args.len() < 3 {
let pat = oxide_runtime_api::to_string(vm.reg(args[1]));
(pat, String::new())
} else {
let pat = oxide_runtime_api::to_string(vm.reg(args[1]));
let fl = oxide_runtime_api::to_string(vm.reg(args[2]));
(pat, fl)
};
let (global, ignore_case, multi_line) = parse_flags(&flags);
let mut obj = JsObject::new_empty(
EMPTY_SHAPE_ID,
JsValue::from_js_object(vm.session().builtin_world().regexp_proto.as_ptr() as *mut JsObject),
);
let compiled = regex::RegexBuilder::new(&pattern)
.case_insensitive(ignore_case)
.multi_line(multi_line)
.build();
match compiled {
Ok(re) => {
let re_ptr = Box::into_raw(Box::new(re));
// SAFETY: re_ptr 是构造器经 `NativeFnPtr::from_raw(re_ptr as *const ())`
// 写入的 `Box<regex::Regex>` 指针。RegExp 对象把 native_fn 字段复用作
// 已编译 Regex 的存放处——而非 NativeFn 指针。对象存活期间有效;
// VM 重置经 `drop_regexp_native` 释放该 Box。
obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(re_ptr as *const ()) }));
}
Err(e) => {
return NativeResult::Err(crate::error::create_syntax_error(
vm,
&format!("Invalid regular expression: {e}"),
));
}
}
set_prop(&mut obj, "lastIndex", JsValue::int(0), vm);
set_prop(&mut obj, "source", vm.new_string(&pattern), vm);
set_prop(&mut obj, "flags", vm.new_string(&flags), vm);
set_prop(&mut obj, "global", JsValue::bool(global), vm);
set_prop(&mut obj, "ignoreCase", JsValue::bool(ignore_case), vm);
set_prop(&mut obj, "multiline", JsValue::bool(multi_line), vm);
set_prop(&mut obj, "dotAll", JsValue::bool(false), vm);
set_prop(&mut obj, "sticky", JsValue::bool(false), vm);
set_prop(&mut obj, "unicode", JsValue::bool(false), vm);
obj.type_tag = JsObject::OBJ_TYPE_REGEXP;
let obj_ptr = vm.alloc_object(obj);
NativeResult::Ok(JsValue::from_js_object(obj_ptr))
}
/// 释放 RegExp 对象 native_fn 槽中编译的 `regex::Regex`,返回释放字节数。
pub fn drop_regexp_native(obj: &mut JsObject) -> u64 {
if !obj.is_regexp_obj() {
return 0;
}
let Some(ptr) = obj.native_fn() else {
return 0;
};
let regex_ptr = ptr.as_ptr() as *mut regex::Regex;
if regex_ptr.is_null() {
return 0;
}
unsafe { drop(Box::from_raw(regex_ptr)) };
obj.set_native_fn(None);
std::mem::size_of::<regex::Regex>() as u64
}
/// `RegExp.prototype.test(string)`:判断是否匹配。global 模式下从 lastIndex 开始匹配。
pub fn regexp_test<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let re_ptr = match get_regexp_ptr(vm, args) {
Ok(ptr) => ptr,
Err(err) => return NativeResult::Err(err),
};
let re = unsafe { &*re_ptr };
let fn_ptr = match re.native_fn() {
None => {
return NativeResult::Err(crate::error::create_type_error(vm, "invalid RegExp"));
}
Some(p) => p,
};
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regex::Regex>` 指针。
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) };
let haystack = oxide_runtime_api::to_string(vm.reg(if args.len() > 1 { args[1] } else { args[0] }));
let last_index = vm.coerce_number_bounded(get_prop(re, 0)).unwrap_or(f64::NAN) as usize;
let is_global = get_prop(re, 3).as_bool();
if is_global {
let result = regex.find_at(&haystack, last_index).is_some();
NativeResult::Ok(JsValue::bool(result))
} else {
NativeResult::Ok(JsValue::bool(regex.is_match(&haystack)))
}
}
/// `RegExp.prototype.exec(string)`:执行匹配并返回数组(含捕获组、index、input)。
/// 无匹配返回 null;global 模式推进/重置 lastIndex。
pub fn regexp_exec<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let re_ptr = match get_regexp_ptr(vm, args) {
Ok(ptr) => ptr,
Err(err) => return NativeResult::Err(err),
};
let fn_ptr = {
let re = unsafe { &*re_ptr };
match re.native_fn() {
None => {
return NativeResult::Err(crate::error::create_type_error(vm, "invalid RegExp"));
}
Some(p) => p,
}
};
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regex::Regex>` 指针。
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) };
let haystack = oxide_runtime_api::to_string(vm.reg(if args.len() > 1 { args[1] } else { args[0] }));
let (last_index, is_global) = {
let re = unsafe { &*re_ptr };
let li = vm.coerce_number_bounded(get_prop(re, 0)).unwrap_or(f64::NAN) as usize;
let g = get_prop(re, 3).as_bool();
(li, g)
};
let match_result = if is_global {
if last_index > haystack.len() {
return NativeResult::Ok(JsValue::null());
}
regex.find_at(&haystack, last_index)
} else {
regex.find(&haystack)
};
if let Some(m) = match_result {
let mut match_obj = JsObject::new_empty(
EMPTY_SHAPE_ID,
JsValue::from_js_object(vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject),
);
let full_match = &haystack[m.start()..m.end()];
let full_val = vm.new_string(full_match);
match_obj.ensure_hash_props().push(full_val);
let captures = regex.captures(&haystack[m.start()..m.end()]);
if let Some(caps) = &captures {
for i in 1..caps.len() {
let cap_str = caps.get(i).map(|cm| cm.as_str()).unwrap_or("");
let cap_val = vm.new_string(cap_str);
match_obj.ensure_hash_props().push(cap_val);
}
}
set_prop(&mut match_obj, "index", JsValue::int(m.start() as i32), vm);
let haystack_val = vm.new_string(&haystack);
set_prop(&mut match_obj, "input", haystack_val, vm);
set_prop(&mut match_obj, "groups", JsValue::undefined(), vm);
if is_global {
set_prop_at(re_ptr, 0, JsValue::int(m.end() as i32));
}
let obj_ptr = vm.alloc_object(match_obj);
NativeResult::Ok(JsValue::from_js_object(obj_ptr))
} else {
if is_global {
set_prop_at(re_ptr, 0, JsValue::int(0));
}
NativeResult::Ok(JsValue::null())
}
}
/// `RegExp.prototype.toString`:按 `/source/flags` 形式返回。
pub fn regexp_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let re_ptr = match get_regexp_ptr(vm, args) {
Ok(ptr) => ptr,
Err(err) => return NativeResult::Err(err),
};
let re = unsafe { &*re_ptr };
let source = {
let val = get_prop(re, 1);
vm.lookup_str(val).unwrap_or_default()
};
let flags = {
let val = get_prop(re, 2);
vm.lookup_str(val).unwrap_or_default()
};
let result = format!("/{}/{}", source, flags);
NativeResult::Ok(vm.new_string(&result))
}