From 0fc105243d31449bfe598f80d5646cff3250cad3 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 9 Oct 2026 11:14:02 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20RegExp.prototype.compile=20=E4=B8=8E?= =?UTF-8?q?=E5=85=A8=E5=B1=80=E9=81=97=E7=95=99=E8=AE=BF=E9=97=AE=E5=99=A8?= =?UTF-8?q?=20$1-$9=20=E6=97=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 38 +- Cargo.toml | 2 +- crates/oxide_builtins/src/regexp.rs | 364 ++++++++++++++++++-- crates/oxide_vm/src/bindings/bind_regexp.rs | 65 +++- 4 files changed, 426 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7d5beeeb..968eba29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 8755effe..a88c77b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ ] [workspace.package] -version = "0.8.99" +version = "0.8.100" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_builtins/src/regexp.rs b/crates/oxide_builtins/src/regexp.rs index b3953e0e..0514af68 100644 --- a/crates/oxide_builtins/src/regexp.rs +++ b/crates/oxide_builtins/src/regexp.rs @@ -371,17 +371,6 @@ pub(crate) fn get_substitution_units( Ok(out) } -fn set_prop(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(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(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(vm: &mut H, args: &[u8]) -> NativeResult { // 写入的 `Box` 指针。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(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(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(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(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::>(); + 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` 指针,同构造器写入口径。 + 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(vm: &mut H, val: JsValue) -> Result, 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(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(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(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(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(vm: &mut H, m: ®ress::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(vm: &mut H, args: &[u8]) -> NativeResult { + legacy_get(vm, args, $slot) + } + pub fn $set(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(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。 /// diff --git a/crates/oxide_vm/src/bindings/bind_regexp.rs b/crates/oxide_vm/src/bindings/bind_regexp.rs index 923b559d..27f318a2 100644 --- a/crates/oxide_vm/src/bindings/bind_regexp.rs +++ b/crates/oxide_vm/src/bindings/bind_regexp.rs @@ -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, session: &KernelSession, global: &mut ("exec", oxide_builtins::regexp::regexp_exec:: as *const (), 1), ("test", oxide_builtins::regexp::regexp_test:: as *const (), 1), ("toString", oxide_builtins::regexp::regexp_to_string:: as *const (), 0), + ("compile", oxide_builtins::regexp::regexp_compile:: 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:: as *const (), + oxide_builtins::regexp::regexp_legacy_set_input:: as *const (), + ); + bind_getset( + "$_", + oxide_builtins::regexp::regexp_legacy_get_input:: as *const (), + oxide_builtins::regexp::regexp_legacy_set_input:: as *const (), + ); + // 其余 18 个 getter(set 恒 undefined)。 + for (name, getter) in [ + ("lastMatch", oxide_builtins::regexp::regexp_legacy_get_last_match:: as *const ()), + ("$&", oxide_builtins::regexp::regexp_legacy_get_last_match:: as *const ()), + ("lastParen", oxide_builtins::regexp::regexp_legacy_get_last_paren:: as *const ()), + ("$+", oxide_builtins::regexp::regexp_legacy_get_last_paren:: as *const ()), + ( + "leftContext", + oxide_builtins::regexp::regexp_legacy_get_left_context:: as *const (), + ), + ("$`", oxide_builtins::regexp::regexp_legacy_get_left_context:: as *const ()), + ( + "rightContext", + oxide_builtins::regexp::regexp_legacy_get_right_context:: as *const (), + ), + ("$'", oxide_builtins::regexp::regexp_legacy_get_right_context:: as *const ()), + ("index", oxide_builtins::regexp::regexp_legacy_get_index:: as *const ()), + ("$1", oxide_builtins::regexp::regexp_legacy_get_dollar_1:: as *const ()), + ("$2", oxide_builtins::regexp::regexp_legacy_get_dollar_2:: as *const ()), + ("$3", oxide_builtins::regexp::regexp_legacy_get_dollar_3:: as *const ()), + ("$4", oxide_builtins::regexp::regexp_legacy_get_dollar_4:: as *const ()), + ("$5", oxide_builtins::regexp::regexp_legacy_get_dollar_5:: as *const ()), + ("$6", oxide_builtins::regexp::regexp_legacy_get_dollar_6:: as *const ()), + ("$7", oxide_builtins::regexp::regexp_legacy_get_dollar_7:: as *const ()), + ("$8", oxide_builtins::regexp::regexp_legacy_get_dollar_8:: as *const ()), + ("$9", oxide_builtins::regexp::regexp_legacy_get_dollar_9:: as *const ()), + ] { + bind_accessor_getter(core, session, ctor, name, getter); + } + // source/flags 只读访问器(set 恒 undefined,getter 读实例字段)。 bind_accessor_getter( core,