mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-25 03:01:12 +08:00
feat: 新增 Intl 特性域骨架实现
This commit is contained in:
@@ -0,0 +1,408 @@
|
||||
//! CLDR 36.1 语言标签规范化与 UTS35 结构校验(纯函数,不依赖 VmHost)。
|
||||
//!
|
||||
//! 映射表与 test262 harness 的 CLDR 36.1 表逐位一致(见 `tables` 模块);
|
||||
//! 结构校验遵循 UTS35(`is_structurally_valid`),不接受 BCP47 宽松形式。
|
||||
//! 规范化结果等于输入当且仅当输入是规范化的合法标签(`canonicalize_locale`
|
||||
//! 的不动点即合法性判据)。
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
use super::tables::*;
|
||||
|
||||
/// UTS35 languageTag 正则(大小写不敏感)。
|
||||
///
|
||||
/// 与 harness 的 `languageTagRE` 逐字一致:language 后接可选 script / region /
|
||||
/// variants,再重复可选 extensions(unicode / transformed / other),最后可选
|
||||
/// privateuse。
|
||||
const LANGUAGE_TAG_PATTERN: &str = r"^(?i:(([a-z]{2,3}|[a-z]{5,8})(-([a-z]{4}))?(-([a-z]{2}|[0-9]{3}))?(-([a-z0-9]{5,8}|(?:[0-9][a-z0-9]{3})))*(-((u((-([a-z0-9][a-z](-[a-z0-9]{3,8})*))+|((-([a-z0-9]{3,8}))+(-([a-z0-9][a-z](-[a-z0-9]{3,8})*))*)))|(t((-(([a-z]{2,3}|[a-z]{5,8})(-([a-z]{4}))?(-([a-z]{2}|[0-9]{3}))?(-([a-z0-9]{5,8}|(?:[0-9][a-z0-9]{3})))*)(-([a-z][0-9](-[a-z0-9]{3,8})+))*)|(-([a-z][0-9](-[a-z0-9]{3,8})+))+))|(([0-9]|[a-sv-wy-z])(-[a-z0-9]{2,8})+)))*(-(x(-[a-z0-9]{1,8})+))?))$";
|
||||
|
||||
fn language_tag_re() -> &'static Regex {
|
||||
static RE: OnceLock<Regex> = OnceLock::new();
|
||||
RE.get_or_init(|| Regex::new(LANGUAGE_TAG_PATTERN).expect("语言标签正则须可编译"))
|
||||
}
|
||||
|
||||
/// 判断子标签是否为单例(singleton):1 字符,数字或 `[a-wy-z]`(大小写不敏感)。
|
||||
fn is_singleton(subtag: &str) -> bool {
|
||||
let bytes = subtag.as_bytes();
|
||||
bytes.len() == 1 && matches!(bytes[0], b'0'..=b'9' | b'a'..=b'w' | b'y'..=b'z')
|
||||
}
|
||||
|
||||
/// 判断子标签是否为 2-8 位字母数字,供重复变体检测的中间段判定。
|
||||
fn is_2_8_alnum(subtag: &str) -> bool {
|
||||
(2..=8).contains(&subtag.len()) && subtag.bytes().all(|b| b.is_ascii_alphanumeric())
|
||||
}
|
||||
|
||||
/// 判断子标签是否为变体(variant):5-8 位字母数字,或数字开头共 4 位。
|
||||
fn is_variant(subtag: &str) -> bool {
|
||||
let bytes = subtag.as_bytes();
|
||||
let is_alnum = |b: u8| b.is_ascii_alphanumeric();
|
||||
(bytes.len() == 4 && bytes[0].is_ascii_digit() && bytes.iter().all(|&b| is_alnum(b)))
|
||||
|| (bytes.len() >= 5 && bytes.len() <= 8 && bytes.iter().all(|&b| is_alnum(b)))
|
||||
}
|
||||
|
||||
/// 判断子标签是否为 transformed extension 的 key(`^[a-z][0-9]$`,大小写不敏感)。
|
||||
fn is_transform_key(subtag: &str) -> bool {
|
||||
let bytes = subtag.as_bytes();
|
||||
bytes.len() == 2 && bytes[0].is_ascii_lowercase() && bytes[1].is_ascii_digit()
|
||||
}
|
||||
|
||||
/// 判断是否存在重复单例子标签(大小写不敏感比较,与 harness 的 `i` 标志一致)。
|
||||
///
|
||||
/// 条件:存在 i < j,两个子标签都是单例且取值相同。
|
||||
fn has_duplicate_singleton(subtags: &[&str]) -> bool {
|
||||
let n = subtags.len();
|
||||
for i in 0..n {
|
||||
if !is_singleton(subtags[i]) {
|
||||
continue;
|
||||
}
|
||||
for j in (i + 1)..n {
|
||||
if subtags[i].eq_ignore_ascii_case(subtags[j]) && is_singleton(subtags[j]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// 判断是否存在重复变体子标签(大小写不敏感比较)。
|
||||
///
|
||||
/// 条件:存在 i < j,`subtags[j]` 是变体且与 `subtags[i]` 相同,`subtags[i-1]`
|
||||
/// 存在且为 2-8 位字母数字,i 与 j 之间的所有子标签均为 2-8 位字母数字。
|
||||
fn has_duplicate_variant(subtags: &[&str]) -> bool {
|
||||
let n = subtags.len();
|
||||
for j in 0..n {
|
||||
if !is_variant(subtags[j]) {
|
||||
continue;
|
||||
}
|
||||
for i in 0..j {
|
||||
if !subtags[i].eq_ignore_ascii_case(subtags[j]) {
|
||||
continue;
|
||||
}
|
||||
if i == 0 || !is_2_8_alnum(subtags[i - 1]) {
|
||||
continue;
|
||||
}
|
||||
if (i + 1..j).all(|k| is_2_8_alnum(subtags[k])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// UTS35 结构校验:languageTag 正则通过,且截断私有域后无重复单例与重复变体。
|
||||
pub fn is_structurally_valid(tag: &str) -> bool {
|
||||
if !language_tag_re().is_match(tag) {
|
||||
return false;
|
||||
}
|
||||
let truncated = tag.split("-x-").next().unwrap_or("");
|
||||
let subtags: Vec<&str> = truncated.split('-').collect();
|
||||
!has_duplicate_singleton(&subtags) && !has_duplicate_variant(&subtags)
|
||||
}
|
||||
|
||||
/// CLDR 36.1 语言标签规范化(harness `canonicalizeLanguageTag` 的逐位移植)。
|
||||
///
|
||||
/// 步骤:整标映射 → 标准段(language / script / region)解析与映射 → variants
|
||||
/// 映射与排序 → extensions 规范化(unicode 键排序、transformed 递归规范化)→
|
||||
/// privateuse 透传 → 重组。
|
||||
pub fn canonicalize_locale(locale: &str) -> String {
|
||||
let locale = locale.to_ascii_lowercase();
|
||||
|
||||
// 整标映射(grandfathered 标签)。
|
||||
if let Some((_, mapped)) = TAG_MAPPINGS.iter().find(|(from, _)| *from == locale) {
|
||||
return mapped.to_string();
|
||||
}
|
||||
|
||||
let subtags: Vec<&str> = locale.split('-').collect();
|
||||
let mut i = 0;
|
||||
|
||||
// 标准段:第一个 variant 或 singleton 之前的全部子标签。
|
||||
let mut language = String::new();
|
||||
let mut script: Option<String> = None;
|
||||
let mut region: Option<String> = None;
|
||||
while i < subtags.len() {
|
||||
let subtag = subtags[i];
|
||||
if i == 0 {
|
||||
language = subtag.to_string();
|
||||
} else if subtag.len() == 2 || subtag.len() == 3 {
|
||||
region = Some(subtag.to_ascii_uppercase());
|
||||
} else if subtag.len() == 4 && !subtag.starts_with(|c: char| c.is_ascii_digit()) {
|
||||
let mut chars = subtag.chars();
|
||||
let first = chars.next().map(|c| c.to_ascii_uppercase().to_string()).unwrap_or_default();
|
||||
let rest = chars.collect::<String>().to_ascii_lowercase();
|
||||
script = Some(format!("{first}{rest}"));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// language 映射:简单表优先,未命中查复合表(复合表可补 script / region)。
|
||||
if let Some((_, mapped)) = LANGUAGE_MAPPINGS.iter().find(|(from, _)| *from == language) {
|
||||
language = mapped.to_string();
|
||||
} else if let Some((_, mapped_lang, mapped_script, mapped_region)) =
|
||||
COMPLEX_LANGUAGE_MAPPINGS.iter().find(|(from, _, _, _)| *from == language)
|
||||
{
|
||||
language = mapped_lang.to_string();
|
||||
if script.is_none() {
|
||||
script = mapped_script.map(|s| s.to_string());
|
||||
}
|
||||
if region.is_none() {
|
||||
region = mapped_region.map(|s| s.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// region 映射:简单表优先,未命中查复合表(按 language[-script] 键选择)。
|
||||
if let Some(region_raw) = region {
|
||||
let mapped = if let Some((_, mapped)) = REGION_MAPPINGS.iter().find(|(from, _)| *from == region_raw) {
|
||||
mapped.to_string()
|
||||
} else if let Some((_, default, entries)) =
|
||||
COMPLEX_REGION_MAPPINGS.iter().find(|(from, _, _)| *from == region_raw)
|
||||
{
|
||||
let mut key = language.clone();
|
||||
if let Some(script) = &script {
|
||||
key.push('-');
|
||||
key.push_str(script);
|
||||
}
|
||||
entries
|
||||
.iter()
|
||||
.find(|(k, _)| *k == key)
|
||||
.map(|(_, v)| v.to_string())
|
||||
.unwrap_or_else(|| default.to_string())
|
||||
} else {
|
||||
region_raw
|
||||
};
|
||||
region = Some(mapped);
|
||||
}
|
||||
|
||||
// variants:映射后按字典序排序。
|
||||
let mut variants: Vec<String> = Vec::new();
|
||||
while i < subtags.len() && subtags[i].len() > 1 {
|
||||
let variant = subtags[i];
|
||||
match VARIANT_MAPPINGS.iter().find(|(from, _, _)| *from == variant) {
|
||||
Some((_, "language", replacement)) => language = replacement.to_string(),
|
||||
Some((_, "region", replacement)) => region = Some(replacement.to_string()),
|
||||
Some((_, "variant", replacement)) => variants.push(replacement.to_string()),
|
||||
Some((_, _, _)) => unreachable!("变体映射类型非法"),
|
||||
None => variants.push(variant.to_string()),
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
variants.sort();
|
||||
|
||||
// extensions:unicode 键按 ASCII 排序、transformed 递归规范化、其余透传。
|
||||
let mut extensions: Vec<String> = Vec::new();
|
||||
while i < subtags.len() && subtags[i] != "x" {
|
||||
let extension_start = i;
|
||||
i += 1;
|
||||
while i < subtags.len() && subtags[i].len() > 1 {
|
||||
i += 1;
|
||||
}
|
||||
|
||||
let extension_key = subtags[extension_start];
|
||||
if extension_key == "u" {
|
||||
let mut j = extension_start + 1;
|
||||
while j < i && subtags[j].len() > 2 {
|
||||
j += 1;
|
||||
}
|
||||
let mut extension = subtags[extension_start..j].join("-");
|
||||
while j < i {
|
||||
let key_start = j;
|
||||
j += 1;
|
||||
while j < i && subtags[j].len() > 2 {
|
||||
j += 1;
|
||||
}
|
||||
let key = subtags[key_start];
|
||||
let value = subtags[key_start + 1..j].join("-");
|
||||
let value = if let Some((_, entries)) = UNICODE_MAPPINGS.iter().find(|(k, _)| *k == key) {
|
||||
entries
|
||||
.iter()
|
||||
.find(|(k, _)| *k == value)
|
||||
.map(|(_, v)| v.to_string())
|
||||
.unwrap_or(value)
|
||||
} else {
|
||||
value
|
||||
};
|
||||
extension.push('-');
|
||||
extension.push_str(key);
|
||||
if !value.is_empty() && value != "true" {
|
||||
extension.push('-');
|
||||
extension.push_str(&value);
|
||||
}
|
||||
}
|
||||
extensions.push(extension);
|
||||
} else if extension_key == "t" {
|
||||
let mut j = extension_start + 1;
|
||||
while j < i && !is_transform_key(subtags[j]) {
|
||||
j += 1;
|
||||
}
|
||||
let mut extension = String::from("t");
|
||||
let transform_language = subtags[extension_start + 1..j].join("-");
|
||||
if !transform_language.is_empty() {
|
||||
extension.push('-');
|
||||
extension.push_str(&canonicalize_locale(&transform_language).to_ascii_lowercase());
|
||||
}
|
||||
// tfield 按字母序排列(UTS35 §3.3.1 规范化要求;true 值不删除)。
|
||||
let mut fields: Vec<(String, String)> = Vec::new();
|
||||
while j < i {
|
||||
let key_start = j;
|
||||
j += 1;
|
||||
while j < i && subtags[j].len() > 2 {
|
||||
j += 1;
|
||||
}
|
||||
let key = subtags[key_start].to_string();
|
||||
let value = subtags[key_start + 1..j].join("-");
|
||||
let value = if let Some((_, entries)) = TRANSFORM_MAPPINGS.iter().find(|(k, _)| *k == key) {
|
||||
entries
|
||||
.iter()
|
||||
.find(|(k, _)| *k == value)
|
||||
.map(|(_, v)| v.to_string())
|
||||
.unwrap_or(value)
|
||||
} else {
|
||||
value
|
||||
};
|
||||
fields.push((key, value));
|
||||
}
|
||||
fields.sort();
|
||||
for (key, value) in fields {
|
||||
extension.push('-');
|
||||
extension.push_str(&key);
|
||||
extension.push('-');
|
||||
extension.push_str(&value);
|
||||
}
|
||||
extensions.push(extension);
|
||||
} else {
|
||||
extensions.push(subtags[extension_start..i].join("-"));
|
||||
}
|
||||
}
|
||||
extensions.sort();
|
||||
|
||||
// privateuse 透传。
|
||||
let private_use = if i < subtags.len() { Some(subtags[i..].join("-")) } else { None };
|
||||
|
||||
// 重组。
|
||||
let mut canonical = language;
|
||||
if let Some(script) = script {
|
||||
canonical.push('-');
|
||||
canonical.push_str(&script);
|
||||
}
|
||||
if let Some(region) = region {
|
||||
canonical.push('-');
|
||||
canonical.push_str(®ion);
|
||||
}
|
||||
if !variants.is_empty() {
|
||||
canonical.push('-');
|
||||
canonical.push_str(&variants.join("-"));
|
||||
}
|
||||
if !extensions.is_empty() {
|
||||
canonical.push('-');
|
||||
canonical.push_str(&extensions.join("-"));
|
||||
}
|
||||
if let Some(private_use) = private_use {
|
||||
if !canonical.is_empty() {
|
||||
canonical.push('-');
|
||||
canonical.push_str(&private_use);
|
||||
} else {
|
||||
canonical = private_use;
|
||||
}
|
||||
}
|
||||
canonical
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn canonicalize_vectors() {
|
||||
// 与 node + harness canonicalizeLanguageTag 的实测向量对齐。
|
||||
assert_eq!(canonicalize_locale("cmn-hans-cn"), "zh-Hans-CN");
|
||||
assert_eq!(canonicalize_locale("sh"), "sr-Latn");
|
||||
assert_eq!(canonicalize_locale("no"), "nb");
|
||||
assert_eq!(canonicalize_locale("cnr"), "sr-ME");
|
||||
// 标准段循环中 2-3 位子标签逐个覆盖 region:`oed` 非变体(3 位),
|
||||
// 按 region 消费并覆盖 `GB`,与 harness 逐位一致。
|
||||
assert_eq!(canonicalize_locale("en-GB-oed"), "en-OED");
|
||||
assert_eq!(canonicalize_locale("heploc"), "heploc");
|
||||
assert_eq!(canonicalize_locale("aa-aaland"), "aa-AX");
|
||||
assert_eq!(canonicalize_locale("es-419"), "es-419");
|
||||
assert_eq!(canonicalize_locale("172"), "172");
|
||||
assert_eq!(canonicalize_locale("en-t-en-GB"), "en-t-en-gb");
|
||||
assert_eq!(canonicalize_locale("ab-cd"), "ab-CD");
|
||||
assert_eq!(canonicalize_locale("de-rt"), "de-RT");
|
||||
assert_eq!(canonicalize_locale("en-x-u-foo"), "en-x-u-foo");
|
||||
assert_eq!(canonicalize_locale("en-a-bar-x-u-foo"), "en-a-bar-x-u-foo");
|
||||
assert_eq!(canonicalize_locale("en-x-u-foo-a-bar"), "en-x-u-foo-a-bar");
|
||||
assert_eq!(canonicalize_locale("en-a-bar-u-baz-x-u-foo"), "en-a-bar-u-baz-x-u-foo");
|
||||
// transformed 扩展:tfield 按字母序排列、tvalue 映射、true 不删除。
|
||||
assert_eq!(canonicalize_locale("sl-t-sl-rozaj-biske-1994"), "sl-t-sl-1994-biske-rozaj");
|
||||
assert_eq!(canonicalize_locale("DE-T-M0-DIN-K0-QWERTZ"), "de-t-k0-qwertz-m0-din");
|
||||
assert_eq!(canonicalize_locale("en-t-m0-true"), "en-t-m0-true");
|
||||
assert_eq!(canonicalize_locale("en-t-iw"), "en-t-he");
|
||||
assert_eq!(canonicalize_locale("und-Latn-t-und-hani-m0-names"), "und-Latn-t-und-hani-m0-prprname");
|
||||
assert_eq!(canonicalize_locale("mo"), "ro");
|
||||
assert_eq!(canonicalize_locale("ces"), "cs");
|
||||
assert_eq!(canonicalize_locale("hy-arevela"), "hy");
|
||||
assert_eq!(canonicalize_locale("hy-arevmda"), "hyw");
|
||||
assert_eq!(canonicalize_locale("aar-x-private"), "aa-x-private");
|
||||
assert_eq!(canonicalize_locale("heb-x-private"), "he-x-private");
|
||||
// unicode 扩展键保持原序(harness 不排序键;test262 允许原序或排序)。
|
||||
assert_eq!(canonicalize_locale("it-u-nu-latn-ca-gregory"), "it-u-nu-latn-ca-gregory");
|
||||
assert_eq!(canonicalize_locale("th-th-u-nu-thai"), "th-TH-u-nu-thai");
|
||||
assert_eq!(canonicalize_locale("art-lojban"), "jbo");
|
||||
assert_eq!(canonicalize_locale("zh-guoyu"), "zh");
|
||||
assert_eq!(canonicalize_locale("zh-hakka"), "hak");
|
||||
assert_eq!(canonicalize_locale("zh-xiang"), "hsn");
|
||||
assert_eq!(canonicalize_locale("es-ES-preeuro"), "es-ES-preeuro");
|
||||
// `cyrillic` 是脚本名而非变体,映射表无此项,原样透传(8 位子标签
|
||||
// 按变体消费)。
|
||||
assert_eq!(canonicalize_locale("uz-UZ-cyrillic"), "uz-UZ-cyrillic");
|
||||
// harness 变体表无 `posix` 项(UTS35 变体映射不覆盖它),原样透传。
|
||||
assert_eq!(canonicalize_locale("posix"), "posix");
|
||||
// harness 变体表仅 5 项(aaland/arevela/arevmda/heploc/polytoni),
|
||||
// `direct` 无映射,原样透传。
|
||||
assert_eq!(canonicalize_locale("hi-direct"), "hi-direct");
|
||||
assert_eq!(canonicalize_locale("zh-pinyin"), "zh-pinyin");
|
||||
assert_eq!(canonicalize_locale("zh-stroke"), "zh-stroke");
|
||||
assert_eq!(canonicalize_locale("de-u-kf"), "de-u-kf");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn structural_validity() {
|
||||
// 结构合法:languageTag 正则通过且无重复单例/重复变体。
|
||||
assert!(is_structurally_valid("en"));
|
||||
assert!(is_structurally_valid("en-US"));
|
||||
assert!(is_structurally_valid("zh-Hans-CN"));
|
||||
assert!(is_structurally_valid("en-x-u-foo"));
|
||||
assert!(is_structurally_valid("en-t-en-gb"));
|
||||
assert!(is_structurally_valid("th-TH-u-nu-thai"));
|
||||
assert!(is_structurally_valid("aa-AX"));
|
||||
assert!(is_structurally_valid("en-POSIX"));
|
||||
assert!(is_structurally_valid("en-US-latin"));
|
||||
assert!(is_structurally_valid("en-GB-1234"));
|
||||
|
||||
// 结构合法但非规范化(规范化判据在 canonicalize_locale 不动点,
|
||||
// 本函数不查):与 harness 的 isCanonicalizedStructurallyValidLanguageTag
|
||||
// 不同,后者额外要求输入等于规范化结果。
|
||||
assert!(is_structurally_valid("cmn-hans-cn"));
|
||||
assert!(is_structurally_valid("no"));
|
||||
assert!(is_structurally_valid("sh"));
|
||||
|
||||
// `u`/`t` 是两个不同单例:无重复单例,结构合法(harness 侧判非法
|
||||
// 源于扩展排序的规范化判据,不属本函数)。
|
||||
assert!(is_structurally_valid("en-u-ca-gregory-t-en"));
|
||||
|
||||
// 结构非法:正则不匹配(OED/oed 3 位非变体、172 非语言、单字符、空串、
|
||||
// 区域在脚本前、重复子标签)或重复单例/重复变体。
|
||||
assert!(!is_structurally_valid("en-GB-OED"));
|
||||
assert!(!is_structurally_valid("en-GB-oed"));
|
||||
assert!(!is_structurally_valid("172"));
|
||||
assert!(!is_structurally_valid("it-IT-Latn"));
|
||||
assert!(!is_structurally_valid("en-u-ca-gregory-u-kf"));
|
||||
assert!(!is_structurally_valid("en-172-172"));
|
||||
assert!(!is_structurally_valid("a"));
|
||||
assert!(!is_structurally_valid("en-GB-xx"));
|
||||
assert!(!is_structurally_valid(""));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
//! Intl 特性域:命名空间对象、十组构造器/原型对、`getCanonicalLocales` 与
|
||||
//! Locale 构造器(骨架阶段)。
|
||||
//!
|
||||
//! 规范化映射表与 CLDR 36.1 逐位一致(test262 harness 同源),结构校验遵循
|
||||
//! UTS35(不接受 BCP47 宽松形式)。真实格式化语义(format / formatToParts /
|
||||
//! resolvedOptions / compare 等)由后续任务填充。
|
||||
|
||||
mod cldr;
|
||||
mod native;
|
||||
mod tables;
|
||||
|
||||
pub use native::*;
|
||||
@@ -0,0 +1,219 @@
|
||||
//! Intl 骨架阶段的 native 实现:`getCanonicalLocales`、十组构造器(含
|
||||
//! FallbackSymbol 语义)、`supportedLocalesOf`(尚无支持域过滤)与 Locale 构造器。
|
||||
//!
|
||||
//! 真实格式化语义(format / formatToParts / resolvedOptions / compare 等)
|
||||
//! 由后续任务填充;本模块只保证构造与元数据面符合规范。
|
||||
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_runtime_api::{
|
||||
to_length, to_number_full, to_object, to_string, to_string_full, NativeResult, ProtoKind, VmHost,
|
||||
};
|
||||
use oxide_types::object::{JsObject, PropAttributes};
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use super::cldr;
|
||||
use crate::temporal::native_try;
|
||||
|
||||
/// 把 `Result<_, String>` 错误文本转为异常值:优先取调用期间产生的未捕获
|
||||
/// 异常(getter 抛出等),无则按文本恢复 kind 创建错误对象。
|
||||
fn engine_error<H: VmHost>(vm: &mut H, text: &str) -> JsValue {
|
||||
vm.take_uncaught_value()
|
||||
.unwrap_or_else(|| crate::error::create_from_text(vm, text))
|
||||
}
|
||||
|
||||
/// 按元素列表构造普通数组(CreateArrayFromList 语义)。
|
||||
fn make_array_from_list<H: VmHost>(vm: &mut H, items: &[JsValue]) -> JsValue {
|
||||
let array_proto = vm.builtin_proto(ProtoKind::ArrayProto);
|
||||
let arr = vm.alloc_object(JsObject::new_array(
|
||||
EMPTY_SHAPE_ID,
|
||||
JsValue::from_js_object(array_proto),
|
||||
items.len().min(oxide_types::object::MAX_DENSE_PROPS),
|
||||
));
|
||||
for (i, item) in items.iter().enumerate() {
|
||||
// SAFETY: arr 是刚经统一入口分配的数组对象,元素区已就绪。
|
||||
unsafe {
|
||||
(*arr).set_prop_at(i, *item);
|
||||
}
|
||||
}
|
||||
// SAFETY: 与 new_array 同 epoch,写入后按实际元素数设 prop_count。
|
||||
unsafe {
|
||||
(*arr).set_prop_count(items.len());
|
||||
}
|
||||
JsValue::from_js_object(arr)
|
||||
}
|
||||
|
||||
/// 单个标签的校验与规范化:结构校验(RangeError)→ 规范化。
|
||||
fn canonicalize_one<H: VmHost>(vm: &mut H, s: &str) -> Result<String, JsValue> {
|
||||
if !cldr::is_structurally_valid(s) {
|
||||
return Err(crate::error::create_range_error(vm, "not a valid language tag"));
|
||||
}
|
||||
Ok(cldr::canonicalize_locale(s))
|
||||
}
|
||||
|
||||
/// CanonicalizeLocaleList(ECMA-402 9.2.1):字符串参数按单元素列表处理;
|
||||
/// 其余 ToObject → ToLength(length) → 逐元素 HasProperty/Get(尊重 getter)→
|
||||
/// 原始串去重 → 结构校验(RangeError)→ 规范化 → 规范化结果去重。
|
||||
/// 非字符串/对象元素抛 TypeError。
|
||||
fn canonicalize_locale_list<H: VmHost>(vm: &mut H, locales: JsValue) -> Result<Vec<String>, JsValue> {
|
||||
if locales.is_undefined() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
// 字符串参数整体是单个 locale(规范步骤 3:包装为单元素数组),
|
||||
// 不按字符序列解释。
|
||||
if locales.is_string() {
|
||||
let s = to_string(locales);
|
||||
return Ok(vec![canonicalize_one(vm, &s)?]);
|
||||
}
|
||||
let o_val = to_object(locales, vm).map_err(|e| engine_error(vm, &e))?;
|
||||
let o_ptr = o_val.as_js_object_ptr();
|
||||
if o_ptr.is_null() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let o = unsafe { &*o_ptr };
|
||||
let length_si = vm.perm_intern("length");
|
||||
let length_raw = vm.ordinary_get(o, length_si, o_val).map_err(|e| engine_error(vm, &e))?;
|
||||
// LengthOfArrayLike:ToNumber 全程强制(symbol 抛 TypeError),再 ToLength。
|
||||
let length_num = to_number_full(length_raw, vm).map_err(|e| engine_error(vm, &e))?;
|
||||
let len = to_length(JsValue::float(length_num)) as usize;
|
||||
|
||||
let mut raw: Vec<String> = Vec::new();
|
||||
for i in 0..len {
|
||||
let key = vm.new_string(&i.to_string());
|
||||
let key_si = vm.property_key_si(key);
|
||||
let present = vm.has_property(o, key_si).map_err(|e| engine_error(vm, &e))?;
|
||||
if !present {
|
||||
continue;
|
||||
}
|
||||
let next = vm.ordinary_get(o, key_si, o_val).map_err(|e| engine_error(vm, &e))?;
|
||||
// 元素须为字符串或对象(对象经 ToString 取标签,尊重自定义 toString);
|
||||
// 其余类型(number/boolean/null/undefined/symbol)抛 TypeError。
|
||||
let s = if next.is_string() {
|
||||
to_string(next)
|
||||
} else if next.is_object() {
|
||||
to_string_full(next, vm).map_err(|e| engine_error(vm, &e))?
|
||||
} else {
|
||||
return Err(crate::error::create_type_error(vm, "locales must be strings"));
|
||||
};
|
||||
if !raw.contains(&s) {
|
||||
raw.push(s);
|
||||
}
|
||||
}
|
||||
|
||||
let mut result: Vec<String> = Vec::new();
|
||||
for s in &raw {
|
||||
let canonical = canonicalize_one(vm, s)?;
|
||||
if !result.contains(&canonical) {
|
||||
result.push(canonical);
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// `Intl.getCanonicalLocales(locales)`:CanonicalizeLocaleList 后 CreateArrayFromList。
|
||||
pub fn get_canonical_locales<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let locales = if args.len() < 2 { JsValue::undefined() } else { vm.reg(args[1]) };
|
||||
let list = native_try!(canonicalize_locale_list(vm, locales));
|
||||
let items: Vec<JsValue> = list.iter().map(|s| vm.new_string_owned(s.clone())).collect();
|
||||
NativeResult::Ok(make_array_from_list(vm, &items))
|
||||
}
|
||||
|
||||
/// 十组构造器的共享骨架:`new` 时 this 已按 newTarget 原型创建(返回 undefined,
|
||||
/// VM 回传 this);非 legacy 构造器(PluralRules 及以后)NewTarget 为 undefined
|
||||
/// 时抛 TypeError;legacy 构造器(NumberFormat/DateTimeFormat/Collator)在既有
|
||||
/// 实例上非构造调用时定义 FallbackSymbol 并返回 this,其余路径新建实例返回。
|
||||
///
|
||||
/// FallbackSymbol 描述固定为 "IntlLegacyConstructedSymbol",属性值取本次调用
|
||||
/// 新建的实例(规范:[[Value]]: numberFormat),描述符
|
||||
/// {writable:false, enumerable:false, configurable:false}。
|
||||
fn intl_ctor_impl<H: VmHost>(vm: &mut H, args: &[u8], proto_kind: ProtoKind, legacy: bool) -> NativeResult {
|
||||
let proto_ptr = vm.builtin_proto(proto_kind);
|
||||
if vm.constructing_native() {
|
||||
return NativeResult::Ok(JsValue::undefined());
|
||||
}
|
||||
if !legacy {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "constructor requires new"));
|
||||
}
|
||||
let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] });
|
||||
if !this_val.is_object() {
|
||||
return make_intl_instance(vm, proto_ptr);
|
||||
}
|
||||
let this_ptr = this_val.as_js_object_ptr();
|
||||
if this_ptr.is_null() {
|
||||
return make_intl_instance(vm, proto_ptr);
|
||||
}
|
||||
// this 的原型链包含本构造器原型(OrdinaryHasInstance 语义):定义
|
||||
// FallbackSymbol 并返回 this。
|
||||
if crate::temporal::is_ctor_call(vm, args, proto_ptr) {
|
||||
let this_obj = unsafe { &mut *this_ptr };
|
||||
// 属性值取本次调用新建的实例(规范:[[Value]]: numberFormat)。
|
||||
let instance_val = match make_intl_instance(vm, proto_ptr) {
|
||||
NativeResult::Ok(v) => v,
|
||||
NativeResult::Err(e) => return NativeResult::Err(e),
|
||||
NativeResult::TailCall { .. } => unreachable!("make_intl_instance 不尾调用"),
|
||||
};
|
||||
let idx = vm.symbol_intern(Some("IntlLegacyConstructedSymbol".to_string()));
|
||||
let key = oxide_types::private_key::encode_symbol_key(vm.realm_id(), idx);
|
||||
let _ = vm.define_data_property(this_obj, key, instance_val, PropAttributes::new(false, false, false));
|
||||
return NativeResult::Ok(this_val);
|
||||
}
|
||||
make_intl_instance(vm, proto_ptr)
|
||||
}
|
||||
|
||||
/// 新建一个以指定原型为 `[[Prototype]]` 的空实例。
|
||||
fn make_intl_instance<H: VmHost>(vm: &mut H, proto_ptr: *mut JsObject) -> NativeResult {
|
||||
let proto = JsValue::from_js_object(proto_ptr);
|
||||
let obj = JsObject::new_empty(EMPTY_SHAPE_ID, proto);
|
||||
NativeResult::Ok(JsValue::from_js_object(vm.alloc_object(obj)))
|
||||
}
|
||||
|
||||
macro_rules! intl_ctor_legacy {
|
||||
($name:ident, $kind:ident) => {
|
||||
pub fn $name<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
intl_ctor_impl(vm, args, ProtoKind::$kind, true)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! intl_ctor {
|
||||
($name:ident, $kind:ident) => {
|
||||
pub fn $name<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
intl_ctor_impl(vm, args, ProtoKind::$kind, false)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// legacy 构造器:可非构造调用(FallbackSymbol 语义)。
|
||||
intl_ctor_legacy!(number_format_constructor, NumberFormatProto);
|
||||
intl_ctor_legacy!(date_time_format_constructor, DateTimeFormatProto);
|
||||
intl_ctor_legacy!(collator_constructor, CollatorProto);
|
||||
// 非 legacy 构造器:NewTarget 为 undefined 时抛 TypeError。
|
||||
intl_ctor!(plural_rules_constructor, PluralRulesProto);
|
||||
intl_ctor!(list_format_constructor, ListFormatProto);
|
||||
intl_ctor!(relative_time_format_constructor, RelativeTimeFormatProto);
|
||||
intl_ctor!(display_names_constructor, DisplayNamesProto);
|
||||
intl_ctor!(segmenter_constructor, SegmenterProto);
|
||||
intl_ctor!(locale_constructor, LocaleProto);
|
||||
intl_ctor!(duration_format_constructor, DurationFormatProto);
|
||||
|
||||
/// `supportedLocalesOf(locales)`:骨架阶段只执行 CanonicalizeLocaleList,
|
||||
/// 尚不做支持域过滤(过滤依赖 ICU 数据,后续任务填充)。
|
||||
pub fn supported_locales_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let locales = if args.len() < 2 { JsValue::undefined() } else { vm.reg(args[1]) };
|
||||
let list = native_try!(canonicalize_locale_list(vm, locales));
|
||||
let items: Vec<JsValue> = list.iter().map(|s| vm.new_string_owned(s.clone())).collect();
|
||||
NativeResult::Ok(make_array_from_list(vm, &items))
|
||||
}
|
||||
|
||||
/// `Intl.supportedValuesOf(key)`:骨架阶段只做键名校验(六类白名单,
|
||||
/// 其余 RangeError);合法键返回空数组(数据层随后续任务填充)。
|
||||
pub fn supported_values_of<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let key = if args.len() < 2 { JsValue::undefined() } else { vm.reg(args[1]) };
|
||||
let key_str = native_try!(to_string_full(key, vm).map_err(|e| engine_error(vm, &e)));
|
||||
if !matches!(
|
||||
key_str.as_str(),
|
||||
"calendar" | "collation" | "currency" | "numberingSystem" | "timeZone" | "unit"
|
||||
) {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "invalid key"));
|
||||
}
|
||||
NativeResult::Ok(make_array_from_list(vm, &[]))
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -43,6 +43,8 @@ pub mod event_target;
|
||||
pub mod function;
|
||||
/// 全局函数模块(URI 编解码与 Annex B 的 escape/unescape)。
|
||||
pub mod global;
|
||||
/// Intl 特性域(骨架阶段):命名空间、十组构造器/原型对、getCanonicalLocales。
|
||||
pub mod intl;
|
||||
/// 迭代器相关实现(Iterator 包装、for-of 底层迭代逻辑)。
|
||||
pub mod iterator;
|
||||
/// JSON 内置对象实现(parse/stringify)。
|
||||
|
||||
@@ -10,7 +10,9 @@ mod plain_year_month;
|
||||
mod time_zone;
|
||||
mod zoned_date_time;
|
||||
|
||||
pub(crate) use common::is_ctor_call;
|
||||
pub(crate) use common::make_instant;
|
||||
pub(crate) use common::native_try;
|
||||
use common::*;
|
||||
use difference::*;
|
||||
pub use duration::*;
|
||||
|
||||
Reference in New Issue
Block a user