use std::sync::Arc; use oxide_compiler::compiler::Compiler; use oxide_types::value::JsValue; use oxide_vm::vm::Vm; fn eval(vm: &mut Vm, source: &str) -> Result { let allocator = oxide_parser::Allocator::default(); let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?; let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?; vm.run(&Arc::new(module)) } fn to_str(vm: &Vm, val: JsValue) -> String { vm.lookup_str(val).unwrap_or_default() } #[test] fn regexp_survives_gc_and_still_matches() { // 回归:正则存入全局对象触发晋升/GC 搬移后,native_fn 槽的已编译 Box // 必须深拷贝到新对象(而非共享指针),否则 epoch 释放与 teardown 双重释放。 let mut vm = Vm::new(); let result = eval(&mut vm, "var r = /ab+c/g; globalThis.r = r; r.test('xxabbcx')").unwrap(); assert!(result.as_bool()); // 触发完整收集:存活正则克隆进新 arena,旧 Box 由 sweep 释放。 vm.reset(); let result = eval(&mut vm, "globalThis.r.test('xxabbcx')").unwrap(); assert!(result.as_bool(), "GC 后正则应仍可匹配"); let result = eval(&mut vm, "globalThis.r.exec('xabbbcx')[0]").unwrap(); assert_eq!(to_str(&vm, result), "abbbc"); } // --- RegExp constructor --- #[test] fn regexp_constructor_creates_object() { let mut vm = Vm::new(); let result = eval(&mut vm, "typeof new RegExp('abc')").unwrap(); assert_eq!(to_str(&vm, result), "object"); } #[test] fn regexp_constructor_with_flags() { let mut vm = Vm::new(); let result = eval(&mut vm, "new RegExp('abc', 'gi').ignoreCase").unwrap(); assert!(result.as_bool()); } #[test] fn regexp_constructor_invalid_pattern_syntax_error() { let mut vm = Vm::new(); let result = eval(&mut vm, "new RegExp('[')"); assert!(result.is_err() || (result.is_ok() && result.unwrap().is_string())); } #[test] fn regexp_source_property() { let mut vm = Vm::new(); let result = eval(&mut vm, "new RegExp('hello', 'g').source").unwrap(); assert_eq!(to_str(&vm, result), "hello"); } #[test] fn regexp_flags_property() { let mut vm = Vm::new(); let result = eval(&mut vm, "new RegExp('abc', 'gi').flags").unwrap(); assert_eq!(to_str(&vm, result), "gi"); } #[test] fn regexp_global_property() { let mut vm = Vm::new(); let result = eval(&mut vm, "new RegExp('abc', 'g').global").unwrap(); assert!(result.as_bool()); } #[test] fn regexp_multiline_property() { let mut vm = Vm::new(); let result = eval(&mut vm, "new RegExp('abc', 'm').multiline").unwrap(); assert!(result.as_bool()); } #[test] fn regexp_last_index_property() { let mut vm = Vm::new(); let result = eval(&mut vm, "new RegExp('abc', 'g').lastIndex").unwrap(); assert_eq!(result.as_int(), 0); } #[test] fn regexp_dotall_sticky_unicode_default_false() { let mut vm = Vm::new(); let result = eval(&mut vm, "new RegExp('abc').dotAll").unwrap(); assert!(!result.as_bool()); } // --- RegExp.prototype.test --- #[test] fn regexp_test_basic() { let mut vm = Vm::new(); let result = eval(&mut vm, "new RegExp('abc').test('abc')").unwrap(); assert!(result.as_bool()); } #[test] fn regexp_test_no_match() { let mut vm = Vm::new(); let result = eval(&mut vm, "new RegExp('abc').test('xyz')").unwrap(); assert!(!result.as_bool()); } #[test] fn regexp_test_case_insensitive() { let mut vm = Vm::new(); let result = eval(&mut vm, "new RegExp('abc', 'i').test('ABC')").unwrap(); assert!(result.as_bool()); } #[test] fn regexp_test_global_flag_iteration() { let mut vm = Vm::new(); let result = eval(&mut vm, "var re = /a/g; re.test('a')").unwrap(); assert!(result.as_bool()); } // --- RegExp.prototype.exec --- #[test] fn regexp_exec_basic() { let mut vm = Vm::new(); let result = eval(&mut vm, "/hello/.exec('hello world') !== null").unwrap(); assert!(result.as_bool()); } #[test] fn regexp_exec_no_match_returns_null() { let mut vm = Vm::new(); let result = eval(&mut vm, "/xyz/.exec('hello')").unwrap(); assert!(result.is_null()); } #[test] fn regexp_exec_returns_index() { let mut vm = Vm::new(); let result = eval(&mut vm, "/ell/.exec('hello')").unwrap(); assert!(result.is_object()); } #[test] fn regexp_exec_returns_input() { let mut vm = Vm::new(); let m = eval(&mut vm, "/ell/.exec('hello')").unwrap(); assert!(m.is_object()); } #[test] fn regexp_exec_global_last_index_advances() { let mut vm = Vm::new(); let re = eval(&mut vm, "var re = /a/g; re.exec('aba'); re.lastIndex").unwrap(); assert_eq!(re.as_int(), 1); } #[test] fn regexp_exec_global_resets_on_exhaustion() { let mut vm = Vm::new(); let result = eval(&mut vm, "var re = /a/g; re.exec('a'); re.exec('a') === null").unwrap(); assert!(result.as_bool()); } // --- RegExp literal compilation --- #[test] fn regexp_literal_basic() { let mut vm = Vm::new(); let result = eval(&mut vm, "/hello/").unwrap(); assert!(result.is_object()); } #[test] fn regexp_literal_with_flags() { let mut vm = Vm::new(); let result = eval(&mut vm, "/abc/i.test('ABC')").unwrap(); assert!(result.as_bool()); } #[test] fn regexp_literal_global_exec() { let mut vm = Vm::new(); let result = eval(&mut vm, "var re = /a/g; re.exec('aba'); re.lastIndex").unwrap(); assert_eq!(result.as_int(), 1); } // --- String integration --- #[test] fn string_match_with_regexp() { let mut vm = Vm::new(); let result = eval(&mut vm, "'hello world'.match(/ell/)").unwrap(); assert!(result.is_object()); } #[test] fn string_replace_with_regexp() { let mut vm = Vm::new(); let result = eval(&mut vm, "'hello'.replace(/ell/, 'ipp')").unwrap(); assert_eq!(to_str(&vm, result), "hippo"); } #[test] fn string_replace_with_regexp_global() { let mut vm = Vm::new(); let result = eval(&mut vm, "'aba'.replace(/a/g, 'c')").unwrap(); assert_eq!(to_str(&vm, result), "cbc"); } #[test] fn string_search_with_regexp() { let mut vm = Vm::new(); let result = eval(&mut vm, "'hello'.search(/ell/)").unwrap(); assert_eq!(result.as_int(), 1); } #[test] fn string_search_not_found() { let mut vm = Vm::new(); let result = eval(&mut vm, "'hello'.search(/xyz/)").unwrap(); assert_eq!(result.as_int(), -1); } #[test] fn string_split_with_regexp() { let mut vm = Vm::new(); let result = eval(&mut vm, "'a,b,c'.split(/,/)").unwrap(); assert!(result.is_object()); let obj = unsafe { &*result.as_js_object_ptr() }; assert_eq!(obj.prop_count(), 3); assert_eq!(to_str(&vm, obj.get_prop_at(1)), "b"); } // --- toString --- #[test] fn regexp_to_string() { let mut vm = Vm::new(); let result = eval(&mut vm, "/abc/gi.toString()").unwrap(); assert_eq!(to_str(&vm, result), "/abc/gi"); } // --- RegExp.escape 静态方法 --- #[test] fn regexp_escape_table_representatives() { let mut vm = Vm::new(); // 首字符 ASCII 字母先走 \xNN 规则,其余语法字符反斜杠加字符本身。 let result = eval(&mut vm, "RegExp.escape('a.b')").unwrap(); assert_eq!(to_str(&vm, result), "\\x61\\.b"); let result = eval(&mut vm, "RegExp.escape('.a1')").unwrap(); assert_eq!(to_str(&vm, result), "\\.a1"); // 控制字符:单字母转义形态。 let result = eval(&mut vm, "RegExp.escape('\\t\\n\\v\\f\\r')").unwrap(); assert_eq!(to_str(&vm, result), "\\t\\n\\v\\f\\r"); // 空白:≤0xFF 走 \xNN,扩展 USP 段与 BOM 走 \uNNNN(十六进制小写)。 let result = eval(&mut vm, "RegExp.escape(' ')").unwrap(); assert_eq!(to_str(&vm, result), "\\x20"); let result = eval(&mut vm, "RegExp.escape('\\uFEFF')").unwrap(); assert_eq!(to_str(&vm, result), "\\ufeff"); let result = eval(&mut vm, "RegExp.escape('\\u202F')").unwrap(); assert_eq!(to_str(&vm, result), "\\u202f"); // 其它标点。 let result = eval(&mut vm, "RegExp.escape(',')").unwrap(); assert_eq!(to_str(&vm, result), "\\x2c"); // 首字符数字/ASCII 字母:\xNN;非首位不转义。 let result = eval(&mut vm, "RegExp.escape('1111')").unwrap(); assert_eq!(to_str(&vm, result), "\\x31111"); let result = eval(&mut vm, "RegExp.escape('aaa')").unwrap(); assert_eq!(to_str(&vm, result), "\\x61aa"); // 下划线不转义。 let result = eval(&mut vm, "RegExp.escape('_hello')").unwrap(); assert_eq!(to_str(&vm, result), "_hello"); } #[test] fn regexp_escape_surrogates_and_non_bmp() { let mut vm = Vm::new(); // 孤立 surrogate:\uXXXX(4 位小写十六进制)。 let result = eval(&mut vm, "RegExp.escape('\\uD800')").unwrap(); assert_eq!(to_str(&vm, result), "\\ud800"); // 非 BMP 码点原样(代理对不按孤立 surrogate 转义)。 let result = eval(&mut vm, "RegExp.escape('\\u{1F600}')").unwrap(); assert_eq!(to_str(&vm, result), "\u{1F600}"); } #[test] fn regexp_escape_non_string_throws_type_error() { let mut vm = Vm::new(); let source = "(() => { try { RegExp.escape(123); return 'no-throw'; } catch (e) { return e instanceof TypeError ? 'TypeError' : 'other'; } })()"; let result = eval(&mut vm, source).unwrap(); assert_eq!(to_str(&vm, result), "TypeError"); } #[test] fn regexp_escape_length_and_name() { let mut vm = Vm::new(); let result = eval(&mut vm, "RegExp.escape.length + ':' + RegExp.escape.name").unwrap(); assert_eq!(to_str(&vm, result), "1:escape"); } // --- Unicode property escapes:Script/Script_Extensions 的 Unknown(Zzzz)取值 --- // vendor 的 Unicode 表须含 Unknown(未分配码点集),否则合法模式编译期 SyntaxError。 #[test] fn regexp_unicode_property_script_unknown_matches_unassigned() { let mut vm = Vm::new(); // U+038B 未分配:Script 与 Script_Extensions 均为 Unknown let result = eval(&mut vm, "/\\p{Script=Unknown}/u.test(String.fromCodePoint(0x038B))").unwrap(); assert!(result.as_bool(), "未分配码点应匹配 Script=Unknown"); let result = eval(&mut vm, "/\\p{Script_Extensions=Unknown}/u.test(String.fromCodePoint(0x038B))").unwrap(); assert!(result.as_bool(), "未分配码点应匹配 Script_Extensions=Unknown"); } #[test] fn regexp_unicode_property_script_unknown_rejects_assigned() { let mut vm = Vm::new(); // 已分配拉丁字母不属于 Unknown let result = eval(&mut vm, "/\\p{Script=Unknown}/u.test('d')").unwrap(); assert!(!result.as_bool(), "已分配码点不应匹配 Script=Unknown"); let result = eval(&mut vm, "/\\P{Script=Unknown}/u.test('d')").unwrap(); assert!(result.as_bool(), "取反后已分配码点应匹配"); } #[test] fn regexp_unicode_property_script_unknown_aliases() { let mut vm = Vm::new(); // 单字母缩写与符号名须同义解析 for pattern in [r"\p{sc=Unknown}", r"\p{scx=Unknown}", r"\p{Script=Zzzz}", r"\p{scx=Zzzz}"] { let source = format!("/{pattern}/u.test(String.fromCodePoint(0x038B))"); let result = eval(&mut vm, &source).unwrap(); assert!(result.as_bool(), "别名 {pattern} 应匹配未分配码点"); } } #[test] fn regexp_unicode_property_script_existing_values_unchanged() { // 补齐 Unknown 不得扰动既有取值:Adlam 等已分配 Script 行为保持 let mut vm = Vm::new(); let result = eval(&mut vm, "/\\p{Script=Adlam}/u.test(String.fromCodePoint(0x1E900))").unwrap(); assert!(result.as_bool(), "既有 Script 取值行为应保持不变"); 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}"); } } // --- RegExp flag 只读访问器(M11)--- #[test] fn regexp_flag_accessors_read_values() { let mut vm = Vm::new(); let result = eval( &mut vm, "var r = new RegExp('a', 'gimsyud'); [r.global, r.ignoreCase, r.multiline, r.dotAll, r.sticky, r.unicode, r.hasIndices, r.unicodeSets].join(',')", ) .unwrap(); assert_eq!(to_str(&vm, result), "true,true,true,true,true,true,true,false"); let result = eval(&mut vm, "new RegExp('a', 'v').unicodeSets").unwrap(); assert!(result.as_bool()); let result = eval(&mut vm, "new RegExp('a', 'u').unicodeSets === false && new RegExp('a').unicode === false").unwrap(); assert!(result.as_bool()); } #[test] fn regexp_flag_accessor_descriptor_and_identity() { let mut vm = Vm::new(); let result = eval( &mut vm, "var d = Object.getOwnPropertyDescriptor(RegExp.prototype, 'global'); d.set === undefined && typeof d.get === 'function' && d.enumerable === false && d.configurable === true && d.get.length === 0 && d.get.name === 'get global'", ) .unwrap(); assert!(result.as_bool()); } #[test] fn regexp_instance_has_no_own_flag_props() { let mut vm = Vm::new(); let result = eval(&mut vm, "Object.getOwnPropertyNames(new RegExp('a', 'g')).sort().join(',')").unwrap(); assert_eq!(to_str(&vm, result), "flags,lastIndex,source"); } #[test] fn regexp_flag_write_is_noop_sloppy() { let mut vm = Vm::new(); let result = eval( &mut vm, "var r = new RegExp('a'); r.global = true; r.ignoreCase = false; r.global === false", ) .unwrap(); assert!(result.as_bool(), "无 setter 访问器在 sloppy 写应静默 no-op"); } #[test] fn regexp_uv_flags_mutual_exclusive() { let mut vm = Vm::new(); let result = eval( &mut vm, "(() => { try { new RegExp('.', 'uv'); return 'no-throw'; } catch (e) { return e instanceof SyntaxError ? 'SyntaxError' : 'other'; } })()", ) .unwrap(); assert_eq!(to_str(&vm, result), "SyntaxError"); // u 与 v 单独使用不受影响。 let result = eval(&mut vm, "new RegExp('.', 'u').unicode + ',' + new RegExp('.', 'v').unicodeSets").unwrap(); assert_eq!(to_str(&vm, result), "true,true"); } #[test] fn regexp_flag_getter_proto_this_returns_undefined() { let mut vm = Vm::new(); let result = eval( &mut vm, "Object.getOwnPropertyDescriptor(RegExp.prototype, 'dotAll').get.call(RegExp.prototype) === undefined", ) .unwrap(); assert!(result.as_bool()); } #[test] fn regexp_flag_getter_non_regexp_this_throws_type_error() { let mut vm = Vm::new(); let source = "(() => { var get = Object.getOwnPropertyDescriptor(RegExp.prototype, 'global').get; try { get.call({}); return 'no-throw'; } catch (e) { return e instanceof TypeError ? 'TypeError' : 'other'; } })()"; let result = eval(&mut vm, source).unwrap(); assert_eq!(to_str(&vm, result), "TypeError"); let source = "(() => { var get = Object.getOwnPropertyDescriptor(RegExp.prototype, 'global').get; try { get.call(null); return 'no-throw'; } catch (e) { return e instanceof TypeError ? 'TypeError' : 'other'; } })()"; let result = eval(&mut vm, source).unwrap(); assert_eq!(to_str(&vm, result), "TypeError"); }