feat: 新增 Intl 特性域骨架实现

This commit is contained in:
2026-10-16 05:08:58 +00:00
parent 5e7808bb08
commit 04b991f5d8
17 changed files with 2417 additions and 31 deletions
Generated
+21 -21
View File
@@ -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",
+1 -1
View File
@@ -25,7 +25,7 @@ members = [
]
[workspace.package]
version = "0.19.22"
version = "0.19.23"
edition = "2021"
license = "MIT"
rust-version = "1.80"
+408
View File
@@ -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(&region);
}
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(""));
}
}
+12
View File
@@ -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::*;
+219
View File
@@ -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
+2
View File
@@ -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::*;
+77 -1
View File
@@ -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<JsObject>; 28] = [
let non_object_protos: [&P<JsObject>; 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);
+113
View File
@@ -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);
+79 -3
View File
@@ -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<JsObject>,
/// `Intl` 命名空间对象(非构造器):构造器/原型对与静态方法由绑定层
/// 安装;脏家族位为 `intl`。
pub intl_object: P<JsObject>,
/// `Intl.NumberFormat` 构造器与原型对:经 `make_named_pair` 成对构造,
/// 骨架阶段构造语义(实例化/resolvedOptions)由绑定层 native 填充。
pub number_format_constructor: P<JsObject>,
pub number_format_proto: P<JsObject>,
/// `Intl.DateTimeFormat` 构造器与原型对。
pub date_time_format_constructor: P<JsObject>,
pub date_time_format_proto: P<JsObject>,
/// `Intl.Collator` 构造器与原型对。
pub collator_constructor: P<JsObject>,
pub collator_proto: P<JsObject>,
/// `Intl.PluralRules` 构造器与原型对。
pub plural_rules_constructor: P<JsObject>,
pub plural_rules_proto: P<JsObject>,
/// `Intl.ListFormat` 构造器与原型对。
pub list_format_constructor: P<JsObject>,
pub list_format_proto: P<JsObject>,
/// `Intl.RelativeTimeFormat` 构造器与原型对。
pub relative_time_format_constructor: P<JsObject>,
pub relative_time_format_proto: P<JsObject>,
/// `Intl.DisplayNames` 构造器与原型对。
pub display_names_constructor: P<JsObject>,
pub display_names_proto: P<JsObject>,
/// `Intl.Segmenter` 构造器与原型对。
pub segmenter_constructor: P<JsObject>,
pub segmenter_proto: P<JsObject>,
/// `Intl.Locale` 构造器与原型对。
pub locale_constructor: P<JsObject>,
pub locale_proto: P<JsObject>,
/// `Intl.DurationFormat` 构造器与原型对。
pub duration_format_constructor: P<JsObject>,
pub duration_format_proto: P<JsObject>,
/// 释放登记表(`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<JsObject>; 108] {
pub(crate) fn all_p_fields(&self) -> [&P<JsObject>; 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,
}
}
}
+50 -5
View File
@@ -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);
+21
View File
@@ -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,使被写的可复用
+22
View File
@@ -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` 能力集合。
+194
View File
@@ -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<JsObject>,
&'a oxide_types::mem::P<JsObject>,
*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::<crate::vm::Vm> as *const (),
1,
),
(
"supportedValuesOf",
oxide_builtins::intl::supported_values_of::<crate::vm::Vm> 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::<crate::vm::Vm> as *const (),
0,
"NumberFormat",
"Intl.NumberFormat",
),
(
&world.date_time_format_constructor,
&world.date_time_format_proto,
oxide_builtins::intl::date_time_format_constructor::<crate::vm::Vm> as *const (),
0,
"DateTimeFormat",
"Intl.DateTimeFormat",
),
(
&world.collator_constructor,
&world.collator_proto,
oxide_builtins::intl::collator_constructor::<crate::vm::Vm> as *const (),
0,
"Collator",
"Intl.Collator",
),
(
&world.plural_rules_constructor,
&world.plural_rules_proto,
oxide_builtins::intl::plural_rules_constructor::<crate::vm::Vm> as *const (),
0,
"PluralRules",
"Intl.PluralRules",
),
(
&world.list_format_constructor,
&world.list_format_proto,
oxide_builtins::intl::list_format_constructor::<crate::vm::Vm> as *const (),
0,
"ListFormat",
"Intl.ListFormat",
),
(
&world.relative_time_format_constructor,
&world.relative_time_format_proto,
oxide_builtins::intl::relative_time_format_constructor::<crate::vm::Vm> as *const (),
0,
"RelativeTimeFormat",
"Intl.RelativeTimeFormat",
),
(
&world.display_names_constructor,
&world.display_names_proto,
oxide_builtins::intl::display_names_constructor::<crate::vm::Vm> as *const (),
2,
"DisplayNames",
"Intl.DisplayNames",
),
(
&world.segmenter_constructor,
&world.segmenter_proto,
oxide_builtins::intl::segmenter_constructor::<crate::vm::Vm> as *const (),
0,
"Segmenter",
"Intl.Segmenter",
),
(
&world.locale_constructor,
&world.locale_proto,
oxide_builtins::intl::locale_constructor::<crate::vm::Vm> as *const (),
1,
"Locale",
"Intl.Locale",
),
(
&world.duration_format_constructor,
&world.duration_format_proto,
oxide_builtins::intl::duration_format_constructor::<crate::vm::Vm> 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::<crate::vm::Vm> 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,
);
}
+6
View File
@@ -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 供后续分支使用。
+20
View File
@@ -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
}