feat: RegExp.prototype.compile 与全局遗留访问器 $1-$9 族

This commit is contained in:
2026-10-09 11:14:02 +00:00
parent bd969b4552
commit 0fc105243d
4 changed files with 426 additions and 43 deletions
Generated
+19 -19
View File
@@ -1160,7 +1160,7 @@ dependencies = [
[[package]]
name = "oxide_builtins"
version = "0.8.99"
version = "0.8.100"
dependencies = [
"chrono",
"fast-float",
@@ -1185,7 +1185,7 @@ dependencies = [
[[package]]
name = "oxide_bytecode"
version = "0.8.99"
version = "0.8.100"
dependencies = [
"num-bigint",
"oxide_log",
@@ -1193,7 +1193,7 @@ dependencies = [
[[package]]
name = "oxide_cfg"
version = "0.8.99"
version = "0.8.100"
dependencies = [
"oxide_bytecode",
"oxide_emit",
@@ -1204,7 +1204,7 @@ dependencies = [
[[package]]
name = "oxide_cli"
version = "0.8.99"
version = "0.8.100"
dependencies = [
"ansi_term",
"bumpalo",
@@ -1226,7 +1226,7 @@ dependencies = [
[[package]]
name = "oxide_code_cache"
version = "0.8.99"
version = "0.8.100"
dependencies = [
"lru",
"oxide_bytecode",
@@ -1235,7 +1235,7 @@ dependencies = [
[[package]]
name = "oxide_compiler"
version = "0.8.99"
version = "0.8.100"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1255,7 +1255,7 @@ dependencies = [
[[package]]
name = "oxide_dce"
version = "0.8.99"
version = "0.8.100"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1269,7 +1269,7 @@ dependencies = [
[[package]]
name = "oxide_emit"
version = "0.8.99"
version = "0.8.100"
dependencies = [
"num-bigint",
"oxc_allocator",
@@ -1284,7 +1284,7 @@ dependencies = [
[[package]]
name = "oxide_engine"
version = "0.8.99"
version = "0.8.100"
dependencies = [
"oxide_bytecode",
"oxide_compiler",
@@ -1296,7 +1296,7 @@ dependencies = [
[[package]]
name = "oxide_ir"
version = "0.8.99"
version = "0.8.100"
dependencies = [
"oxide_bytecode",
"oxide_log",
@@ -1305,7 +1305,7 @@ dependencies = [
[[package]]
name = "oxide_kernel"
version = "0.8.99"
version = "0.8.100"
dependencies = [
"bumpalo",
"dashmap",
@@ -1319,7 +1319,7 @@ dependencies = [
[[package]]
name = "oxide_liveness"
version = "0.8.99"
version = "0.8.100"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1331,7 +1331,7 @@ dependencies = [
[[package]]
name = "oxide_log"
version = "0.8.99"
version = "0.8.100"
dependencies = [
"tracing",
"tracing-appender",
@@ -1340,7 +1340,7 @@ dependencies = [
[[package]]
name = "oxide_parser"
version = "0.8.99"
version = "0.8.100"
dependencies = [
"oxc_allocator",
"oxc_ast",
@@ -1355,7 +1355,7 @@ dependencies = [
[[package]]
name = "oxide_regalloc"
version = "0.8.99"
version = "0.8.100"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1371,7 +1371,7 @@ dependencies = [
[[package]]
name = "oxide_runtime_api"
version = "0.8.99"
version = "0.8.100"
dependencies = [
"num-bigint",
"num-traits",
@@ -1384,7 +1384,7 @@ dependencies = [
[[package]]
name = "oxide_test262"
version = "0.8.99"
version = "0.8.100"
dependencies = [
"oxide_bytecode",
"oxide_compiler",
@@ -1402,7 +1402,7 @@ dependencies = [
[[package]]
name = "oxide_types"
version = "0.8.99"
version = "0.8.100"
dependencies = [
"bumpalo",
"dashmap",
@@ -1415,7 +1415,7 @@ dependencies = [
[[package]]
name = "oxide_vm"
version = "0.8.99"
version = "0.8.100"
dependencies = [
"bumpalo",
"chrono",
+1 -1
View File
@@ -23,7 +23,7 @@ members = [
]
[workspace.package]
version = "0.8.99"
version = "0.8.100"
edition = "2021"
license = "MIT"
rust-version = "1.80"
+343 -21
View File
@@ -371,17 +371,6 @@ pub(crate) fn get_substitution_units<H: VmHost>(
Ok(out)
}
fn set_prop<H: VmHost>(obj: &mut JsObject, name: &str, val: JsValue, vm: &H) {
let si = vm.perm_intern(name);
set_prop_by_si(obj, si, val, vm);
}
fn set_prop_by_si<H: VmHost>(obj: &mut JsObject, prop_name_si: u32, val: JsValue, vm: &H) {
let shape_id = vm.make_shape(obj.shape_id(), prop_name_si);
obj.set_shape_id(shape_id);
obj.ensure_hash_props().push(val);
}
/// 规范化 flags 顺序为规范序(d g i m s u v y):同一组 flag 不论书写顺序
/// 恒得同一串,保证 `flags`/`toString` 输出与 V8 一致。
fn normalize_flags(flags: &str) -> String {
@@ -490,8 +479,20 @@ pub fn regexp_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
));
}
let mut obj =
JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(vm.builtin_proto(ProtoKind::RegExpProto)));
// 构造路径(new / Reflect.construct,new.target 为对象):复用调用方按
// newTarget.prototype 预分配的 receiver,原型基由 newTarget 决定(子类实例
// proto = 子类 prototype,与 %RegExp.prototype% 不可恒等);普通调用
// (new.target 非对象)自分配 %RegExp.prototype% 基新对象,忽略传入的 this。
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
let new_target = vm.reg(255);
let this_ptr = if this_val.is_object() && new_target.is_object() {
this_val.as_js_object_ptr()
} else {
vm.alloc_object(JsObject::new_empty(
EMPTY_SHAPE_ID,
JsValue::from_js_object(vm.builtin_proto(ProtoKind::RegExpProto)),
))
};
// regress 用 JS flag 字符串编译:g/i/m/s/y/u/v 原样透传(vendored fork
// 原生识别 v 标志,u/v 互斥已在上方前置校验)。
@@ -504,7 +505,9 @@ pub fn regexp_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
// 写入的 `Box<regress::Regex>` 指针。RegExp 对象把 native_fn 字段复用作
// 已编译 Regex 的存放处——而非 NativeFn 指针。对象存活期间有效;
// VM 重置经 `drop_regexp_native` 释放该 Box。
obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(re_ptr as *const ()) }));
unsafe {
(*this_ptr).set_native_fn(Some(NativeFnPtr::from_raw(re_ptr as *const ())));
}
}
Err(e) => {
return NativeResult::Err(crate::error::create_syntax_error(
@@ -514,14 +517,22 @@ pub fn regexp_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
}
}
set_prop(&mut obj, "lastIndex", JsValue::int(0), vm);
obj.type_tag = JsObject::OBJ_TYPE_REGEXP;
// source/flags 存实例字段:原型访问器从本字段读取,不作为自身属性枚举。
obj.set_regexp_source(vm.new_string_units_owned(pattern_units));
obj.set_regexp_flags(vm.new_string_units_owned(flags_units));
// lastIndex 作实例自身属性写入(缺省描述符,与既有构造器口径一致)。
{
let li_si = vm.perm_intern("lastIndex");
let shape_id = vm.make_shape(unsafe { (*this_ptr).shape_id() }, li_si);
unsafe {
let obj = &mut *this_ptr;
obj.set_shape_id(shape_id);
obj.push_prop(JsValue::int(0));
obj.type_tag = JsObject::OBJ_TYPE_REGEXP;
// source/flags 存实例字段:原型访问器从本字段读取,不作为自身属性枚举。
obj.set_regexp_source(vm.new_string_units_owned(pattern_units));
obj.set_regexp_flags(vm.new_string_units_owned(flags_units));
}
}
let obj_ptr = vm.alloc_object(obj);
NativeResult::Ok(JsValue::from_js_object(obj_ptr))
NativeResult::Ok(JsValue::from_js_object(this_ptr))
}
/// 只读核算持有编译正则对象(RegExp / matchAll 载体)的正则字节(不释放)。
@@ -741,6 +752,12 @@ pub fn regexp_exec<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
return NativeResult::Ok(JsValue::null());
};
// 命中后更新 15 个遗留静态槽(input / lastMatch / lastParen / leftContext /
// rightContext / index / $1-$9):值以非枚举可配置数据属性落 %RegExp% 构造器
// (字符串 / 整数,非对象边)。仅 exec 接线——test 不建结果数组,语料不测
// test 后的槽值,取舍在此。
update_legacy_slots(vm, &m, &text, &haystack);
let range = m.range();
let group_count = m.captures.len();
let n = 1 + group_count;
@@ -790,6 +807,311 @@ pub fn regexp_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
NativeResult::Ok(vm.new_string_owned(result))
}
/// `RegExp.prototype.compile(pattern, flags)`(Annex B):就地重编译本正则并
/// 将 lastIndex 归零,返回 this。
///
/// # 步骤
/// 1. this 门禁:须为对象、`is_regexp_obj()`、且 proto 与 %RegExp.prototype%
/// 精确恒等(非对象 / 非 RegExp / 子类实例均抛 TypeError)。
/// 2. pattern 为 RegExp 实例(`is_regexp_obj()`)时:flags 必须为 undefined
/// (否则 TypeError),且直读 [[OriginalSource]]/[[OriginalFlags]] 槽
/// (不触发属性访问)。
/// 3. pattern 非 RegExp 时:undefined → 空串,否则完整 ToString(异常原样
/// 传播);flags 同。
/// 4. flags 校验(未知 / 重复 / u-v 互斥 → SyntaxError)→ regress 编译(失败
/// → SyntaxError);编译失败时对象状态不变(槽与 lastIndex 均不动)。
/// 5. 编译成功后才更新 [[OriginalSource]]/[[OriginalFlags]] 槽并替换 native_fn
/// 槽(先释放旧 Box 再写新指针,顺序不可反)。
/// 6. Set(this, "lastIndex", 0, true)(strict,可抛 TypeError;此时槽已更新)。
/// 7. 返回 this。
///
/// # 边界与前提
/// - 子类实例(proto = 子类 prototype)在第 1 步即抛 TypeError,不触后续。
/// - pattern 为 RegExp 时不读任何属性(源 / 旗直取内部槽),访问器计数恒 0。
///
/// # 副作用
/// - 编译成功时更新对象内部槽(source / flags / native_fn)与 lastIndex。
pub fn regexp_compile<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
// 第 1 步:this 门禁。
let this_val = vm.reg(args[0]);
if !this_val.is_object() {
return NativeResult::Err(crate::error::create_type_error(vm, "RegExp.prototype.compile called on non-object"));
}
let this_ptr = this_val.as_js_object_ptr();
if this_ptr.is_null() {
return NativeResult::Err(crate::error::create_type_error(vm, "null object"));
}
if !unsafe { &*this_ptr }.is_regexp_obj() {
return NativeResult::Err(crate::error::create_type_error(vm, "RegExp.prototype.compile called on non-RegExp object"));
}
// proto 精确恒等:子类实例(proto = 子类 prototype)抛 TypeError。
let proto_ptr = vm.builtin_proto(ProtoKind::RegExpProto);
if unsafe { &*this_ptr }.proto() != JsValue::from_js_object(proto_ptr) {
return NativeResult::Err(crate::error::create_type_error(vm, "RegExp.prototype.compile called on subclass instance"));
}
// 第 2 / 3 步:pattern / flags 取参。
let pattern_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
let flags_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
let (pattern_units, flags_units) = if pattern_val.is_object() {
let p_ptr = pattern_val.as_js_object_ptr();
if !p_ptr.is_null() && unsafe { &*p_ptr }.is_regexp_obj() {
// pattern 为 RegExp 实例:flags 必须为 undefined。
if !flags_val.is_undefined() {
return NativeResult::Err(crate::error::create_type_error(
vm,
"RegExp.prototype.compile: flags must be undefined when pattern is a RegExp",
));
}
// 直读 [[OriginalSource]]/[[OriginalFlags]] 槽(不触发属性访问)。
let source = unsafe { &*p_ptr }.get_regexp_source();
let flags = unsafe { &*p_ptr }.get_regexp_flags();
(vm.string_units(source).into_owned(), vm.string_units(flags).into_owned())
} else {
// pattern 非 RegExp:undefined → 空串,否则完整 ToString。
(
match to_pattern_units(vm, pattern_val) {
Ok(u) => u,
Err(e) => return NativeResult::Err(e),
},
match to_pattern_units(vm, flags_val) {
Ok(u) => u,
Err(e) => return NativeResult::Err(e),
},
)
}
} else {
(
match to_pattern_units(vm, pattern_val) {
Ok(u) => u,
Err(e) => return NativeResult::Err(e),
},
match to_pattern_units(vm, flags_val) {
Ok(u) => u,
Err(e) => return NativeResult::Err(e),
},
)
};
let pattern = String::from_utf16_lossy(&pattern_units);
// 第 4 步:flags 校验(未知 / 重复 / u-v 互斥均抛 SyntaxError)。
let mut flag_seen = [false; 8];
for &unit in &flags_units {
let ch = char::from_u32(unit as u32).unwrap_or('\u{FFFD}');
let idx = match ch {
'd' => 0,
'g' => 1,
'i' => 2,
'm' => 3,
's' => 4,
'u' => 5,
'v' => 6,
'y' => 7,
_ => {
return NativeResult::Err(crate::error::create_syntax_error(
vm,
&format!("Invalid regular expression flags: '{ch}' is not a valid flag"),
));
}
};
if flag_seen[idx] {
return NativeResult::Err(crate::error::create_syntax_error(
vm,
&format!("Invalid regular expression flags: '{ch}' is duplicated"),
));
}
flag_seen[idx] = true;
}
let flags = normalize_flags(&String::from_utf16_lossy(&flags_units));
let flags_units = flags.encode_utf16().collect::<Vec<u16>>();
if flags.contains('u') && flags.contains('v') {
return NativeResult::Err(crate::error::create_syntax_error(
vm,
"Invalid regular expression flags: 'u' and 'v' are mutually exclusive",
));
}
// 第 4 步续:regress 编译(失败 → SyntaxError,对象状态不变)。
let re = match regress::Regex::with_flags(&pattern, flags.as_str()) {
Ok(re) => re,
Err(e) => {
return NativeResult::Err(crate::error::create_syntax_error(
vm,
&format!("Invalid regular expression: {e}"),
));
}
};
// 第 5 步:更新槽并替换 native_fn 槽(先释放旧 Box 再写新指针)。
let re_ptr = Box::into_raw(Box::new(re));
unsafe {
let obj = &mut *this_ptr;
drop_regexp_native(obj);
// SAFETY: re_ptr 为 `Box<regress::Regex>` 指针,同构造器写入口径。
obj.set_native_fn(Some(NativeFnPtr::from_raw(re_ptr as *const ())));
obj.set_regexp_source(vm.new_string_units_owned(pattern_units));
obj.set_regexp_flags(vm.new_string_units_owned(flags_units));
}
// 第 6 步:Set(this, "lastIndex", 0, true)(strict,可抛 TypeError)。
if let Err(err) = set_last_index(vm, this_ptr, this_val, 0) {
return NativeResult::Err(err);
}
// 第 7 步:返回 this。
NativeResult::Ok(this_val)
}
/// compile 的 pattern / flags 单元序列转换:undefined → 空串,其余完整
/// ToString(异常原样传播)。
fn to_pattern_units<H: VmHost>(vm: &mut H, val: JsValue) -> Result<Vec<u16>, JsValue> {
if val.is_undefined() {
return Ok(Vec::new());
}
match oxide_runtime_api::to_units_full(val, vm) {
Ok(u) => Ok(u),
Err(e) => Err(crate::iterator::engine_error(vm, &e)),
}
}
/// 遗留静态访问器门禁:`SameValue(%RegExp%, this)` 为假抛 TypeError(非对象、
/// 实例、prototype、子类构造器、全部原始值均抛)。
fn legacy_gate<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<(), JsValue> {
let this_val = vm.reg(args[0]);
if !this_val.is_object() {
return Err(crate::error::create_type_error(vm, "RegExp legacy accessor requires %RegExp% receiver"));
}
let this_ptr = this_val.as_js_object_ptr();
let ctor_ptr = vm.builtin_proto(ProtoKind::RegExpConstructor);
if this_ptr != ctor_ptr {
return Err(crate::error::create_type_error(vm, "RegExp legacy accessor requires %RegExp% receiver"));
}
Ok(())
}
/// 把遗留槽数据属性写入 %RegExp% 构造器:{writable:true, enumerable:false,
/// configurable:true},幂等(既有槽原位覆写值,不追加重复槽)。
fn legacy_slot_write<H: VmHost>(vm: &mut H, slot: &str, val: JsValue) {
let ctor_ptr = vm.builtin_proto(ProtoKind::RegExpConstructor);
let si = vm.perm_intern(slot);
// SAFETY: ctor_ptr 为 builtin world 固定 P 字段,session 独占期间始终有效。
unsafe {
let ctor = &mut *ctor_ptr;
vm.set_or_create_prop_value(ctor, si, val);
if let Some(pos) = vm.get_own_property_slot(ctor, si) {
ctor.set_data_meta(pos, oxide_types::object::PropAttributes::new(true, false, true));
}
}
}
/// 遗留静态 getter:读 %RegExp% 构造器上的槽数据属性(槽未建时返 undefined)。
fn legacy_get<H: VmHost>(vm: &mut H, args: &[u8], slot: &str) -> NativeResult {
if let Err(e) = legacy_gate(vm, args) {
return NativeResult::Err(e);
}
let ctor_ptr = vm.builtin_proto(ProtoKind::RegExpConstructor);
let si = vm.perm_intern(slot);
// SAFETY: ctor_ptr 为 builtin world 固定 P 字段,session 独占期间始终有效。
let val = unsafe {
let ctor = &*ctor_ptr;
match vm.get_own_property_slot(ctor, si) {
Some(pos) => ctor.get_prop_at(pos),
None => JsValue::undefined(),
}
};
NativeResult::Ok(val)
}
/// 遗留静态 setter:把槽数据属性写入 %RegExp% 构造器(幂等数据属性写,不触发
/// 用户代码)。
fn legacy_set<H: VmHost>(vm: &mut H, args: &[u8], slot: &str) -> NativeResult {
if let Err(e) = legacy_gate(vm, args) {
return NativeResult::Err(e);
}
let val = vm.reg(args[1]);
legacy_slot_write(vm, slot, val);
NativeResult::Ok(JsValue::undefined())
}
/// exec 命中后按匹配结果更新 15 个遗留静态槽:input = haystack 文本、
/// lastMatch = 完整匹配串、lastParen = 末捕获组(无捕获组时 undefined)、
/// leftContext = 匹配前文本、rightContext = 匹配后文本、index = 匹配起点
/// (码元口径整数)、$1-$9 = 捕获组 1-9(未参与为 undefined)。
fn update_legacy_slots<H: VmHost>(vm: &mut H, m: &regress::Match, text: &MatchText, haystack: &OwnedText) {
let range = m.range();
let group_count = m.captures.len();
// 各槽值先落入局部再写入(legacy_slot_write 与值分配均可变借 vm,不可同式)。
let input_val = haystack.to_value(vm);
legacy_slot_write(vm, "input", input_val);
let last_match_val = vm.new_string_units_owned(text.slice(range.start, range.end).into_owned());
legacy_slot_write(vm, "lastMatch", last_match_val);
let last_paren = if group_count > 0 {
match m.group(group_count) {
Some(g) => vm.new_string_units_owned(text.slice(g.start, g.end).into_owned()),
None => JsValue::undefined(),
}
} else {
JsValue::undefined()
};
legacy_slot_write(vm, "lastParen", last_paren);
let left_val = vm.new_string_units_owned(text.slice(0, range.start).into_owned());
legacy_slot_write(vm, "leftContext", left_val);
let right_val = vm.new_string_units_owned(text.slice(range.end, text.len_units()).into_owned());
legacy_slot_write(vm, "rightContext", right_val);
legacy_slot_write(vm, "index", JsValue::int(text.unit_pos(range.start) as i32));
for i in 1..=9 {
let cap = if i <= group_count {
match m.group(i) {
Some(g) => vm.new_string_units_owned(text.slice(g.start, g.end).into_owned()),
None => JsValue::undefined(),
}
} else {
JsValue::undefined()
};
legacy_slot_write(vm, &format!("${i}"), cap);
}
}
/// 生成一对遗留 get/set 访问器(共享同一槽名,如 `input` 与 `$_` 同槽)。
macro_rules! legacy_accessor_pair {
($get:ident, $set:ident, $slot:literal) => {
pub fn $get<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
legacy_get(vm, args, $slot)
}
pub fn $set<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
legacy_set(vm, args, $slot)
}
};
}
/// 生成单个遗留 getter(set 恒 undefined,如 `index` 与 `$1`-`$9`)。
macro_rules! legacy_accessor_getter {
($get:ident, $slot:literal) => {
pub fn $get<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
legacy_get(vm, args, $slot)
}
};
}
// 仅 input/$_ 为 get+set 对(唯一带 setter 的遗留访问器);其余共享槽
// (lastMatch / lastParen / leftContext / rightContext)为 getter-only。
legacy_accessor_pair!(regexp_legacy_get_input, regexp_legacy_set_input, "input");
legacy_accessor_getter!(regexp_legacy_get_last_match, "lastMatch");
legacy_accessor_getter!(regexp_legacy_get_last_paren, "lastParen");
legacy_accessor_getter!(regexp_legacy_get_left_context, "leftContext");
legacy_accessor_getter!(regexp_legacy_get_right_context, "rightContext");
legacy_accessor_getter!(regexp_legacy_get_index, "index");
legacy_accessor_getter!(regexp_legacy_get_dollar_1, "$1");
legacy_accessor_getter!(regexp_legacy_get_dollar_2, "$2");
legacy_accessor_getter!(regexp_legacy_get_dollar_3, "$3");
legacy_accessor_getter!(regexp_legacy_get_dollar_4, "$4");
legacy_accessor_getter!(regexp_legacy_get_dollar_5, "$5");
legacy_accessor_getter!(regexp_legacy_get_dollar_6, "$6");
legacy_accessor_getter!(regexp_legacy_get_dollar_7, "$7");
legacy_accessor_getter!(regexp_legacy_get_dollar_8, "$8");
legacy_accessor_getter!(regexp_legacy_get_dollar_9, "$9");
/// `RegExp.escape(string)`:返回语法字符、其它标点、空白/行终止符、孤立
/// surrogate、首字符数字/ASCII 字母均被转义的新字符串;参数非字符串抛 TypeError。
///
+63 -2
View File
@@ -1,8 +1,8 @@
use std::sync::Arc;
use crate::bindings::{
apply_binding_table, bind_accessor_getter, bind_accessor_getter_key, bind_well_known_method,
configure_native_constructor,
apply_binding_table, bind_accessor_getter, bind_accessor_getset, bind_accessor_getter_key,
bind_well_known_method, configure_native_constructor,
};
use oxide_kernel::kernel::{KernelCore, KernelSession};
use oxide_types::object::JsObject;
@@ -37,9 +37,70 @@ pub fn bind_regexp(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
("exec", oxide_builtins::regexp::regexp_exec::<crate::vm::Vm> as *const (), 1),
("test", oxide_builtins::regexp::regexp_test::<crate::vm::Vm> as *const (), 1),
("toString", oxide_builtins::regexp::regexp_to_string::<crate::vm::Vm> as *const (), 0),
("compile", oxide_builtins::regexp::regexp_compile::<crate::vm::Vm> as *const (), 2),
],
);
// 遗留静态访问器(Annex B):15 个访问器落 %RegExp% 构造器,描述符
// {enumerable:false, configurable:true}。仅 input/$_ 为 get+set 对(共享
// [[RegExpInput]] 槽);其余 18 个为 getter(set 恒 undefined):lastMatch/$&、
// lastParen/$+、leftContext/$`、rightContext/$' 各共享一槽,index 与 $1-$9 各
// 独占一槽。
let sf = core.perm_interner().as_ref();
let mut bind_getset = |name: &str, getter: *const (), setter: *const ()| {
let key = sf.intern(name).0;
bind_accessor_getset(
core,
session,
ctor,
key,
&format!("get {name}"),
&format!("set {name}"),
getter,
setter,
);
};
// input/$_:get+set 对(唯一带 setter 的遗留访问器)。
bind_getset(
"input",
oxide_builtins::regexp::regexp_legacy_get_input::<crate::vm::Vm> as *const (),
oxide_builtins::regexp::regexp_legacy_set_input::<crate::vm::Vm> as *const (),
);
bind_getset(
"$_",
oxide_builtins::regexp::regexp_legacy_get_input::<crate::vm::Vm> as *const (),
oxide_builtins::regexp::regexp_legacy_set_input::<crate::vm::Vm> as *const (),
);
// 其余 18 个 getter(set 恒 undefined)。
for (name, getter) in [
("lastMatch", oxide_builtins::regexp::regexp_legacy_get_last_match::<crate::vm::Vm> as *const ()),
("$&", oxide_builtins::regexp::regexp_legacy_get_last_match::<crate::vm::Vm> as *const ()),
("lastParen", oxide_builtins::regexp::regexp_legacy_get_last_paren::<crate::vm::Vm> as *const ()),
("$+", oxide_builtins::regexp::regexp_legacy_get_last_paren::<crate::vm::Vm> as *const ()),
(
"leftContext",
oxide_builtins::regexp::regexp_legacy_get_left_context::<crate::vm::Vm> as *const (),
),
("$`", oxide_builtins::regexp::regexp_legacy_get_left_context::<crate::vm::Vm> as *const ()),
(
"rightContext",
oxide_builtins::regexp::regexp_legacy_get_right_context::<crate::vm::Vm> as *const (),
),
("$'", oxide_builtins::regexp::regexp_legacy_get_right_context::<crate::vm::Vm> as *const ()),
("index", oxide_builtins::regexp::regexp_legacy_get_index::<crate::vm::Vm> as *const ()),
("$1", oxide_builtins::regexp::regexp_legacy_get_dollar_1::<crate::vm::Vm> as *const ()),
("$2", oxide_builtins::regexp::regexp_legacy_get_dollar_2::<crate::vm::Vm> as *const ()),
("$3", oxide_builtins::regexp::regexp_legacy_get_dollar_3::<crate::vm::Vm> as *const ()),
("$4", oxide_builtins::regexp::regexp_legacy_get_dollar_4::<crate::vm::Vm> as *const ()),
("$5", oxide_builtins::regexp::regexp_legacy_get_dollar_5::<crate::vm::Vm> as *const ()),
("$6", oxide_builtins::regexp::regexp_legacy_get_dollar_6::<crate::vm::Vm> as *const ()),
("$7", oxide_builtins::regexp::regexp_legacy_get_dollar_7::<crate::vm::Vm> as *const ()),
("$8", oxide_builtins::regexp::regexp_legacy_get_dollar_8::<crate::vm::Vm> as *const ()),
("$9", oxide_builtins::regexp::regexp_legacy_get_dollar_9::<crate::vm::Vm> as *const ()),
] {
bind_accessor_getter(core, session, ctor, name, getter);
}
// source/flags 只读访问器(set 恒 undefined,getter 读实例字段)。
bind_accessor_getter(
core,