mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-25 11:11:11 +08:00
fix: 正则非 u 标志按码元匹配,u/v 标志按码点
This commit is contained in:
Vendored
+8
-5
@@ -77,16 +77,19 @@ upstream 0.11.1, other than this note; upstream repo-only files —
|
||||
`utf16` feature) to a per-compilation runtime flag, so enabling `utf16`
|
||||
no longer disables the byte passes on the str path:
|
||||
- `src/optimizer.rs` — the literal-byte pass is gated by the new
|
||||
`optimize_with_byte_literals(ire, byte_literals)` entry; `optimize`
|
||||
keeps the previous behavior as its wrapper.
|
||||
`optimize_with_byte_literals(ire, byte_literals, decompose_nonbmp)`
|
||||
entry; `optimize` keeps the previous behavior as its wrapper. The
|
||||
`decompose_nonbmp` flag splits non-BMP codepoint literals into
|
||||
surrogate pairs for the code-unit (UCS-2) unit IR only.
|
||||
- `src/classicalbacktrack.rs` — the start-predicate dispatch in the
|
||||
matcher's next-match loop switches on input encoding at runtime: byte
|
||||
inputs (UTF-8/ASCII) keep the anchored/byte-positioning fast paths,
|
||||
unit inputs (UTF-16/UCS-2) fall back to position-by-position tries.
|
||||
- `src/api.rs` — `Regex` retains the pattern's source code-point
|
||||
sequence plus a lazily materialized unit-side IR (`OnceLock`),
|
||||
re-parsed with the byte-literal pass disabled on first unit-input
|
||||
entry; the unit entry points compile through that IR.
|
||||
sequence plus lazily materialized unit-side IRs (two `OnceLock`s:
|
||||
code-point-oriented and code-unit-oriented), re-parsed with the
|
||||
byte-literal pass disabled on first unit-input entry; the unit entry
|
||||
points compile through those IRs.
|
||||
3. `Cargo.toml` — the upstream workspace-members table (for the repo's
|
||||
`regress-tool`/`gen-unicode` subdirectories, not vendored here) is
|
||||
replaced by a standalone empty `[workspace]` table, and the `utf16`
|
||||
|
||||
Vendored
+43
-18
@@ -381,8 +381,12 @@ pub struct Regex {
|
||||
pattern: Option<Vec<u32>>,
|
||||
|
||||
// 单元向编译产物(未跑字节字面量 pass),首次单元输入入口时惰性物化。
|
||||
// 两路缓存:码点向入口(`find_from_utf16`)不拆非 BMP 码点字面量;
|
||||
// 码元向入口(`find_from_ucs2`)拆为 surrogate 对。两 IR 不得共用。
|
||||
#[cfg_attr(not(feature = "utf16"), allow(dead_code))]
|
||||
units_cr: OnceLock<CompiledRegex>,
|
||||
#[cfg_attr(not(feature = "utf16"), allow(dead_code))]
|
||||
units_cr_ucs2: OnceLock<CompiledRegex>,
|
||||
}
|
||||
|
||||
impl From<CompiledRegex> for Regex {
|
||||
@@ -391,6 +395,7 @@ impl From<CompiledRegex> for Regex {
|
||||
cr,
|
||||
pattern: None,
|
||||
units_cr: OnceLock::new(),
|
||||
units_cr_ucs2: OnceLock::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -441,27 +446,47 @@ impl Regex {
|
||||
cr,
|
||||
pattern: Some(pattern),
|
||||
units_cr: OnceLock::new(),
|
||||
units_cr_ucs2: OnceLock::new(),
|
||||
})
|
||||
}
|
||||
|
||||
/// 单元向编译产物:首次使用时按同源码同 flags 重解析、关字节字面量 pass
|
||||
/// 重新优化并 emit,得到可在 UTF-16/UCS-2 单元输入上执行的 IR。
|
||||
/// 编译本正则所用的 flags(u/v 标志决定码点语义,供调用方选择
|
||||
/// 码点向或码元向的单元输入入口)。
|
||||
pub fn flags(&self) -> &Flags {
|
||||
&self.cr.flags
|
||||
}
|
||||
|
||||
/// 单元向编译产物(码点向):首次使用时按同源码同 flags 重解析、关字节
|
||||
/// 字面量 pass 重新优化并 emit,得到可在 UTF-16 单元输入上执行的 IR。
|
||||
/// 非 BMP 码点字面量保持单码点元素(码点向输入把代理对视作单码点)。
|
||||
/// 无源码构造(`From<CompiledRegex>`)无重解析来源,回退复用既有产物。
|
||||
#[cfg(feature = "utf16")]
|
||||
fn units_cr(&self) -> &CompiledRegex {
|
||||
self.units_cr.get_or_init(|| {
|
||||
let Some(pattern) = &self.pattern else {
|
||||
return self.cr.clone();
|
||||
};
|
||||
self.units_cr.get_or_init(|| self.compile_units(false))
|
||||
}
|
||||
|
||||
// 同源码同 flags 在构造期已成功解析编译,重解析不可能失败。
|
||||
let mut ire =
|
||||
parse::try_parse(pattern.iter().copied(), self.cr.flags).expect("构造期已成功解析的模式重解析失败");
|
||||
if !self.cr.flags.no_opt {
|
||||
optimizer::optimize_with_byte_literals(&mut ire, false);
|
||||
}
|
||||
emit::emit(&ire)
|
||||
})
|
||||
/// 单元向编译产物(码元向):同 `units_cr`,但非 BMP 码点字面量拆为
|
||||
/// 高+低 surrogate 码元,使码元向输入(UCS-2,每单元独立元素)可命中。
|
||||
#[cfg(feature = "utf16")]
|
||||
fn units_cr_ucs2(&self) -> &CompiledRegex {
|
||||
self.units_cr_ucs2.get_or_init(|| self.compile_units(true))
|
||||
}
|
||||
|
||||
/// 两路单元向缓存共用的惰性编译体:`decompose` 决定非 BMP 码点字面量
|
||||
/// 是否拆为 surrogate 对。
|
||||
#[cfg(feature = "utf16")]
|
||||
fn compile_units(&self, decompose: bool) -> CompiledRegex {
|
||||
let Some(pattern) = &self.pattern else {
|
||||
return self.cr.clone();
|
||||
};
|
||||
|
||||
// 同源码同 flags 在构造期已成功解析编译,重解析不可能失败。
|
||||
let mut ire =
|
||||
parse::try_parse(pattern.iter().copied(), self.cr.flags).expect("构造期已成功解析的模式重解析失败");
|
||||
if !self.cr.flags.no_opt {
|
||||
optimizer::optimize_with_byte_literals(&mut ire, false, decompose);
|
||||
}
|
||||
emit::emit(&ire)
|
||||
}
|
||||
|
||||
/// Searches `text` to find the first match.
|
||||
@@ -528,7 +553,7 @@ impl Regex {
|
||||
start: usize,
|
||||
) -> exec::Matches<super::classicalbacktrack::BacktrackExecutor<'r, indexing::Utf16Input<'t>>>
|
||||
{
|
||||
// 单元向 IR 无字节指令,UTF-16 单元输入可执行。
|
||||
// 码点向 IR 无字节指令,UTF-16 单元输入可执行;代理对按单码点匹配。
|
||||
let cr = self.units_cr();
|
||||
let input = Utf16Input::new(text, cr.flags.unicode);
|
||||
exec::Matches::new(
|
||||
@@ -548,9 +573,9 @@ impl Regex {
|
||||
start: usize,
|
||||
) -> exec::Matches<super::classicalbacktrack::BacktrackExecutor<'r, indexing::Ucs2Input<'t>>>
|
||||
{
|
||||
// 与 `find_from_utf16` 共用单元向 IR:UCS-2 同为单元输入,
|
||||
// str IR 的字节指令在单元索引器上不可执行。
|
||||
let cr = self.units_cr();
|
||||
// 码元向 IR:UCS-2 同为单元输入(str IR 的字节指令在单元索引器上
|
||||
// 不可执行),且非 BMP 码点字面量须拆为 surrogate 对以命中码元。
|
||||
let cr = self.units_cr_ucs2();
|
||||
let input = Ucs2Input::new(text, cr.flags.unicode);
|
||||
exec::Matches::new(
|
||||
super::classicalbacktrack::BacktrackExecutor::new(
|
||||
|
||||
Vendored
+72
-4
@@ -521,17 +521,80 @@ fn simplify_brackets(n: &mut Node, _walk: &Walk) -> PassAction {
|
||||
}
|
||||
}
|
||||
|
||||
/// 非 BMP 码点 → (高 surrogate, 低 surrogate)。
|
||||
fn surrogate_pair(c: u32) -> (u32, u32) {
|
||||
(0xD800 + ((c - 0x10000) >> 10), 0xDC00 + ((c - 0x10000) & 0x3FF))
|
||||
}
|
||||
|
||||
/// 非 BMP 码点字面量拆为高+低 surrogate 对(仅码元向 IR 需要)。
|
||||
///
|
||||
/// 字节指令无法在单元输入(UTF-16/UCS-2)上执行,单元向 IR 的字符字面量是
|
||||
/// 单码元元素:码元向输入(UCS-2)中每个 u16 是独立元素,非 BMP 码点(> 0xFFFF)
|
||||
/// 须拆成连续两个 surrogate 码元才能命中输入中的代理对。码点向输入(UTF-16)
|
||||
/// 把代理对视作单码点元素,不得拆分。
|
||||
fn decompose_nonbmp_chars(n: &mut Node, _w: &Walk) -> PassAction {
|
||||
match n {
|
||||
Node::Char { c, icase } if *c > 0xFFFF => {
|
||||
let (high, low) = surrogate_pair(*c);
|
||||
PassAction::Replace(Node::Cat(vec![
|
||||
Node::Char { c: high, icase: *icase },
|
||||
Node::Char { c: low, icase: *icase },
|
||||
]))
|
||||
}
|
||||
Node::CharSet(chars) if chars.iter().any(|&c| c > 0xFFFF) => {
|
||||
// BMP 码点保持单码元集合,非 BMP 码点各拆为高+低 surrogate 序列,
|
||||
// 两侧以 Alt 合并(单元素集合直接降为 Char)。
|
||||
let bmp: Vec<u32> = chars.iter().copied().filter(|&c| c <= 0xFFFF).collect();
|
||||
let nonbmp: Vec<u32> = chars.iter().copied().filter(|&c| c > 0xFFFF).collect();
|
||||
let bmp_node = match bmp.len() {
|
||||
0 => None,
|
||||
1 => Some(Node::Char { c: bmp[0], icase: false }),
|
||||
_ => Some(Node::CharSet(bmp)),
|
||||
};
|
||||
let mut seqs: Vec<Node> = nonbmp
|
||||
.iter()
|
||||
.map(|&c| {
|
||||
let (high, low) = surrogate_pair(c);
|
||||
Node::Cat(vec![
|
||||
Node::Char { c: high, icase: false },
|
||||
Node::Char { c: low, icase: false },
|
||||
])
|
||||
})
|
||||
.collect();
|
||||
let nonbmp_node = if seqs.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let mut acc = seqs.pop().unwrap();
|
||||
for s in seqs.into_iter().rev() {
|
||||
acc = Node::Alt(Box::new(s), Box::new(acc));
|
||||
}
|
||||
Some(acc)
|
||||
};
|
||||
match (bmp_node, nonbmp_node) {
|
||||
(Some(b), Some(nb)) => PassAction::Replace(Node::Alt(Box::new(b), Box::new(nb))),
|
||||
(b, nb) => PassAction::Replace(b.or(nb).unwrap()),
|
||||
}
|
||||
}
|
||||
_ => PassAction::Keep,
|
||||
}
|
||||
}
|
||||
|
||||
/// 编译主入口:字节字面量 pass 恒开,编译产物与旧的全局 feature 门控形态逐位一致。
|
||||
pub fn optimize(r: &mut Regex) {
|
||||
optimize_with_byte_literals(r, true)
|
||||
optimize_with_byte_literals(r, true, false)
|
||||
}
|
||||
|
||||
/// 与 `optimize` 同一条定点 pass 链,仅字节字面量 pass
|
||||
/// (Char → ByteSequence、ASCII 集合 → ByteSet)由 `byte_literals` 控制。
|
||||
/// (Char → ByteSequence、ASCII 集合 → ByteSet)由 `byte_literals` 控制,
|
||||
/// 非 BMP 码点字面量拆分由 `decompose_nonbmp` 控制。
|
||||
///
|
||||
/// 字节指令无法在单元输入(UTF-16/UCS-2)上执行:面向单元输入的 IR 必须以
|
||||
/// `false` 编译,字节输入(UTF-8)IR 以 `true` 编译保持编译与执行策略不变。
|
||||
pub fn optimize_with_byte_literals(r: &mut Regex, byte_literals: bool) {
|
||||
/// `byte_literals = false` 编译,字节输入(UTF-8)IR 以 `true` 编译保持编译
|
||||
/// 与执行策略不变。非 BMP 码点字面量拆分仅码元向 IR 需要(`decompose_nonbmp`
|
||||
/// 传 `true`):码元输入中每个 u16 是独立元素,非 BMP 码点须拆成连续两个
|
||||
/// surrogate 码元才能命中;码点向输入(UTF-16)把代理对视作单码点元素,
|
||||
/// 不得拆分。
|
||||
pub fn optimize_with_byte_literals(r: &mut Regex, byte_literals: bool, decompose_nonbmp: bool) {
|
||||
run_pass(r, &mut simplify_brackets);
|
||||
loop {
|
||||
let mut changed = false;
|
||||
@@ -539,6 +602,11 @@ pub fn optimize_with_byte_literals(r: &mut Regex, byte_literals: bool) {
|
||||
if r.flags.icase {
|
||||
changed |= run_pass(r, &mut unfold_icase_chars);
|
||||
}
|
||||
// 码元向 IR:非 BMP 码点字面量拆为 surrogate 对,须先于 unroll/promote
|
||||
// (否则非 BMP 字符会被提升进 1 字符循环而逃过拆分)。
|
||||
if !byte_literals && decompose_nonbmp {
|
||||
changed |= run_pass(r, &mut decompose_nonbmp_chars);
|
||||
}
|
||||
changed |= run_pass(r, &mut unroll_loops);
|
||||
changed |= run_pass(r, &mut promote_1char_loops);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user