mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: 补全 String locale 三方法与 localeCompare 基本档
This commit is contained in:
@@ -0,0 +1,527 @@
|
||||
//! String locale 三方法:toLocaleLowerCase / toLocaleUpperCase / localeCompare。
|
||||
//!
|
||||
//! toLocaleLowerCase 与 toLowerCase 同映射表,增量仅 Final Sigma 条件映射
|
||||
//! (σ/ς 判定按原始单元序列、双向跳过 Mn/Cf 找最近可见字符);
|
||||
//! toLocaleUpperCase 与 toUpperCase 同表无条件映射;localeCompare 基本档 =
|
||||
//! NFC 归一后 UTF-16 单元序比较,第二参缺省/undefined 落 "undefined" 串。
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use oxide_runtime_api::{NativeResult, VmHost};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use crate::builtins_debug;
|
||||
|
||||
use super::common::try_string;
|
||||
use super::{as_units, map_well_formed_segments, this_units};
|
||||
|
||||
// ── Mn/Cf 判定表 ─────────────────────────────────────────────────────────
|
||||
//
|
||||
// Final Sigma 条件映射的"不可见"判定:Mn(非连接修饰符号)与 Cf(格式字符)
|
||||
// 全集范围表(UCD 快照,367 段,起点升序)。表取全集而非语料钉点,防语料
|
||||
// 扩充翻车;仅当未来 Unicode 版本新增 Mn/Cf 码点且语料随之扩充时需重生成。
|
||||
|
||||
const MN_CF_RANGES: &[(u32, u32)] = &[
|
||||
(0xad, 0xad),
|
||||
(0x300, 0x36f),
|
||||
(0x483, 0x487),
|
||||
(0x591, 0x5bd),
|
||||
(0x5bf, 0x5bf),
|
||||
(0x5c1, 0x5c2),
|
||||
(0x5c4, 0x5c5),
|
||||
(0x5c7, 0x5c7),
|
||||
(0x600, 0x605),
|
||||
(0x610, 0x61a),
|
||||
(0x61c, 0x61c),
|
||||
(0x64b, 0x65f),
|
||||
(0x670, 0x670),
|
||||
(0x6d6, 0x6dc),
|
||||
(0x6dd, 0x6dd),
|
||||
(0x6df, 0x6e4),
|
||||
(0x6e7, 0x6e8),
|
||||
(0x6ea, 0x6ed),
|
||||
(0x70f, 0x70f),
|
||||
(0x711, 0x711),
|
||||
(0x730, 0x74a),
|
||||
(0x7a6, 0x7b0),
|
||||
(0x7eb, 0x7f3),
|
||||
(0x7fd, 0x7fd),
|
||||
(0x816, 0x819),
|
||||
(0x81b, 0x823),
|
||||
(0x825, 0x827),
|
||||
(0x829, 0x82d),
|
||||
(0x859, 0x85b),
|
||||
(0x890, 0x891),
|
||||
(0x898, 0x89f),
|
||||
(0x8ca, 0x8e1),
|
||||
(0x8e2, 0x8e2),
|
||||
(0x8e3, 0x902),
|
||||
(0x93a, 0x93a),
|
||||
(0x93c, 0x93c),
|
||||
(0x941, 0x948),
|
||||
(0x94d, 0x94d),
|
||||
(0x951, 0x957),
|
||||
(0x962, 0x963),
|
||||
(0x981, 0x981),
|
||||
(0x9bc, 0x9bc),
|
||||
(0x9c1, 0x9c4),
|
||||
(0x9cd, 0x9cd),
|
||||
(0x9e2, 0x9e3),
|
||||
(0x9fe, 0x9fe),
|
||||
(0xa01, 0xa02),
|
||||
(0xa3c, 0xa3c),
|
||||
(0xa41, 0xa42),
|
||||
(0xa47, 0xa48),
|
||||
(0xa4b, 0xa4d),
|
||||
(0xa51, 0xa51),
|
||||
(0xa70, 0xa71),
|
||||
(0xa75, 0xa75),
|
||||
(0xa81, 0xa82),
|
||||
(0xabc, 0xabc),
|
||||
(0xac1, 0xac5),
|
||||
(0xac7, 0xac8),
|
||||
(0xacd, 0xacd),
|
||||
(0xae2, 0xae3),
|
||||
(0xafa, 0xaff),
|
||||
(0xb01, 0xb01),
|
||||
(0xb3c, 0xb3c),
|
||||
(0xb3f, 0xb3f),
|
||||
(0xb41, 0xb44),
|
||||
(0xb4d, 0xb4d),
|
||||
(0xb55, 0xb56),
|
||||
(0xb62, 0xb63),
|
||||
(0xb82, 0xb82),
|
||||
(0xbc0, 0xbc0),
|
||||
(0xbcd, 0xbcd),
|
||||
(0xc00, 0xc00),
|
||||
(0xc04, 0xc04),
|
||||
(0xc3c, 0xc3c),
|
||||
(0xc3e, 0xc40),
|
||||
(0xc46, 0xc48),
|
||||
(0xc4a, 0xc4d),
|
||||
(0xc55, 0xc56),
|
||||
(0xc62, 0xc63),
|
||||
(0xc81, 0xc81),
|
||||
(0xcbc, 0xcbc),
|
||||
(0xcbf, 0xcbf),
|
||||
(0xcc6, 0xcc6),
|
||||
(0xccc, 0xccd),
|
||||
(0xce2, 0xce3),
|
||||
(0xd00, 0xd01),
|
||||
(0xd3b, 0xd3c),
|
||||
(0xd41, 0xd44),
|
||||
(0xd4d, 0xd4d),
|
||||
(0xd62, 0xd63),
|
||||
(0xd81, 0xd81),
|
||||
(0xdca, 0xdca),
|
||||
(0xdd2, 0xdd4),
|
||||
(0xdd6, 0xdd6),
|
||||
(0xe31, 0xe31),
|
||||
(0xe34, 0xe3a),
|
||||
(0xe47, 0xe4e),
|
||||
(0xeb1, 0xeb1),
|
||||
(0xeb4, 0xebc),
|
||||
(0xec8, 0xece),
|
||||
(0xf18, 0xf19),
|
||||
(0xf35, 0xf35),
|
||||
(0xf37, 0xf37),
|
||||
(0xf39, 0xf39),
|
||||
(0xf71, 0xf7e),
|
||||
(0xf80, 0xf84),
|
||||
(0xf86, 0xf87),
|
||||
(0xf8d, 0xf97),
|
||||
(0xf99, 0xfbc),
|
||||
(0xfc6, 0xfc6),
|
||||
(0x102d, 0x1030),
|
||||
(0x1032, 0x1037),
|
||||
(0x1039, 0x103a),
|
||||
(0x103d, 0x103e),
|
||||
(0x1058, 0x1059),
|
||||
(0x105e, 0x1060),
|
||||
(0x1071, 0x1074),
|
||||
(0x1082, 0x1082),
|
||||
(0x1085, 0x1086),
|
||||
(0x108d, 0x108d),
|
||||
(0x109d, 0x109d),
|
||||
(0x135d, 0x135f),
|
||||
(0x1712, 0x1714),
|
||||
(0x1732, 0x1733),
|
||||
(0x1752, 0x1753),
|
||||
(0x1772, 0x1773),
|
||||
(0x17b4, 0x17b5),
|
||||
(0x17b7, 0x17bd),
|
||||
(0x17c6, 0x17c6),
|
||||
(0x17c9, 0x17d3),
|
||||
(0x17dd, 0x17dd),
|
||||
(0x180b, 0x180d),
|
||||
(0x180e, 0x180e),
|
||||
(0x180f, 0x180f),
|
||||
(0x1885, 0x1886),
|
||||
(0x18a9, 0x18a9),
|
||||
(0x1920, 0x1922),
|
||||
(0x1927, 0x1928),
|
||||
(0x1932, 0x1932),
|
||||
(0x1939, 0x193b),
|
||||
(0x1a17, 0x1a18),
|
||||
(0x1a1b, 0x1a1b),
|
||||
(0x1a56, 0x1a56),
|
||||
(0x1a58, 0x1a5e),
|
||||
(0x1a60, 0x1a60),
|
||||
(0x1a62, 0x1a62),
|
||||
(0x1a65, 0x1a6c),
|
||||
(0x1a73, 0x1a7c),
|
||||
(0x1a7f, 0x1a7f),
|
||||
(0x1ab0, 0x1abd),
|
||||
(0x1abf, 0x1ace),
|
||||
(0x1b00, 0x1b03),
|
||||
(0x1b34, 0x1b34),
|
||||
(0x1b36, 0x1b3a),
|
||||
(0x1b3c, 0x1b3c),
|
||||
(0x1b42, 0x1b42),
|
||||
(0x1b6b, 0x1b73),
|
||||
(0x1b80, 0x1b81),
|
||||
(0x1ba2, 0x1ba5),
|
||||
(0x1ba8, 0x1ba9),
|
||||
(0x1bab, 0x1bad),
|
||||
(0x1be6, 0x1be6),
|
||||
(0x1be8, 0x1be9),
|
||||
(0x1bed, 0x1bed),
|
||||
(0x1bef, 0x1bf1),
|
||||
(0x1c2c, 0x1c33),
|
||||
(0x1c36, 0x1c37),
|
||||
(0x1cd0, 0x1cd2),
|
||||
(0x1cd4, 0x1ce0),
|
||||
(0x1ce2, 0x1ce8),
|
||||
(0x1ced, 0x1ced),
|
||||
(0x1cf4, 0x1cf4),
|
||||
(0x1cf8, 0x1cf9),
|
||||
(0x1dc0, 0x1dff),
|
||||
(0x200b, 0x200f),
|
||||
(0x202a, 0x202e),
|
||||
(0x2060, 0x2064),
|
||||
(0x2066, 0x206f),
|
||||
(0x20d0, 0x20dc),
|
||||
(0x20e1, 0x20e1),
|
||||
(0x20e5, 0x20f0),
|
||||
(0x2cef, 0x2cf1),
|
||||
(0x2d7f, 0x2d7f),
|
||||
(0x2de0, 0x2dff),
|
||||
(0x302a, 0x302d),
|
||||
(0x3099, 0x309a),
|
||||
(0xa66f, 0xa66f),
|
||||
(0xa674, 0xa67d),
|
||||
(0xa69e, 0xa69f),
|
||||
(0xa6f0, 0xa6f1),
|
||||
(0xa802, 0xa802),
|
||||
(0xa806, 0xa806),
|
||||
(0xa80b, 0xa80b),
|
||||
(0xa825, 0xa826),
|
||||
(0xa82c, 0xa82c),
|
||||
(0xa8c4, 0xa8c5),
|
||||
(0xa8e0, 0xa8f1),
|
||||
(0xa8ff, 0xa8ff),
|
||||
(0xa926, 0xa92d),
|
||||
(0xa947, 0xa951),
|
||||
(0xa980, 0xa982),
|
||||
(0xa9b3, 0xa9b3),
|
||||
(0xa9b6, 0xa9b9),
|
||||
(0xa9bc, 0xa9bd),
|
||||
(0xa9e5, 0xa9e5),
|
||||
(0xaa29, 0xaa2e),
|
||||
(0xaa31, 0xaa32),
|
||||
(0xaa35, 0xaa36),
|
||||
(0xaa43, 0xaa43),
|
||||
(0xaa4c, 0xaa4c),
|
||||
(0xaa7c, 0xaa7c),
|
||||
(0xaab0, 0xaab0),
|
||||
(0xaab2, 0xaab4),
|
||||
(0xaab7, 0xaab8),
|
||||
(0xaabe, 0xaabf),
|
||||
(0xaac1, 0xaac1),
|
||||
(0xaaec, 0xaaed),
|
||||
(0xaaf6, 0xaaf6),
|
||||
(0xabe5, 0xabe5),
|
||||
(0xabe8, 0xabe8),
|
||||
(0xabed, 0xabed),
|
||||
(0xfb1e, 0xfb1e),
|
||||
(0xfe00, 0xfe0f),
|
||||
(0xfe20, 0xfe2f),
|
||||
(0xfeff, 0xfeff),
|
||||
(0xfff9, 0xfffb),
|
||||
(0x101fd, 0x101fd),
|
||||
(0x102e0, 0x102e0),
|
||||
(0x10376, 0x1037a),
|
||||
(0x10a01, 0x10a03),
|
||||
(0x10a05, 0x10a06),
|
||||
(0x10a0c, 0x10a0f),
|
||||
(0x10a38, 0x10a3a),
|
||||
(0x10a3f, 0x10a3f),
|
||||
(0x10ae5, 0x10ae6),
|
||||
(0x10d24, 0x10d27),
|
||||
(0x10eab, 0x10eac),
|
||||
(0x10efd, 0x10eff),
|
||||
(0x10f46, 0x10f50),
|
||||
(0x10f82, 0x10f85),
|
||||
(0x11001, 0x11001),
|
||||
(0x11038, 0x11046),
|
||||
(0x11070, 0x11070),
|
||||
(0x11073, 0x11074),
|
||||
(0x1107f, 0x11081),
|
||||
(0x110b3, 0x110b6),
|
||||
(0x110b9, 0x110ba),
|
||||
(0x110bd, 0x110bd),
|
||||
(0x110c2, 0x110c2),
|
||||
(0x110cd, 0x110cd),
|
||||
(0x11100, 0x11102),
|
||||
(0x11127, 0x1112b),
|
||||
(0x1112d, 0x11134),
|
||||
(0x11173, 0x11173),
|
||||
(0x11180, 0x11181),
|
||||
(0x111b6, 0x111be),
|
||||
(0x111c9, 0x111cc),
|
||||
(0x111cf, 0x111cf),
|
||||
(0x1122f, 0x11231),
|
||||
(0x11234, 0x11234),
|
||||
(0x11236, 0x11237),
|
||||
(0x1123e, 0x1123e),
|
||||
(0x11241, 0x11241),
|
||||
(0x112df, 0x112df),
|
||||
(0x112e3, 0x112ea),
|
||||
(0x11300, 0x11301),
|
||||
(0x1133b, 0x1133c),
|
||||
(0x11340, 0x11340),
|
||||
(0x11366, 0x1136c),
|
||||
(0x11370, 0x11374),
|
||||
(0x11438, 0x1143f),
|
||||
(0x11442, 0x11444),
|
||||
(0x11446, 0x11446),
|
||||
(0x1145e, 0x1145e),
|
||||
(0x114b3, 0x114b8),
|
||||
(0x114ba, 0x114ba),
|
||||
(0x114bf, 0x114c0),
|
||||
(0x114c2, 0x114c3),
|
||||
(0x115b2, 0x115b5),
|
||||
(0x115bc, 0x115bd),
|
||||
(0x115bf, 0x115c0),
|
||||
(0x115dc, 0x115dd),
|
||||
(0x11633, 0x1163a),
|
||||
(0x1163d, 0x1163d),
|
||||
(0x1163f, 0x11640),
|
||||
(0x116ab, 0x116ab),
|
||||
(0x116ad, 0x116ad),
|
||||
(0x116b0, 0x116b5),
|
||||
(0x116b7, 0x116b7),
|
||||
(0x1171d, 0x1171f),
|
||||
(0x11722, 0x11725),
|
||||
(0x11727, 0x1172b),
|
||||
(0x1182f, 0x11837),
|
||||
(0x11839, 0x1183a),
|
||||
(0x1193b, 0x1193c),
|
||||
(0x1193e, 0x1193e),
|
||||
(0x11943, 0x11943),
|
||||
(0x119d4, 0x119d7),
|
||||
(0x119da, 0x119db),
|
||||
(0x119e0, 0x119e0),
|
||||
(0x11a01, 0x11a0a),
|
||||
(0x11a33, 0x11a38),
|
||||
(0x11a3b, 0x11a3e),
|
||||
(0x11a47, 0x11a47),
|
||||
(0x11a51, 0x11a56),
|
||||
(0x11a59, 0x11a5b),
|
||||
(0x11a8a, 0x11a96),
|
||||
(0x11a98, 0x11a99),
|
||||
(0x11c30, 0x11c36),
|
||||
(0x11c38, 0x11c3d),
|
||||
(0x11c3f, 0x11c3f),
|
||||
(0x11c92, 0x11ca7),
|
||||
(0x11caa, 0x11cb0),
|
||||
(0x11cb2, 0x11cb3),
|
||||
(0x11cb5, 0x11cb6),
|
||||
(0x11d31, 0x11d36),
|
||||
(0x11d3a, 0x11d3a),
|
||||
(0x11d3c, 0x11d3d),
|
||||
(0x11d3f, 0x11d45),
|
||||
(0x11d47, 0x11d47),
|
||||
(0x11d90, 0x11d91),
|
||||
(0x11d95, 0x11d95),
|
||||
(0x11d97, 0x11d97),
|
||||
(0x11ef3, 0x11ef4),
|
||||
(0x11f00, 0x11f01),
|
||||
(0x11f36, 0x11f3a),
|
||||
(0x11f40, 0x11f40),
|
||||
(0x11f42, 0x11f42),
|
||||
(0x13430, 0x1343f),
|
||||
(0x13440, 0x13440),
|
||||
(0x13447, 0x13455),
|
||||
(0x16af0, 0x16af4),
|
||||
(0x16b30, 0x16b36),
|
||||
(0x16f4f, 0x16f4f),
|
||||
(0x16f8f, 0x16f92),
|
||||
(0x16fe4, 0x16fe4),
|
||||
(0x1bc9d, 0x1bc9e),
|
||||
(0x1bca0, 0x1bca3),
|
||||
(0x1cf00, 0x1cf2d),
|
||||
(0x1cf30, 0x1cf46),
|
||||
(0x1d167, 0x1d169),
|
||||
(0x1d173, 0x1d17a),
|
||||
(0x1d17b, 0x1d182),
|
||||
(0x1d185, 0x1d18b),
|
||||
(0x1d1aa, 0x1d1ad),
|
||||
(0x1d242, 0x1d244),
|
||||
(0x1da00, 0x1da36),
|
||||
(0x1da3b, 0x1da6c),
|
||||
(0x1da75, 0x1da75),
|
||||
(0x1da84, 0x1da84),
|
||||
(0x1da9b, 0x1da9f),
|
||||
(0x1daa1, 0x1daaf),
|
||||
(0x1e000, 0x1e006),
|
||||
(0x1e008, 0x1e018),
|
||||
(0x1e01b, 0x1e021),
|
||||
(0x1e023, 0x1e024),
|
||||
(0x1e026, 0x1e02a),
|
||||
(0x1e08f, 0x1e08f),
|
||||
(0x1e130, 0x1e136),
|
||||
(0x1e2ae, 0x1e2ae),
|
||||
(0x1e2ec, 0x1e2ef),
|
||||
(0x1e4ec, 0x1e4ef),
|
||||
(0x1e8d0, 0x1e8d6),
|
||||
(0x1e944, 0x1e94a),
|
||||
(0xe0001, 0xe0001),
|
||||
(0xe0020, 0xe007f),
|
||||
(0xe0100, 0xe01ef),
|
||||
];
|
||||
|
||||
/// 码点是否属 Mn/Cf(范围表二分判定,起点升序)。
|
||||
fn is_mn_cf(cp: u32) -> bool {
|
||||
let idx = MN_CF_RANGES.partition_point(|&(lo, _)| lo <= cp);
|
||||
idx > 0 && cp <= MN_CF_RANGES[idx - 1].1
|
||||
}
|
||||
|
||||
// ── Final Sigma 条件映射 ──────────────────────────────────────────────────
|
||||
|
||||
/// Final Sigma 条件小写映射:逐码点小写,其中 U+03A3(Σ)按条件规则取 σ/ς。
|
||||
///
|
||||
/// σ/ς 判定按原始单元序列(非变换后):后向(索引升方向)跳过 Mn/Cf 单元
|
||||
/// 存在可见字符 → σ;后向无且前向(索引降方向)跳过 Mn/Cf 存在可见字符 → ς;
|
||||
/// 前后均无可见字符 → σ。孤立 surrogate 原样透传(与 toLowerCase 同口径)。
|
||||
fn final_sigma_lower(units: &[u16]) -> Vec<u16> {
|
||||
let mut out = Vec::with_capacity(units.len());
|
||||
let mut i = 0;
|
||||
while i < units.len() {
|
||||
let u = units[i];
|
||||
if u == 0x03A3 {
|
||||
// 双向跳过 Mn/Cf 找最近可见字符(含行尾前的任意非 Mn/Cf 字符)。
|
||||
let mut j = i + 1;
|
||||
while j < units.len() && is_mn_cf(units[j] as u32) {
|
||||
j += 1;
|
||||
}
|
||||
let after_visible = j < units.len();
|
||||
let mut j = i;
|
||||
while j > 0 && is_mn_cf(units[j - 1] as u32) {
|
||||
j -= 1;
|
||||
}
|
||||
let before_visible = j > 0;
|
||||
out.push(if after_visible || !before_visible { 0x03C3 } else { 0x03C2 });
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if (0xD800..=0xDBFF).contains(&u) && i + 1 < units.len() && (0xDC00..=0xDFFF).contains(&units[i + 1]) {
|
||||
// 代理对整体解码为码点小写后回写 UTF-16。
|
||||
let cp = (((u as u32) - 0xD800) << 10 | (units[i + 1] as u32 - 0xDC00)) + 0x10000;
|
||||
let c = char::from_u32(cp).expect("合法代理对必解码为合法码点");
|
||||
out.extend(c.to_lowercase().collect::<String>().encode_utf16());
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
if (0xD800..=0xDFFF).contains(&u) {
|
||||
out.push(u);
|
||||
} else if let Some(c) = char::from_u32(u as u32) {
|
||||
out.extend(c.to_lowercase().collect::<String>().encode_utf16());
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
// ── 三方法 ───────────────────────────────────────────────────────────────
|
||||
|
||||
/// `String.prototype.toLocaleLowerCase`:locale 无关小写(与 toLowerCase 同表,
|
||||
/// 增量 Final Sigma 条件映射)。
|
||||
pub fn string_to_locale_lower_case<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_debug!("String.prototype.toLocaleLowerCase called with {} args", args.len());
|
||||
let s = try_string!(this_units(vm, args)).into_owned();
|
||||
let out = final_sigma_lower(&s);
|
||||
NativeResult::Ok(vm.new_string_units_owned(out))
|
||||
}
|
||||
|
||||
/// `String.prototype.toLocaleUpperCase`:locale 无关大写(与 toUpperCase 同表,
|
||||
/// 良形段映射,孤立 surrogate 单元原样保留)。
|
||||
pub fn string_to_locale_upper_case<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_debug!("String.prototype.toLocaleUpperCase called with {} args", args.len());
|
||||
let s = try_string!(this_units(vm, args));
|
||||
let out = map_well_formed_segments(&s, |seg| seg.to_uppercase());
|
||||
NativeResult::Ok(vm.new_string_units_owned(out))
|
||||
}
|
||||
|
||||
/// `String.prototype.localeCompare(compareString)`:基本档比较——两侧 NFC 归一后
|
||||
/// 按 UTF-16 单元序(良形文本下与码元序等价)返回 -1/0/1。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 第二参缺省或 undefined 落 "undefined" 串(规范基本档允许);其余非字符串经
|
||||
/// 完整 ToString,Symbol 抛 TypeError;this 的 null/undefined/symbol 经
|
||||
/// this_units 抛 TypeError。
|
||||
///
|
||||
/// # 注意事项
|
||||
/// - 非 NFC 等价但 UCA 可分的串按单元序判(如 "ö" vs "o"),属基本档固有边界。
|
||||
pub fn string_locale_compare<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
builtins_debug!("String.prototype.localeCompare called with {} args", args.len());
|
||||
use unicode_normalization::UnicodeNormalization;
|
||||
let a = try_string!(this_units(vm, args)).into_owned();
|
||||
let b = if args.len() > 1 && !vm.reg(args[1]).is_undefined() {
|
||||
try_string!(as_units(vm, vm.reg(args[1]))).into_owned()
|
||||
} else {
|
||||
"undefined".encode_utf16().collect()
|
||||
};
|
||||
// NFC 归一(良形段映射,孤立 surrogate 透传,与 normalize 同路径)。
|
||||
let a = map_well_formed_segments(&a, |seg| seg.nfc().collect());
|
||||
let b = map_well_formed_segments(&b, |seg| seg.nfc().collect());
|
||||
NativeResult::Ok(JsValue::int(match a.cmp(&b) {
|
||||
Ordering::Less => -1,
|
||||
Ordering::Equal => 0,
|
||||
Ordering::Greater => 1,
|
||||
}))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn mn_cf_table_corpus_points() {
|
||||
// 语料钉死码点:Cf/Mn 在表、Lu 不属表。
|
||||
assert!(is_mn_cf(0x00AD));
|
||||
assert!(is_mn_cf(0x0345));
|
||||
assert!(is_mn_cf(0x180E));
|
||||
assert!(is_mn_cf(0x1D242));
|
||||
assert!(!is_mn_cf(0x1D4A2));
|
||||
assert!(!is_mn_cf(0x0041));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn final_sigma_conditional_mapping() {
|
||||
// (输入单元, 期望输出):单枚/前后可见/行首行尾 Mn/Cf 环绕。
|
||||
let cases: &[(&[u16], &[u16])] = &[
|
||||
(&[0x03A3], &[0x03C3]),
|
||||
(&[0x41, 0x03A3], &[0x61, 0x03C2]),
|
||||
(&[0xD835, 0xDCA2, 0x03A3], &[0xD835, 0xDCA2, 0x03C2]),
|
||||
(&[0x0345, 0x03A3], &[0x0345, 0x03C3]),
|
||||
(&[0x41, 0x03A3, 0x2E], &[0x61, 0x03C3, 0x2E]),
|
||||
(&[0x41, 0x03A3, 0x0345], &[0x61, 0x03C2, 0x0345]),
|
||||
(&[0x41, 0x03A3, 0x180E], &[0x61, 0x03C2, 0x180E]),
|
||||
(&[0x41, 0x03A3, 0x180E, 0x42], &[0x61, 0x03C3, 0x180E, 0x62]),
|
||||
];
|
||||
for (input, want) in cases {
|
||||
assert_eq!(&final_sigma_lower(input), want);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
//! String 内置对象实现(constructor 与全部 String.prototype 方法)。
|
||||
//!
|
||||
//! 按职责分 `common` 文本通道、`basic` 基础方法族、`regex` 正则与匹配族,
|
||||
//! 三族经本模块 glob 重导出,供绑定层(`oxide_builtins::string::`)与 `regexp` 模块(`crate::string::`)消费。
|
||||
//! 按职责分 `common` 文本通道、`basic` 基础方法族、`regex` 正则与匹配族、
|
||||
//! `locale` locale 三方法族,各族经本模块 glob 重导出,供绑定层
|
||||
//! (`oxide_builtins::string::`)与 `regexp` 模块(`crate::string::`)消费。
|
||||
|
||||
mod basic;
|
||||
mod common;
|
||||
mod locale;
|
||||
mod regex;
|
||||
|
||||
pub use basic::*;
|
||||
pub(crate) use common::*;
|
||||
pub use locale::*;
|
||||
pub use regex::*;
|
||||
|
||||
@@ -247,6 +247,9 @@ impl BuiltinWorld {
|
||||
("substring", methods.substring, 2),
|
||||
("toUpperCase", methods.to_upper_case, 0),
|
||||
("toLowerCase", methods.to_lower_case, 0),
|
||||
("toLocaleUpperCase", methods.to_locale_upper_case, 0),
|
||||
("toLocaleLowerCase", methods.to_locale_lower_case, 0),
|
||||
("localeCompare", methods.locale_compare, 1),
|
||||
("trim", methods.trim, 0),
|
||||
("repeat", methods.repeat, 1),
|
||||
("padStart", methods.pad_start, 1),
|
||||
|
||||
@@ -122,6 +122,9 @@ pub struct StringMethods {
|
||||
pub is_well_formed: *const (),
|
||||
pub to_well_formed: *const (),
|
||||
pub from_raw: *const (),
|
||||
pub to_locale_lower_case: *const (),
|
||||
pub to_locale_upper_case: *const (),
|
||||
pub locale_compare: *const (),
|
||||
}
|
||||
|
||||
/// RegExp 原型方法的 native 函数指针集合。
|
||||
|
||||
@@ -19,6 +19,9 @@ pub fn bind_string(core: &Arc<KernelCore>, session: &KernelSession, global: &mut
|
||||
substring: oxide_builtins::string::string_substring::<crate::vm::Vm> as *const (),
|
||||
to_upper_case: oxide_builtins::string::string_to_upper_case::<crate::vm::Vm> as *const (),
|
||||
to_lower_case: oxide_builtins::string::string_to_lower_case::<crate::vm::Vm> as *const (),
|
||||
to_locale_upper_case: oxide_builtins::string::string_to_locale_upper_case::<crate::vm::Vm> as *const (),
|
||||
to_locale_lower_case: oxide_builtins::string::string_to_locale_lower_case::<crate::vm::Vm> as *const (),
|
||||
locale_compare: oxide_builtins::string::string_locale_compare::<crate::vm::Vm> as *const (),
|
||||
trim: oxide_builtins::string::string_trim::<crate::vm::Vm> as *const (),
|
||||
repeat: oxide_builtins::string::string_repeat::<crate::vm::Vm> as *const (),
|
||||
pad_start: oxide_builtins::string::string_pad_start::<crate::vm::Vm> as *const (),
|
||||
|
||||
@@ -1404,3 +1404,111 @@ fn unicode_brace_escape_supplementary_and_lone_surrogate() {
|
||||
.unwrap();
|
||||
assert!(r.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_to_locale_lower_case_final_sigma() {
|
||||
// Final Sigma 条件映射 21 断言面(special_casing_conditional 15 + U180E 6):
|
||||
// σ/ς 按原始序列双向跳过 Mn/Cf 找最近可见字符判定。
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(\"\\u03A3\".toLocaleLowerCase() === \"\\u03C3\" && \
|
||||
\"A\\u03A3\".toLocaleLowerCase() === \"a\\u03C2\" && \
|
||||
\"\\uD835\\uDCA2\\u03A3\".toLocaleLowerCase() === \"\\uD835\\uDCA2\\u03C2\" && \
|
||||
\"A.\\u03A3\".toLocaleLowerCase() === \"a.\\u03C2\" && \
|
||||
\"A\\u00AD\\u03A3\".toLocaleLowerCase() === \"a\\u00AD\\u03C2\" && \
|
||||
\"A\\uD834\\uDE42\\u03A3\".toLocaleLowerCase() === \"a\\uD834\\uDE42\\u03C2\" && \
|
||||
\"\\u0345\\u03A3\".toLocaleLowerCase() === \"\\u0345\\u03C3\" && \
|
||||
\"\\u0391\\u0345\\u03A3\".toLocaleLowerCase() === \"\\u03B1\\u0345\\u03C2\" && \
|
||||
\"A\\u03A3B\".toLocaleLowerCase() === \"a\\u03C3b\" && \
|
||||
\"A\\u03A3\\uD835\\uDCA2\".toLocaleLowerCase() === \"a\\u03C3\\uD835\\uDCA2\" && \
|
||||
\"A\\u03A3.b\".toLocaleLowerCase() === \"a\\u03C3.b\" && \
|
||||
\"A\\u03A3\\u00ADB\".toLocaleLowerCase() === \"a\\u03C3\\u00ADb\" && \
|
||||
\"A\\u03A3\\uD834\\uDE42B\".toLocaleLowerCase() === \"a\\u03C3\\uD834\\uDE42b\" && \
|
||||
\"A\\u03A3\\u0345\".toLocaleLowerCase() === \"a\\u03C2\\u0345\" && \
|
||||
\"A\\u03A3\\u0345\\u0391\".toLocaleLowerCase() === \"a\\u03C3\\u0345\\u03B1\" && \
|
||||
\"A\\u180E\\u03A3\".toLocaleLowerCase() === \"a\\u180E\\u03C2\" && \
|
||||
\"A\\u180E\\u03A3B\".toLocaleLowerCase() === \"a\\u180E\\u03C3b\" && \
|
||||
\"A\\u03A3\\u180E\".toLocaleLowerCase() === \"a\\u03C2\\u180E\" && \
|
||||
\"A\\u03A3\\u180EB\".toLocaleLowerCase() === \"a\\u03C3\\u180Eb\" && \
|
||||
\"A\\u180E\\u03A3\\u180E\".toLocaleLowerCase() === \"a\\u180E\\u03C2\\u180E\" && \
|
||||
\"A\\u180E\\u03A3\\u180EB\".toLocaleLowerCase() === \"a\\u180E\\u03C3\\u180Eb\")",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(r.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_to_locale_case_basic_mapping() {
|
||||
// 与 toLowerCase/toUpperCase 同表:ß 小写不变、大写 SS;Σ 大写恒 Σ;
|
||||
// 补充平面代理对整体映射。
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"\"ß\".toLocaleLowerCase() === \"ß\" && \
|
||||
\"ß\".toLocaleUpperCase() === \"SS\" && \
|
||||
\"\\u03A3\".toLocaleUpperCase() === \"\\u03A3\" && \
|
||||
\"\\u03C3\".toLocaleLowerCase() === \"\\u03C3\" && \
|
||||
\"\\u03C3\".toLocaleUpperCase() === \"\\u03A3\" && \
|
||||
\"aB\".toLocaleUpperCase() === \"AB\" && \
|
||||
\"aB\".toLocaleLowerCase() === \"ab\" && \
|
||||
\"\\uD83D\\uDE00\".toLocaleLowerCase() === \"\\uD83D\\uDE00\"",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(r.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_locale_compare_canonical_and_order() {
|
||||
// NFC 规范等价对全 0(含组合符重排与 Hangul 合成对);缺省/undefined/
|
||||
// "undefined" 三式等价;码元序方向与反对称。
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(\"o\\u0308\".localeCompare(\"ö\") === 0 && \
|
||||
\"Ç\".localeCompare(\"C\\u0327\") === 0 && \
|
||||
\"가\".localeCompare(\"\\u1100\\u1161\") === 0 && \
|
||||
\"ô\".localeCompare(\"o\\u0302\") === 0 && \
|
||||
\"ṩ\".localeCompare(\"s\\u0323\\u0307\") === 0 && \
|
||||
\"a\".localeCompare() === \"a\".localeCompare(undefined) && \
|
||||
\"a\".localeCompare() === \"a\".localeCompare(\"undefined\") && \
|
||||
\"a\".localeCompare(\"b\") === -1 && \"b\".localeCompare(\"a\") === 1 && \
|
||||
\"h\".localeCompare(\"H\") === -\"H\".localeCompare(\"h\") && \
|
||||
\"a\".localeCompare(1) === \"a\".localeCompare(\"1\"))",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(r.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_locale_methods_type_errors() {
|
||||
// null/undefined/symbol 接收者抛 TypeError;localeCompare 第二参
|
||||
// Symbol 抛 TypeError。
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"((function(){ try { (null).toLocaleLowerCase(); return false; } catch (e) { return e.name === \"TypeError\"; } })() && \
|
||||
(function(){ try { (undefined).toLocaleUpperCase(); return false; } catch (e) { return e.name === \"TypeError\"; } })() && \
|
||||
(function(){ try { (null).localeCompare(\"a\"); return false; } catch (e) { return e.name === \"TypeError\"; } })() && \
|
||||
(function(){ try { \"a\".localeCompare(Symbol(\"s\")); return false; } catch (e) { return e.name === \"TypeError\"; } })())",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(r.as_bool());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_locale_methods_length_and_name() {
|
||||
// 绑定描述符面:length 0/0/1、name 与方法名一致。
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"String.prototype.toLocaleLowerCase.length === 0 && \
|
||||
String.prototype.toLocaleUpperCase.length === 0 && \
|
||||
String.prototype.localeCompare.length === 1 && \
|
||||
String.prototype.toLocaleLowerCase.name === \"toLocaleLowerCase\" && \
|
||||
String.prototype.toLocaleUpperCase.name === \"toLocaleUpperCase\" && \
|
||||
String.prototype.localeCompare.name === \"localeCompare\"",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(r.as_bool());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user