From 8a35dcd06ee41c049906639fab90f60caeb556ba Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Sun, 13 Sep 2026 19:40:48 +0000 Subject: [PATCH] =?UTF-8?q?deps/builtins:=20regress=20=E5=90=AF=E7=94=A8?= =?UTF-8?q?=20utf16=20feature=20+=20=E5=8D=95=E5=85=83=E5=90=91=E5=8C=B9?= =?UTF-8?q?=E9=85=8D=E5=85=A5=E5=8F=A3=E5=9B=9E=E5=BD=92=E9=92=89=E4=B8=89?= =?UTF-8?q?=E5=88=99=E2=80=94=E2=80=94vendor=20fork=20=E7=9A=84=E5=AD=97?= =?UTF-8?q?=E8=8A=82=E5=AD=97=E9=9D=A2=E9=87=8F=E4=BC=98=E5=8C=96=20gate?= =?UTF-8?q?=20=E5=B7=B2=E8=BF=90=E8=A1=8C=E6=97=B6=E5=8C=96=EF=BC=88?= =?UTF-8?q?=E9=80=90=E7=BC=96=E8=AF=91=20byte=5Fliterals=20=E5=8F=82?= =?UTF-8?q?=E6=95=B0=EF=BC=89=EF=BC=8C=E5=85=A8=E5=B1=80=E5=90=AF=E7=94=A8?= =?UTF-8?q?=20utf16=20feature=20=E4=B8=8D=E5=86=8D=E5=85=B3=E6=8E=89=20str?= =?UTF-8?q?=20=E8=B7=AF=E5=BE=84=E7=9A=84=E5=AD=97=E8=8A=82=20pass=20?= =?UTF-8?q?=E4=B8=8E=E6=89=A7=E8=A1=8C=E5=99=A8=E8=B5=B7=E5=A7=8B=E8=B0=93?= =?UTF-8?q?=E8=AF=8D=E5=BF=AB=E8=B7=AF=E5=BE=84=EF=BC=8Cstr=20IR=20?= =?UTF-8?q?=E7=BC=96=E8=AF=91=E4=B8=8E=E6=89=A7=E8=A1=8C=E4=BF=9D=E6=8C=81?= =?UTF-8?q?=E9=80=90=E4=BD=8D=E4=B8=8D=E5=8F=98=EF=BC=9B=E6=9C=AC=E4=BE=A7?= =?UTF-8?q?=E7=BA=AF=E5=8A=A0=E6=80=A7=EF=BC=9A=E6=A0=B9=20manifest=20?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=E5=A2=9E=20utf16=20feature=EF=BC=88patch=20?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=E8=A1=A5=E5=90=AF=E7=94=A8=E5=90=8E=E6=9E=9C?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=EF=BC=89=EF=BC=8C=E6=96=B0=E5=A2=9E=E4=B8=89?= =?UTF-8?q?=E6=9E=9A=E7=9B=B4=E8=B5=B0=20regress=20API=20=E7=9A=84?= =?UTF-8?q?=E5=9B=9E=E5=BD=92=E9=92=89=EF=BC=88=E5=AD=A4=E7=AB=8B=20surrog?= =?UTF-8?q?ate=20=E5=8D=95=E5=85=83=E5=91=BD=E4=B8=AD=20GC=3DSurrogate=20?= =?UTF-8?q?=E8=80=8C=E4=BB=A3=E7=90=86=E5=AF=B9/=E6=99=AE=E9=80=9A?= =?UTF-8?q?=E5=AD=97=E7=AC=A6=E4=B8=8D=E5=91=BD=E4=B8=AD=E3=80=81=E4=BB=A3?= =?UTF-8?q?=E7=90=86=E5=AF=B9=E5=8D=95=E5=85=83=E5=BA=8F=E5=88=97=E6=8C=89?= =?UTF-8?q?=E5=8D=95=E7=A0=81=E7=82=B9=E5=8C=B9=E9=85=8D=20=E2=89=A1=20str?= =?UTF-8?q?=20=E8=B7=AF=E5=BE=84=E3=80=81well-formed=20=E8=AF=AD=E6=96=99?= =?UTF-8?q?=E5=8D=95=E5=85=83=E8=B7=AF=E5=BE=84=E5=8C=B9=E9=85=8D=20?= =?UTF-8?q?=E2=89=A1=20str=20=E8=B7=AF=E5=BE=84=EF=BC=89=EF=BC=8C=E4=B8=BA?= =?UTF-8?q?=E5=90=8E=E7=BB=AD=E5=AD=A4=E7=AB=8B=20surrogate=20=E6=B6=88?= =?UTF-8?q?=E8=B4=B9=E9=9D=A2=E9=87=8D=E6=9E=84=E9=92=89=E4=BD=8F=E5=8D=95?= =?UTF-8?q?=E5=85=83=E5=85=A5=E5=8F=A3=E5=A5=91=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 5 +- .../oxide_vm/tests/builtins_regexp_tests.rs | 56 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 68dfd622..fa8ca7a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,7 @@ fast-float = "0.2" ryu = "1" memchr = "2" regex = "1" -regress = "0.11" +regress = { version = "0.11", features = ["utf16"] } indexmap = "2" lru = "0.18" smallvec = { version = "1", features = ["union"] } @@ -74,5 +74,8 @@ panic = "abort" # 上游 0.11.1 的 Unicode 表缺 Script/Script_Extensions 的 Unknown(Zzzz,未分配 # 码点集)取值,规范合法模式 \p{Script=Unknown} 无法编译。vendor 补丁在 0.11.1 # 上补齐该取值,其余行为与上游 0.11.1 逐位一致。 +# utf16 feature 启用单元向匹配入口(find_from_utf16/ucs2):字节字面量优化已是 +# 逐编译运行时 gate,启用该 feature 不再劣化 str 路径(详见 fork 内 optimizer.rs +# 的 optimize_with_byte_literals)。 [patch.crates-io] regress = { path = "third_party/regress" } diff --git a/crates/oxide_vm/tests/builtins_regexp_tests.rs b/crates/oxide_vm/tests/builtins_regexp_tests.rs index 9f57be8b..d30e7c6d 100644 --- a/crates/oxide_vm/tests/builtins_regexp_tests.rs +++ b/crates/oxide_vm/tests/builtins_regexp_tests.rs @@ -293,3 +293,59 @@ fn regexp_unicode_property_script_existing_values_unchanged() { let result = eval(&mut vm, "/\\p{Script=Adlam}/u.test('d')").unwrap(); assert!(!result.as_bool(), "既有 Script 取值行为应保持不变"); } + +// --- UTF-16 单元向匹配入口:gate 运行时化 + 惰性 units IR 回归钉 --- +// 引擎构建已启用 regress 的 utf16 feature(纯加性 API)。字节字面量 pass 改为 +// 逐编译运行时 gate 后,str 路径编译与执行不变;单元向 IR 在首次 +// find_from_utf16 时惰性编译(关字节 pass)并缓存。以下钉直接走 regress API, +// 不依赖 JS 侧单元视图接线(后续批次才落地)。 + +fn units_of(text: &str) -> Vec { + text.encode_utf16().collect() +} + +#[test] +fn regexp_utf16_entry_surrogate_property() { + // 孤立 surrogate 单元命中 \p{General_Category=Surrogate};普通字符不命中; + // 代理对(单码点)不命中。 + let re = regress::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 不应命中"); + assert!( + re.find_from_utf16(&[0xD834, 0xDE00], 0).next().is_none(), + "代理对是单码点,不应按两个孤立 surrogate 命中" + ); +} + +#[test] +fn regexp_utf16_entry_surrogate_pair_equals_str_path() { + // U+1D11E = 代理对 [0xD834, 0xDE00]:单元路径按单码点匹配,与 str 路径等价。 + let ch = char::from_u32(0x1D11E).unwrap(); + let re = regress::Regex::with_flags(&ch.to_string(), "").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 = units_of(&text); + let m_u16 = re.find_from_utf16(&units, 0).next().expect("单元路径应命中"); + assert_eq!(m_u16.range(), 0..2, "单元路径命中 1 个码点(2 个单元)"); +} + +#[test] +fn regexp_utf16_entry_well_formed_equals_str_path() { + // well-formed 语料:单元路径与 str 路径匹配结果逐一相等(含非 ASCII)。 + for (pattern, text) in [(r"\d+", "abc123x456"), (r"a[bc]d", "xabcdyabd"), (r"\w+\s+\w+", "héllo wörld")] { + let re = regress::Regex::new(pattern).unwrap(); + let str_m = re.find(text).map(|m| m.as_str(text).to_string()); + let units = units_of(text); + let u16_m = re.find_from_utf16(&units, 0).next().map(|m| { + units[m.range()] + .iter() + .flat_map(|&u| char::from_u32(u as u32).map(|c| c.to_string())) + .collect::() + }); + assert_eq!(str_m, u16_m, "pattern {pattern}"); + } +}