mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 02:31:13 +08:00
feat: 迁移到 regress 引擎(backref/lookaround/命名组/v-flag)+ 手动 \$ 展开 replace + exec 捕获组数组
This commit is contained in:
@@ -15,6 +15,7 @@ fast-float.workspace = true
|
||||
ryu.workspace = true
|
||||
memchr.workspace = true
|
||||
regex.workspace = true
|
||||
regress.workspace = true
|
||||
serde_json.workspace = true
|
||||
chrono.workspace = true
|
||||
unicode-normalization.workspace = true
|
||||
|
||||
@@ -36,7 +36,11 @@ fn parse_flags(flags: &str) -> (bool, bool, bool) {
|
||||
|
||||
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);
|
||||
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.kernel_core().shape_forge().make_shape(obj.shape_id(), prop_name_si);
|
||||
obj.set_shape_id(shape_id);
|
||||
obj.ensure_hash_props().push(val);
|
||||
}
|
||||
@@ -59,8 +63,9 @@ fn set_prop_at(obj: *mut JsObject, idx: usize, val: JsValue) {
|
||||
}
|
||||
}
|
||||
|
||||
/// `RegExp(pattern, flags)` 构造逻辑:用 regex crate 编译模式,
|
||||
/// 支持 g/i/m 标志;非法模式抛 SyntaxError。编译结果存于对象的 native_fn 槽。
|
||||
/// `RegExp(pattern, flags)` 构造逻辑:用 regress 引擎编译模式(ECMAScript 语法,
|
||||
/// 支持 backreference/lookaround/命名组/v-flag);非法模式抛 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())
|
||||
@@ -80,16 +85,21 @@ pub fn regexp_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
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();
|
||||
// regress 用 JS flag 字符串编译(i/m 当前支持;u/s/y 由 parse_flags 处理)。
|
||||
let mut flags_in: String = String::new();
|
||||
if ignore_case {
|
||||
flags_in.push('i');
|
||||
}
|
||||
if multi_line {
|
||||
flags_in.push('m');
|
||||
}
|
||||
let compiled = regress::Regex::with_flags(&pattern, flags_in.as_str());
|
||||
|
||||
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 字段复用作
|
||||
// 写入的 `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 ()) }));
|
||||
@@ -117,7 +127,7 @@ pub fn regexp_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
NativeResult::Ok(JsValue::from_js_object(obj_ptr))
|
||||
}
|
||||
|
||||
/// 释放 RegExp 对象 native_fn 槽中编译的 `regex::Regex`,返回释放字节数。
|
||||
/// 释放 RegExp 对象 native_fn 槽中编译的 `regress::Regex`,返回释放字节数。
|
||||
pub fn drop_regexp_native(obj: &mut JsObject) -> u64 {
|
||||
if !obj.is_regexp_obj() {
|
||||
return 0;
|
||||
@@ -125,13 +135,13 @@ pub fn drop_regexp_native(obj: &mut JsObject) -> u64 {
|
||||
let Some(ptr) = obj.native_fn() else {
|
||||
return 0;
|
||||
};
|
||||
let regex_ptr = ptr.as_ptr() as *mut regex::Regex;
|
||||
let regex_ptr = ptr.as_ptr() as *mut regress::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
|
||||
std::mem::size_of::<regress::Regex>() as u64
|
||||
}
|
||||
|
||||
/// `RegExp.prototype.test(string)`:判断是否匹配。global 模式下从 lastIndex 开始匹配。
|
||||
@@ -149,17 +159,17 @@ pub fn regexp_test<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
Some(p) => p,
|
||||
};
|
||||
|
||||
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regex::Regex>` 指针。
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) };
|
||||
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regress::Regex>` 指针。
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::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();
|
||||
let result = regex.find_from(&haystack, last_index).next().is_some();
|
||||
NativeResult::Ok(JsValue::bool(result))
|
||||
} else {
|
||||
NativeResult::Ok(JsValue::bool(regex.is_match(&haystack)))
|
||||
NativeResult::Ok(JsValue::bool(regex.find(&haystack).is_some()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,8 +191,8 @@ pub fn regexp_exec<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
};
|
||||
|
||||
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regex::Regex>` 指针。
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) };
|
||||
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regress::Regex>` 指针。
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
|
||||
let haystack = oxide_runtime_api::to_string(vm.reg(if args.len() > 1 { args[1] } else { args[0] }));
|
||||
|
||||
let (last_index, is_global) = {
|
||||
@@ -196,41 +206,41 @@ pub fn regexp_exec<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
if last_index > haystack.len() {
|
||||
return NativeResult::Ok(JsValue::null());
|
||||
}
|
||||
regex.find_at(&haystack, last_index)
|
||||
regex.find_from(&haystack, last_index).next()
|
||||
} else {
|
||||
regex.find(&haystack)
|
||||
};
|
||||
|
||||
if let Some(m) = match_result {
|
||||
let mut match_obj = JsObject::new_empty(
|
||||
let range = m.range();
|
||||
let group_count = m.captures.len();
|
||||
let n = 1 + group_count;
|
||||
let proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject;
|
||||
let arr = vm.epoch().alloc(JsObject::new_array(
|
||||
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);
|
||||
JsValue::from_js_object(proto),
|
||||
n,
|
||||
vm.epoch().bump(),
|
||||
));
|
||||
unsafe {
|
||||
(*arr).set_prop_at(0, vm.new_string(&haystack[range.start..range.end]));
|
||||
// 捕获组:未参与匹配的组为 undefined。
|
||||
for i in 1..=group_count {
|
||||
match m.group(i) {
|
||||
Some(g) => (*arr).set_prop_at(i, vm.new_string(&haystack[g.start..g.end])),
|
||||
None => (*arr).set_prop_at(i, JsValue::undefined()),
|
||||
}
|
||||
}
|
||||
(*arr).set_prop_count(n);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// ponytail: 引擎数组的 shape 属性槽与元素 prop_vec 共用 hash_props 索引,
|
||||
// 设 index/input/groups 属性会与元素冲突/膨胀 length。暂只填元素。
|
||||
if is_global {
|
||||
set_prop_at(re_ptr, 0, JsValue::int(m.end() as i32));
|
||||
set_prop_at(re_ptr, 0, JsValue::int(range.end as i32));
|
||||
}
|
||||
|
||||
let obj_ptr = vm.alloc_object(match_obj);
|
||||
NativeResult::Ok(JsValue::from_js_object(obj_ptr))
|
||||
NativeResult::Ok(JsValue::from_js_object(arr))
|
||||
} else {
|
||||
if is_global {
|
||||
set_prop_at(re_ptr, 0, JsValue::int(0));
|
||||
@@ -257,3 +267,4 @@ pub fn regexp_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let result = format!("/{}/{}", source, flags);
|
||||
NativeResult::Ok(vm.new_string(&result))
|
||||
}
|
||||
|
||||
|
||||
@@ -142,6 +142,75 @@ 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))
|
||||
}
|
||||
|
||||
/// 正则替换的手动实现:按 JS 语义展开 replacement 中的 `$` 引用
|
||||
/// (`$$`、`$&`、`` $` ``、`$'`、`$n`),global 全替换否则首个。
|
||||
fn regex_replace_manual(regex: ®ress::Regex, text: &str, replacement: &str, global: bool) -> String {
|
||||
let mut out = String::new();
|
||||
let mut last_end = 0;
|
||||
let matches: Vec<regress::Match> = if global {
|
||||
regex.find_iter(text).collect()
|
||||
} else {
|
||||
regex.find(text).into_iter().collect()
|
||||
};
|
||||
for m in matches {
|
||||
let range = m.range();
|
||||
out.push_str(&text[last_end..range.start]);
|
||||
out.push_str(&expand_dollar(text, &m, replacement));
|
||||
last_end = range.end;
|
||||
}
|
||||
out.push_str(&text[last_end..]);
|
||||
out
|
||||
}
|
||||
|
||||
/// 展开单个匹配的 replacement `$` 引用。
|
||||
fn expand_dollar(text: &str, m: ®ress::Match, replacement: &str) -> String {
|
||||
let mut out = String::new();
|
||||
let bytes = replacement.as_bytes();
|
||||
let mut i = 0;
|
||||
while i < bytes.len() {
|
||||
if bytes[i] != b'$' {
|
||||
out.push(replacement[i..].chars().next().unwrap_or('\u{FFFD}'));
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
let range = m.range();
|
||||
let rest = &replacement[i..];
|
||||
if rest.starts_with("$$") {
|
||||
out.push('$');
|
||||
i += 2;
|
||||
} else if rest.starts_with("$&") {
|
||||
out.push_str(&text[range.start..range.end]);
|
||||
i += 2;
|
||||
} else if rest.starts_with("$`") {
|
||||
out.push_str(&text[..range.start]);
|
||||
i += 2;
|
||||
} else if rest.starts_with("$'") {
|
||||
out.push_str(&text[range.end..]);
|
||||
i += 2;
|
||||
} else {
|
||||
// $n(1-2 位数字)→ 捕获组;未匹配/越界 → 空串。
|
||||
let mut j = 1;
|
||||
while j < bytes.len().saturating_sub(i) && bytes[i + j].is_ascii_digit() {
|
||||
j += 1;
|
||||
}
|
||||
let digits = &replacement[i + 1..i + j];
|
||||
if digits.is_empty() {
|
||||
out.push('$');
|
||||
i += 1;
|
||||
} else {
|
||||
let n: usize = digits.parse().unwrap_or(0);
|
||||
let g = m.group(n);
|
||||
match g {
|
||||
Some(g) => out.push_str(&text[g.start..g.end]),
|
||||
None => {}
|
||||
}
|
||||
i += 1 + digits.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn char_len(s: &str) -> usize {
|
||||
s.chars().count()
|
||||
}
|
||||
@@ -579,25 +648,28 @@ pub fn string_split<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
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 regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
|
||||
let mut parts: Vec<String> = Vec::new();
|
||||
let mut last_end = 0;
|
||||
for caps in regex.captures_iter(&s) {
|
||||
for m in regex.find_iter(&s) {
|
||||
if parts.len() >= limit {
|
||||
break;
|
||||
}
|
||||
let full = caps.get(0).unwrap();
|
||||
parts.push(s[last_end..full.start()].to_string());
|
||||
let range = m.range();
|
||||
parts.push(s[last_end..range.start].to_string());
|
||||
if parts.len() >= limit {
|
||||
break;
|
||||
}
|
||||
for i in 1..caps.len() {
|
||||
for i in 1..=m.captures.len() {
|
||||
if parts.len() >= limit {
|
||||
break;
|
||||
}
|
||||
parts.push(caps.get(i).map(|m| m.as_str()).unwrap_or("").to_string());
|
||||
match m.group(i) {
|
||||
Some(g) => parts.push(s[g.start..g.end].to_string()),
|
||||
None => parts.push(String::new()),
|
||||
}
|
||||
}
|
||||
last_end = full.end();
|
||||
last_end = range.end;
|
||||
}
|
||||
if last_end <= s.len() && parts.len() < limit {
|
||||
parts.push(s[last_end..].to_string());
|
||||
@@ -630,26 +702,30 @@ pub fn string_replace<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
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) };
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::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("")));
|
||||
if let Some(m) = regex.find(&s) {
|
||||
let range = m.range();
|
||||
let mut cb_args: Vec<JsValue> = Vec::with_capacity(m.captures.len() + 2);
|
||||
cb_args.push(vm.new_string(&s[range.start..range.end]));
|
||||
for i in 1..=m.captures.len() {
|
||||
match m.group(i) {
|
||||
Some(g) => cb_args.push(vm.new_string(&s[g.start..g.end])),
|
||||
None => cb_args.push(JsValue::undefined()),
|
||||
}
|
||||
}
|
||||
cb_args.push(JsValue::int(full.start() as i32));
|
||||
cb_args.push(JsValue::int(range.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()..]);
|
||||
let output =
|
||||
format!("{}{}{}", &s[..range.start], &result_str, &s[range.end..]);
|
||||
return NativeResult::Ok(vm.new_string(&output));
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -665,13 +741,10 @@ pub fn string_replace<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
}
|
||||
}
|
||||
|
||||
let replacement = if args.len() > 2 { as_string(vm, vm.reg(args[2])) } else { String::new() };
|
||||
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()
|
||||
};
|
||||
// 手动展开 $ 引用(regress 的 replace 对 $n 展开为空)。
|
||||
let result = regex_replace_manual(regex, &s, &replacement, is_global);
|
||||
return NativeResult::Ok(vm.new_string(&result));
|
||||
}
|
||||
// 无原生正则的类正则对象回退到字符串路径。
|
||||
@@ -700,17 +773,21 @@ pub fn string_match_fn<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
Some(p) => p,
|
||||
None => return NativeResult::Ok(JsValue::null()),
|
||||
};
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) };
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::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();
|
||||
let matches: Vec<String> = regex.find_iter(&s).map(|m| s[m.range()].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());
|
||||
if let Some(m) = regex.find(&s) {
|
||||
let range = m.range();
|
||||
let mut parts: Vec<String> = Vec::with_capacity(m.captures.len() + 1);
|
||||
parts.push(s[range.start..range.end].to_string());
|
||||
for i in 1..=m.captures.len() {
|
||||
match m.group(i) {
|
||||
Some(g) => parts.push(s[g.start..g.end].to_string()),
|
||||
None => parts.push(String::new()),
|
||||
}
|
||||
}
|
||||
return NativeResult::Ok(make_string_array(vm, &parts));
|
||||
}
|
||||
@@ -740,10 +817,10 @@ pub fn string_search<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
Some(p) => p,
|
||||
None => return NativeResult::Ok(JsValue::int(-1)),
|
||||
};
|
||||
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regex::Regex>` 指针。
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) };
|
||||
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regress::Regex>` 指针。
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
|
||||
if let Some(m) = regex.find(&s) {
|
||||
return NativeResult::Ok(JsValue::int(m.start() as i32));
|
||||
return NativeResult::Ok(JsValue::int(m.range().start as i32));
|
||||
}
|
||||
return NativeResult::Ok(JsValue::int(-1));
|
||||
}
|
||||
@@ -837,9 +914,9 @@ pub fn string_match_all<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
pattern_val
|
||||
} else {
|
||||
let pattern_str = as_string(vm, pattern_val);
|
||||
let escaped = regex::escape(&pattern_str);
|
||||
let escaped = regress::escape(&pattern_str);
|
||||
let rx_str = if escaped.is_empty() { String::from("(?:)") } else { format!("({})", escaped) };
|
||||
let compiled = match regex::Regex::new(&rx_str) {
|
||||
let compiled = match regress::Regex::new(&rx_str) {
|
||||
Ok(rx) => rx,
|
||||
Err(e) => {
|
||||
return NativeResult::Err(crate::error::create_syntax_error(vm, &format!("Invalid regex: {}", e)));
|
||||
@@ -925,17 +1002,19 @@ pub fn string_match_all_next<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
|
||||
Some(p) => p,
|
||||
None => return make_match_done_result(vm, JsValue::undefined()),
|
||||
};
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) };
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
|
||||
|
||||
if let Some(m) = regex.find_at(&input_str, idx) {
|
||||
if let Some(m) = regex.find_from(&input_str, idx).next() {
|
||||
let range = m.range();
|
||||
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());
|
||||
parts.push(input_str[range.start..range.end].to_string());
|
||||
for i in 1..=m.captures.len() {
|
||||
match m.group(i) {
|
||||
Some(g) => parts.push(input_str[g.start..g.end].to_string()),
|
||||
None => parts.push(String::new()),
|
||||
}
|
||||
}
|
||||
idx = m.end();
|
||||
idx = range.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)
|
||||
@@ -975,9 +1054,9 @@ pub fn string_replace_all<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
Some(p) => p,
|
||||
None => return NativeResult::Ok(vm.new_string(&s)),
|
||||
};
|
||||
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regex::Regex>` 指针。
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) };
|
||||
let result = regex.replace_all(&s, replacement.as_str()).to_string();
|
||||
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regress::Regex>` 指针。
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
|
||||
let result = regex_replace_manual(regex, &s, &replacement, true);
|
||||
return NativeResult::Ok(vm.new_string(&result));
|
||||
}
|
||||
let pattern = as_string(vm, pattern_val);
|
||||
@@ -985,3 +1064,5 @@ pub fn string_replace_all<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
NativeResult::Ok(vm.new_string(&result))
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -11,3 +11,5 @@ oxide_parser = { path = "../oxide_parser" }
|
||||
oxide_bytecode = { path = "../oxide_bytecode" }
|
||||
oxide_ir = { path = "../oxide_ir" }
|
||||
regex.workspace = true
|
||||
regress.workspace = true
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ impl Emitter {
|
||||
fn emit_string_literal_expression(
|
||||
&self, s: &oxide_parser::StringLiteral, ctx: &mut CompileCtx,
|
||||
) -> Result<u32, String> {
|
||||
eprintln!("[DBG] string literal value={:?}", s.value.as_str());
|
||||
let idx = ctx.add_constant(Constant::String(s.value.to_string()));
|
||||
let r = ctx.alloc_reg();
|
||||
ctx.inst(Inst::load_const(Operand::Reg(r), idx));
|
||||
@@ -68,12 +69,8 @@ impl Emitter {
|
||||
let pattern = raw_str[1..last_slash].to_string();
|
||||
let flags = raw_str[last_slash + 1..].to_string();
|
||||
// 非法正则字面量须在编译期报 SyntaxError(负面测试期望编译失败)。
|
||||
if regex::RegexBuilder::new(&pattern)
|
||||
.case_insensitive(flags.contains('i'))
|
||||
.multi_line(flags.contains('m'))
|
||||
.build()
|
||||
.is_err()
|
||||
{
|
||||
// 用 regress(ECMAScript 语法)校验,避免误报 backreference/lookaround 等合法模式。
|
||||
if regress::Regex::with_flags(&pattern, flags.as_str()).is_err() {
|
||||
return Err(format!(
|
||||
"SyntaxError: Invalid regular expression: /{pattern}/{flags}"
|
||||
));
|
||||
@@ -100,3 +97,5 @@ impl Emitter {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user