From 2eea4f1fbc72a5e7e7405814174fbca9c45971bd Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 13 Sep 2026 17:56:09 +0000 Subject: [PATCH] =?UTF-8?q?deps:=20vendor=20regress=20=E5=AD=97=E8=8A=82?= =?UTF-8?q?=E5=AD=97=E9=9D=A2=E9=87=8F=E4=BC=98=E5=8C=96=20gate=20?= =?UTF-8?q?=E6=94=B9=E9=80=90=E7=BC=96=E8=AF=91=E8=BF=90=E8=A1=8C=E6=97=B6?= =?UTF-8?q?=E6=A0=87=E5=BF=97=20+=20=E6=83=B0=E6=80=A7=E7=89=A9=E5=8C=96?= =?UTF-8?q?=E5=8D=95=E5=85=83=E5=90=91=20IR=E2=80=94=E2=80=94utf16=20featu?= =?UTF-8?q?re=20=E7=BC=96=E8=AF=91=E6=9C=9F=E5=85=A8=E5=B1=80=20gate=20?= =?UTF-8?q?=E5=8E=9F=E4=BC=9A=E5=85=B3=E6=8E=89=E5=85=A8=E9=83=A8=20regex?= =?UTF-8?q?=20=E7=9A=84=E5=AD=97=E8=8A=82=20pass=20=E4=B8=8E=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E5=99=A8=E8=B5=B7=E5=A7=8B=E8=B0=93=E8=AF=8D=E5=BF=AB?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=EF=BC=8C=E5=90=AF=E7=94=A8=E5=8D=B3=E5=8A=A3?= =?UTF-8?q?=E5=8C=96=E5=85=A8=E9=83=A8=20str=20=E8=B7=AF=E5=BE=84=EF=BC=9B?= =?UTF-8?q?=E7=8E=B0=E5=AD=97=E8=8A=82=20pass=20=E7=94=B1=20byte=5Fliteral?= =?UTF-8?q?s=20=E7=BC=96=E8=AF=91=E5=8F=82=E6=95=B0=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=EF=BC=88str=20IR=20=E4=BC=A0=20true=20=E4=B8=8E=E6=97=A7?= =?UTF-8?q?=E8=A1=8C=E4=B8=BA=E9=80=90=E4=BD=8D=E4=B8=80=E8=87=B4=E3=80=81?= =?UTF-8?q?=E5=8D=95=E5=85=83=20IR=20=E4=BC=A0=20false=EF=BC=89=EF=BC=8C?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E5=99=A8=E8=B5=B7=E5=A7=8B=E8=B0=93=E8=AF=8D?= =?UTF-8?q?=E5=88=86=E6=B4=BE=E6=8C=89=E8=BE=93=E5=85=A5=E7=BC=96=E7=A0=81?= =?UTF-8?q?=E5=88=87=E6=8D=A2=EF=BC=88=E5=AD=97=E8=8A=82=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E4=BF=9D=E7=95=99=E9=94=9A=E5=AE=9A/=E5=AD=97=E8=8A=82?= =?UTF-8?q?=E5=AE=9A=E4=BD=8D=E5=BF=AB=E8=B7=AF=E5=BE=84=E3=80=81=E5=8D=95?= =?UTF-8?q?=E5=85=83=E8=BE=93=E5=85=A5=E9=80=90=E4=BD=8D=E5=B0=9D=E8=AF=95?= =?UTF-8?q?=EF=BC=89=EF=BC=8C=E5=8D=95=E5=85=83=E5=90=91=20IR=20=E9=A6=96?= =?UTF-8?q?=E6=AC=A1=20find=5Ffrom=5Futf16/find=5Ffrom=5Fucs2=20=E6=97=B6?= =?UTF-8?q?=E9=87=8D=E8=A7=A3=E6=9E=90=E7=BC=96=E8=AF=91=E5=B9=B6=E7=BC=93?= =?UTF-8?q?=E5=AD=98=EF=BC=88=E6=AD=A3=E5=88=99=E7=95=99=E5=AD=98=E6=BA=90?= =?UTF-8?q?=E7=A0=81=E7=A0=81=E7=82=B9=E5=BA=8F=E5=88=97=EF=BC=89=EF=BC=8C?= =?UTF-8?q?no=5Fopt=20=E6=A0=87=E5=BF=97=E5=90=8C=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E8=B4=AF=E7=A9=BF=EF=BC=9Bfork=20=E5=A2=9E=E9=87=8F=E8=BE=B9?= =?UTF-8?q?=E7=95=8C=20=3D=20optimizer.rs=20=E4=B8=A4=E5=A4=84=20cfg=20?= =?UTF-8?q?=E6=94=B9=E8=BF=90=E8=A1=8C=E6=97=B6=E6=A0=87=E5=BF=97=EF=BC=88?= =?UTF-8?q?optimize=20=E9=99=8D=E4=B8=BA=E5=8C=85=E8=A3=85=EF=BC=89+=20api?= =?UTF-8?q?.rs=20Regex=20=E4=B8=A4=E5=AD=97=E6=AE=B5=E4=B8=8E=20utf16=20?= =?UTF-8?q?=E4=B8=A4=E5=85=A5=E5=8F=A3=E6=94=B9=E8=B5=B0=E5=8D=95=E5=85=83?= =?UTF-8?q?=E4=BA=A7=E7=89=A9=20+=20classicalbacktrack.rs=20=E4=B8=A4?= =?UTF-8?q?=E5=A4=84=20cfg=20=E5=8E=BB=E9=99=A4=E5=88=86=E6=B4=BE=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E6=97=B6=E5=8C=96=20+=20manifest=20=E7=8B=AC=E7=AB=8B?= =?UTF-8?q?=20[workspace]=20=E8=A1=A8=E4=B8=8E=20feature=20=E6=B3=A8?= =?UTF-8?q?=E9=87=8A=E5=88=B7=E6=96=B0=EF=BC=8C=E5=85=B6=E4=BD=99=E4=B8=8E?= =?UTF-8?q?=E4=B8=8A=E6=B8=B8=200.11.1=20=E9=80=90=E4=BD=8D=E4=B8=80?= =?UTF-8?q?=E8=87=B4=EF=BC=9B=E6=96=B0=E5=A2=9E=E5=9B=9E=E5=BD=92=E9=92=89?= =?UTF-8?q?=E4=BA=94=E5=88=99=EF=BC=88well-formed=20=E5=8C=B9=E9=85=8D?= =?UTF-8?q?=E9=80=90=E4=B8=80=E7=9B=B8=E7=AD=89/=E5=AD=A4=E7=AB=8B=20surro?= =?UTF-8?q?gate=20=E5=B1=9E=E6=80=A7=E5=91=BD=E4=B8=AD/=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E5=AF=B9=20=E2=89=A1=20str=20=E8=B7=AF=E5=BE=84/=E5=AD=97?= =?UTF-8?q?=E8=8A=82=E6=8C=87=E4=BB=A4=E4=B8=8E=E8=B5=B7=E5=A7=8B=E8=B0=93?= =?UTF-8?q?=E8=AF=8D=E6=8C=89=20IR=20=E9=97=A8=E6=8E=A7/no=5Fopt=20?= =?UTF-8?q?=E8=B4=AF=E7=A9=BF=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- third_party/regress/Cargo.toml | 9 +- third_party/regress/src/api.rs | 279 +++++++++++++++++- third_party/regress/src/classicalbacktrack.rs | 51 ++-- third_party/regress/src/optimizer.rs | 20 +- 4 files changed, 320 insertions(+), 39 deletions(-) diff --git a/third_party/regress/Cargo.toml b/third_party/regress/Cargo.toml index 780efa10..4a5e9eec 100644 --- a/third_party/regress/Cargo.toml +++ b/third_party/regress/Cargo.toml @@ -1,3 +1,9 @@ +# Standalone workspace root: this vendored crate is built and tested on its own +# (standalone `cargo build` / `cargo test` in this directory), not as a member +# of the host workspace that vendors it. The empty table keeps it out of the +# parent workspace's member resolution. +[workspace] + [package] name = "regress" version = "0.11.1" @@ -23,7 +29,8 @@ index-positions = [] # Prohibits all uses of unsafe code, for the paranoid. prohibit-unsafe = [] -# Enables UTF-16 support. This disables some optimizations, so it should only be used when necessary. +# Enables UTF-16 support. Byte-literal optimization is decided per compilation +# (runtime gate), so enabling this feature no longer degrades the str path. utf16 = [] # Enables Pattern trait implementation for str::find, str::contains, etc. diff --git a/third_party/regress/src/api.rs b/third_party/regress/src/api.rs index 1e43cdf2..66aeaab1 100644 --- a/third_party/regress/src/api.rs +++ b/third_party/regress/src/api.rs @@ -17,6 +17,7 @@ use crate::{ #[cfg(feature = "backend-pikevm")] use crate::pikevm; use crate::util::to_char_sat; +use std::sync::OnceLock; use core::{fmt, str::FromStr}; #[cfg(feature = "std")] @@ -373,11 +374,24 @@ impl<'m> FusedIterator for NamedGroups<'m> {} #[derive(Debug, Clone)] pub struct Regex { cr: CompiledRegex, + + // 模式源码码点序列:str 路径编译一次消费,留存供单元向 IR 惰性重解析。 + // 仅 `From` 构造无源码(None)。 + #[cfg_attr(not(feature = "utf16"), allow(dead_code))] + pattern: Option>, + + // 单元向编译产物(未跑字节字面量 pass),首次单元输入入口时惰性物化。 + #[cfg_attr(not(feature = "utf16"), allow(dead_code))] + units_cr: OnceLock, } impl From for Regex { fn from(cr: CompiledRegex) -> Self { - Self { cr } + Self { + cr, + pattern: None, + units_cr: OnceLock::new(), + } } } @@ -415,12 +429,39 @@ impl Regex { F: Into, { let flags = flags.into(); - let mut ire = parse::try_parse(pattern, flags)?; + + // 先收集源码:str 路径编译照旧消费一次,留存的码点序列供单元向 IR 惰性重解析。 + let pattern: Vec = pattern.collect(); + let mut ire = parse::try_parse(pattern.iter().copied(), flags)?; if !flags.no_opt { optimizer::optimize(&mut ire); } let cr = emit::emit(&ire); - Ok(Regex { cr }) + Ok(Regex { + cr, + pattern: Some(pattern), + units_cr: OnceLock::new(), + }) + } + + /// 单元向编译产物:首次使用时按同源码同 flags 重解析、关字节字面量 pass + /// 重新优化并 emit,得到可在 UTF-16/UCS-2 单元输入上执行的 IR。 + /// 无源码构造(`From`)无重解析来源,回退复用既有产物。 + #[cfg(feature = "utf16")] + fn units_cr(&self) -> &CompiledRegex { + self.units_cr.get_or_init(|| { + 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); + } + emit::emit(&ire) + }) } /// Searches `text` to find the first match. @@ -487,11 +528,13 @@ impl Regex { start: usize, ) -> exec::Matches>> { - let input = Utf16Input::new(text, self.cr.flags.unicode); + // 单元向 IR 无字节指令,UTF-16 单元输入可执行。 + let cr = self.units_cr(); + let input = Utf16Input::new(text, cr.flags.unicode); exec::Matches::new( super::classicalbacktrack::BacktrackExecutor::new( input, - MatchAttempter::new(&self.cr, input.left_end()), + MatchAttempter::new(cr, input.left_end()), ), start, ) @@ -505,11 +548,14 @@ impl Regex { start: usize, ) -> exec::Matches>> { - let input = Ucs2Input::new(text, self.cr.flags.unicode); + // 与 `find_from_utf16` 共用单元向 IR:UCS-2 同为单元输入, + // str IR 的字节指令在单元索引器上不可执行。 + let cr = self.units_cr(); + let input = Ucs2Input::new(text, cr.flags.unicode); exec::Matches::new( super::classicalbacktrack::BacktrackExecutor::new( input, - MatchAttempter::new(&self.cr, input.left_end()), + MatchAttempter::new(cr, input.left_end()), ), start, ) @@ -912,7 +958,7 @@ pub mod backends { use super::exec; use super::indexing; pub use crate::emit::emit; - pub use crate::optimizer::optimize; + pub use crate::optimizer::{optimize, optimize_with_byte_literals}; pub use crate::parse::try_parse; /// An Executor using the classical backtracking algorithm. @@ -984,3 +1030,220 @@ pub fn escape(text: &str) -> String { result } + +#[cfg(all(test, feature = "utf16"))] +mod utf16_tests { + use super::*; + use crate::insn::{Insn, StartPredicate}; + + /// 单元序列解码为码点序列:代理对合并为单码点,孤立 surrogate 保留单元值。 + fn decode_units(units: &[u16]) -> Vec { + let mut out = Vec::with_capacity(units.len()); + let mut i = 0; + while i < units.len() { + let u = units[i]; + + // 高 surrogate 后随低 surrogate 时合并为代理对码点。 + if (0xD800..0xDC00).contains(&u) && i + 1 < units.len() { + let lo = units[i + 1]; + if (0xDC00..0xE000).contains(&lo) { + out.push(0x10000 + ((u as u32 - 0xD800) << 10) + (lo as u32 - 0xDC00)); + i += 2; + continue; + } + } + out.push(u as u32); + i += 1; + } + out + } + + fn to_units(text: &str) -> Vec { + let mut out = Vec::with_capacity(text.len()); + for c in text.chars() { + let mut buf = [0u16; 2]; + out.extend_from_slice(c.encode_utf16(&mut buf)); + } + out + } + + /// 单元下标 → 码点下标映射:代理对的两个单元同指一个码点。 + fn unit_to_cp_index(units: &[u16]) -> Vec { + let mut map = Vec::with_capacity(units.len()); + let mut cp = 0usize; + let mut i = 0; + while i < units.len() { + let u = units[i]; + if (0xD800..0xDC00).contains(&u) + && i + 1 < units.len() + && (0xDC00..0xE000).contains(&units[i + 1]) + { + map.push(cp); + map.push(cp); + cp += 1; + i += 2; + } else { + map.push(cp); + cp += 1; + i += 1; + } + } + map + } + + /// 收集全部匹配(主匹配 + 捕获组,均码点序列)。 + /// 两种输入形态的偏移口径(字节 / 单元)经码点归一后直接可比。 + type MatchCps = (Vec, Vec>>); + + fn str_matches(re: &Regex, text: &str) -> Vec { + let decode = |s: &str| s.chars().map(u32::from).collect::>(); + re.find_iter(text) + .map(|m| { + let caps = m + .captures + .iter() + .map(|c| c.as_ref().map(|r| decode(&text[r.clone()]))) + .collect(); + (decode(m.as_str(text)), caps) + }) + .collect() + } + + fn utf16_matches(re: &Regex, units: &[u16]) -> Vec { + let cps = decode_units(units); + let idx = unit_to_cp_index(units); + + // 空匹配不占码点(含尾部位);非空匹配经单元→码点下标映射切片。 + let cp_slice = |r: &core::ops::Range| -> Vec { + if r.start == r.end { + return Vec::new(); + } + cps[idx[r.start]..(idx[r.end - 1] + 1)].to_vec() + }; + re.find_from_utf16(units, 0) + .map(|m| { + let caps = m + .captures + .iter() + .map(|c| c.as_ref().map(|r| cp_slice(&r))) + .collect(); + (cp_slice(&m.range()), caps) + }) + .collect() + } + + fn is_byte_insn(i: &Insn) -> bool { + matches!( + i, + Insn::ByteSet2(_) | Insn::ByteSet3(_) | Insn::ByteSet4(_) + | Insn::ByteSeq1(_) + | Insn::ByteSeq2(_) + | Insn::ByteSeq3(_) + | Insn::ByteSeq4(_) + | Insn::ByteSeq5(_) + | Insn::ByteSeq6(_) + | Insn::ByteSeq7(_) + | Insn::ByteSeq8(_) + | Insn::ByteSeq9(_) + | Insn::ByteSeq10(_) + | Insn::ByteSeq11(_) + | Insn::ByteSeq12(_) + | Insn::ByteSeq13(_) + | Insn::ByteSeq14(_) + | Insn::ByteSeq15(_) + | Insn::ByteSeq16(_) + ) + } + + /// well-formed 语料:units IR 与 str IR 匹配结果逐一相等。 + #[test] + fn units_ir_matches_equal_str_path_on_well_formed() { + let cases = [ + (r"\d+", "abc123x456"), + (r"a[bc]d", "xabcdyabd"), + (r"(f+)(o+)(r*)", "foo of foo"), + (r"\w+\s+\w+", "héllo wörld"), + (r"x*", "abab"), + (r"(a)|(b)", "abab"), + (r"nope", "nothing here"), + ]; + for (pattern, text) in cases { + let re = Regex::new(pattern).unwrap(); + let units = to_units(text); + assert_eq!(str_matches(&re, text), utf16_matches(&re, &units), "pattern {pattern}"); + } + } + + /// 孤立 surrogate 单元在 \p{General_Category=Surrogate} 下命中, + /// 普通字符不命中,代理对(单码点)不命中。 + #[test] + fn units_ir_matches_lone_surrogate_property() { + let re = Regex::with_flags(r"\p{General_Category=Surrogate}", "u").unwrap(); + let m = re.find_from_utf16(&[0xD800], 0).next(); + assert!(m.is_some(), "孤立 surrogate 单元应命中 GC=Surrogate"); + assert_eq!(m.unwrap().range(), 0..1); + assert!(re.find_from_utf16(&[0x41], 0).next().is_none(), "A 不应命中"); + + // 代理对是单码点,不是两个孤立 surrogate。 + assert!(re.find_from_utf16(&[0xD834, 0xDE00], 0).next().is_none()); + } + + /// 代理对单元序列按单码点匹配,与 str 路径逐位等价。 + #[test] + fn units_ir_surrogate_pair_equals_str_path() { + let cp: u32 = 0x1D11E; + let re = Regex::from_unicode([cp].iter().copied(), "").unwrap(); + let ch = char::from_u32(cp).unwrap(); + let text: String = [ch, 'a', 'b', 'c'].iter().copied().collect(); + let m_str = re.find(&text).expect("str 路径应命中"); + assert_eq!(m_str.range(), 0..4, "str 路径命中 1 个码点(4 字节)"); + + let units = to_units(&text); + let m_u16 = re.find_from_utf16(&units, 0).next().expect("单元路径应命中"); + assert_eq!(m_u16.range(), 0..2, "单元路径命中 1 个码点(2 个单元)"); + assert_eq!(decode_units(&units[m_u16.range()]), vec![cp]); + assert_eq!(str_matches(&re, &text), utf16_matches(&re, &units)); + } + + /// 字节字面量 pass 的运行时门控:str IR 含字节指令与字节形起始谓词, + /// units IR 两者皆无;well-formed 输入上两 IR 匹配结果一致。 + #[test] + fn byte_literal_pass_gated_per_ir() { + let re = Regex::new("abcdef").unwrap(); + assert!( + re.cr.insns.iter().any(is_byte_insn), + "str IR 应含字节指令(字节字面量 pass 生效)" + ); + assert!( + !matches!(re.cr.start_pred, StartPredicate::Arbitrary), + "str 起始谓词应为字节形" + ); + + let units = re.units_cr(); + assert!(!units.insns.iter().any(is_byte_insn), "units IR 不得含字节指令"); + assert!( + matches!(units.start_pred, StartPredicate::Arbitrary), + "units 起始谓词应为 Arbitrary" + ); + + let text = "xxabcdefyy"; + assert_eq!(str_matches(&re, text), utf16_matches(&re, &to_units(text))); + } + + /// no_opt 标志贯穿单元向惰性编译:两 IR 均不跑优化 pass,指令流同长且匹配一致。 + #[test] + fn no_opt_flag_respected_by_units_ir() { + let flags = Flags { + no_opt: true, + ..Default::default() + }; + let re = Regex::with_flags("abcdef", flags).unwrap(); + assert!(!re.cr.insns.iter().any(is_byte_insn), "no_opt 下 str IR 不含字节指令"); + let units = re.units_cr(); + assert!(!units.insns.iter().any(is_byte_insn)); + assert_eq!(re.cr.insns.len(), units.insns.len(), "no_opt 下两 IR 指令流同长"); + + let text = "xxabcdefyy"; + assert_eq!(str_matches(&re, text), utf16_matches(&re, &to_units(text))); + } +} diff --git a/third_party/regress/src/classicalbacktrack.rs b/third_party/regress/src/classicalbacktrack.rs index 9f744640..5d4ce9d2 100644 --- a/third_party/regress/src/classicalbacktrack.rs +++ b/third_party/regress/src/classicalbacktrack.rs @@ -7,7 +7,6 @@ use crate::cursor::{Backward, Direction, Forward}; use crate::exec; use crate::indexing; use crate::indexing::{AsciiInput, ElementType, InputIndexer, Utf8Input}; -#[cfg(not(feature = "utf16"))] use crate::insn::StartPredicate; use crate::insn::{CompiledRegex, Insn, LoopFields}; use crate::matchers; @@ -1053,7 +1052,6 @@ impl BacktrackExecutor<'_, Input> { /// \return the next match for an anchored regex that only matches at the start. /// This avoids any string searching and only tries matching at the given position. - #[cfg(not(feature = "utf16"))] fn next_match_anchored( &mut self, pos: Input::Position, @@ -1118,31 +1116,32 @@ impl exec::MatchProducer for BacktrackExecutor<'_, Input> { pos: Input::Position, next_start: &mut Option, ) -> Option { - // When UTF-16 support is active prefix search is not used due to the different encoding. - #[cfg(feature = "utf16")] - return self.next_match_with_prefix_search(pos, next_start, &bytesearch::EmptyString {}); - - #[cfg(not(feature = "utf16"))] - match &self.matcher.re.start_pred { - StartPredicate::Arbitrary => { - self.next_match_with_prefix_search(pos, next_start, &bytesearch::EmptyString {}) - } - StartPredicate::StartAnchored => self.next_match_anchored(pos, next_start), - StartPredicate::ByteSet1(bytes) => { - self.next_match_with_prefix_search(pos, next_start, bytes) - } - StartPredicate::ByteSet2(bytes) => { - self.next_match_with_prefix_search(pos, next_start, bytes) - } - StartPredicate::ByteSet3(bytes) => { - self.next_match_with_prefix_search(pos, next_start, bytes) - } - StartPredicate::ByteSeq(bytes) => { - self.next_match_with_prefix_search(pos, next_start, bytes.as_ref()) - } - StartPredicate::ByteBracket(bitmap) => { - self.next_match_with_prefix_search(pos, next_start, bitmap) + // 字节输入(UTF-8/ASCII)按起始谓词走定位快路径; + // 单元输入(UTF-16/UCS-2)无法字节级前缀搜索,逐位尝试匹配。 + if Input::CODE_UNITS_ARE_BYTES { + match &self.matcher.re.start_pred { + StartPredicate::Arbitrary => { + self.next_match_with_prefix_search(pos, next_start, &bytesearch::EmptyString {}) + } + StartPredicate::StartAnchored => self.next_match_anchored(pos, next_start), + StartPredicate::ByteSet1(bytes) => { + self.next_match_with_prefix_search(pos, next_start, bytes) + } + StartPredicate::ByteSet2(bytes) => { + self.next_match_with_prefix_search(pos, next_start, bytes) + } + StartPredicate::ByteSet3(bytes) => { + self.next_match_with_prefix_search(pos, next_start, bytes) + } + StartPredicate::ByteSeq(bytes) => { + self.next_match_with_prefix_search(pos, next_start, bytes.as_ref()) + } + StartPredicate::ByteBracket(bitmap) => { + self.next_match_with_prefix_search(pos, next_start, bitmap) + } } + } else { + self.next_match_with_prefix_search(pos, next_start, &bytesearch::EmptyString {}) } } } diff --git a/third_party/regress/src/optimizer.rs b/third_party/regress/src/optimizer.rs index b604ea5c..066ea2ce 100644 --- a/third_party/regress/src/optimizer.rs +++ b/third_party/regress/src/optimizer.rs @@ -405,9 +405,9 @@ fn promote_1char_loops(n: &mut Node, _w: &Walk) -> PassAction { /// Replace Cat(Char) with ByteSeq. /// Also replace chars with literal bytes. -/// Don't do this in utf16 mode because UTF-16 should never match against bytes. +/// 字节指令无法在单元输入(UTF-16/UCS-2)上执行,面向单元的编译由调用方 +/// 关闭本 pass(见 `optimize_with_byte_literals`)。 /// TODO: this seems to do too much; consider breaking this up. -#[cfg(not(feature = "utf16"))] fn form_literal_bytes(n: &mut Node, walk: &Walk) -> PassAction { // Helper to return a mutable reference to the nodes of a literal bytes. fn get_literal_bytes(n: &mut Node) -> Option<&mut Vec> { @@ -521,7 +521,17 @@ fn simplify_brackets(n: &mut Node, _walk: &Walk) -> PassAction { } } +/// 编译主入口:字节字面量 pass 恒开,编译产物与旧的全局 feature 门控形态逐位一致。 pub fn optimize(r: &mut Regex) { + optimize_with_byte_literals(r, true) +} + +/// 与 `optimize` 同一条定点 pass 链,仅字节字面量 pass +/// (Char → ByteSequence、ASCII 集合 → ByteSet)由 `byte_literals` 控制。 +/// +/// 字节指令无法在单元输入(UTF-16/UCS-2)上执行:面向单元输入的 IR 必须以 +/// `false` 编译,字节输入(UTF-8)IR 以 `true` 编译保持编译与执行策略不变。 +pub fn optimize_with_byte_literals(r: &mut Regex, byte_literals: bool) { run_pass(r, &mut simplify_brackets); loop { let mut changed = false; @@ -531,10 +541,12 @@ pub fn optimize(r: &mut Regex) { } changed |= run_pass(r, &mut unroll_loops); changed |= run_pass(r, &mut promote_1char_loops); - #[cfg(not(feature = "utf16"))] - { + + // 字节字面量 pass:仅字节输入编译允许,单元输入 IR 不得含字节指令。 + if byte_literals { changed |= run_pass(r, &mut form_literal_bytes); } + changed |= run_pass(r, &mut remove_empties); changed |= run_pass(r, &mut propagate_early_fails); if !changed {