mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: RegExp.prototype.compile 与全局遗留访问器 $1-$9 族
This commit is contained in:
Generated
+19
-19
@@ -1160,7 +1160,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_builtins"
|
name = "oxide_builtins"
|
||||||
version = "0.8.99"
|
version = "0.8.100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"fast-float",
|
"fast-float",
|
||||||
@@ -1185,7 +1185,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_bytecode"
|
name = "oxide_bytecode"
|
||||||
version = "0.8.99"
|
version = "0.8.100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"oxide_log",
|
"oxide_log",
|
||||||
@@ -1193,7 +1193,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_cfg"
|
name = "oxide_cfg"
|
||||||
version = "0.8.99"
|
version = "0.8.100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_emit",
|
"oxide_emit",
|
||||||
@@ -1204,7 +1204,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_cli"
|
name = "oxide_cli"
|
||||||
version = "0.8.99"
|
version = "0.8.100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term",
|
"ansi_term",
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
@@ -1226,7 +1226,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_code_cache"
|
name = "oxide_code_cache"
|
||||||
version = "0.8.99"
|
version = "0.8.100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lru",
|
"lru",
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
@@ -1235,7 +1235,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_compiler"
|
name = "oxide_compiler"
|
||||||
version = "0.8.99"
|
version = "0.8.100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_cfg",
|
"oxide_cfg",
|
||||||
@@ -1255,7 +1255,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_dce"
|
name = "oxide_dce"
|
||||||
version = "0.8.99"
|
version = "0.8.100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_cfg",
|
"oxide_cfg",
|
||||||
@@ -1269,7 +1269,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_emit"
|
name = "oxide_emit"
|
||||||
version = "0.8.99"
|
version = "0.8.100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"oxc_allocator",
|
"oxc_allocator",
|
||||||
@@ -1284,7 +1284,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_engine"
|
name = "oxide_engine"
|
||||||
version = "0.8.99"
|
version = "0.8.100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_compiler",
|
"oxide_compiler",
|
||||||
@@ -1296,7 +1296,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_ir"
|
name = "oxide_ir"
|
||||||
version = "0.8.99"
|
version = "0.8.100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_log",
|
"oxide_log",
|
||||||
@@ -1305,7 +1305,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_kernel"
|
name = "oxide_kernel"
|
||||||
version = "0.8.99"
|
version = "0.8.100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
@@ -1319,7 +1319,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_liveness"
|
name = "oxide_liveness"
|
||||||
version = "0.8.99"
|
version = "0.8.100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_cfg",
|
"oxide_cfg",
|
||||||
@@ -1331,7 +1331,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_log"
|
name = "oxide_log"
|
||||||
version = "0.8.99"
|
version = "0.8.100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-appender",
|
"tracing-appender",
|
||||||
@@ -1340,7 +1340,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_parser"
|
name = "oxide_parser"
|
||||||
version = "0.8.99"
|
version = "0.8.100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxc_allocator",
|
"oxc_allocator",
|
||||||
"oxc_ast",
|
"oxc_ast",
|
||||||
@@ -1355,7 +1355,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_regalloc"
|
name = "oxide_regalloc"
|
||||||
version = "0.8.99"
|
version = "0.8.100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_cfg",
|
"oxide_cfg",
|
||||||
@@ -1371,7 +1371,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_runtime_api"
|
name = "oxide_runtime_api"
|
||||||
version = "0.8.99"
|
version = "0.8.100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
@@ -1384,7 +1384,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_test262"
|
name = "oxide_test262"
|
||||||
version = "0.8.99"
|
version = "0.8.100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_compiler",
|
"oxide_compiler",
|
||||||
@@ -1402,7 +1402,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_types"
|
name = "oxide_types"
|
||||||
version = "0.8.99"
|
version = "0.8.100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
@@ -1415,7 +1415,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_vm"
|
name = "oxide_vm"
|
||||||
version = "0.8.99"
|
version = "0.8.100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.8.99"
|
version = "0.8.100"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
rust-version = "1.80"
|
rust-version = "1.80"
|
||||||
|
|||||||
@@ -371,17 +371,6 @@ pub(crate) fn get_substitution_units<H: VmHost>(
|
|||||||
Ok(out)
|
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 顺序为规范序(d g i m s u v y):同一组 flag 不论书写顺序
|
||||||
/// 恒得同一串,保证 `flags`/`toString` 输出与 V8 一致。
|
/// 恒得同一串,保证 `flags`/`toString` 输出与 V8 一致。
|
||||||
fn normalize_flags(flags: &str) -> String {
|
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 =
|
// 构造路径(new / Reflect.construct,new.target 为对象):复用调用方按
|
||||||
JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(vm.builtin_proto(ProtoKind::RegExpProto)));
|
// 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
|
// regress 用 JS flag 字符串编译:g/i/m/s/y/u/v 原样透传(vendored fork
|
||||||
// 原生识别 v 标志,u/v 互斥已在上方前置校验)。
|
// 原生识别 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 字段复用作
|
// 写入的 `Box<regress::Regex>` 指针。RegExp 对象把 native_fn 字段复用作
|
||||||
// 已编译 Regex 的存放处——而非 NativeFn 指针。对象存活期间有效;
|
// 已编译 Regex 的存放处——而非 NativeFn 指针。对象存活期间有效;
|
||||||
// VM 重置经 `drop_regexp_native` 释放该 Box。
|
// 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) => {
|
Err(e) => {
|
||||||
return NativeResult::Err(crate::error::create_syntax_error(
|
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);
|
// lastIndex 作实例自身属性写入(缺省描述符,与既有构造器口径一致)。
|
||||||
obj.type_tag = JsObject::OBJ_TYPE_REGEXP;
|
{
|
||||||
// source/flags 存实例字段:原型访问器从本字段读取,不作为自身属性枚举。
|
let li_si = vm.perm_intern("lastIndex");
|
||||||
obj.set_regexp_source(vm.new_string_units_owned(pattern_units));
|
let shape_id = vm.make_shape(unsafe { (*this_ptr).shape_id() }, li_si);
|
||||||
obj.set_regexp_flags(vm.new_string_units_owned(flags_units));
|
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(this_ptr))
|
||||||
NativeResult::Ok(JsValue::from_js_object(obj_ptr))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 只读核算持有编译正则对象(RegExp / matchAll 载体)的正则字节(不释放)。
|
/// 只读核算持有编译正则对象(RegExp / matchAll 载体)的正则字节(不释放)。
|
||||||
@@ -741,6 +752,12 @@ pub fn regexp_exec<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
return NativeResult::Ok(JsValue::null());
|
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 range = m.range();
|
||||||
let group_count = m.captures.len();
|
let group_count = m.captures.len();
|
||||||
let n = 1 + group_count;
|
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))
|
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: ®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<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)`:返回语法字符、其它标点、空白/行终止符、孤立
|
/// `RegExp.escape(string)`:返回语法字符、其它标点、空白/行终止符、孤立
|
||||||
/// surrogate、首字符数字/ASCII 字母均被转义的新字符串;参数非字符串抛 TypeError。
|
/// surrogate、首字符数字/ASCII 字母均被转义的新字符串;参数非字符串抛 TypeError。
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::bindings::{
|
use crate::bindings::{
|
||||||
apply_binding_table, bind_accessor_getter, bind_accessor_getter_key, bind_well_known_method,
|
apply_binding_table, bind_accessor_getter, bind_accessor_getset, bind_accessor_getter_key,
|
||||||
configure_native_constructor,
|
bind_well_known_method, configure_native_constructor,
|
||||||
};
|
};
|
||||||
use oxide_kernel::kernel::{KernelCore, KernelSession};
|
use oxide_kernel::kernel::{KernelCore, KernelSession};
|
||||||
use oxide_types::object::JsObject;
|
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),
|
("exec", oxide_builtins::regexp::regexp_exec::<crate::vm::Vm> as *const (), 1),
|
||||||
("test", oxide_builtins::regexp::regexp_test::<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),
|
("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 读实例字段)。
|
// source/flags 只读访问器(set 恒 undefined,getter 读实例字段)。
|
||||||
bind_accessor_getter(
|
bind_accessor_getter(
|
||||||
core,
|
core,
|
||||||
|
|||||||
Reference in New Issue
Block a user