Files
OxideJS-New/crates/oxide_builtins/src/string/locale.rs
T

529 lines
15 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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;
if let Some(c) = char::from_u32(cp) {
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);
}
}
}