From 04b991f5d835e28a8cbc7d9f4bd938ecc8a1fbd8 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 16 Oct 2026 05:08:58 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20Intl=20=E7=89=B9?= =?UTF-8?q?=E6=80=A7=E5=9F=9F=E9=AA=A8=E6=9E=B6=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 42 +- Cargo.toml | 2 +- crates/oxide_builtins/src/intl/cldr.rs | 408 ++++++ crates/oxide_builtins/src/intl/mod.rs | 12 + crates/oxide_builtins/src/intl/native.rs | 219 ++++ crates/oxide_builtins/src/intl/tables.rs | 1170 ++++++++++++++++++ crates/oxide_builtins/src/lib.rs | 2 + crates/oxide_builtins/src/temporal/mod.rs | 2 + crates/oxide_kernel/src/builtin/construct.rs | 78 +- crates/oxide_kernel/src/builtin/rebuild.rs | 113 ++ crates/oxide_kernel/src/builtin/world.rs | 82 +- crates/oxide_kernel/src/kernel/builtin_id.rs | 55 +- crates/oxide_kernel/src/kernel/session.rs | 21 + crates/oxide_runtime_api/src/host.rs | 22 + crates/oxide_vm/src/bindings/bind_intl.rs | 194 +++ crates/oxide_vm/src/bindings/mod.rs | 6 + crates/oxide_vm/src/vm/vmhost.rs | 20 + 17 files changed, 2417 insertions(+), 31 deletions(-) create mode 100644 crates/oxide_builtins/src/intl/cldr.rs create mode 100644 crates/oxide_builtins/src/intl/mod.rs create mode 100644 crates/oxide_builtins/src/intl/native.rs create mode 100644 crates/oxide_builtins/src/intl/tables.rs create mode 100644 crates/oxide_vm/src/bindings/bind_intl.rs diff --git a/Cargo.lock b/Cargo.lock index 4dbbae6c..8f15bcf0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.19.22" +version = "0.19.23" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.19.22" +version = "0.19.23" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.19.22" +version = "0.19.23" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.19.22" +version = "0.19.23" dependencies = [ "ansi_term", "bumpalo", @@ -1228,7 +1228,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.19.22" +version = "0.19.23" dependencies = [ "lru", "oxide_bytecode", @@ -1237,7 +1237,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.19.22" +version = "0.19.23" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1258,7 +1258,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.19.22" +version = "0.19.23" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1272,7 +1272,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.19.22" +version = "0.19.23" dependencies = [ "num-bigint", "oxc_allocator", @@ -1288,7 +1288,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.19.22" +version = "0.19.23" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1300,7 +1300,7 @@ dependencies = [ [[package]] name = "oxide_fold" -version = "0.19.22" +version = "0.19.23" dependencies = [ "num-bigint", "oxide_bytecode", @@ -1312,7 +1312,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.19.22" +version = "0.19.23" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.19.22" +version = "0.19.23" dependencies = [ "bumpalo", "dashmap", @@ -1335,7 +1335,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.19.22" +version = "0.19.23" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1347,7 +1347,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.19.22" +version = "0.19.23" dependencies = [ "tracing", "tracing-appender", @@ -1356,11 +1356,11 @@ dependencies = [ [[package]] name = "oxide_net" -version = "0.19.22" +version = "0.19.23" [[package]] name = "oxide_parser" -version = "0.19.22" +version = "0.19.23" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1375,7 +1375,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.19.22" +version = "0.19.23" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.19.22" +version = "0.19.23" dependencies = [ "num-bigint", "num-traits", @@ -1404,7 +1404,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.19.22" +version = "0.19.23" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1424,7 +1424,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.19.22" +version = "0.19.23" dependencies = [ "bumpalo", "dashmap", @@ -1437,7 +1437,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.19.22" +version = "0.19.23" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 5806041f..fa8831cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ members = [ ] [workspace.package] -version = "0.19.22" +version = "0.19.23" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_builtins/src/intl/cldr.rs b/crates/oxide_builtins/src/intl/cldr.rs new file mode 100644 index 00000000..b6da0181 --- /dev/null +++ b/crates/oxide_builtins/src/intl/cldr.rs @@ -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 = 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 = None; + let mut region: Option = 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::().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 = 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 = 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("")); + } +} diff --git a/crates/oxide_builtins/src/intl/mod.rs b/crates/oxide_builtins/src/intl/mod.rs new file mode 100644 index 00000000..f43a1f3f --- /dev/null +++ b/crates/oxide_builtins/src/intl/mod.rs @@ -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::*; diff --git a/crates/oxide_builtins/src/intl/native.rs b/crates/oxide_builtins/src/intl/native.rs new file mode 100644 index 00000000..5db63c2d --- /dev/null +++ b/crates/oxide_builtins/src/intl/native.rs @@ -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(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(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(vm: &mut H, s: &str) -> Result { + 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(vm: &mut H, locales: JsValue) -> Result, 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 = 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 = 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(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 = 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(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(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(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(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(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 = 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(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, &[])) +} diff --git a/crates/oxide_builtins/src/intl/tables.rs b/crates/oxide_builtins/src/intl/tables.rs new file mode 100644 index 00000000..082529b7 --- /dev/null +++ b/crates/oxide_builtins/src/intl/tables.rs @@ -0,0 +1,1170 @@ + +/// 复合区域映射条目:区域码、默认映射、按「语言[-脚本]」键选择的覆盖表。 +type ComplexRegionMapping = (&'static str, &'static str, &'static [(&'static str, &'static str)]); + +pub(crate) const TAG_MAPPINGS: [(&str, &str); 5] = [ + ("art-lojban", "jbo"), + ("cel-gaulish", "xtg"), + ("zh-guoyu", "zh"), + ("zh-hakka", "hak"), + ("zh-xiang", "hsn"), +]; + +pub(crate) const LANGUAGE_MAPPINGS: [(&str, &str); 354] = [ + ("aam", "aas"), + ("aar", "aa"), + ("abk", "ab"), + ("adp", "dz"), + ("afr", "af"), + ("aju", "jrb"), + ("aka", "ak"), + ("alb", "sq"), + ("als", "sq"), + ("amh", "am"), + ("ara", "ar"), + ("arb", "ar"), + ("arg", "an"), + ("arm", "hy"), + ("asd", "snz"), + ("asm", "as"), + ("aue", "ktz"), + ("ava", "av"), + ("ave", "ae"), + ("aym", "ay"), + ("ayr", "ay"), + ("ayx", "nun"), + ("aze", "az"), + ("azj", "az"), + ("bak", "ba"), + ("bam", "bm"), + ("baq", "eu"), + ("bcc", "bal"), + ("bcl", "bik"), + ("bel", "be"), + ("ben", "bn"), + ("bgm", "bcg"), + ("bh", "bho"), + ("bih", "bho"), + ("bis", "bi"), + ("bjd", "drl"), + ("bod", "bo"), + ("bos", "bs"), + ("bre", "br"), + ("bul", "bg"), + ("bur", "my"), + ("bxk", "luy"), + ("bxr", "bua"), + ("cat", "ca"), + ("ccq", "rki"), + ("ces", "cs"), + ("cha", "ch"), + ("che", "ce"), + ("chi", "zh"), + ("chu", "cu"), + ("chv", "cv"), + ("cjr", "mom"), + ("cka", "cmr"), + ("cld", "syr"), + ("cmk", "xch"), + ("cmn", "zh"), + ("cor", "kw"), + ("cos", "co"), + ("coy", "pij"), + ("cqu", "quh"), + ("cre", "cr"), + ("cwd", "cr"), + ("cym", "cy"), + ("cze", "cs"), + ("dan", "da"), + ("deu", "de"), + ("dgo", "doi"), + ("dhd", "mwr"), + ("dik", "din"), + ("diq", "zza"), + ("dit", "dif"), + ("div", "dv"), + ("drh", "mn"), + ("dut", "nl"), + ("dzo", "dz"), + ("ekk", "et"), + ("ell", "el"), + ("emk", "man"), + ("eng", "en"), + ("epo", "eo"), + ("esk", "ik"), + ("est", "et"), + ("eus", "eu"), + ("ewe", "ee"), + ("fao", "fo"), + ("fas", "fa"), + ("fat", "ak"), + ("fij", "fj"), + ("fin", "fi"), + ("fra", "fr"), + ("fre", "fr"), + ("fry", "fy"), + ("fuc", "ff"), + ("ful", "ff"), + ("gav", "dev"), + ("gaz", "om"), + ("gbo", "grb"), + ("geo", "ka"), + ("ger", "de"), + ("gfx", "vaj"), + ("ggn", "gvr"), + ("gla", "gd"), + ("gle", "ga"), + ("glg", "gl"), + ("glv", "gv"), + ("gno", "gon"), + ("gre", "el"), + ("grn", "gn"), + ("gti", "nyc"), + ("gug", "gn"), + ("guj", "gu"), + ("guv", "duz"), + ("gya", "gba"), + ("hat", "ht"), + ("hau", "ha"), + ("hdn", "hai"), + ("hea", "hmn"), + ("heb", "he"), + ("her", "hz"), + ("him", "srx"), + ("hin", "hi"), + ("hmo", "ho"), + ("hrr", "jal"), + ("hrv", "hr"), + ("hun", "hu"), + ("hye", "hy"), + ("ibi", "opa"), + ("ibo", "ig"), + ("ice", "is"), + ("ido", "io"), + ("iii", "ii"), + ("ike", "iu"), + ("iku", "iu"), + ("ile", "ie"), + ("ilw", "gal"), + ("in", "id"), + ("ina", "ia"), + ("ind", "id"), + ("ipk", "ik"), + ("isl", "is"), + ("ita", "it"), + ("iw", "he"), + ("jav", "jv"), + ("jeg", "oyb"), + ("ji", "yi"), + ("jpn", "ja"), + ("jw", "jv"), + ("kal", "kl"), + ("kan", "kn"), + ("kas", "ks"), + ("kat", "ka"), + ("kau", "kr"), + ("kaz", "kk"), + ("kgc", "tdf"), + ("kgh", "kml"), + ("khk", "mn"), + ("khm", "km"), + ("kik", "ki"), + ("kin", "rw"), + ("kir", "ky"), + ("kmr", "ku"), + ("knc", "kr"), + ("kng", "kg"), + ("knn", "kok"), + ("koj", "kwv"), + ("kom", "kv"), + ("kon", "kg"), + ("kor", "ko"), + ("kpv", "kv"), + ("krm", "bmf"), + ("ktr", "dtp"), + ("kua", "kj"), + ("kur", "ku"), + ("kvs", "gdj"), + ("kwq", "yam"), + ("kxe", "tvd"), + ("kzj", "dtp"), + ("kzt", "dtp"), + ("lao", "lo"), + ("lat", "la"), + ("lav", "lv"), + ("lbk", "bnc"), + ("lii", "raq"), + ("lim", "li"), + ("lin", "ln"), + ("lit", "lt"), + ("llo", "ngt"), + ("lmm", "rmx"), + ("ltz", "lb"), + ("lub", "lu"), + ("lug", "lg"), + ("lvs", "lv"), + ("mac", "mk"), + ("mah", "mh"), + ("mal", "ml"), + ("mao", "mi"), + ("mar", "mr"), + ("may", "ms"), + ("meg", "cir"), + ("mhr", "chm"), + ("mkd", "mk"), + ("mlg", "mg"), + ("mlt", "mt"), + ("mnk", "man"), + ("mo", "ro"), + ("mol", "ro"), + ("mon", "mn"), + ("mri", "mi"), + ("msa", "ms"), + ("mst", "mry"), + ("mup", "raj"), + ("mwj", "vaj"), + ("mya", "my"), + ("myd", "aog"), + ("myt", "mry"), + ("nad", "xny"), + ("nau", "na"), + ("nav", "nv"), + ("nbl", "nr"), + ("ncp", "kdz"), + ("nde", "nd"), + ("ndo", "ng"), + ("nep", "ne"), + ("nld", "nl"), + ("nno", "nn"), + ("nns", "nbr"), + ("nnx", "ngv"), + ("no", "nb"), + ("nob", "nb"), + ("nor", "nb"), + ("npi", "ne"), + ("nts", "pij"), + ("nya", "ny"), + ("oci", "oc"), + ("ojg", "oj"), + ("oji", "oj"), + ("ori", "or"), + ("orm", "om"), + ("ory", "or"), + ("oss", "os"), + ("oun", "vaj"), + ("pan", "pa"), + ("pbu", "ps"), + ("pcr", "adx"), + ("per", "fa"), + ("pes", "fa"), + ("pli", "pi"), + ("plt", "mg"), + ("pmc", "huw"), + ("pmu", "phr"), + ("pnb", "lah"), + ("pol", "pl"), + ("por", "pt"), + ("ppa", "bfy"), + ("ppr", "lcq"), + ("pry", "prt"), + ("pus", "ps"), + ("puz", "pub"), + ("que", "qu"), + ("quz", "qu"), + ("rmy", "rom"), + ("roh", "rm"), + ("ron", "ro"), + ("rum", "ro"), + ("run", "rn"), + ("rus", "ru"), + ("sag", "sg"), + ("san", "sa"), + ("sca", "hle"), + ("scc", "sr"), + ("scr", "hr"), + ("sin", "si"), + ("skk", "oyb"), + ("slk", "sk"), + ("slo", "sk"), + ("slv", "sl"), + ("sme", "se"), + ("smo", "sm"), + ("sna", "sn"), + ("snd", "sd"), + ("som", "so"), + ("sot", "st"), + ("spa", "es"), + ("spy", "kln"), + ("sqi", "sq"), + ("src", "sc"), + ("srd", "sc"), + ("srp", "sr"), + ("ssw", "ss"), + ("sun", "su"), + ("swa", "sw"), + ("swe", "sv"), + ("swh", "sw"), + ("tah", "ty"), + ("tam", "ta"), + ("tat", "tt"), + ("tdu", "dtp"), + ("tel", "te"), + ("tgk", "tg"), + ("tgl", "fil"), + ("tha", "th"), + ("thc", "tpo"), + ("thx", "oyb"), + ("tib", "bo"), + ("tie", "ras"), + ("tir", "ti"), + ("tkk", "twm"), + ("tl", "fil"), + ("tlw", "weo"), + ("tmp", "tyj"), + ("tne", "kak"), + ("ton", "to"), + ("tsf", "taj"), + ("tsn", "tn"), + ("tso", "ts"), + ("ttq", "tmh"), + ("tuk", "tk"), + ("tur", "tr"), + ("tw", "ak"), + ("twi", "ak"), + ("uig", "ug"), + ("ukr", "uk"), + ("umu", "del"), + ("uok", "ema"), + ("urd", "ur"), + ("uzb", "uz"), + ("uzn", "uz"), + ("ven", "ve"), + ("vie", "vi"), + ("vol", "vo"), + ("wel", "cy"), + ("wln", "wa"), + ("wol", "wo"), + ("xba", "cax"), + ("xho", "xh"), + ("xia", "acn"), + ("xkh", "waw"), + ("xpe", "kpe"), + ("xsj", "suj"), + ("xsl", "den"), + ("ybd", "rki"), + ("ydd", "yi"), + ("yid", "yi"), + ("yma", "lrr"), + ("ymt", "mtm"), + ("yor", "yo"), + ("yos", "zom"), + ("yuu", "yug"), + ("zai", "zap"), + ("zha", "za"), + ("zho", "zh"), + ("zsm", "ms"), + ("zul", "zu"), + ("zyb", "za"), +]; + +pub(crate) const REGION_MAPPINGS: [(&str, &str); 323] = [ + ("004", "AF"), + ("008", "AL"), + ("010", "AQ"), + ("012", "DZ"), + ("016", "AS"), + ("020", "AD"), + ("024", "AO"), + ("028", "AG"), + ("031", "AZ"), + ("032", "AR"), + ("036", "AU"), + ("040", "AT"), + ("044", "BS"), + ("048", "BH"), + ("050", "BD"), + ("051", "AM"), + ("052", "BB"), + ("056", "BE"), + ("060", "BM"), + ("062", "034"), + ("064", "BT"), + ("068", "BO"), + ("070", "BA"), + ("072", "BW"), + ("074", "BV"), + ("076", "BR"), + ("084", "BZ"), + ("086", "IO"), + ("090", "SB"), + ("092", "VG"), + ("096", "BN"), + ("100", "BG"), + ("104", "MM"), + ("108", "BI"), + ("112", "BY"), + ("116", "KH"), + ("120", "CM"), + ("124", "CA"), + ("132", "CV"), + ("136", "KY"), + ("140", "CF"), + ("144", "LK"), + ("148", "TD"), + ("152", "CL"), + ("156", "CN"), + ("158", "TW"), + ("162", "CX"), + ("166", "CC"), + ("170", "CO"), + ("174", "KM"), + ("175", "YT"), + ("178", "CG"), + ("180", "CD"), + ("184", "CK"), + ("188", "CR"), + ("191", "HR"), + ("192", "CU"), + ("196", "CY"), + ("203", "CZ"), + ("204", "BJ"), + ("208", "DK"), + ("212", "DM"), + ("214", "DO"), + ("218", "EC"), + ("222", "SV"), + ("226", "GQ"), + ("230", "ET"), + ("231", "ET"), + ("232", "ER"), + ("233", "EE"), + ("234", "FO"), + ("238", "FK"), + ("239", "GS"), + ("242", "FJ"), + ("246", "FI"), + ("248", "AX"), + ("249", "FR"), + ("250", "FR"), + ("254", "GF"), + ("258", "PF"), + ("260", "TF"), + ("262", "DJ"), + ("266", "GA"), + ("268", "GE"), + ("270", "GM"), + ("275", "PS"), + ("276", "DE"), + ("278", "DE"), + ("280", "DE"), + ("288", "GH"), + ("292", "GI"), + ("296", "KI"), + ("300", "GR"), + ("304", "GL"), + ("308", "GD"), + ("312", "GP"), + ("316", "GU"), + ("320", "GT"), + ("324", "GN"), + ("328", "GY"), + ("332", "HT"), + ("334", "HM"), + ("336", "VA"), + ("340", "HN"), + ("344", "HK"), + ("348", "HU"), + ("352", "IS"), + ("356", "IN"), + ("360", "ID"), + ("364", "IR"), + ("368", "IQ"), + ("372", "IE"), + ("376", "IL"), + ("380", "IT"), + ("384", "CI"), + ("388", "JM"), + ("392", "JP"), + ("398", "KZ"), + ("400", "JO"), + ("404", "KE"), + ("408", "KP"), + ("410", "KR"), + ("414", "KW"), + ("417", "KG"), + ("418", "LA"), + ("422", "LB"), + ("426", "LS"), + ("428", "LV"), + ("430", "LR"), + ("434", "LY"), + ("438", "LI"), + ("440", "LT"), + ("442", "LU"), + ("446", "MO"), + ("450", "MG"), + ("454", "MW"), + ("458", "MY"), + ("462", "MV"), + ("466", "ML"), + ("470", "MT"), + ("474", "MQ"), + ("478", "MR"), + ("480", "MU"), + ("484", "MX"), + ("492", "MC"), + ("496", "MN"), + ("498", "MD"), + ("499", "ME"), + ("500", "MS"), + ("504", "MA"), + ("508", "MZ"), + ("512", "OM"), + ("516", "NA"), + ("520", "NR"), + ("524", "NP"), + ("528", "NL"), + ("531", "CW"), + ("533", "AW"), + ("534", "SX"), + ("535", "BQ"), + ("540", "NC"), + ("548", "VU"), + ("554", "NZ"), + ("558", "NI"), + ("562", "NE"), + ("566", "NG"), + ("570", "NU"), + ("574", "NF"), + ("578", "NO"), + ("580", "MP"), + ("581", "UM"), + ("583", "FM"), + ("584", "MH"), + ("585", "PW"), + ("586", "PK"), + ("591", "PA"), + ("598", "PG"), + ("600", "PY"), + ("604", "PE"), + ("608", "PH"), + ("612", "PN"), + ("616", "PL"), + ("620", "PT"), + ("624", "GW"), + ("626", "TL"), + ("630", "PR"), + ("634", "QA"), + ("638", "RE"), + ("642", "RO"), + ("643", "RU"), + ("646", "RW"), + ("652", "BL"), + ("654", "SH"), + ("659", "KN"), + ("660", "AI"), + ("662", "LC"), + ("663", "MF"), + ("666", "PM"), + ("670", "VC"), + ("674", "SM"), + ("678", "ST"), + ("682", "SA"), + ("686", "SN"), + ("688", "RS"), + ("690", "SC"), + ("694", "SL"), + ("702", "SG"), + ("703", "SK"), + ("704", "VN"), + ("705", "SI"), + ("706", "SO"), + ("710", "ZA"), + ("716", "ZW"), + ("720", "YE"), + ("724", "ES"), + ("728", "SS"), + ("729", "SD"), + ("732", "EH"), + ("736", "SD"), + ("740", "SR"), + ("744", "SJ"), + ("748", "SZ"), + ("752", "SE"), + ("756", "CH"), + ("760", "SY"), + ("762", "TJ"), + ("764", "TH"), + ("768", "TG"), + ("772", "TK"), + ("776", "TO"), + ("780", "TT"), + ("784", "AE"), + ("788", "TN"), + ("792", "TR"), + ("795", "TM"), + ("796", "TC"), + ("798", "TV"), + ("800", "UG"), + ("804", "UA"), + ("807", "MK"), + ("818", "EG"), + ("826", "GB"), + ("830", "JE"), + ("831", "GG"), + ("832", "JE"), + ("833", "IM"), + ("834", "TZ"), + ("840", "US"), + ("850", "VI"), + ("854", "BF"), + ("858", "UY"), + ("860", "UZ"), + ("862", "VE"), + ("876", "WF"), + ("882", "WS"), + ("886", "YE"), + ("887", "YE"), + ("891", "RS"), + ("894", "ZM"), + ("958", "AA"), + ("959", "QM"), + ("960", "QN"), + ("962", "QP"), + ("963", "QQ"), + ("964", "QR"), + ("965", "QS"), + ("966", "QT"), + ("967", "EU"), + ("968", "QV"), + ("969", "QW"), + ("970", "QX"), + ("971", "QY"), + ("972", "QZ"), + ("973", "XA"), + ("974", "XB"), + ("975", "XC"), + ("976", "XD"), + ("977", "XE"), + ("978", "XF"), + ("979", "XG"), + ("980", "XH"), + ("981", "XI"), + ("982", "XJ"), + ("983", "XK"), + ("984", "XL"), + ("985", "XM"), + ("986", "XN"), + ("987", "XO"), + ("988", "XP"), + ("989", "XQ"), + ("990", "XR"), + ("991", "XS"), + ("992", "XT"), + ("993", "XU"), + ("994", "XV"), + ("995", "XW"), + ("996", "XX"), + ("997", "XY"), + ("998", "XZ"), + ("999", "ZZ"), + ("BU", "MM"), + ("CS", "RS"), + ("CT", "KI"), + ("DD", "DE"), + ("DY", "BJ"), + ("FQ", "AQ"), + ("FX", "FR"), + ("HV", "BF"), + ("JT", "UM"), + ("MI", "UM"), + ("NH", "VU"), + ("NQ", "AQ"), + ("PU", "UM"), + ("PZ", "PA"), + ("QU", "EU"), + ("RH", "ZW"), + ("TP", "TL"), + ("UK", "GB"), + ("VD", "VN"), + ("WK", "UM"), + ("YD", "YE"), + ("YU", "RS"), + ("ZR", "CD"), +]; + +pub(crate) const COMPLEX_LANGUAGE_MAPPINGS: [(&str, &str, Option<&str>, Option<&str>); 7] = [ + ("cnr", "sr", None, Some("ME")), + ("drw", "fa", None, Some("AF")), + ("hbs", "sr", Some("Latn"), None), + ("prs", "fa", None, Some("AF")), + ("sh", "sr", Some("Latn"), None), + ("swc", "sw", None, Some("CD")), + ("tnf", "fa", None, Some("AF")), +]; + +pub(crate) const COMPLEX_REGION_MAPPINGS: [ComplexRegionMapping; 12] = [ + ( + "172", + "RU", + &[ + ("ab", "GE"), + ("az", "AZ"), + ("be", "BY"), + ("crh", "UA"), + ("gag", "MD"), + ("got", "UA"), + ("hy", "AM"), + ("ji", "UA"), + ("ka", "GE"), + ("kaa", "UZ"), + ("kk", "KZ"), + ("ku-Yezi", "GE"), + ("ky", "KG"), + ("os", "GE"), + ("rue", "UA"), + ("sog", "UZ"), + ("tg", "TJ"), + ("tk", "TM"), + ("tkr", "AZ"), + ("tly", "AZ"), + ("ttt", "AZ"), + ("ug-Cyrl", "KZ"), + ("uk", "UA"), + ("und-Armn", "AM"), + ("und-Chrs", "UZ"), + ("und-Geor", "GE"), + ("und-Goth", "UA"), + ("und-Sogd", "UZ"), + ("und-Sogo", "UZ"), + ("und-Yezi", "GE"), + ("uz", "UZ"), + ("xco", "UZ"), + ("xmf", "GE"), + ], + ), + ("200", "CZ", &[("sk", "SK")]), + ("530", "CW", &[("vic", "SX")]), + ("532", "CW", &[("vic", "SX")]), + ( + "536", + "SA", + &[ + ("akk", "IQ"), + ("ckb", "IQ"), + ("ku-Arab", "IQ"), + ("mis", "IQ"), + ("syr", "IQ"), + ("und-Hatr", "IQ"), + ("und-Syrc", "IQ"), + ("und-Xsux", "IQ"), + ], + ), + ("582", "FM", &[("mh", "MH"), ("pau", "PW")]), + ( + "810", + "RU", + &[ + ("ab", "GE"), + ("az", "AZ"), + ("be", "BY"), + ("crh", "UA"), + ("et", "EE"), + ("gag", "MD"), + ("got", "UA"), + ("hy", "AM"), + ("ji", "UA"), + ("ka", "GE"), + ("kaa", "UZ"), + ("kk", "KZ"), + ("ku-Yezi", "GE"), + ("ky", "KG"), + ("lt", "LT"), + ("ltg", "LV"), + ("lv", "LV"), + ("os", "GE"), + ("rue", "UA"), + ("sgs", "LT"), + ("sog", "UZ"), + ("tg", "TJ"), + ("tk", "TM"), + ("tkr", "AZ"), + ("tly", "AZ"), + ("ttt", "AZ"), + ("ug-Cyrl", "KZ"), + ("uk", "UA"), + ("und-Armn", "AM"), + ("und-Chrs", "UZ"), + ("und-Geor", "GE"), + ("und-Goth", "UA"), + ("und-Sogd", "UZ"), + ("und-Sogo", "UZ"), + ("und-Yezi", "GE"), + ("uz", "UZ"), + ("vro", "EE"), + ("xco", "UZ"), + ("xmf", "GE"), + ], + ), + ("890", "RS", &[("bs", "BA"), ("hr", "HR"), ("mk", "MK"), ("sl", "SI")]), + ("AN", "CW", &[("vic", "SX")]), + ( + "NT", + "SA", + &[ + ("akk", "IQ"), + ("ckb", "IQ"), + ("ku-Arab", "IQ"), + ("mis", "IQ"), + ("syr", "IQ"), + ("und-Hatr", "IQ"), + ("und-Syrc", "IQ"), + ("und-Xsux", "IQ"), + ], + ), + ("PC", "FM", &[("mh", "MH"), ("pau", "PW")]), + ( + "SU", + "RU", + &[ + ("ab", "GE"), + ("az", "AZ"), + ("be", "BY"), + ("crh", "UA"), + ("et", "EE"), + ("gag", "MD"), + ("got", "UA"), + ("hy", "AM"), + ("ji", "UA"), + ("ka", "GE"), + ("kaa", "UZ"), + ("kk", "KZ"), + ("ku-Yezi", "GE"), + ("ky", "KG"), + ("lt", "LT"), + ("ltg", "LV"), + ("lv", "LV"), + ("os", "GE"), + ("rue", "UA"), + ("sgs", "LT"), + ("sog", "UZ"), + ("tg", "TJ"), + ("tk", "TM"), + ("tkr", "AZ"), + ("tly", "AZ"), + ("ttt", "AZ"), + ("ug-Cyrl", "KZ"), + ("uk", "UA"), + ("und-Armn", "AM"), + ("und-Chrs", "UZ"), + ("und-Geor", "GE"), + ("und-Goth", "UA"), + ("und-Sogd", "UZ"), + ("und-Sogo", "UZ"), + ("und-Yezi", "GE"), + ("uz", "UZ"), + ("vro", "EE"), + ("xco", "UZ"), + ("xmf", "GE"), + ], + ), +]; + +pub(crate) const VARIANT_MAPPINGS: [(&str, &str, &str); 5] = [ + ("aaland", "region", "AX"), + ("arevela", "language", "hy"), + ("arevmda", "language", "hyw"), + ("heploc", "variant", "alalc97"), + ("polytoni", "variant", "polyton"), +]; + +pub(crate) const UNICODE_MAPPINGS: [(&str, &[(&str, &str)]); 11] = [ + ("ca", &[("ethiopic-amete-alem", "ethioaa"), ("islamicc", "islamic-civil")]), + ("kb", &[("yes", "true")]), + ("kc", &[("yes", "true")]), + ("kh", &[("yes", "true")]), + ("kk", &[("yes", "true")]), + ("kn", &[("yes", "true")]), + ("ks", &[("primary", "level1"), ("tertiary", "level3")]), + ("ms", &[("imperial", "uksystem")]), + ( + "rg", + &[ + ("cn11", "cnbj"), + ("cn12", "cntj"), + ("cn13", "cnhe"), + ("cn14", "cnsx"), + ("cn15", "cnmn"), + ("cn21", "cnln"), + ("cn22", "cnjl"), + ("cn23", "cnhl"), + ("cn31", "cnsh"), + ("cn32", "cnjs"), + ("cn33", "cnzj"), + ("cn34", "cnah"), + ("cn35", "cnfj"), + ("cn36", "cnjx"), + ("cn37", "cnsd"), + ("cn41", "cnha"), + ("cn42", "cnhb"), + ("cn43", "cnhn"), + ("cn44", "cngd"), + ("cn45", "cngx"), + ("cn46", "cnhi"), + ("cn50", "cncq"), + ("cn51", "cnsc"), + ("cn52", "cngz"), + ("cn53", "cnyn"), + ("cn54", "cnxz"), + ("cn61", "cnsn"), + ("cn62", "cngs"), + ("cn63", "cnqh"), + ("cn64", "cnnx"), + ("cn65", "cnxj"), + ("cz10a", "cz110"), + ("cz10b", "cz111"), + ("cz10c", "cz112"), + ("cz10d", "cz113"), + ("cz10e", "cz114"), + ("cz10f", "cz115"), + ("cz611", "cz663"), + ("cz612", "cz632"), + ("cz613", "cz633"), + ("cz614", "cz634"), + ("cz615", "cz635"), + ("cz621", "cz641"), + ("cz622", "cz642"), + ("cz623", "cz643"), + ("cz624", "cz644"), + ("cz626", "cz646"), + ("cz627", "cz647"), + ("czjc", "cz31"), + ("czjm", "cz64"), + ("czka", "cz41"), + ("czkr", "cz52"), + ("czli", "cz51"), + ("czmo", "cz80"), + ("czol", "cz71"), + ("czpa", "cz53"), + ("czpl", "cz32"), + ("czpr", "cz10"), + ("czst", "cz20"), + ("czus", "cz42"), + ("czvy", "cz63"), + ("czzl", "cz72"), + ("fra", "frges"), + ("frb", "frnaq"), + ("frc", "frara"), + ("frd", "frbfc"), + ("fre", "frbre"), + ("frf", "frcvl"), + ("frg", "frges"), + ("frh", "frcor"), + ("fri", "frbfc"), + ("frj", "fridf"), + ("frk", "frocc"), + ("frl", "frnaq"), + ("frm", "frges"), + ("frn", "frocc"), + ("fro", "frhdf"), + ("frp", "frnor"), + ("frq", "frnor"), + ("frr", "frpdl"), + ("frs", "frhdf"), + ("frt", "frnaq"), + ("fru", "frpac"), + ("frv", "frara"), + ("laxn", "laxs"), + ("lud", "lucl"), + ("lug", "luec"), + ("lul", "luca"), + ("mrnkc", "mr13"), + ("no23", "no50"), + ("nzn", "nzauk"), + ("nzs", "nzcan"), + ("omba", "ombj"), + ("omsh", "omsj"), + ("plds", "pl02"), + ("plkp", "pl04"), + ("pllb", "pl08"), + ("plld", "pl10"), + ("pllu", "pl06"), + ("plma", "pl12"), + ("plmz", "pl14"), + ("plop", "pl16"), + ("plpd", "pl20"), + ("plpk", "pl18"), + ("plpm", "pl22"), + ("plsk", "pl26"), + ("plsl", "pl24"), + ("plwn", "pl28"), + ("plwp", "pl30"), + ("plzp", "pl32"), + ("tteto", "tttob"), + ("ttrcm", "ttmrc"), + ("ttwto", "tttob"), + ("twkhq", "twkhh"), + ("twtnq", "twtnn"), + ("twtpq", "twnwt"), + ("twtxq", "twtxg"), + ], + ), + ( + "sd", + &[ + ("cn11", "cnbj"), + ("cn12", "cntj"), + ("cn13", "cnhe"), + ("cn14", "cnsx"), + ("cn15", "cnmn"), + ("cn21", "cnln"), + ("cn22", "cnjl"), + ("cn23", "cnhl"), + ("cn31", "cnsh"), + ("cn32", "cnjs"), + ("cn33", "cnzj"), + ("cn34", "cnah"), + ("cn35", "cnfj"), + ("cn36", "cnjx"), + ("cn37", "cnsd"), + ("cn41", "cnha"), + ("cn42", "cnhb"), + ("cn43", "cnhn"), + ("cn44", "cngd"), + ("cn45", "cngx"), + ("cn46", "cnhi"), + ("cn50", "cncq"), + ("cn51", "cnsc"), + ("cn52", "cngz"), + ("cn53", "cnyn"), + ("cn54", "cnxz"), + ("cn61", "cnsn"), + ("cn62", "cngs"), + ("cn63", "cnqh"), + ("cn64", "cnnx"), + ("cn65", "cnxj"), + ("cz10a", "cz110"), + ("cz10b", "cz111"), + ("cz10c", "cz112"), + ("cz10d", "cz113"), + ("cz10e", "cz114"), + ("cz10f", "cz115"), + ("cz611", "cz663"), + ("cz612", "cz632"), + ("cz613", "cz633"), + ("cz614", "cz634"), + ("cz615", "cz635"), + ("cz621", "cz641"), + ("cz622", "cz642"), + ("cz623", "cz643"), + ("cz624", "cz644"), + ("cz626", "cz646"), + ("cz627", "cz647"), + ("czjc", "cz31"), + ("czjm", "cz64"), + ("czka", "cz41"), + ("czkr", "cz52"), + ("czli", "cz51"), + ("czmo", "cz80"), + ("czol", "cz71"), + ("czpa", "cz53"), + ("czpl", "cz32"), + ("czpr", "cz10"), + ("czst", "cz20"), + ("czus", "cz42"), + ("czvy", "cz63"), + ("czzl", "cz72"), + ("fra", "frges"), + ("frb", "frnaq"), + ("frc", "frara"), + ("frd", "frbfc"), + ("fre", "frbre"), + ("frf", "frcvl"), + ("frg", "frges"), + ("frh", "frcor"), + ("fri", "frbfc"), + ("frj", "fridf"), + ("frk", "frocc"), + ("frl", "frnaq"), + ("frm", "frges"), + ("frn", "frocc"), + ("fro", "frhdf"), + ("frp", "frnor"), + ("frq", "frnor"), + ("frr", "frpdl"), + ("frs", "frhdf"), + ("frt", "frnaq"), + ("fru", "frpac"), + ("frv", "frara"), + ("laxn", "laxs"), + ("lud", "lucl"), + ("lug", "luec"), + ("lul", "luca"), + ("mrnkc", "mr13"), + ("no23", "no50"), + ("nzn", "nzauk"), + ("nzs", "nzcan"), + ("omba", "ombj"), + ("omsh", "omsj"), + ("plds", "pl02"), + ("plkp", "pl04"), + ("pllb", "pl08"), + ("plld", "pl10"), + ("pllu", "pl06"), + ("plma", "pl12"), + ("plmz", "pl14"), + ("plop", "pl16"), + ("plpd", "pl20"), + ("plpk", "pl18"), + ("plpm", "pl22"), + ("plsk", "pl26"), + ("plsl", "pl24"), + ("plwn", "pl28"), + ("plwp", "pl30"), + ("plzp", "pl32"), + ("tteto", "tttob"), + ("ttrcm", "ttmrc"), + ("ttwto", "tttob"), + ("twkhq", "twkhh"), + ("twtnq", "twtnn"), + ("twtpq", "twnwt"), + ("twtxq", "twtxg"), + ], + ), + ( + "tz", + &[ + ("aqams", "nzakl"), + ("cnckg", "cnsha"), + ("cnhrb", "cnsha"), + ("cnkhg", "cnurc"), + ("cuba", "cuhav"), + ("egypt", "egcai"), + ("eire", "iedub"), + ("est", "utcw05"), + ("gmt0", "gmt"), + ("hongkong", "hkhkg"), + ("hst", "utcw10"), + ("iceland", "isrey"), + ("iran", "irthr"), + ("israel", "jeruslm"), + ("jamaica", "jmkin"), + ("japan", "jptyo"), + ("libya", "lytip"), + ("mst", "utcw07"), + ("navajo", "usden"), + ("poland", "plwaw"), + ("portugal", "ptlis"), + ("prc", "cnsha"), + ("roc", "twtpe"), + ("rok", "krsel"), + ("turkey", "trist"), + ("uct", "utc"), + ("usnavajo", "usden"), + ("zulu", "utc"), + ], + ), +]; + +pub(crate) const TRANSFORM_MAPPINGS: [(&str, &[(&str, &str)]); 2] = + [("d0", &[("name", "charname")]), ("m0", &[("names", "prprname")])]; diff --git a/crates/oxide_builtins/src/lib.rs b/crates/oxide_builtins/src/lib.rs index b2576c40..579c485e 100644 --- a/crates/oxide_builtins/src/lib.rs +++ b/crates/oxide_builtins/src/lib.rs @@ -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)。 diff --git a/crates/oxide_builtins/src/temporal/mod.rs b/crates/oxide_builtins/src/temporal/mod.rs index 74c16455..318c408e 100644 --- a/crates/oxide_builtins/src/temporal/mod.rs +++ b/crates/oxide_builtins/src/temporal/mod.rs @@ -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::*; diff --git a/crates/oxide_kernel/src/builtin/construct.rs b/crates/oxide_kernel/src/builtin/construct.rs index c09914dc..34a66ea5 100644 --- a/crates/oxide_kernel/src/builtin/construct.rs +++ b/crates/oxide_kernel/src/builtin/construct.rs @@ -347,6 +347,16 @@ pub(crate) fn wire_builtin_world_links(world: &BuiltinWorld) { wire_ctor_proto(&world.plain_month_day_constructor, &world.plain_month_day_proto); wire_ctor_proto(&world.plain_year_month_constructor, &world.plain_year_month_proto); wire_ctor_proto(&world.bigint_constructor, &world.bigint_proto); + wire_ctor_proto(&world.number_format_constructor, &world.number_format_proto); + wire_ctor_proto(&world.date_time_format_constructor, &world.date_time_format_proto); + wire_ctor_proto(&world.collator_constructor, &world.collator_proto); + wire_ctor_proto(&world.plural_rules_constructor, &world.plural_rules_proto); + wire_ctor_proto(&world.list_format_constructor, &world.list_format_proto); + wire_ctor_proto(&world.relative_time_format_constructor, &world.relative_time_format_proto); + wire_ctor_proto(&world.display_names_constructor, &world.display_names_proto); + wire_ctor_proto(&world.segmenter_constructor, &world.segmenter_proto); + wire_ctor_proto(&world.locale_constructor, &world.locale_proto); + wire_ctor_proto(&world.duration_format_constructor, &world.duration_format_proto); // 非 TypedArray 构造器的 [[Prototype]] 指向 Function.prototype: // 标准内置函数对象均继承 Function.prototype。 @@ -376,12 +386,22 @@ pub(crate) fn wire_builtin_world_links(world: &BuiltinWorld) { &world.plain_date_time_constructor, &world.plain_month_day_constructor, &world.plain_year_month_constructor, + &world.number_format_constructor, + &world.date_time_format_constructor, + &world.collator_constructor, + &world.plural_rules_constructor, + &world.list_format_constructor, + &world.relative_time_format_constructor, + &world.display_names_constructor, + &world.segmenter_constructor, + &world.locale_constructor, + &world.duration_format_constructor, ] { set_proto_if_changed(ctor, fn_proto_val); } let obj_proto_val = JsValue::from_js_object(world.object_proto.as_ptr() as *mut JsObject); - let non_object_protos: [&P; 28] = [ + let non_object_protos: [&P; 38] = [ &world.array_proto, &world.function_proto, &world.string_proto, @@ -410,6 +430,16 @@ pub(crate) fn wire_builtin_world_links(world: &BuiltinWorld) { &world.disposable_stack_proto, &world.async_disposable_stack_proto, &world.async_iterator_proto, + &world.number_format_proto, + &world.date_time_format_proto, + &world.collator_proto, + &world.plural_rules_proto, + &world.list_format_proto, + &world.relative_time_format_proto, + &world.display_names_proto, + &world.segmenter_proto, + &world.locale_proto, + &world.duration_format_proto, ]; for proto in &non_object_protos { set_proto_if_changed(proto, obj_proto_val); @@ -419,6 +449,9 @@ pub(crate) fn wire_builtin_world_links(world: &BuiltinWorld) { set_proto_if_changed(&world.temporal_object, obj_proto_val); set_proto_if_changed(&world.temporal_now_object, obj_proto_val); + // Intl 命名空间对象(非构造器)继承 Object.prototype。 + set_proto_if_changed(&world.intl_object, obj_proto_val); + // Console 对象(单例命名空间)继承 Object.prototype。 set_proto_if_changed(&world.console_object, obj_proto_val); @@ -569,6 +602,28 @@ impl BuiltinWorld { let (bigint_proto, bigint_constructor) = make_named_pair(string_forge, shape_forge, labels, "BigInt"); let stub_objects = Vec::new(); + // Intl 命名空间对象(非构造器空对象)与十组构造器/原型对:骨架阶段 + // 构造语义(实例化/resolvedOptions/静态方法)由绑定层 native 填充, + // 本处只预留 P 字段使快照/脏检查/收尾枚举覆盖到这二十一枚对象。 + let intl_object = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())); + let (number_format_proto, number_format_constructor) = + make_named_pair(string_forge, shape_forge, labels, "NumberFormat"); + let (date_time_format_proto, date_time_format_constructor) = + make_named_pair(string_forge, shape_forge, labels, "DateTimeFormat"); + let (collator_proto, collator_constructor) = make_named_pair(string_forge, shape_forge, labels, "Collator"); + let (plural_rules_proto, plural_rules_constructor) = + make_named_pair(string_forge, shape_forge, labels, "PluralRules"); + let (list_format_proto, list_format_constructor) = + make_named_pair(string_forge, shape_forge, labels, "ListFormat"); + let (relative_time_format_proto, relative_time_format_constructor) = + make_named_pair(string_forge, shape_forge, labels, "RelativeTimeFormat"); + let (display_names_proto, display_names_constructor) = + make_named_pair(string_forge, shape_forge, labels, "DisplayNames"); + let (segmenter_proto, segmenter_constructor) = make_named_pair(string_forge, shape_forge, labels, "Segmenter"); + let (locale_proto, locale_constructor) = make_named_pair(string_forge, shape_forge, labels, "Locale"); + let (duration_format_proto, duration_format_constructor) = + make_named_pair(string_forge, shape_forge, labels, "DurationFormat"); + // 迭代器原型家族:链关系(→ %IteratorPrototype% → Object.prototype)在 // wire_builtin_world_links 中建立,next/@@iterator 方法由绑定层 // (oxide_builtins 中安装方法 wrapper 的代码)安装。 @@ -699,6 +754,27 @@ impl BuiltinWorld { custom_event_proto, custom_event_constructor, event_target_proto, + intl_object, + number_format_constructor, + number_format_proto, + date_time_format_constructor, + date_time_format_proto, + collator_constructor, + collator_proto, + plural_rules_constructor, + plural_rules_proto, + list_format_constructor, + list_format_proto, + relative_time_format_constructor, + relative_time_format_proto, + display_names_constructor, + display_names_proto, + segmenter_constructor, + segmenter_proto, + locale_constructor, + locale_proto, + duration_format_constructor, + duration_format_proto, leaked_objects: std::cell::RefCell::new(Vec::new()), }; wire_builtin_world_links(&world); diff --git a/crates/oxide_kernel/src/builtin/rebuild.rs b/crates/oxide_kernel/src/builtin/rebuild.rs index b9dd0279..4827080d 100644 --- a/crates/oxide_kernel/src/builtin/rebuild.rs +++ b/crates/oxide_kernel/src/builtin/rebuild.rs @@ -460,6 +460,98 @@ impl BuiltinWorld { ) }; + // Intl 命名空间对象(非构造器空对象)与十组构造器/原型对:脏则换新 + // 空对象占位(与全量构造同形),未脏沿用旧指针。 + let ( + intl_object, + number_format_constructor, + number_format_proto, + date_time_format_constructor, + date_time_format_proto, + collator_constructor, + collator_proto, + plural_rules_constructor, + plural_rules_proto, + list_format_constructor, + list_format_proto, + relative_time_format_constructor, + relative_time_format_proto, + display_names_constructor, + display_names_proto, + segmenter_constructor, + segmenter_proto, + locale_constructor, + locale_proto, + duration_format_constructor, + duration_format_proto, + ) = if dirty.intl { + let (number_format_proto, number_format_constructor) = + make_named_pair(string_forge, shape_forge, labels, "NumberFormat"); + let (date_time_format_proto, date_time_format_constructor) = + make_named_pair(string_forge, shape_forge, labels, "DateTimeFormat"); + let (collator_proto, collator_constructor) = make_named_pair(string_forge, shape_forge, labels, "Collator"); + let (plural_rules_proto, plural_rules_constructor) = + make_named_pair(string_forge, shape_forge, labels, "PluralRules"); + let (list_format_proto, list_format_constructor) = + make_named_pair(string_forge, shape_forge, labels, "ListFormat"); + let (relative_time_format_proto, relative_time_format_constructor) = + make_named_pair(string_forge, shape_forge, labels, "RelativeTimeFormat"); + let (display_names_proto, display_names_constructor) = + make_named_pair(string_forge, shape_forge, labels, "DisplayNames"); + let (segmenter_proto, segmenter_constructor) = + make_named_pair(string_forge, shape_forge, labels, "Segmenter"); + let (locale_proto, locale_constructor) = make_named_pair(string_forge, shape_forge, labels, "Locale"); + let (duration_format_proto, duration_format_constructor) = + make_named_pair(string_forge, shape_forge, labels, "DurationFormat"); + ( + P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), + number_format_constructor, + number_format_proto, + date_time_format_constructor, + date_time_format_proto, + collator_constructor, + collator_proto, + plural_rules_constructor, + plural_rules_proto, + list_format_constructor, + list_format_proto, + relative_time_format_constructor, + relative_time_format_proto, + display_names_constructor, + display_names_proto, + segmenter_constructor, + segmenter_proto, + locale_constructor, + locale_proto, + duration_format_constructor, + duration_format_proto, + ) + } else { + ( + current.intl_object.clone(), + current.number_format_constructor.clone(), + current.number_format_proto.clone(), + current.date_time_format_constructor.clone(), + current.date_time_format_proto.clone(), + current.collator_constructor.clone(), + current.collator_proto.clone(), + current.plural_rules_constructor.clone(), + current.plural_rules_proto.clone(), + current.list_format_constructor.clone(), + current.list_format_proto.clone(), + current.relative_time_format_constructor.clone(), + current.relative_time_format_proto.clone(), + current.display_names_constructor.clone(), + current.display_names_proto.clone(), + current.segmenter_constructor.clone(), + current.segmenter_proto.clone(), + current.locale_constructor.clone(), + current.locale_proto.clone(), + current.duration_format_constructor.clone(), + current.duration_format_proto.clone(), + ) + }; + // 迭代器原型与资源栈原型依赖 Object.prototype(链到其上):object 家族重建时 // 一并重建,否则旧原型链指向已释放的 object_proto。 let ( @@ -623,6 +715,27 @@ impl BuiltinWorld { custom_event_proto, custom_event_constructor, event_target_proto, + intl_object, + number_format_constructor, + number_format_proto, + date_time_format_constructor, + date_time_format_proto, + collator_constructor, + collator_proto, + plural_rules_constructor, + plural_rules_proto, + list_format_constructor, + list_format_proto, + relative_time_format_constructor, + relative_time_format_proto, + display_names_constructor, + display_names_proto, + segmenter_constructor, + segmenter_proto, + locale_constructor, + locale_proto, + duration_format_constructor, + duration_format_proto, leaked_objects: std::cell::RefCell::new(Vec::new()), }; wire_builtin_world_links(&world); diff --git a/crates/oxide_kernel/src/builtin/world.rs b/crates/oxide_kernel/src/builtin/world.rs index c9d1405b..e69126a9 100644 --- a/crates/oxide_kernel/src/builtin/world.rs +++ b/crates/oxide_kernel/src/builtin/world.rs @@ -1,5 +1,5 @@ -//! 注册表职责:BuiltinWorld 结构(108 固定 P 字段 + stub 族 + Box::into_raw -//! 登记表)、get_by_id 派发、all_p_fields 108 元组枚举(新增 P 字段四处同步 +//! 注册表职责:BuiltinWorld 结构(129 固定 P 字段 + stub 族 + Box::into_raw +//! 登记表)、get_by_id 派发、all_p_fields 129 元组枚举(新增 P 字段四处同步 //! 约束载体)与登记表 track/find/inherit/teardown。 use oxide_types::mem::P; @@ -177,6 +177,40 @@ pub struct BuiltinWorld { /// `EventTarget.prototype` 槽位:纯内核接线占位(空对象),真实原型 /// 本体由绑定层后续任务填充(EventTarget 无构造器,仅暴露原型)。 pub event_target_proto: P, + /// `Intl` 命名空间对象(非构造器):构造器/原型对与静态方法由绑定层 + /// 安装;脏家族位为 `intl`。 + pub intl_object: P, + /// `Intl.NumberFormat` 构造器与原型对:经 `make_named_pair` 成对构造, + /// 骨架阶段构造语义(实例化/resolvedOptions)由绑定层 native 填充。 + pub number_format_constructor: P, + pub number_format_proto: P, + /// `Intl.DateTimeFormat` 构造器与原型对。 + pub date_time_format_constructor: P, + pub date_time_format_proto: P, + /// `Intl.Collator` 构造器与原型对。 + pub collator_constructor: P, + pub collator_proto: P, + /// `Intl.PluralRules` 构造器与原型对。 + pub plural_rules_constructor: P, + pub plural_rules_proto: P, + /// `Intl.ListFormat` 构造器与原型对。 + pub list_format_constructor: P, + pub list_format_proto: P, + /// `Intl.RelativeTimeFormat` 构造器与原型对。 + pub relative_time_format_constructor: P, + pub relative_time_format_proto: P, + /// `Intl.DisplayNames` 构造器与原型对。 + pub display_names_constructor: P, + pub display_names_proto: P, + /// `Intl.Segmenter` 构造器与原型对。 + pub segmenter_constructor: P, + pub segmenter_proto: P, + /// `Intl.Locale` 构造器与原型对。 + pub locale_constructor: P, + pub locale_proto: P, + /// `Intl.DurationFormat` 构造器与原型对。 + pub duration_format_constructor: P, + pub duration_format_proto: P, /// 释放登记表(`Box::into_raw` 对象的清单,session 收尾统一释放,非内存泄漏): /// 绑定层经 `Box::into_raw` 持有的函数/宿主对象(方法 wrapper、访问器、 /// 错误构造器、Reflect/Iterator、内建原型构造器、`$262` 宿主等)。 @@ -407,7 +441,7 @@ impl BuiltinWorld { /// session 收尾(`teardown_heap_data`)与选择性重建收尾(`retire_replaced`) /// 的 P 字段枚举唯一入口:`BuiltinWorld` 新增 P 字段须在此同步补一行,否则 /// 收尾时该字段属性区无法释放、重建原型槽改写/释放漏掉该字段。 - pub(crate) fn all_p_fields(&self) -> [&P; 108] { + pub(crate) fn all_p_fields(&self) -> [&P; 129] { [ &self.object_proto, &self.array_proto, @@ -517,6 +551,27 @@ impl BuiltinWorld { &self.custom_event_proto, &self.custom_event_constructor, &self.event_target_proto, + &self.intl_object, + &self.number_format_constructor, + &self.number_format_proto, + &self.date_time_format_constructor, + &self.date_time_format_proto, + &self.collator_constructor, + &self.collator_proto, + &self.plural_rules_constructor, + &self.plural_rules_proto, + &self.list_format_constructor, + &self.list_format_proto, + &self.relative_time_format_constructor, + &self.relative_time_format_proto, + &self.display_names_constructor, + &self.display_names_proto, + &self.segmenter_constructor, + &self.segmenter_proto, + &self.locale_constructor, + &self.locale_proto, + &self.duration_format_constructor, + &self.duration_format_proto, ] } @@ -663,6 +718,27 @@ impl BuiltinWorld { BuiltinId::CustomEventProto => &self.custom_event_proto, BuiltinId::CustomEventCtor => &self.custom_event_constructor, BuiltinId::EventTargetProto => &self.event_target_proto, + BuiltinId::IntlObject => &self.intl_object, + BuiltinId::NumberFormatConstructor => &self.number_format_constructor, + BuiltinId::NumberFormatProto => &self.number_format_proto, + BuiltinId::DateTimeFormatConstructor => &self.date_time_format_constructor, + BuiltinId::DateTimeFormatProto => &self.date_time_format_proto, + BuiltinId::CollatorConstructor => &self.collator_constructor, + BuiltinId::CollatorProto => &self.collator_proto, + BuiltinId::PluralRulesConstructor => &self.plural_rules_constructor, + BuiltinId::PluralRulesProto => &self.plural_rules_proto, + BuiltinId::ListFormatConstructor => &self.list_format_constructor, + BuiltinId::ListFormatProto => &self.list_format_proto, + BuiltinId::RelativeTimeFormatConstructor => &self.relative_time_format_constructor, + BuiltinId::RelativeTimeFormatProto => &self.relative_time_format_proto, + BuiltinId::DisplayNamesConstructor => &self.display_names_constructor, + BuiltinId::DisplayNamesProto => &self.display_names_proto, + BuiltinId::SegmenterConstructor => &self.segmenter_constructor, + BuiltinId::SegmenterProto => &self.segmenter_proto, + BuiltinId::LocaleConstructor => &self.locale_constructor, + BuiltinId::LocaleProto => &self.locale_proto, + BuiltinId::DurationFormatConstructor => &self.duration_format_constructor, + BuiltinId::DurationFormatProto => &self.duration_format_proto, } } } diff --git a/crates/oxide_kernel/src/kernel/builtin_id.rs b/crates/oxide_kernel/src/kernel/builtin_id.rs index 9df26521..1953c091 100644 --- a/crates/oxide_kernel/src/kernel/builtin_id.rs +++ b/crates/oxide_kernel/src/kernel/builtin_id.rs @@ -1,4 +1,4 @@ -//! 内置对象 id 枚举(`BuiltinId` 98 变体 + `ALL` 顺序钉表)、世代快照 +//! 内置对象 id 枚举(`BuiltinId` 119 变体 + `ALL` 顺序钉表)、世代快照 //! (`BuiltinSnapshot`)与按家族划分的脏标记位集(`BuiltinDirtySet`); //! `NUM_BUILTINS` 文档承载"新增 BuiltinWorld 字段须同步"四处约束注记。 @@ -11,7 +11,7 @@ use crate::builtin::BuiltinWorld; /// 维护注意:每个新增的 `BuiltinWorld` 对象字段都必须加到这里以及 /// `KernelSession::dirty_since_snapshot()`,以便选择性重置重建正确的 /// builtin 家族。 -pub const NUM_BUILTINS: usize = 98; +pub const NUM_BUILTINS: usize = 119; /// 内置对象枚举 id,与 `BuiltinWorld` 中的存储槽一一对应。 /// @@ -118,6 +118,27 @@ pub enum BuiltinId { CustomEventProto = 95, CustomEventCtor = 96, EventTargetProto = 97, + IntlObject = 98, + NumberFormatConstructor = 99, + NumberFormatProto = 100, + DateTimeFormatConstructor = 101, + DateTimeFormatProto = 102, + CollatorConstructor = 103, + CollatorProto = 104, + PluralRulesConstructor = 105, + PluralRulesProto = 106, + ListFormatConstructor = 107, + ListFormatProto = 108, + RelativeTimeFormatConstructor = 109, + RelativeTimeFormatProto = 110, + DisplayNamesConstructor = 111, + DisplayNamesProto = 112, + SegmenterConstructor = 113, + SegmenterProto = 114, + LocaleConstructor = 115, + LocaleProto = 116, + DurationFormatConstructor = 117, + DurationFormatProto = 118, } impl BuiltinId { @@ -223,6 +244,27 @@ impl BuiltinId { BuiltinId::CustomEventProto, BuiltinId::CustomEventCtor, BuiltinId::EventTargetProto, + BuiltinId::IntlObject, + BuiltinId::NumberFormatConstructor, + BuiltinId::NumberFormatProto, + BuiltinId::DateTimeFormatConstructor, + BuiltinId::DateTimeFormatProto, + BuiltinId::CollatorConstructor, + BuiltinId::CollatorProto, + BuiltinId::PluralRulesConstructor, + BuiltinId::PluralRulesProto, + BuiltinId::ListFormatConstructor, + BuiltinId::ListFormatProto, + BuiltinId::RelativeTimeFormatConstructor, + BuiltinId::RelativeTimeFormatProto, + BuiltinId::DisplayNamesConstructor, + BuiltinId::DisplayNamesProto, + BuiltinId::SegmenterConstructor, + BuiltinId::SegmenterProto, + BuiltinId::LocaleConstructor, + BuiltinId::LocaleProto, + BuiltinId::DurationFormatConstructor, + BuiltinId::DurationFormatProto, ]; } @@ -301,6 +343,7 @@ pub struct BuiltinDirtySet { pub message_channel: bool, pub broadcast_channel: bool, pub event: bool, + pub intl: bool, } impl BuiltinDirtySet { @@ -337,6 +380,7 @@ impl BuiltinDirtySet { message_channel: true, broadcast_channel: true, event: true, + intl: true, } } @@ -367,6 +411,7 @@ impl BuiltinDirtySet { || self.message_channel || self.broadcast_channel || self.event + || self.intl } /// 是否存在任何污染(builtin world 或 global object)。 @@ -379,13 +424,13 @@ impl BuiltinDirtySet { mod tests { use super::*; - /// 槽对齐面:ALL 表与判别值 0..97 严格同序,快照数组随 NUM_BUILTINS + /// 槽对齐面:ALL 表与判别值 0..118 严格同序,快照数组随 NUM_BUILTINS /// 自动扩维、逐槽对齐。 #[test] fn builtin_snapshot_all_slots_aligned() { - assert_eq!(NUM_BUILTINS, 98); + assert_eq!(NUM_BUILTINS, 119); assert_eq!(BuiltinId::ALL.len(), NUM_BUILTINS); - for i in 0..97usize { + for i in 0..118usize { assert_eq!(BuiltinId::ALL[i] as usize, i); } assert_eq!(BuiltinId::ALL[81], BuiltinId::SharedArrayBufferProto); diff --git a/crates/oxide_kernel/src/kernel/session.rs b/crates/oxide_kernel/src/kernel/session.rs index decbd429..5def4d2d 100644 --- a/crates/oxide_kernel/src/kernel/session.rs +++ b/crates/oxide_kernel/src/kernel/session.rs @@ -209,6 +209,27 @@ impl KernelSession { || gen(BuiltinId::CustomEventProto) != snap(BuiltinId::CustomEventProto) || gen(BuiltinId::CustomEventCtor) != snap(BuiltinId::CustomEventCtor) || gen(BuiltinId::EventTargetProto) != snap(BuiltinId::EventTargetProto), + intl: gen(BuiltinId::IntlObject) != snap(BuiltinId::IntlObject) + || gen(BuiltinId::NumberFormatConstructor) != snap(BuiltinId::NumberFormatConstructor) + || gen(BuiltinId::NumberFormatProto) != snap(BuiltinId::NumberFormatProto) + || gen(BuiltinId::DateTimeFormatConstructor) != snap(BuiltinId::DateTimeFormatConstructor) + || gen(BuiltinId::DateTimeFormatProto) != snap(BuiltinId::DateTimeFormatProto) + || gen(BuiltinId::CollatorConstructor) != snap(BuiltinId::CollatorConstructor) + || gen(BuiltinId::CollatorProto) != snap(BuiltinId::CollatorProto) + || gen(BuiltinId::PluralRulesConstructor) != snap(BuiltinId::PluralRulesConstructor) + || gen(BuiltinId::PluralRulesProto) != snap(BuiltinId::PluralRulesProto) + || gen(BuiltinId::ListFormatConstructor) != snap(BuiltinId::ListFormatConstructor) + || gen(BuiltinId::ListFormatProto) != snap(BuiltinId::ListFormatProto) + || gen(BuiltinId::RelativeTimeFormatConstructor) != snap(BuiltinId::RelativeTimeFormatConstructor) + || gen(BuiltinId::RelativeTimeFormatProto) != snap(BuiltinId::RelativeTimeFormatProto) + || gen(BuiltinId::DisplayNamesConstructor) != snap(BuiltinId::DisplayNamesConstructor) + || gen(BuiltinId::DisplayNamesProto) != snap(BuiltinId::DisplayNamesProto) + || gen(BuiltinId::SegmenterConstructor) != snap(BuiltinId::SegmenterConstructor) + || gen(BuiltinId::SegmenterProto) != snap(BuiltinId::SegmenterProto) + || gen(BuiltinId::LocaleConstructor) != snap(BuiltinId::LocaleConstructor) + || gen(BuiltinId::LocaleProto) != snap(BuiltinId::LocaleProto) + || gen(BuiltinId::DurationFormatConstructor) != snap(BuiltinId::DurationFormatConstructor) + || gen(BuiltinId::DurationFormatProto) != snap(BuiltinId::DurationFormatProto), }; // wrapper 本体被写不落在任何家族位上(写的是 wrapper 自身而非所属 P // 对象):收缩为全脏,强制重建全部家族与 global,使被写的可复用 diff --git a/crates/oxide_runtime_api/src/host.rs b/crates/oxide_runtime_api/src/host.rs index 57ba90c0..bf105694 100644 --- a/crates/oxide_runtime_api/src/host.rs +++ b/crates/oxide_runtime_api/src/host.rs @@ -122,6 +122,28 @@ pub enum ProtoKind { MessageEventProto, ErrorEventProto, CustomEventProto, + // Intl 十组构造器与原型(Intl 命名空间对象不占 ProtoKind:builtins 不读它, + // 绑定层直接访问 world 的 intl_object 槽) + NumberFormatConstructor, + NumberFormatProto, + DateTimeFormatConstructor, + DateTimeFormatProto, + CollatorConstructor, + CollatorProto, + PluralRulesConstructor, + PluralRulesProto, + ListFormatConstructor, + ListFormatProto, + RelativeTimeFormatConstructor, + RelativeTimeFormatProto, + DisplayNamesConstructor, + DisplayNamesProto, + SegmenterConstructor, + SegmenterProto, + LocaleConstructor, + LocaleProto, + DurationFormatConstructor, + DurationFormatProto, } /// builtins 依赖的 `Vm` 能力集合。 diff --git a/crates/oxide_vm/src/bindings/bind_intl.rs b/crates/oxide_vm/src/bindings/bind_intl.rs new file mode 100644 index 00000000..7a058fd7 --- /dev/null +++ b/crates/oxide_vm/src/bindings/bind_intl.rs @@ -0,0 +1,194 @@ +use crate::bindings::{ + apply_binding_table, bind_global_value, bind_well_known_data_property, configure_native_constructor, BindEnv, +}; +use oxide_types::object::{JsObject, PropAttributes}; +use oxide_types::value::JsValue; + +/// 一组 Intl 构造器/原型对:(构造器槽,原型槽,构造器 native 函数, +/// 构造器 length 槽,命名空间槽名,原型标签)。 +type IntlCtorPair<'a> = ( + &'a oxide_types::mem::P, + &'a oxide_types::mem::P, + *const (), + u32, + &'a str, + &'a str, +); + +/// 把 Intl 命名空间对象与十组构造器/原型对绑定到 global。 +/// +/// `realm_id` 是目标所属 realm 的编号:`@@toStringTag` 符号键按 (realm 编号, +/// 局部下标) 编码,realm 编号为 0 时与旧编码逐字节一致。 +/// +/// 构造器/原型对与 `prototype`/`constructor` 互指、命名空间与构造器的 +/// `[[Prototype]]` 链由内核接线(`wire_builtin_world_links`)预置,本函数 +/// 只安装 native 函数面、`length` 与 `@@toStringTag`。 +pub fn bind_intl(env: BindEnv<'_>, global: &mut JsObject) { + let world = env.world(); + let intl_ptr = world.intl_object.as_ptr() as *mut JsObject; + let intl = unsafe { &mut *intl_ptr }; + + // Intl.getCanonicalLocales 与 Intl.supportedValuesOf:命名空间对象上的 + // 静态方法(各 length 1,非构造器)。 + apply_binding_table( + env, + intl, + &[ + ( + "getCanonicalLocales", + oxide_builtins::intl::get_canonical_locales:: as *const (), + 1, + ), + ( + "supportedValuesOf", + oxide_builtins::intl::supported_values_of:: as *const (), + 1, + ), + ], + ); + bind_well_known_data_property( + env.core, + intl, + 9, + JsValue::perm_string(env.string_forge().string_ptr(env.string_forge().intern("Intl").0)), + PropAttributes::new(false, false, true), + env.realm_id, + ); + + // 十组构造器/原型对:构造器(length 0)+ supportedLocalesOf 静态方法 + + // 命名空间槽(Intl.NumberFormat 等)+ 原型 @@toStringTag。 + let pairs: [IntlCtorPair; 10] = [ + ( + &world.number_format_constructor, + &world.number_format_proto, + oxide_builtins::intl::number_format_constructor:: as *const (), + 0, + "NumberFormat", + "Intl.NumberFormat", + ), + ( + &world.date_time_format_constructor, + &world.date_time_format_proto, + oxide_builtins::intl::date_time_format_constructor:: as *const (), + 0, + "DateTimeFormat", + "Intl.DateTimeFormat", + ), + ( + &world.collator_constructor, + &world.collator_proto, + oxide_builtins::intl::collator_constructor:: as *const (), + 0, + "Collator", + "Intl.Collator", + ), + ( + &world.plural_rules_constructor, + &world.plural_rules_proto, + oxide_builtins::intl::plural_rules_constructor:: as *const (), + 0, + "PluralRules", + "Intl.PluralRules", + ), + ( + &world.list_format_constructor, + &world.list_format_proto, + oxide_builtins::intl::list_format_constructor:: as *const (), + 0, + "ListFormat", + "Intl.ListFormat", + ), + ( + &world.relative_time_format_constructor, + &world.relative_time_format_proto, + oxide_builtins::intl::relative_time_format_constructor:: as *const (), + 0, + "RelativeTimeFormat", + "Intl.RelativeTimeFormat", + ), + ( + &world.display_names_constructor, + &world.display_names_proto, + oxide_builtins::intl::display_names_constructor:: as *const (), + 2, + "DisplayNames", + "Intl.DisplayNames", + ), + ( + &world.segmenter_constructor, + &world.segmenter_proto, + oxide_builtins::intl::segmenter_constructor:: as *const (), + 0, + "Segmenter", + "Intl.Segmenter", + ), + ( + &world.locale_constructor, + &world.locale_proto, + oxide_builtins::intl::locale_constructor:: as *const (), + 1, + "Locale", + "Intl.Locale", + ), + ( + &world.duration_format_constructor, + &world.duration_format_proto, + oxide_builtins::intl::duration_format_constructor:: as *const (), + 0, + "DurationFormat", + "Intl.DurationFormat", + ), + ]; + for (ctor_p, proto_p, ctor_fn, length, name, tag) in pairs { + let ctor = unsafe { &mut *(ctor_p.as_ptr() as *mut JsObject) }; + let proto = unsafe { &mut *(proto_p.as_ptr() as *mut JsObject) }; + bind_intl_ctor_pair(env, ctor, proto, ctor_fn, length, tag); + // 命名空间对象上的构造器槽(如 Intl.NumberFormat):既有槽原位更新。 + bind_global_value(env.core, intl, name, JsValue::from_js_object(ctor_p.as_ptr() as *mut JsObject)); + } + + // 命名空间对象挂到 global(既有槽原位更新,幂等)。 + bind_global_value(env.core, global, "Intl", JsValue::from_js_object(intl_ptr)); +} + +/// 安装一组 Intl 构造器/原型对:构造器 native 函数与 `length`、 +/// `supportedLocalesOf` 静态方法、原型 `@@toStringTag`。 +fn bind_intl_ctor_pair( + env: BindEnv<'_>, + ctor: &mut JsObject, + proto: &mut JsObject, + ctor_fn: *const (), + length: u32, + tag: &str, +) { + // 构造器:length 槽的 native 函数;`prototype`/`name` 槽由内核接线 + // 预置(wire_ctor_proto / make_pair),此处不重复建槽。 + configure_native_constructor(ctor, ctor_fn, 0); + let length_si = env.string_forge().intern("length").0; + let length_shape = env.shape_forge().make_shape(ctor.shape_id(), length_si); + ctor.set_shape_id(length_shape); + ctor.ensure_hash_props().push(JsValue::int(length as i32)); + let length_pos = ctor.hash_props_vec().map_or(0, |props| props.len() as u32).saturating_sub(1); + ctor.set_data_meta(length_pos, PropAttributes::new(false, false, true)); + + // supportedLocalesOf:普通静态方法(非构造器、无 prototype),length 1。 + apply_binding_table( + env, + ctor, + &[( + "supportedLocalesOf", + oxide_builtins::intl::supported_locales_of:: as *const (), + 1, + )], + ); + + // 原型 @@toStringTag:{ writable:false, enumerable:false, configurable:true }。 + bind_well_known_data_property( + env.core, + proto, + 9, + JsValue::perm_string(env.string_forge().string_ptr(env.string_forge().intern(tag).0)), + PropAttributes::new(false, false, true), + env.realm_id, + ); +} diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index 15ecee6f..e1238053 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -33,6 +33,8 @@ pub mod bind_event; pub mod bind_function; /// global 对象上的普通全局函数(parseInt、isNaN 等)绑定。 pub mod bind_global; +/// Intl 命名空间对象与十组构造器/原型的 native 绑定(骨架阶段)。 +pub mod bind_intl; /// Iterator 相关全局辅助对象(%IteratorPrototype% 等)绑定。 pub mod bind_iterator; /// JSON 单例对象及其 native 方法绑定。 @@ -1118,6 +1120,7 @@ pub fn bind_global_builtin_slots( ("JSON", JsValue::from_js_object(world.json_object.as_ptr() as *mut JsObject)), ("Temporal", JsValue::from_js_object(world.temporal_object.as_ptr() as *mut JsObject)), ("Atomics", JsValue::from_js_object(world.atomics_object.as_ptr() as *mut JsObject)), + ("Intl", JsValue::from_js_object(world.intl_object.as_ptr() as *mut JsObject)), ] { bind_existing_global(core, global, name, value); } @@ -1343,6 +1346,9 @@ pub fn rebind_dirty_builtins( if dirty.map_or(true, |d| d.temporal) { bind_temporal::bind_temporal(env, global); } + if dirty.map_or(true, |d| d.intl) { + bind_intl::bind_intl(env, global); + } // 独占函数(bind_stubs / bind_weak_map)需要 session 的可变借用,与 env // 的共享借用冲突;env 的最后一次使用在上面的 temporal 分支,独占调用 // 之后重建 env 供后续分支使用。 diff --git a/crates/oxide_vm/src/vm/vmhost.rs b/crates/oxide_vm/src/vm/vmhost.rs index 461b190a..c3df65ab 100644 --- a/crates/oxide_vm/src/vm/vmhost.rs +++ b/crates/oxide_vm/src/vm/vmhost.rs @@ -148,6 +148,26 @@ impl oxide_runtime_api::VmHost for Vm { ProtoKind::MessageEventProto => &world.message_event_proto, ProtoKind::ErrorEventProto => &world.error_event_proto, ProtoKind::CustomEventProto => &world.custom_event_proto, + ProtoKind::NumberFormatConstructor => &world.number_format_constructor, + ProtoKind::NumberFormatProto => &world.number_format_proto, + ProtoKind::DateTimeFormatConstructor => &world.date_time_format_constructor, + ProtoKind::DateTimeFormatProto => &world.date_time_format_proto, + ProtoKind::CollatorConstructor => &world.collator_constructor, + ProtoKind::CollatorProto => &world.collator_proto, + ProtoKind::PluralRulesConstructor => &world.plural_rules_constructor, + ProtoKind::PluralRulesProto => &world.plural_rules_proto, + ProtoKind::ListFormatConstructor => &world.list_format_constructor, + ProtoKind::ListFormatProto => &world.list_format_proto, + ProtoKind::RelativeTimeFormatConstructor => &world.relative_time_format_constructor, + ProtoKind::RelativeTimeFormatProto => &world.relative_time_format_proto, + ProtoKind::DisplayNamesConstructor => &world.display_names_constructor, + ProtoKind::DisplayNamesProto => &world.display_names_proto, + ProtoKind::SegmenterConstructor => &world.segmenter_constructor, + ProtoKind::SegmenterProto => &world.segmenter_proto, + ProtoKind::LocaleConstructor => &world.locale_constructor, + ProtoKind::LocaleProto => &world.locale_proto, + ProtoKind::DurationFormatConstructor => &world.duration_format_constructor, + ProtoKind::DurationFormatProto => &world.duration_format_proto, }; P::as_ptr(p) as *mut JsObject }