perf: replace 函数 replacer 回调免每匹配整串复制
回调第 4 参(原字符串)由每匹配复制整串改为调用方预构一次: 原始字符串 receiver 直接复用原值零拷贝,对象 receiver 提升为单次会话串。 覆盖 String.prototype.replace/replaceAll 与 RegExp.prototype[Symbol.replace] 的函数 replacer 路径。补回调第 4 参断言测试与 fn-replacer 压测。
This commit is contained in:
@@ -384,7 +384,11 @@ pub fn regexp_symbol_replace<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
|
||||
if replacer_val.is_object() {
|
||||
let o = unsafe { &*replacer_val.as_js_object_ptr() };
|
||||
if o.is_function() {
|
||||
crate::string::regex_replace_fn(vm, regex, &haystack, replacer_val, is_global)
|
||||
// 回调第 4 参(原字符串)预构一次:原始字符串参数直接复用零拷贝,
|
||||
// 对象参数复用已转换的 haystack 建单个会话串(原每匹配整串复制)。
|
||||
let text_val = vm.reg(if args.len() > 1 { args[1] } else { args[0] });
|
||||
let text_arg = if text_val.is_string() { text_val } else { vm.new_string(&haystack) };
|
||||
crate::string::regex_replace_fn(vm, regex, &haystack, replacer_val, is_global, text_arg)
|
||||
} else {
|
||||
let replacement = oxide_runtime_api::to_string(replacer_val);
|
||||
NativeResult::Ok(vm.new_string(&crate::string::regex_replace_manual(
|
||||
|
||||
@@ -306,8 +306,9 @@ fn call_replacer<H: VmHost>(vm: &mut H, replacer: JsValue, cb_args: &[JsValue])
|
||||
}
|
||||
|
||||
/// 函数 replacer 的回调参数:匹配串、各捕获组(未匹配为 undefined)、position、原字符串。
|
||||
/// position 按字符索引(非字节偏移)。
|
||||
fn replacer_cb_args<H: VmHost>(vm: &mut H, text: &str, m: ®ress::Match) -> Vec<JsValue> {
|
||||
/// position 按字符索引(非字节偏移)。原字符串参数由调用方预构传入(`text_arg`),
|
||||
/// 避免每匹配复制整个源串——字符串不可变,同一 `JsValue` 可安全复用。
|
||||
fn replacer_cb_args<H: VmHost>(vm: &mut H, text: &str, m: ®ress::Match, text_arg: JsValue) -> Vec<JsValue> {
|
||||
let range = m.range();
|
||||
let mut cb_args: Vec<JsValue> = Vec::with_capacity(m.captures.len() + 2);
|
||||
cb_args.push(vm.new_string(&text[range.start..range.end]));
|
||||
@@ -318,14 +319,15 @@ fn replacer_cb_args<H: VmHost>(vm: &mut H, text: &str, m: ®ress::Match) -> Ve
|
||||
}
|
||||
}
|
||||
cb_args.push(JsValue::int(text[..range.start].chars().count() as i32));
|
||||
cb_args.push(vm.new_string(text));
|
||||
cb_args.push(text_arg);
|
||||
cb_args
|
||||
}
|
||||
|
||||
/// 正则模式 + 函数 replacer:global 全替换否则替换首个,逐匹配调用回调,
|
||||
/// 返回值 ToString 作为替换文本(不展开 `$` 引用)。
|
||||
/// 返回值 ToString 作为替换文本(不展开 `$` 引用)。`text_arg` 为回调第 4 参
|
||||
/// (原字符串),调用方预构一次,回调期按值复用。
|
||||
pub(crate) fn regex_replace_fn<H: VmHost>(
|
||||
vm: &mut H, regex: ®ress::Regex, text: &str, replacer: JsValue, global: bool,
|
||||
vm: &mut H, regex: ®ress::Regex, text: &str, replacer: JsValue, global: bool, text_arg: JsValue,
|
||||
) -> NativeResult {
|
||||
let matches: Vec<regress::Match> = if global {
|
||||
regex.find_iter(text).collect()
|
||||
@@ -337,7 +339,7 @@ pub(crate) fn regex_replace_fn<H: VmHost>(
|
||||
for m in matches {
|
||||
let range = m.range();
|
||||
out.push_str(&text[last_end..range.start]);
|
||||
let cb_args = replacer_cb_args(vm, text, &m);
|
||||
let cb_args = replacer_cb_args(vm, text, &m, text_arg);
|
||||
let repl = try_string!(call_replacer(vm, replacer, &cb_args));
|
||||
out.push_str(&repl);
|
||||
last_end = range.end;
|
||||
@@ -348,7 +350,10 @@ pub(crate) fn regex_replace_fn<H: VmHost>(
|
||||
|
||||
/// 字符串模式 + 函数 replacer:all 全替换否则替换首个。回调参数
|
||||
/// `(match, position, string)`(无捕获组)。空模式在每个字符边界匹配一次。
|
||||
fn string_replace_fn<H: VmHost>(vm: &mut H, text: &str, pattern: &str, replacer: JsValue, all: bool) -> NativeResult {
|
||||
/// `text_arg` 为回调第 4 参(原字符串),调用方预构一次。
|
||||
fn string_replace_fn<H: VmHost>(
|
||||
vm: &mut H, text: &str, pattern: &str, replacer: JsValue, all: bool, text_arg: JsValue,
|
||||
) -> NativeResult {
|
||||
let search_length = pattern.len();
|
||||
let mut positions: Vec<usize> = Vec::new();
|
||||
if search_length == 0 {
|
||||
@@ -378,7 +383,7 @@ fn string_replace_fn<H: VmHost>(vm: &mut H, text: &str, pattern: &str, replacer:
|
||||
let cb_args = [
|
||||
vm.new_string(&text[p..p + search_length]),
|
||||
JsValue::int(text[..p].chars().count() as i32),
|
||||
vm.new_string(text),
|
||||
text_arg,
|
||||
];
|
||||
let repl = try_string!(call_replacer(vm, replacer, &cb_args));
|
||||
out.push_str(&repl);
|
||||
@@ -1079,16 +1084,19 @@ fn string_replace_impl<H: VmHost>(vm: &mut H, args: &[u8], all: bool) -> NativeR
|
||||
// 会 E0499,receiver 必须 owned 本地 String(跨回调的硬约束)。
|
||||
if let Some(replacer_val) = replacer_val {
|
||||
let s = try_string!(this_string(vm, args)).into_owned();
|
||||
// 回调第 4 参(原字符串)预构一次:原始字符串 receiver 直接复用
|
||||
// this_val 零拷贝,对象 receiver 提升为单次会话串(原每匹配整串复制)。
|
||||
let text_arg = if this_val.is_string() { this_val } else { vm.new_string(&s) };
|
||||
if has_native_re {
|
||||
let re_ptr = pattern_val.as_js_object_ptr();
|
||||
let re = unsafe { &*re_ptr };
|
||||
let fn_ptr = re.native_fn().unwrap();
|
||||
// SAFETY: fn_ptr 持有 regexp_constructor 存放的 `Box<regress::Regex>` 指针。
|
||||
let regex = unsafe { &*(fn_ptr.as_ptr() as *const regress::Regex) };
|
||||
return regex_replace_fn(vm, regex, &s, replacer_val, is_global);
|
||||
return regex_replace_fn(vm, regex, &s, replacer_val, is_global, text_arg);
|
||||
}
|
||||
let pattern = as_string(vm, pattern_val).into_owned();
|
||||
return string_replace_fn(vm, &s, &pattern, replacer_val, all);
|
||||
return string_replace_fn(vm, &s, &pattern, replacer_val, all, text_arg);
|
||||
}
|
||||
|
||||
let replacement_is_string = args.len() <= 2 || replacement_val.is_string();
|
||||
|
||||
@@ -333,6 +333,28 @@ fn string_replace_function_replacer_string_pattern() {
|
||||
assert_eq!(to_str(&vm, s), "a1c");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_replace_function_replacer_string_arg() {
|
||||
// 回调第 4 参为原字符串:原始 receiver 直接传值(=== 原串),
|
||||
// 对象 receiver 传 ToString 内容;字符串/正则 pattern、replace/replaceAll 全覆盖。
|
||||
let mut vm = Vm::new();
|
||||
let s = eval(&mut vm, "'abc'.replace('b', function(m, o, str){ return str })").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "aabcc");
|
||||
let s = eval(&mut vm, "'abc'.replace(/b/, function(m, o, str){ return str })").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "aabcc");
|
||||
let s = eval(&mut vm, "'abc'.replaceAll('b', function(m, o, str){ return str })").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "aabcc");
|
||||
// 第 4 参与原始字符串按内容恒等(=== 对字符串按值比较)。
|
||||
let s = eval(&mut vm, "var str='abc'; str.replace('b', function(m, o, str2){ return str2 === str })").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "atruec");
|
||||
// boxed receiver 走对象 ToString 路径,第 4 参为解箱后内容。
|
||||
let s = eval(&mut vm, "new String('abc').replace('b', function(m, o, str){ return str })").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "aabcc");
|
||||
// RegExp.prototype[Symbol.replace] 直调路径(非 String.prototype.replace 快路径)。
|
||||
let s = eval(&mut vm, "/b/[Symbol.replace]('abc', function(m, o, str){ return str })").unwrap();
|
||||
assert_eq!(to_str(&vm, s), "aabcc");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_replace_missing_replacement_is_undefined_string() {
|
||||
// 缺失 replaceValue 按 ToString(undefined)="undefined" 替换(非空串)。
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// 函数 replacer 场景:global 正则多匹配 + 长源串,回调第 4 参为原字符串。
|
||||
// 每匹配整串复制的消除在此路径可测(m×n 字节拷贝 → 0)。
|
||||
var ITERATIONS = 2000;
|
||||
|
||||
var s = "";
|
||||
for (var i = 0; i < 50; i++) {
|
||||
s += "ab" + i + "c";
|
||||
}
|
||||
var sum = 0;
|
||||
for (var i = 0; i < ITERATIONS; i++) {
|
||||
var r = s.replace(/b/g, function (m) {
|
||||
return "X";
|
||||
});
|
||||
sum += r.length;
|
||||
}
|
||||
sum
|
||||
Reference in New Issue
Block a user