fix: 同名捕获组回引与 groups 字段改末次已定义语义

This commit is contained in:
2026-10-14 08:05:13 +00:00
parent 3d3bdaa5cf
commit fe430d4bcb
12 changed files with 287 additions and 42 deletions
Generated
+21 -21
View File
@@ -1160,7 +1160,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_builtins" name = "oxide_builtins"
version = "0.19.7" version = "0.19.8"
dependencies = [ dependencies = [
"chrono", "chrono",
"fast-float", "fast-float",
@@ -1185,7 +1185,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_bytecode" name = "oxide_bytecode"
version = "0.19.7" version = "0.19.8"
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.19.7" version = "0.19.8"
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.19.7" version = "0.19.8"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"bumpalo", "bumpalo",
@@ -1228,7 +1228,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_code_cache" name = "oxide_code_cache"
version = "0.19.7" version = "0.19.8"
dependencies = [ dependencies = [
"lru", "lru",
"oxide_bytecode", "oxide_bytecode",
@@ -1237,7 +1237,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_compiler" name = "oxide_compiler"
version = "0.19.7" version = "0.19.8"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1258,7 +1258,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_dce" name = "oxide_dce"
version = "0.19.7" version = "0.19.8"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1272,7 +1272,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_emit" name = "oxide_emit"
version = "0.19.7" version = "0.19.8"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxc_allocator", "oxc_allocator",
@@ -1288,7 +1288,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_engine" name = "oxide_engine"
version = "0.19.7" version = "0.19.8"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_compiler", "oxide_compiler",
@@ -1300,7 +1300,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_fold" name = "oxide_fold"
version = "0.19.7" version = "0.19.8"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxide_bytecode", "oxide_bytecode",
@@ -1312,7 +1312,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_ir" name = "oxide_ir"
version = "0.19.7" version = "0.19.8"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_log", "oxide_log",
@@ -1321,7 +1321,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_kernel" name = "oxide_kernel"
version = "0.19.7" version = "0.19.8"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"dashmap", "dashmap",
@@ -1335,7 +1335,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_liveness" name = "oxide_liveness"
version = "0.19.7" version = "0.19.8"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1347,7 +1347,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_log" name = "oxide_log"
version = "0.19.7" version = "0.19.8"
dependencies = [ dependencies = [
"tracing", "tracing",
"tracing-appender", "tracing-appender",
@@ -1356,11 +1356,11 @@ dependencies = [
[[package]] [[package]]
name = "oxide_net" name = "oxide_net"
version = "0.19.7" version = "0.19.8"
[[package]] [[package]]
name = "oxide_parser" name = "oxide_parser"
version = "0.19.7" version = "0.19.8"
dependencies = [ dependencies = [
"oxc_allocator", "oxc_allocator",
"oxc_ast", "oxc_ast",
@@ -1375,7 +1375,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_regalloc" name = "oxide_regalloc"
version = "0.19.7" version = "0.19.8"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1391,7 +1391,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_runtime_api" name = "oxide_runtime_api"
version = "0.19.7" version = "0.19.8"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"num-traits", "num-traits",
@@ -1404,7 +1404,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_test262" name = "oxide_test262"
version = "0.19.7" version = "0.19.8"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_compiler", "oxide_compiler",
@@ -1424,7 +1424,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_types" name = "oxide_types"
version = "0.19.7" version = "0.19.8"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"dashmap", "dashmap",
@@ -1437,7 +1437,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_vm" name = "oxide_vm"
version = "0.19.7" version = "0.19.8"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"chrono", "chrono",
+1 -1
View File
@@ -25,7 +25,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.19.7" version = "0.19.8"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
rust-version = "1.80" rust-version = "1.80"
+76 -1
View File
@@ -18,7 +18,7 @@ pub(crate) fn build_groups_object<H: VmHost>(vm: &mut H, m: &regress::Match, tex
let groups_obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); let groups_obj = vm.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let groups_ptr = unsafe { &mut *groups_obj }; let groups_ptr = unsafe { &mut *groups_obj };
// 遍历所有命名捕获组,按名称设置属性(重复名称取首个已定义的出现)。 // 遍历所有命名捕获组,按名称设置属性(重复名称取末次已定义的出现)。
for (name, range) in named { for (name, range) in named {
let name_si = vm.perm_intern(name); let name_si = vm.perm_intern(name);
let value = match range { let value = match range {
@@ -2222,3 +2222,78 @@ pub fn regexp_symbol_match_all<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResu
vm.set_or_create_prop_value(wrapper_obj, unicode_si, JsValue::bool(is_unicode)); vm.set_or_create_prop_value(wrapper_obj, unicode_si, JsValue::bool(is_unicode));
NativeResult::Ok(JsValue::from_js_object(wrapper)) NativeResult::Ok(JsValue::from_js_object(wrapper))
} }
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use oxide_vm::vm::Vm;
/// 编译并执行单脚本,返回 VM 与完成值。
fn eval(source: &str) -> Result<(Vm, JsValue), String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("parse: {:?}", e))?;
let module = oxide_compiler::compiler::Compiler::new()
.compile(&program)
.map_err(|e| format!("compile: {e}"))?;
let mut vm = Vm::new();
let result = vm.run(&Arc::new(module))?;
Ok((vm, result))
}
/// 读对象自身属性值(按名称),无则 undefined。
fn obj_prop(vm: &Vm, obj: &JsObject, name: &str) -> JsValue {
let si = vm.perm_intern(name);
match vm.get_own_property_slot(obj, si) {
Some(idx) => obj.get_prop_at(idx),
None => JsValue::undefined(),
}
}
/// 读 exec 结果数组第 i 个元素的字符串值(按值)。
fn arr_str(vm: &Vm, arr: &JsObject, i: usize) -> Option<String> {
vm.lookup_str(arr.get_prop_at(i))
}
/// groups.x 取末次已定义出现;result 数组各出现独立交付(首出现被迭代 2
/// 重入清槽,末次出现命中 b)。
#[test]
fn groups_last_defined() {
let (vm, result) = eval(r"/(?:(?<x>a)|(?<x>b)){2}/.exec('ab')").unwrap();
let arr = unsafe { &*result.as_js_object_ptr() };
assert_eq!(arr_str(&vm, arr, 0).as_deref(), Some("ab"));
assert!(arr.get_prop_at(1).is_undefined());
assert_eq!(arr_str(&vm, arr, 2).as_deref(), Some("b"));
let groups = obj_prop(&vm, arr, "groups");
let groups_obj = unsafe { &*groups.as_js_object_ptr() };
assert_eq!(vm.lookup_str(obj_prop(&vm, groups_obj, "x")).as_deref(), Some("b"));
}
/// 同名回引读末次已定义:迭代 2 中末次出现命中 b,回引读 b 命中 "aabb"。
#[test]
fn backref_last_defined_aabb() {
let (vm, result) = eval(r"/(?:(?:(?<x>a)|(?<x>b))\k<x>){2}/.exec('aabb')").unwrap();
let arr = unsafe { &*result.as_js_object_ptr() };
assert_eq!(arr_str(&vm, arr, 0).as_deref(), Some("aabb"));
assert!(arr.get_prop_at(1).is_undefined(), "首出现重入未命中");
assert_eq!(arr_str(&vm, arr, 2).as_deref(), Some("b"));
let groups = obj_prop(&vm, arr, "groups");
let groups_obj = unsafe { &*groups.as_js_object_ptr() };
assert_eq!(vm.lookup_str(obj_prop(&vm, groups_obj, "x")).as_deref(), Some("b"));
}
/// 全部出现未定义时回引空匹配;末次已定义值不匹配时整体失败(不回落空匹配)。
#[test]
fn backref_undefined_empty_and_no_fallthrough() {
let (vm, result) = eval(r"/^(?:(?<a>x)|(?<a>y)|z)\k<a>$/.exec('z')").unwrap();
let arr = unsafe { &*result.as_js_object_ptr() };
assert_eq!(arr_str(&vm, arr, 0).as_deref(), Some("z"));
assert!(arr.get_prop_at(1).is_undefined());
assert!(arr.get_prop_at(2).is_undefined());
// 末次已定义为 x(位 2 为 y,不匹配 x),整体应失败。
let (_, result) = eval(r"/(?:(?<a>y)|(?<a>x)){2}\k<a>/.exec('yxy')").unwrap();
assert!(result.is_null(), "末次已定义值不匹配时整体应失败");
}
}
+114 -6
View File
@@ -339,15 +339,14 @@ impl<'m> Iterator for NamedGroups<'m> {
continue; continue;
} }
// This is the first occurrence of this name. Find the best range value. // This is the first occurrence of this name. Resolve the value to
// Prefer a Some value over None when there are duplicate names. // the last occurrence of this name that matched: a later duplicate
// overrides an earlier one only when it is defined.
let mut best_range = self.mat.captures[idx].clone(); let mut best_range = self.mat.captures[idx].clone();
for check_idx in (idx + 1)..end { for check_idx in (idx + 1)..end {
if self.mat.group_names[check_idx].as_ref() == name { if self.mat.group_names[check_idx].as_ref() == name {
// Found a duplicate name. Prefer a Some value over None. if let Some(range) = self.mat.captures[check_idx].as_ref() {
if best_range.is_none() && self.mat.captures[check_idx].is_some() { best_range = Some(range.clone());
best_range = self.mat.captures[check_idx].clone();
break; // Stop once we find a Some value
} }
} }
} }
@@ -1272,3 +1271,112 @@ mod utf16_tests {
assert_eq!(str_matches(&re, text), utf16_matches(&re, &to_units(text))); assert_eq!(str_matches(&re, text), utf16_matches(&re, &to_units(text)));
} }
} }
#[cfg(test)]
mod named_groups_tests {
use super::Regex;
/// 同名双定义(循环内各迭代命中不同出现):named_groups 取末次已定义出现,
/// 键序为首现源序。
#[test]
fn named_groups_last_defined_wins() {
let re = Regex::new(r"(?:(?<x>a)|(?<x>b)){2}").unwrap();
let m = re.find("ab").unwrap();
let named: Vec<_> = m.named_groups().collect();
assert_eq!(named, vec![("x", Some(1..2))], "末次已定义出现(b)胜出");
// 首出现被迭代 2 重入清槽,末次出现命中 b。
assert_eq!(m.captures, vec![None, Some(1..2)], "result 数组各出现独立交付");
}
/// 末次出现未定义时回退至更早已定义出现。
#[test]
fn named_groups_falls_back_to_earlier_defined() {
let re = Regex::new(r"(?:(?<x>a)|(?<x>b)|c){2}").unwrap();
let m = re.find("ca").unwrap();
let named: Vec<_> = m.named_groups().collect();
assert_eq!(named, vec![("x", Some(1..2))], "末次出现未定义,回退首出现");
assert_eq!(m.captures, vec![Some(1..2), None]);
}
/// 全部出现未定义时取 undefined。
#[test]
fn named_groups_all_undefined() {
let re = Regex::new(r"(?:(?<x>a)|(?<x>b)|c){2}").unwrap();
let m = re.find("cc").unwrap();
let named: Vec<_> = m.named_groups().collect();
assert_eq!(named, vec![("x", None)]);
assert_eq!(m.captures, vec![None, None]);
}
/// 同名回引读末次已定义出现:迭代 2 中末次出现命中 b,回引读 b 命中 "aabb"。
#[test]
fn backref_reads_last_defined() {
let re = Regex::new(r"(?:(?:(?<x>a)|(?<x>b))\k<x>){2}").unwrap();
let m = re.find("aabb").unwrap();
assert_eq!(m.range(), 0..4);
assert_eq!(m.captures, vec![None, Some(2..3)], "各出现独立交付");
}
/// 单轮同名回引:末次已定义分别为首出现(aa)与次出现(bb)。
#[test]
fn backref_single_round() {
let re = Regex::new(r"(?:(?<x>a)|(?<x>b))\k<x>").unwrap();
let m = re.find("aa").unwrap();
assert_eq!(m.range(), 0..2);
assert_eq!(m.captures, vec![Some(0..1), None]);
let m = re.find("bb").unwrap();
assert_eq!(m.range(), 0..2);
assert_eq!(m.captures, vec![None, Some(0..1)]);
}
/// 全部出现未定义时回引空匹配;末次已定义值不匹配时整体失败(不回落空匹配,
/// 也不回落更早已定义出现)。
#[test]
fn backref_undefined_empty_and_no_fallthrough() {
let re = Regex::new(r"^(?:(?<a>x)|(?<a>y)|z)\k<a>$").unwrap();
let m = re.find("z").unwrap();
assert_eq!(m.range(), 0..1);
assert_eq!(m.captures, vec![None, None]);
assert!(re.find("zz").is_none(), "末次已定义值不匹配时整体失败");
// 末次已定义为 x(位 2 为 y,不匹配 x);更早已定义为 y(位 2 亦不匹配)。
let re = Regex::new(r"(?:(?<a>y)|(?<a>x)){2}\k<a>").unwrap();
assert!(re.find("yxy").is_none(), "末次已定义值不匹配时整体失败");
// 对照:末次已定义值匹配时命中。
let m = re.find("yxx").unwrap();
assert_eq!(m.range(), 0..3);
}
/// 循环重入清槽后全部未定义:回引空匹配,结果数组各出现 undefined。
#[test]
fn backref_loop_reentry_clears() {
let re = Regex::new(r"^(?:(?<a>x)|(?<a>y)|z){2}\k<a>$").unwrap();
let m = re.find("xz").unwrap();
assert_eq!(m.range(), 0..2);
assert_eq!(m.captures, vec![None, None]);
assert!(re.find("xzx").is_none());
}
/// 未定义组回引空匹配(单出现面不变)。
#[test]
fn backref_single_occurrence_undefined_empty() {
let re = Regex::new(r"(?<a>x)|(?:zy\k<a>)").unwrap();
let m = re.find("zy").unwrap();
assert_eq!(m.range(), 0..2);
assert_eq!(m.captures, vec![None]);
}
/// PikeVM 执行器与回退执行器对同名回引口径一致。
#[cfg(feature = "backend-pikevm")]
#[test]
fn pikevm_backref_agrees() {
use crate::backends::PikeVMExecutor;
use crate::exec::{Executor, Matches};
let re = Regex::new(r"(?:(?:(?<x>a)|(?<x>b))\k<x>){2}").unwrap();
let m = Matches::new(PikeVMExecutor::new(&re.cr, "aabb"), 0)
.next()
.expect("pikevm 应命中 aabb");
assert_eq!(m.range(), 0..4);
assert_eq!(m.captures, vec![None, Some(2..3)]);
}
}
+21
View File
@@ -886,6 +886,27 @@ impl<'a, Input: InputIndexer> MatchAttempter<'a, Input> {
next_or_bt!(matched) next_or_bt!(matched)
} }
Insn::BackRefLastDefined(cg_indices) => {
// Scan the occurrences in reverse source order and match the
// text of the last occurrence that is defined; a later
// occurrence overrides an earlier one only when it matched.
let mut matched = true;
for &cg_idx in cg_indices.iter().rev() {
let cg = self.s.groups.mat(cg_idx as usize);
if let Some(orig_range) = cg.as_range() {
if re.flags.icase {
matched = matchers::backref_icase(input, dir, orig_range, &mut pos);
} else {
matched = matchers::backref(input, dir, orig_range, &mut pos);
}
break;
}
}
// No occurrence is defined and so the match succeeds (ES6
// 21.2.2.9).
next_or_bt!(matched)
}
&Insn::Lookahead { &Insn::Lookahead {
negate, negate,
start_group, start_group,
+8
View File
@@ -340,6 +340,14 @@ impl Emitter {
self.emit_insn(Insn::BackRef(group - 1)) self.emit_insn(Insn::BackRef(group - 1))
} }
Node::BackRefLastDefined(groups) => {
debug_assert!(!groups.is_empty(), "Should not be empty");
// -1 because group indices are 1-based in the IR.
let indices: Vec<CaptureGroupID> =
groups.iter().map(|g| (*g - 1) as CaptureGroupID).collect();
self.emit_insn(Insn::BackRefLastDefined(indices))
}
Node::ByteSet(bytes) => self.emit_byte_set_insn(bytes), Node::ByteSet(bytes) => self.emit_byte_set_insn(bytes),
Node::CharSet(chars) => { Node::CharSet(chars) => {
+5
View File
@@ -102,6 +102,11 @@ pub enum Insn {
/// Perform a backreference match. /// Perform a backreference match.
BackRef(u32), BackRef(u32),
/// Perform a backreference match against the last-defined occurrence of a
/// name shared by multiple capture groups. Indices are 0-based, in source
/// order; if no occurrence is defined the match succeeds.
BackRefLastDefined(Vec<CaptureGroupID>),
/// Match the next character against the bracket contents, stored at the given index in the CompiledRegex. /// Match the next character against the bracket contents, stored at the given index in the CompiledRegex.
Bracket(usize), Bracket(usize),
+12 -2
View File
@@ -84,10 +84,14 @@ pub enum Node {
/// A backreference. /// A backreference.
/// Indices are 1-based. That is, it matches JS syntax: \1 is the first capture group, /// Indices are 1-based. That is, it matches JS syntax: \1 is the first capture group,
/// not everything. /// not everything.
/// A backreference that logically may match multiple capture groups (through shared names)
/// are emitted through alternations of backreferences.
BackRef(u32), BackRef(u32),
/// A backreference to a name shared by multiple capture groups.
/// Indices are 1-based, one per occurrence in source order. At match time it
/// matches the text of the last occurrence that is defined; if none is
/// defined, it matches the empty string.
BackRefLastDefined(Vec<u32>),
/// A bracket. /// A bracket.
Bracket(BracketContents), Bracket(BracketContents),
@@ -232,6 +236,7 @@ impl Node {
} }
&Node::WordBoundary { invert } => Node::WordBoundary { invert }, &Node::WordBoundary { invert } => Node::WordBoundary { invert },
&Node::BackRef(idx) => Node::BackRef(idx), &Node::BackRef(idx) => Node::BackRef(idx),
Node::BackRefLastDefined(groups) => Node::BackRefLastDefined(groups.clone()),
Node::Bracket(bc) => Node::Bracket(bc.clone()), Node::Bracket(bc) => Node::Bracket(bc.clone()),
// Do not reverse into lookarounds, they already have the right sense. // Do not reverse into lookarounds, they already have the right sense.
Node::LookaroundAssertion { Node::LookaroundAssertion {
@@ -308,6 +313,7 @@ where
| Node::CharSet(..) | Node::CharSet(..)
| Node::WordBoundary { .. } | Node::WordBoundary { .. }
| Node::BackRef { .. } | Node::BackRef { .. }
| Node::BackRefLastDefined { .. }
| Node::Bracket { .. } | Node::Bracket { .. }
| Node::MatchAny | Node::MatchAny
| Node::MatchAnyExceptLineTerminator | Node::MatchAnyExceptLineTerminator
@@ -381,6 +387,7 @@ where
| Node::Anchor { .. } | Node::Anchor { .. }
| Node::WordBoundary { .. } | Node::WordBoundary { .. }
| Node::BackRef { .. } | Node::BackRef { .. }
| Node::BackRefLastDefined { .. }
| Node::Bracket { .. } => {} | Node::Bracket { .. } => {}
Node::Cat(nodes) => { Node::Cat(nodes) => {
nodes.iter_mut().for_each(|node| self.process(node)); nodes.iter_mut().for_each(|node| self.process(node));
@@ -548,6 +555,9 @@ fn display_node(node: &Node, depth: usize, f: &mut fmt::Formatter) -> fmt::Resul
&Node::BackRef(group) => { &Node::BackRef(group) => {
writeln!(f, "BackRef {:?} ", group)?; writeln!(f, "BackRef {:?} ", group)?;
} }
Node::BackRefLastDefined(groups) => {
writeln!(f, "BackRefLastDefined {:?} ", groups)?;
}
Node::Bracket(contents) => { Node::Bracket(contents) => {
writeln!(f, "Bracket {:?}", contents)?; writeln!(f, "Bracket {:?}", contents)?;
} }
+4 -1
View File
@@ -160,7 +160,10 @@ fn remove_empties(n: &mut Node, _w: &Walk) -> PassAction {
// Capture groups could in principle be optimized if they only match empties. // Capture groups could in principle be optimized if they only match empties.
PassAction::Keep PassAction::Keep
} }
Node::WordBoundary { .. } | Node::BackRef { .. } | Node::Bracket { .. } => PassAction::Keep, Node::WordBoundary { .. }
| Node::BackRef { .. }
| Node::BackRefLastDefined { .. }
| Node::Bracket { .. } => PassAction::Keep,
Node::LookaroundAssertion { Node::LookaroundAssertion {
negate, contents, .. negate, contents, ..
} => { } => {
+3 -9
View File
@@ -1689,15 +1689,9 @@ where
ir::Node::BackRef(group_indices[0] + 1) ir::Node::BackRef(group_indices[0] + 1)
} }
_ => { _ => {
// Unusual case of multiple groups sharing a name: the backref should try each in turn. // Multiple groups sharing a name: the backref reads the
// Lower to alternations of backreferences. Reverse to keep it right-associative: a | (b | (c | d))... // last occurrence that is defined at match time.
let backrefs = group_indices ir::Node::BackRefLastDefined(group_indices.iter().map(|i| i + 1).collect())
.iter()
.rev()
.map(|group_index| ir::Node::BackRef(*group_index + 1));
backrefs
.reduce(|right, left| ir::Node::Alt(Box::new(left), Box::new(right)))
.unwrap()
} }
}; };
Ok(node) Ok(node)
+21
View File
@@ -238,6 +238,27 @@ fn try_match_state<Input: InputIndexer, Dir: Direction>(
nextinsn_or_fail!(matched) nextinsn_or_fail!(matched)
} }
Insn::BackRefLastDefined(group_indices) => {
// Scan the occurrences in reverse source order and match the text of
// the last occurrence that is defined; a later occurrence overrides
// an earlier one only when it matched.
let mut matched = true;
for &group_idx in group_indices.iter().rev() {
let group = &s.groups[group_idx as usize];
if let Some(orig_range) = group.as_range() {
if re.flags.icase {
matched = matchers::backref_icase(input, dir, orig_range, &mut s.pos);
} else {
matched = matchers::backref(input, dir, orig_range, &mut s.pos);
}
break;
}
}
// No occurrence is defined, and therefore the match succeeds
// (ES6 21.2.2.9).
nextinsn_or_fail!(matched)
}
&Insn::Lookahead { &Insn::Lookahead {
negate, negate,
start_group: _, start_group: _,
+1 -1
View File
@@ -123,7 +123,7 @@ fn compute_start_predicate(n: &Node) -> Option<AbstractStartPredicate> {
Node::Empty => arbitrary, Node::Empty => arbitrary,
Node::Goal => arbitrary, Node::Goal => arbitrary,
Node::BackRef(..) => arbitrary, Node::BackRef(..) | Node::BackRefLastDefined(..) => arbitrary,
Node::CharSet(chars) => { Node::CharSet(chars) => {
// Pick the first bytes out. // Pick the first bytes out.