mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 10:41:11 +08:00
feat: PlainMonthDay/PlainYearMonth 对象地基——构造器/getter/toString/toJSON 注册与 BuiltinWorld 接线
This commit is contained in:
@@ -7,10 +7,13 @@ use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
// Temporal 命名空间的最小实现子集:Temporal.Now / Temporal.Instant /
|
||||
// Temporal.PlainDate / Temporal.PlainTime / Temporal.PlainDateTime / Temporal.ZonedDateTime。
|
||||
// Temporal.PlainDate / Temporal.PlainTime / Temporal.PlainDateTime /
|
||||
// Temporal.PlainMonthDay / Temporal.PlainYearMonth / Temporal.ZonedDateTime。
|
||||
// 内部数据按对象类型存入 prop 槽:
|
||||
// Instant 存纪元纳秒(BigInt,prop 0)、PlainDate 存年/月/日/日历 ID(prop 0-3)、
|
||||
// PlainTime 存午夜后纳秒(f64,prop 0)、PlainDateTime 存年/月/日/午夜后纳秒/日历 ID(prop 0-4),
|
||||
// PlainTime 存午夜后纳秒(f64,prop 0)、PlainDateTime 存年/月/日/午夜后纳秒/日历 ID(prop 0-4)、
|
||||
// PlainMonthDay 存月/日/参考年/日历 ID(prop 0-3)、
|
||||
// PlainYearMonth 存年/月/参考日/日历 ID(prop 0-3)、
|
||||
// ZonedDateTime 存纪元纳秒、时区 ID、日历 ID(prop 0-2)。
|
||||
// 日历 ID 未显式给定时统一取 "iso8601"。
|
||||
|
||||
@@ -2089,6 +2092,8 @@ fn reject_partial_object_with_calendar_or_time_zone<H: VmHost>(vm: &mut H, value
|
||||
|| obj.is_plain_date_time_obj()
|
||||
|| obj.is_plain_time_obj()
|
||||
|| obj.is_zoned_date_time_obj()
|
||||
|| obj.is_plain_month_day_obj()
|
||||
|| obj.is_plain_year_month_obj()
|
||||
{
|
||||
return Err(crate::error::create_type_error(vm, "invalid argument"));
|
||||
}
|
||||
@@ -5563,6 +5568,9 @@ fn temporal_calendar_id<H: VmHost>(vm: &mut H, value: JsValue) -> Result<Option<
|
||||
if obj.is_zoned_date_time_obj() {
|
||||
return Ok(Some(get_calendar_id(obj, 2)));
|
||||
}
|
||||
if obj.is_plain_month_day_obj() || obj.is_plain_year_month_obj() {
|
||||
return Ok(Some(get_calendar_id(obj, 3)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(crate::error::create_type_error(vm, "invalid calendar"))
|
||||
@@ -7483,6 +7491,415 @@ pub fn plain_date_since<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
)
|
||||
}
|
||||
|
||||
// Temporal.PlainMonthDay / Temporal.PlainYearMonth 对象地基:
|
||||
// 数字分量转换、槽对象构造、构造器与 getter/toString/toJSON。
|
||||
|
||||
/// Temporal 数字分量转换(number-only 路径):先 ToPrimitive(Number)——对象经
|
||||
/// valueOf/toString 取值;Symbol/BigInt → TypeError;NaN/±Inf → RangeError
|
||||
/// (undefined 与不可解析串都归 NaN,由此统一抛 RangeError);其余截断取整。
|
||||
fn temporal_number_component<H: VmHost>(vm: &mut H, value: JsValue) -> Result<f64, JsValue> {
|
||||
let primitive = oxide_runtime_api::to_primitive(value, oxide_runtime_api::ToPrimitiveHint::Number, vm)
|
||||
.map_err(|error| native_engine_error(vm, &error))?;
|
||||
if primitive.is_symbol() || primitive.is_bigint() {
|
||||
return Err(crate::error::create_type_error(vm, "invalid number"));
|
||||
}
|
||||
let number = to_number(primitive);
|
||||
if number.is_infinite() || number.is_nan() {
|
||||
return Err(crate::error::create_range_error(vm, "invalid number"));
|
||||
}
|
||||
Ok(number.trunc())
|
||||
}
|
||||
|
||||
/// ISO 年-月是否落在 PlainYearMonth 可表示范围(ISOYearMonthWithinLimits):
|
||||
/// 仅 -271821 年 3 月及以前、+275760 年 10 月及以后越界。
|
||||
fn iso_year_month_within_limits(year: i32, month: u32) -> bool {
|
||||
!((year == -271821 && month < 4) || (year == 275760 && month > 9))
|
||||
}
|
||||
|
||||
/// 构造 PlainMonthDay 实例对象(槽 0-3 = 月/日/参考年/日历 ID)。
|
||||
/// from/toPlainDate 等返回新对象的成员使用;构造器走 receiver 初始化路径。
|
||||
#[expect(dead_code)]
|
||||
fn make_plain_month_day<H: VmHost>(vm: &mut H, month: u32, day: u32, ref_year: i32, calendar: &str) -> NativeResult {
|
||||
let proto = JsValue::from_js_object(vm.session().builtin_world().plain_month_day_proto.as_ptr() as *mut JsObject);
|
||||
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, proto);
|
||||
obj.type_tag = JsObject::OBJ_TYPE_PLAIN_MONTH_DAY;
|
||||
obj.set_prop_at(0, JsValue::float(month as f64));
|
||||
obj.set_prop_at(1, JsValue::float(day as f64));
|
||||
obj.set_prop_at(2, JsValue::float(ref_year as f64));
|
||||
obj.set_prop_at(3, vm.new_string(calendar));
|
||||
NativeResult::Ok(JsValue::from_js_object(vm.alloc_object(obj)))
|
||||
}
|
||||
|
||||
/// 构造 PlainYearMonth 实例对象(槽 0-3 = 年/月/参考日/日历 ID)。
|
||||
/// from/toPlainDate 等返回新对象的成员使用;构造器走 receiver 初始化路径。
|
||||
#[expect(dead_code)]
|
||||
fn make_plain_year_month<H: VmHost>(vm: &mut H, year: i32, month: u32, ref_day: u32, calendar: &str) -> NativeResult {
|
||||
let proto = JsValue::from_js_object(vm.session().builtin_world().plain_year_month_proto.as_ptr() as *mut JsObject);
|
||||
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, proto);
|
||||
obj.type_tag = JsObject::OBJ_TYPE_PLAIN_YEAR_MONTH;
|
||||
obj.set_prop_at(0, JsValue::float(year as f64));
|
||||
obj.set_prop_at(1, JsValue::float(month as f64));
|
||||
obj.set_prop_at(2, JsValue::float(ref_day as f64));
|
||||
obj.set_prop_at(3, vm.new_string(calendar));
|
||||
NativeResult::Ok(JsValue::from_js_object(vm.alloc_object(obj)))
|
||||
}
|
||||
|
||||
fn ensure_plain_month_day<H: VmHost>(vm: &mut H, obj: &JsObject) -> Result<(), JsValue> {
|
||||
if !obj.is_plain_month_day_obj() {
|
||||
return Err(crate::error::create_type_error(vm, "called on incompatible receiver"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_plain_year_month<H: VmHost>(vm: &mut H, obj: &JsObject) -> Result<(), JsValue> {
|
||||
if !obj.is_plain_year_month_obj() {
|
||||
return Err(crate::error::create_type_error(vm, "called on incompatible receiver"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 读 PlainMonthDay 的 月/日/参考年 三元组(槽 0-2),含 receiver 校验。
|
||||
fn plain_month_day_mdy<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<(f64, f64, f64), JsValue> {
|
||||
let ptr = receiver_obj(vm, args)?;
|
||||
let obj = unsafe { &*ptr };
|
||||
ensure_plain_month_day(vm, obj)?;
|
||||
Ok((get_double_prop(obj, 0), get_double_prop(obj, 1), get_double_prop(obj, 2)))
|
||||
}
|
||||
|
||||
/// 读 PlainYearMonth 的 年/月/参考日 三元组(槽 0-2),含 receiver 校验。
|
||||
fn plain_year_month_ymd<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<(f64, f64, f64), JsValue> {
|
||||
let ptr = receiver_obj(vm, args)?;
|
||||
let obj = unsafe { &*ptr };
|
||||
ensure_plain_year_month(vm, obj)?;
|
||||
Ok((get_double_prop(obj, 0), get_double_prop(obj, 1), get_double_prop(obj, 2)))
|
||||
}
|
||||
|
||||
/// 构造器日历参数解析(ToTemporalCalendarSlotValue,缺省 "iso8601"):
|
||||
/// undefined → 缺省;字符串 → 严格白名单(非法 RangeError);Temporal 日期实例 →
|
||||
/// 日历槽直读(不触发属性 getter);函数对象 → 缺省(无日历行为);
|
||||
/// 其他对象与原始值 → TypeError。
|
||||
fn temporal_constructor_calendar_id<H: VmHost>(vm: &mut H, value: JsValue) -> Result<String, JsValue> {
|
||||
if value.is_undefined() {
|
||||
return Ok("iso8601".to_string());
|
||||
}
|
||||
if value.is_string() {
|
||||
return temporal_calendar_id_strict(vm, value)
|
||||
.map(|calendar| calendar.unwrap_or_else(|| "iso8601".to_string()));
|
||||
}
|
||||
if value.is_object() {
|
||||
let ptr = value.as_js_object_ptr();
|
||||
if !ptr.is_null() {
|
||||
let obj = unsafe { &*ptr };
|
||||
if obj.is_function() {
|
||||
return Ok("iso8601".to_string());
|
||||
}
|
||||
let slot = if obj.is_plain_date_obj() {
|
||||
3
|
||||
} else if obj.is_plain_date_time_obj() {
|
||||
4
|
||||
} else if obj.is_zoned_date_time_obj() {
|
||||
2
|
||||
} else if obj.is_plain_month_day_obj() || obj.is_plain_year_month_obj() {
|
||||
3
|
||||
} else {
|
||||
return Err(crate::error::create_type_error(vm, "invalid calendar"));
|
||||
};
|
||||
return Ok(get_calendar_id(obj, slot));
|
||||
}
|
||||
}
|
||||
Err(crate::error::create_type_error(vm, "invalid calendar"))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainMonthDay` 构造器:`new PlainMonthDay(month, day[, calendar[, referenceISOYear]])`。
|
||||
pub fn plain_month_day_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let ctor_proto = vm.session().builtin_world().plain_month_day_proto.as_ptr();
|
||||
if !is_ctor_call(vm, args, ctor_proto) {
|
||||
return NativeResult::Err(crate::error::create_type_error(
|
||||
vm,
|
||||
"Class constructor Temporal.PlainMonthDay cannot be invoked without 'new'",
|
||||
));
|
||||
}
|
||||
// 转换序:month → day → calendar → referenceISOYear(后两者有缺省值)。
|
||||
let month = native_try!(temporal_number_component(
|
||||
vm,
|
||||
if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() },
|
||||
));
|
||||
let day = native_try!(temporal_number_component(
|
||||
vm,
|
||||
if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() },
|
||||
));
|
||||
let calendar_raw = if args.len() > 3 { vm.reg(args[3]) } else { JsValue::undefined() };
|
||||
let calendar = native_try!(temporal_constructor_calendar_id(vm, calendar_raw));
|
||||
let ref_year = if args.len() > 4 && !vm.reg(args[4]).is_undefined() {
|
||||
native_try!(temporal_number_component(vm, vm.reg(args[4]))) as i32
|
||||
} else {
|
||||
1972
|
||||
};
|
||||
if !valid_iso_date(ref_year, month as u32, day as u32) {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "invalid ISO date"));
|
||||
}
|
||||
// 表示范围(ISODateWithinLimits):-271821-04-19 … +275760-09-13。
|
||||
let day_count = days_from_civil(i128::from(ref_year), i128::from(month as i32), i128::from(day as i32));
|
||||
if !(-100_000_001..=100_000_000).contains(&day_count) {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "ISO date is out of range"));
|
||||
}
|
||||
let calendar_value = vm.new_string(&calendar);
|
||||
initialize_temporal_receiver(
|
||||
vm,
|
||||
args,
|
||||
JsObject::OBJ_TYPE_PLAIN_MONTH_DAY,
|
||||
[
|
||||
JsValue::float(month),
|
||||
JsValue::float(day),
|
||||
JsValue::float(ref_year as f64),
|
||||
calendar_value,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
/// `Temporal.PlainYearMonth` 构造器:`new PlainYearMonth(year, month[, calendar[, referenceISODay]])`。
|
||||
pub fn plain_year_month_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let ctor_proto = vm.session().builtin_world().plain_year_month_proto.as_ptr();
|
||||
if !is_ctor_call(vm, args, ctor_proto) {
|
||||
return NativeResult::Err(crate::error::create_type_error(
|
||||
vm,
|
||||
"Class constructor Temporal.PlainYearMonth cannot be invoked without 'new'",
|
||||
));
|
||||
}
|
||||
// 转换序:year → month → calendar → referenceISODay(后两者有缺省值)。
|
||||
let year = native_try!(temporal_number_component(
|
||||
vm,
|
||||
if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() },
|
||||
)) as i32;
|
||||
let month = native_try!(temporal_number_component(
|
||||
vm,
|
||||
if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() },
|
||||
));
|
||||
let calendar_raw = if args.len() > 3 { vm.reg(args[3]) } else { JsValue::undefined() };
|
||||
let calendar = native_try!(temporal_constructor_calendar_id(vm, calendar_raw));
|
||||
let ref_day = if args.len() > 4 && !vm.reg(args[4]).is_undefined() {
|
||||
native_try!(temporal_number_component(vm, vm.reg(args[4])))
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let month_i = month as i32;
|
||||
if !(1..=12).contains(&month_i) {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "invalid ISO date"));
|
||||
}
|
||||
let ref_day_i = ref_day as i32;
|
||||
if ref_day_i < 1 || ref_day_i > days_in_month_iso(year, month_i as u32) as i32 {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "invalid ISO date"));
|
||||
}
|
||||
if !iso_year_month_within_limits(year, month_i as u32) {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "ISO date is out of range"));
|
||||
}
|
||||
let calendar_value = vm.new_string(&calendar);
|
||||
initialize_temporal_receiver(
|
||||
vm,
|
||||
args,
|
||||
JsObject::OBJ_TYPE_PLAIN_YEAR_MONTH,
|
||||
[
|
||||
JsValue::float(year as f64),
|
||||
JsValue::float(month),
|
||||
JsValue::float(ref_day),
|
||||
calendar_value,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
/// `Temporal.PlainMonthDay.prototype.day` getter(槽 1)。
|
||||
pub fn plain_month_day_day<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let (_, day, _) = native_try!(plain_month_day_mdy(vm, args));
|
||||
NativeResult::Ok(JsValue::float(day))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainMonthDay.prototype.monthCode` getter:`M01`..`M12`。
|
||||
pub fn plain_month_day_month_code<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let (month, _, _) = native_try!(plain_month_day_mdy(vm, args));
|
||||
NativeResult::Ok(vm.new_string(&format!("M{:02}", month as u32)))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainMonthDay.prototype.calendarId` getter:读日历槽(槽 3)。
|
||||
pub fn plain_month_day_calendar_id<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let ptr = native_try!(receiver_obj(vm, args));
|
||||
let obj = unsafe { &*ptr };
|
||||
native_try!(ensure_plain_month_day(vm, obj));
|
||||
NativeResult::Ok(vm.new_string(&get_calendar_id(obj, 3)))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainYearMonth.prototype.year` getter(槽 0)。
|
||||
pub fn plain_year_month_year<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let (year, _, _) = native_try!(plain_year_month_ymd(vm, args));
|
||||
NativeResult::Ok(JsValue::float(year))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainYearMonth.prototype.month` getter(槽 1)。
|
||||
pub fn plain_year_month_month<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let (_, month, _) = native_try!(plain_year_month_ymd(vm, args));
|
||||
NativeResult::Ok(JsValue::float(month))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainYearMonth.prototype.monthCode` getter:`M01`..`M12`。
|
||||
pub fn plain_year_month_month_code<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let (_, month, _) = native_try!(plain_year_month_ymd(vm, args));
|
||||
NativeResult::Ok(vm.new_string(&format!("M{:02}", month as u32)))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainYearMonth.prototype.calendarId` getter:读日历槽(槽 3)。
|
||||
pub fn plain_year_month_calendar_id<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let ptr = native_try!(receiver_obj(vm, args));
|
||||
let obj = unsafe { &*ptr };
|
||||
native_try!(ensure_plain_year_month(vm, obj));
|
||||
NativeResult::Ok(vm.new_string(&get_calendar_id(obj, 3)))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainYearMonth.prototype.daysInMonth` getter:按年月的 ISO 月长。
|
||||
pub fn plain_year_month_days_in_month<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let (year, month, _) = native_try!(plain_year_month_ymd(vm, args));
|
||||
NativeResult::Ok(JsValue::float(days_in_month_iso(year as i32, month as u32) as f64))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainYearMonth.prototype.daysInYear` getter:366(闰年)或 365。
|
||||
pub fn plain_year_month_days_in_year<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let (year, _, _) = native_try!(plain_year_month_ymd(vm, args));
|
||||
NativeResult::Ok(JsValue::float(if is_leap_year_iso(year as i32) { 366.0 } else { 365.0 }))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainYearMonth.prototype.monthsInYear` getter:恒 12(ISO 日历)。
|
||||
pub fn plain_year_month_months_in_year<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let _ = native_try!(plain_year_month_ymd(vm, args));
|
||||
NativeResult::Ok(JsValue::float(12.0))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainYearMonth.prototype.inLeapYear` getter。
|
||||
pub fn plain_year_month_in_leap_year<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let (year, _, _) = native_try!(plain_year_month_ymd(vm, args));
|
||||
NativeResult::Ok(JsValue::bool(is_leap_year_iso(year as i32)))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainYearMonth.prototype.era` getter:ISO 日历无纪元,恒 undefined。
|
||||
pub fn plain_year_month_era<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let _ = native_try!(plain_year_month_ymd(vm, args));
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `Temporal.PlainYearMonth.prototype.eraYear` getter:ISO 日历无纪元,恒 undefined。
|
||||
pub fn plain_year_month_era_year<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let _ = native_try!(plain_year_month_ymd(vm, args));
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// toString 的 calendarName 选项取值(auto/never 同默认形)。
|
||||
enum ShowCalendar {
|
||||
Omitted,
|
||||
Always,
|
||||
Critical,
|
||||
}
|
||||
|
||||
/// 读并校验 toString 的 calendarName 选项:options 非对象 → TypeError;
|
||||
/// 值非字符串先 ToPrimitive(String) 转换(Symbol → TypeError);
|
||||
/// 仅 auto/always/never/critical 合法(大小写敏感),否则 RangeError。
|
||||
fn temporal_to_show_calendar<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<ShowCalendar, JsValue> {
|
||||
let options = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
if options.is_undefined() {
|
||||
return Ok(ShowCalendar::Omitted);
|
||||
}
|
||||
if !options.is_object() {
|
||||
return Err(crate::error::create_type_error(vm, "options must be an object"));
|
||||
}
|
||||
let ptr = options.as_js_object_ptr();
|
||||
if ptr.is_null() {
|
||||
return Err(crate::error::create_type_error(vm, "options must be an object"));
|
||||
}
|
||||
let raw = temporal_option_value(vm, unsafe { &*ptr }, options, "calendarName")?;
|
||||
if raw.is_undefined() {
|
||||
return Ok(ShowCalendar::Omitted);
|
||||
}
|
||||
let value = temporal_option_string(vm, raw)?;
|
||||
match value.as_str() {
|
||||
"always" => Ok(ShowCalendar::Always),
|
||||
"critical" => Ok(ShowCalendar::Critical),
|
||||
"auto" | "never" => Ok(ShowCalendar::Omitted),
|
||||
_ => Err(crate::error::create_range_error(vm, "invalid calendarName")),
|
||||
}
|
||||
}
|
||||
|
||||
/// PlainMonthDay 默认形串(`MM-DD`),含 receiver 校验,不读 options。
|
||||
fn plain_month_day_default_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let ptr = native_try!(receiver_obj(vm, args));
|
||||
let obj = unsafe { &*ptr };
|
||||
native_try!(ensure_plain_month_day(vm, obj));
|
||||
let month = get_double_prop(obj, 0) as i32;
|
||||
let day = get_double_prop(obj, 1) as i32;
|
||||
NativeResult::Ok(vm.new_string_owned(format!("{month:02}-{day:02}")))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainMonthDay.prototype.toString([options])`:
|
||||
/// 默认 `MM-DD`;always/critical 补参考年与 `[u-ca=…]`/`[!u-ca=…]` 注解。
|
||||
pub fn plain_month_day_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let ptr = native_try!(receiver_obj(vm, args));
|
||||
let obj = unsafe { &*ptr };
|
||||
native_try!(ensure_plain_month_day(vm, obj));
|
||||
let show = native_try!(temporal_to_show_calendar(vm, args));
|
||||
if matches!(show, ShowCalendar::Omitted) {
|
||||
return plain_month_day_default_string(vm, args);
|
||||
}
|
||||
let month = get_double_prop(obj, 0) as i32;
|
||||
let day = get_double_prop(obj, 1) as i32;
|
||||
let ref_year = get_double_prop(obj, 2) as i32;
|
||||
let calendar = get_calendar_id(obj, 3);
|
||||
let annotation = match show {
|
||||
ShowCalendar::Always => format!("[u-ca={calendar}]"),
|
||||
_ => format!("[!u-ca={calendar}]"),
|
||||
};
|
||||
NativeResult::Ok(
|
||||
vm.new_string_owned(format!("{}-{month:02}-{day:02}{annotation}", format_iso_year(ref_year as i128))),
|
||||
)
|
||||
}
|
||||
|
||||
/// `Temporal.PlainMonthDay.prototype.toJSON()`:默认形串,忽略参数。
|
||||
pub fn plain_month_day_to_json<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
plain_month_day_default_string(vm, args)
|
||||
}
|
||||
|
||||
/// PlainYearMonth 默认形串(`±YYYY-MM`),含 receiver 校验,不读 options。
|
||||
fn plain_year_month_default_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let ptr = native_try!(receiver_obj(vm, args));
|
||||
let obj = unsafe { &*ptr };
|
||||
native_try!(ensure_plain_year_month(vm, obj));
|
||||
let year = get_double_prop(obj, 0) as i32;
|
||||
let month = get_double_prop(obj, 1) as i32;
|
||||
NativeResult::Ok(vm.new_string_owned(format!("{}-{month:02}", format_iso_year(year as i128))))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainYearMonth.prototype.toString([options])`:
|
||||
/// 默认 `±YYYY-MM`;always/critical 补参考日与 `[u-ca=…]`/`[!u-ca=…]` 注解。
|
||||
pub fn plain_year_month_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let ptr = native_try!(receiver_obj(vm, args));
|
||||
let obj = unsafe { &*ptr };
|
||||
native_try!(ensure_plain_year_month(vm, obj));
|
||||
let show = native_try!(temporal_to_show_calendar(vm, args));
|
||||
if matches!(show, ShowCalendar::Omitted) {
|
||||
return plain_year_month_default_string(vm, args);
|
||||
}
|
||||
let year = get_double_prop(obj, 0) as i32;
|
||||
let month = get_double_prop(obj, 1) as i32;
|
||||
let ref_day = get_double_prop(obj, 2) as i32;
|
||||
let calendar = get_calendar_id(obj, 3);
|
||||
let annotation = match show {
|
||||
ShowCalendar::Always => format!("[u-ca={calendar}]"),
|
||||
_ => format!("[!u-ca={calendar}]"),
|
||||
};
|
||||
NativeResult::Ok(
|
||||
vm.new_string_owned(format!("{}-{month:02}-{ref_day:02}{annotation}", format_iso_year(year as i128))),
|
||||
)
|
||||
}
|
||||
|
||||
/// `Temporal.PlainYearMonth.prototype.toJSON()`:默认形串,忽略参数。
|
||||
pub fn plain_year_month_to_json<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
plain_year_month_default_string(vm, args)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -285,6 +285,10 @@ pub struct BuiltinWorld {
|
||||
pub zoned_date_time_proto: P<JsObject>,
|
||||
pub plain_date_time_constructor: P<JsObject>,
|
||||
pub plain_date_time_proto: P<JsObject>,
|
||||
pub plain_month_day_constructor: P<JsObject>,
|
||||
pub plain_month_day_proto: P<JsObject>,
|
||||
pub plain_year_month_constructor: P<JsObject>,
|
||||
pub plain_year_month_proto: P<JsObject>,
|
||||
pub bigint_constructor: P<JsObject>,
|
||||
pub bigint_proto: P<JsObject>,
|
||||
/// `%IteratorPrototype%`:各集合迭代器原型的公共祖先,持有 `@@iterator`(返回自身)。
|
||||
@@ -600,6 +604,8 @@ fn wire_builtin_world_links(world: &BuiltinWorld) {
|
||||
wire_ctor_proto(&world.duration_constructor, &world.duration_proto);
|
||||
wire_ctor_proto(&world.zoned_date_time_constructor, &world.zoned_date_time_proto);
|
||||
wire_ctor_proto(&world.plain_date_time_constructor, &world.plain_date_time_proto);
|
||||
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);
|
||||
|
||||
// 所有内置构造器的 [[Prototype]] 指向 %FunctionPrototype%(ECMA-262 §17:
|
||||
@@ -627,12 +633,14 @@ fn wire_builtin_world_links(world: &BuiltinWorld) {
|
||||
&world.duration_constructor,
|
||||
&world.zoned_date_time_constructor,
|
||||
&world.plain_date_time_constructor,
|
||||
&world.plain_month_day_constructor,
|
||||
&world.plain_year_month_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>; 23] = [
|
||||
let non_object_protos: [&P<JsObject>; 25] = [
|
||||
&world.array_proto,
|
||||
&world.function_proto,
|
||||
&world.string_proto,
|
||||
@@ -653,6 +661,8 @@ fn wire_builtin_world_links(world: &BuiltinWorld) {
|
||||
&world.duration_proto,
|
||||
&world.zoned_date_time_proto,
|
||||
&world.plain_date_time_proto,
|
||||
&world.plain_month_day_proto,
|
||||
&world.plain_year_month_proto,
|
||||
&world.bigint_proto,
|
||||
&world.disposable_stack_proto,
|
||||
&world.async_disposable_stack_proto,
|
||||
@@ -911,7 +921,7 @@ impl BuiltinWorld {
|
||||
/// session 收尾(`teardown_heap_data`)与选择性重建收尾(`retire_replaced`)
|
||||
/// 的 P 字段枚举唯一入口:`BuiltinWorld` 新增 P 字段须在此同步补一行,否则
|
||||
/// 收尾时该字段属性区永久泄漏、重建重指/释放漏掉该字段。
|
||||
pub(crate) fn all_p_fields(&self) -> [&P<JsObject>; 100] {
|
||||
pub(crate) fn all_p_fields(&self) -> [&P<JsObject>; 104] {
|
||||
[
|
||||
&self.object_proto,
|
||||
&self.array_proto,
|
||||
@@ -1001,6 +1011,10 @@ impl BuiltinWorld {
|
||||
&self.zoned_date_time_proto,
|
||||
&self.plain_date_time_constructor,
|
||||
&self.plain_date_time_proto,
|
||||
&self.plain_month_day_constructor,
|
||||
&self.plain_month_day_proto,
|
||||
&self.plain_year_month_constructor,
|
||||
&self.plain_year_month_proto,
|
||||
&self.bigint_constructor,
|
||||
&self.bigint_proto,
|
||||
&self.iterator_proto,
|
||||
@@ -1148,6 +1162,10 @@ impl BuiltinWorld {
|
||||
BuiltinId::ZonedDateTimeProto => &self.zoned_date_time_proto,
|
||||
BuiltinId::PlainDateTimeConstructor => &self.plain_date_time_constructor,
|
||||
BuiltinId::PlainDateTimeProto => &self.plain_date_time_proto,
|
||||
BuiltinId::PlainMonthDayConstructor => &self.plain_month_day_constructor,
|
||||
BuiltinId::PlainMonthDayProto => &self.plain_month_day_proto,
|
||||
BuiltinId::PlainYearMonthConstructor => &self.plain_year_month_constructor,
|
||||
BuiltinId::PlainYearMonthProto => &self.plain_year_month_proto,
|
||||
BuiltinId::BigIntConstructor => &self.bigint_constructor,
|
||||
BuiltinId::BigIntProto => &self.bigint_proto,
|
||||
BuiltinId::Console => &self.console_object,
|
||||
@@ -1210,6 +1228,10 @@ impl BuiltinWorld {
|
||||
make_named_pair(string_forge, shape_forge, labels, "ZonedDateTime");
|
||||
let (plain_date_time_proto, plain_date_time_constructor) =
|
||||
make_named_pair(string_forge, shape_forge, labels, "PlainDateTime");
|
||||
let (plain_month_day_proto, plain_month_day_constructor) =
|
||||
make_named_pair(string_forge, shape_forge, labels, "PlainMonthDay");
|
||||
let (plain_year_month_proto, plain_year_month_constructor) =
|
||||
make_named_pair(string_forge, shape_forge, labels, "PlainYearMonth");
|
||||
let (bigint_proto, bigint_constructor) = make_named_pair(string_forge, shape_forge, labels, "BigInt");
|
||||
let stub_objects = Vec::new();
|
||||
|
||||
@@ -1314,6 +1336,10 @@ impl BuiltinWorld {
|
||||
zoned_date_time_proto,
|
||||
plain_date_time_constructor,
|
||||
plain_date_time_proto,
|
||||
plain_month_day_constructor,
|
||||
plain_month_day_proto,
|
||||
plain_year_month_constructor,
|
||||
plain_year_month_proto,
|
||||
bigint_constructor,
|
||||
bigint_proto,
|
||||
iterator_proto,
|
||||
@@ -1548,6 +1574,10 @@ impl BuiltinWorld {
|
||||
zoned_date_time_constructor,
|
||||
plain_date_time_proto,
|
||||
plain_date_time_constructor,
|
||||
plain_month_day_proto,
|
||||
plain_month_day_constructor,
|
||||
plain_year_month_proto,
|
||||
plain_year_month_constructor,
|
||||
) = if dirty.temporal {
|
||||
let (instant_proto, instant_constructor) = make_named_pair(string_forge, shape_forge, labels, "Instant");
|
||||
let (plain_date_proto, plain_date_constructor) =
|
||||
@@ -1559,6 +1589,10 @@ impl BuiltinWorld {
|
||||
make_named_pair(string_forge, shape_forge, labels, "ZonedDateTime");
|
||||
let (plain_date_time_proto, plain_date_time_constructor) =
|
||||
make_named_pair(string_forge, shape_forge, labels, "PlainDateTime");
|
||||
let (plain_month_day_proto, plain_month_day_constructor) =
|
||||
make_named_pair(string_forge, shape_forge, labels, "PlainMonthDay");
|
||||
let (plain_year_month_proto, plain_year_month_constructor) =
|
||||
make_named_pair(string_forge, shape_forge, labels, "PlainYearMonth");
|
||||
(
|
||||
P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
|
||||
P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
|
||||
@@ -1574,6 +1608,10 @@ impl BuiltinWorld {
|
||||
zoned_date_time_constructor,
|
||||
plain_date_time_proto,
|
||||
plain_date_time_constructor,
|
||||
plain_month_day_proto,
|
||||
plain_month_day_constructor,
|
||||
plain_year_month_proto,
|
||||
plain_year_month_constructor,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
@@ -1591,6 +1629,10 @@ impl BuiltinWorld {
|
||||
current.zoned_date_time_constructor.clone(),
|
||||
current.plain_date_time_proto.clone(),
|
||||
current.plain_date_time_constructor.clone(),
|
||||
current.plain_month_day_proto.clone(),
|
||||
current.plain_month_day_constructor.clone(),
|
||||
current.plain_year_month_proto.clone(),
|
||||
current.plain_year_month_constructor.clone(),
|
||||
)
|
||||
};
|
||||
let (bigint_proto, bigint_constructor) = if dirty.stubs {
|
||||
@@ -1732,6 +1774,10 @@ impl BuiltinWorld {
|
||||
zoned_date_time_proto,
|
||||
plain_date_time_constructor,
|
||||
plain_date_time_proto,
|
||||
plain_month_day_constructor,
|
||||
plain_month_day_proto,
|
||||
plain_year_month_constructor,
|
||||
plain_year_month_proto,
|
||||
bigint_constructor,
|
||||
bigint_proto,
|
||||
iterator_proto,
|
||||
|
||||
@@ -318,7 +318,7 @@ pub struct KernelSession {
|
||||
/// 维护注意:每个新增的 `BuiltinWorld` 对象字段都必须加到这里以及
|
||||
/// `KernelSession::dirty_since_snapshot()`,以便选择性重置重建正确的
|
||||
/// builtin 家族。
|
||||
pub const NUM_BUILTINS: usize = 90;
|
||||
pub const NUM_BUILTINS: usize = 94;
|
||||
|
||||
/// 内置对象枚举 id,与 `BuiltinWorld` 中的存储槽一一对应。
|
||||
///
|
||||
@@ -418,6 +418,10 @@ pub enum BuiltinId {
|
||||
SymDispose = 87,
|
||||
SuppressedErrorProto = 88,
|
||||
Console = 89,
|
||||
PlainMonthDayConstructor = 90,
|
||||
PlainMonthDayProto = 91,
|
||||
PlainYearMonthConstructor = 92,
|
||||
PlainYearMonthProto = 93,
|
||||
}
|
||||
|
||||
impl BuiltinId {
|
||||
@@ -515,6 +519,10 @@ impl BuiltinId {
|
||||
BuiltinId::SymDispose,
|
||||
BuiltinId::SuppressedErrorProto,
|
||||
BuiltinId::Console,
|
||||
BuiltinId::PlainMonthDayConstructor,
|
||||
BuiltinId::PlainMonthDayProto,
|
||||
BuiltinId::PlainYearMonthConstructor,
|
||||
BuiltinId::PlainYearMonthProto,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -776,7 +784,11 @@ impl KernelSession {
|
||||
|| gen(BuiltinId::ZonedDateTimeConstructor) != snap(BuiltinId::ZonedDateTimeConstructor)
|
||||
|| gen(BuiltinId::ZonedDateTimeProto) != snap(BuiltinId::ZonedDateTimeProto)
|
||||
|| gen(BuiltinId::PlainDateTimeConstructor) != snap(BuiltinId::PlainDateTimeConstructor)
|
||||
|| gen(BuiltinId::PlainDateTimeProto) != snap(BuiltinId::PlainDateTimeProto),
|
||||
|| gen(BuiltinId::PlainDateTimeProto) != snap(BuiltinId::PlainDateTimeProto)
|
||||
|| gen(BuiltinId::PlainMonthDayConstructor) != snap(BuiltinId::PlainMonthDayConstructor)
|
||||
|| gen(BuiltinId::PlainMonthDayProto) != snap(BuiltinId::PlainMonthDayProto)
|
||||
|| gen(BuiltinId::PlainYearMonthConstructor) != snap(BuiltinId::PlainYearMonthConstructor)
|
||||
|| gen(BuiltinId::PlainYearMonthProto) != snap(BuiltinId::PlainYearMonthProto),
|
||||
stubs: world.stub_objects.len() != snapshot.stub_objects_len
|
||||
|| stub_generations_dirty
|
||||
|| gen(BuiltinId::BigIntConstructor) != snap(BuiltinId::BigIntConstructor)
|
||||
|
||||
@@ -661,6 +661,10 @@ impl JsObject {
|
||||
pub const OBJ_TYPE_DISPOSABLE_STACK: u8 = 24;
|
||||
/// AsyncDisposableStack 对象(异步资源栈):状态盒与同步栈同构,预留 type_tag。
|
||||
pub const OBJ_TYPE_ASYNC_DISPOSABLE_STACK: u8 = 25;
|
||||
/// Temporal.PlainMonthDay 对象:月/日/参考年/日历 ID 存于 prop 0-3。
|
||||
pub const OBJ_TYPE_PLAIN_MONTH_DAY: u8 = 26;
|
||||
/// Temporal.PlainYearMonth 对象:年/月/参考日/日历 ID 存于 prop 0-3。
|
||||
pub const OBJ_TYPE_PLAIN_YEAR_MONTH: u8 = 27;
|
||||
/// `is_session_epoch` 字段中的 session 标记位。
|
||||
pub const SESSION_EPOCH_BIT: u8 = 0x01;
|
||||
/// `is_session_epoch` 字段中的 GC 标记位。
|
||||
@@ -764,6 +768,16 @@ impl JsObject {
|
||||
pub fn is_plain_date_time_obj(&self) -> bool {
|
||||
self.type_tag == Self::OBJ_TYPE_PLAIN_DATE_TIME
|
||||
}
|
||||
/// 是否 Temporal.PlainMonthDay 对象。
|
||||
#[inline]
|
||||
pub fn is_plain_month_day_obj(&self) -> bool {
|
||||
self.type_tag == Self::OBJ_TYPE_PLAIN_MONTH_DAY
|
||||
}
|
||||
/// 是否 Temporal.PlainYearMonth 对象。
|
||||
#[inline]
|
||||
pub fn is_plain_year_month_obj(&self) -> bool {
|
||||
self.type_tag == Self::OBJ_TYPE_PLAIN_YEAR_MONTH
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_arguments_obj(&self) -> bool {
|
||||
|
||||
@@ -957,6 +957,150 @@ pub fn bind_temporal(core: &Arc<KernelCore>, session: &KernelSession, global: &m
|
||||
PropAttributes::new(false, false, true),
|
||||
);
|
||||
|
||||
// Temporal.PlainMonthDay:构造器、3 个分量 getter 与 toString/toJSON。
|
||||
let plain_month_day_ctor_ptr = world.plain_month_day_constructor.as_ptr() as *mut JsObject;
|
||||
let plain_month_day_ctor = unsafe { &mut *plain_month_day_ctor_ptr };
|
||||
configure_native_constructor(
|
||||
plain_month_day_ctor,
|
||||
oxide_builtins::temporal::plain_month_day_constructor::<crate::vm::Vm> as *const (),
|
||||
2,
|
||||
);
|
||||
let length_si = core.perm_interner().intern("length").0;
|
||||
let length_shape = core.shape_forge().make_shape(plain_month_day_ctor.shape_id(), length_si);
|
||||
plain_month_day_ctor.set_shape_id(length_shape);
|
||||
plain_month_day_ctor.ensure_hash_props().push(JsValue::int(2));
|
||||
let length_pos = plain_month_day_ctor
|
||||
.hash_props_vec()
|
||||
.map_or(0, |props| props.len() as u32)
|
||||
.saturating_sub(1);
|
||||
plain_month_day_ctor.set_data_meta(length_pos, PropAttributes::new(false, false, true));
|
||||
|
||||
let plain_month_day_proto_ptr = world.plain_month_day_proto.as_ptr() as *mut JsObject;
|
||||
let plain_month_day_proto = unsafe { &mut *plain_month_day_proto_ptr };
|
||||
for (name, getter) in [
|
||||
("day", oxide_builtins::temporal::plain_month_day_day::<crate::vm::Vm> as *const ()),
|
||||
(
|
||||
"monthCode",
|
||||
oxide_builtins::temporal::plain_month_day_month_code::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"calendarId",
|
||||
oxide_builtins::temporal::plain_month_day_calendar_id::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
] {
|
||||
bind_accessor_getter(core, session, plain_month_day_proto, name, getter);
|
||||
}
|
||||
apply_binding_table(
|
||||
world,
|
||||
plain_month_day_proto,
|
||||
core,
|
||||
&[
|
||||
(
|
||||
"toString",
|
||||
oxide_builtins::temporal::plain_month_day_to_string::<crate::vm::Vm> as *const (),
|
||||
0,
|
||||
),
|
||||
(
|
||||
"toJSON",
|
||||
oxide_builtins::temporal::plain_month_day_to_json::<crate::vm::Vm> as *const (),
|
||||
0,
|
||||
),
|
||||
],
|
||||
);
|
||||
bind_well_known_data_property(
|
||||
core,
|
||||
plain_month_day_proto,
|
||||
9,
|
||||
JsValue::perm_string(
|
||||
core.perm_interner()
|
||||
.string_ptr(core.perm_interner().intern("Temporal.PlainMonthDay").0),
|
||||
),
|
||||
PropAttributes::new(false, false, true),
|
||||
);
|
||||
|
||||
// Temporal.PlainYearMonth:构造器、10 个 getter 与 toString/toJSON。
|
||||
let plain_year_month_ctor_ptr = world.plain_year_month_constructor.as_ptr() as *mut JsObject;
|
||||
let plain_year_month_ctor = unsafe { &mut *plain_year_month_ctor_ptr };
|
||||
configure_native_constructor(
|
||||
plain_year_month_ctor,
|
||||
oxide_builtins::temporal::plain_year_month_constructor::<crate::vm::Vm> as *const (),
|
||||
2,
|
||||
);
|
||||
let length_si = core.perm_interner().intern("length").0;
|
||||
let length_shape = core.shape_forge().make_shape(plain_year_month_ctor.shape_id(), length_si);
|
||||
plain_year_month_ctor.set_shape_id(length_shape);
|
||||
plain_year_month_ctor.ensure_hash_props().push(JsValue::int(2));
|
||||
let length_pos = plain_year_month_ctor
|
||||
.hash_props_vec()
|
||||
.map_or(0, |props| props.len() as u32)
|
||||
.saturating_sub(1);
|
||||
plain_year_month_ctor.set_data_meta(length_pos, PropAttributes::new(false, false, true));
|
||||
|
||||
let plain_year_month_proto_ptr = world.plain_year_month_proto.as_ptr() as *mut JsObject;
|
||||
let plain_year_month_proto = unsafe { &mut *plain_year_month_proto_ptr };
|
||||
for (name, getter) in [
|
||||
("year", oxide_builtins::temporal::plain_year_month_year::<crate::vm::Vm> as *const ()),
|
||||
("month", oxide_builtins::temporal::plain_year_month_month::<crate::vm::Vm> as *const ()),
|
||||
(
|
||||
"monthCode",
|
||||
oxide_builtins::temporal::plain_year_month_month_code::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"calendarId",
|
||||
oxide_builtins::temporal::plain_year_month_calendar_id::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"daysInMonth",
|
||||
oxide_builtins::temporal::plain_year_month_days_in_month::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"daysInYear",
|
||||
oxide_builtins::temporal::plain_year_month_days_in_year::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"monthsInYear",
|
||||
oxide_builtins::temporal::plain_year_month_months_in_year::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"inLeapYear",
|
||||
oxide_builtins::temporal::plain_year_month_in_leap_year::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
("era", oxide_builtins::temporal::plain_year_month_era::<crate::vm::Vm> as *const ()),
|
||||
(
|
||||
"eraYear",
|
||||
oxide_builtins::temporal::plain_year_month_era_year::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
] {
|
||||
bind_accessor_getter(core, session, plain_year_month_proto, name, getter);
|
||||
}
|
||||
apply_binding_table(
|
||||
world,
|
||||
plain_year_month_proto,
|
||||
core,
|
||||
&[
|
||||
(
|
||||
"toString",
|
||||
oxide_builtins::temporal::plain_year_month_to_string::<crate::vm::Vm> as *const (),
|
||||
0,
|
||||
),
|
||||
(
|
||||
"toJSON",
|
||||
oxide_builtins::temporal::plain_year_month_to_json::<crate::vm::Vm> as *const (),
|
||||
0,
|
||||
),
|
||||
],
|
||||
);
|
||||
bind_well_known_data_property(
|
||||
core,
|
||||
plain_year_month_proto,
|
||||
9,
|
||||
JsValue::perm_string(
|
||||
core.perm_interner()
|
||||
.string_ptr(core.perm_interner().intern("Temporal.PlainYearMonth").0),
|
||||
),
|
||||
PropAttributes::new(false, false, true),
|
||||
);
|
||||
|
||||
// 把子对象挂到 Temporal 命名空间对象上,再把 Temporal 挂到 global。
|
||||
bind_global_value(core, temporal, "Now", JsValue::from_js_object(now_ptr));
|
||||
bind_global_value(core, temporal, "Instant", JsValue::from_js_object(instant_ctor_ptr));
|
||||
@@ -965,5 +1109,7 @@ pub fn bind_temporal(core: &Arc<KernelCore>, session: &KernelSession, global: &m
|
||||
bind_global_value(core, temporal, "PlainDateTime", JsValue::from_js_object(plain_date_time_ctor_ptr));
|
||||
bind_global_value(core, temporal, "Duration", JsValue::from_js_object(duration_ctor_ptr));
|
||||
bind_global_value(core, temporal, "ZonedDateTime", JsValue::from_js_object(zoned_date_time_ctor_ptr));
|
||||
bind_global_value(core, temporal, "PlainMonthDay", JsValue::from_js_object(plain_month_day_ctor_ptr));
|
||||
bind_global_value(core, temporal, "PlainYearMonth", JsValue::from_js_object(plain_year_month_ctor_ptr));
|
||||
bind_global_value(core, global, "Temporal", JsValue::from_js_object(temporal_ptr));
|
||||
}
|
||||
|
||||
@@ -2604,3 +2604,196 @@ fn duration_total_relative_zdt() {
|
||||
.unwrap();
|
||||
assert_eq!(r.as_double(), 1.0);
|
||||
}
|
||||
|
||||
// -- Temporal.PlainMonthDay / PlainYearMonth(E 批)--
|
||||
|
||||
#[test]
|
||||
fn plain_month_day_ctor_basic_and_instanceof() {
|
||||
let mut vm = Vm::new();
|
||||
// 构造成功:instanceof / length / name / 槽值 / 参考年默认 1972 经 always 形回读。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(() => {
|
||||
const md = new Temporal.PlainMonthDay(5, 2);
|
||||
return [
|
||||
md instanceof Temporal.PlainMonthDay,
|
||||
Temporal.PlainMonthDay.length,
|
||||
Temporal.PlainMonthDay.name,
|
||||
Temporal.PlainYearMonth.length,
|
||||
md.monthCode,
|
||||
md.day,
|
||||
md.calendarId,
|
||||
md.toString({calendarName: 'always'}),
|
||||
].join('|');
|
||||
})()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "true|2|PlainMonthDay|2|M05|2|iso8601|1972-05-02[u-ca=iso8601]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_month_day_ctor_rejects() {
|
||||
let mut vm = Vm::new();
|
||||
// BigInt/Symbol 分量 → TypeError;NaN/±Inf → RangeError;缺参 → RangeError;
|
||||
// 非闰参考年 2/29 → RangeError,闰参考年 1972 合法;非 new 调用 → TypeError。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(() => {
|
||||
const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } };
|
||||
return [
|
||||
kind(() => new Temporal.PlainMonthDay(1n, 1)),
|
||||
kind(() => new Temporal.PlainMonthDay(Symbol(), 1)),
|
||||
kind(() => new Temporal.PlainMonthDay(NaN, 1)),
|
||||
kind(() => new Temporal.PlainMonthDay(1, Infinity)),
|
||||
kind(() => new Temporal.PlainMonthDay()),
|
||||
kind(() => new Temporal.PlainMonthDay(2, 29, 'iso8601', 2023)),
|
||||
new Temporal.PlainMonthDay(2, 29).day,
|
||||
kind(() => Temporal.PlainMonthDay(1, 2)),
|
||||
].join('|');
|
||||
})()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
str_val(&vm, r),
|
||||
"TypeError|TypeError|RangeError|RangeError|RangeError|RangeError|29|TypeError"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_month_day_ctor_calendar_argument() {
|
||||
let mut vm = Vm::new();
|
||||
// 日历参数:{} → TypeError;函数对象按缺省 iso8601;大小写不敏感白名单;
|
||||
// 未知字符串 → RangeError。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(() => {
|
||||
const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } };
|
||||
return [
|
||||
kind(() => new Temporal.PlainMonthDay(1, 1, {})),
|
||||
new Temporal.PlainMonthDay(1, 1, () => 'iso8601').calendarId,
|
||||
new Temporal.PlainMonthDay(1, 1, 'iSo8601').calendarId,
|
||||
kind(() => new Temporal.PlainMonthDay(1, 1, 'local')),
|
||||
].join('|');
|
||||
})()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "TypeError|iso8601|iso8601|RangeError");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_year_month_ctor_limits_and_ref_day() {
|
||||
let mut vm = Vm::new();
|
||||
// 年月边界:-271821-03 与 275760-10 越界抛 RangeError,相邻月合法;
|
||||
// refISODay 缺省 1,须落在目标月长内;非 new 调用 → TypeError;
|
||||
// NaN 分量(undefined/不可解析串)→ RangeError,null → 0。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(() => {
|
||||
const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } };
|
||||
const ym = new Temporal.PlainYearMonth(-271821, 4, 'iso8601', 18);
|
||||
return [
|
||||
kind(() => new Temporal.PlainYearMonth(-271821, 3)),
|
||||
kind(() => new Temporal.PlainYearMonth(275760, 10)),
|
||||
new Temporal.PlainYearMonth(-271821, 4).monthCode,
|
||||
new Temporal.PlainYearMonth(275760, 9).monthCode,
|
||||
ym.toString({calendarName: 'always'}),
|
||||
new Temporal.PlainYearMonth(2021, 2).toString({calendarName: 'always'}),
|
||||
kind(() => new Temporal.PlainYearMonth(2021, 2, 'iso8601', 0)),
|
||||
kind(() => new Temporal.PlainYearMonth(2021, 2, 'iso8601', 29)),
|
||||
kind(() => new Temporal.PlainYearMonth(undefined, 11)),
|
||||
kind(() => new Temporal.PlainYearMonth('invalid', 11)),
|
||||
new Temporal.PlainYearMonth(null, 11).year,
|
||||
kind(() => new Temporal.PlainMonthDay(undefined, 24)),
|
||||
kind(() => Temporal.PlainYearMonth(1970, 1)),
|
||||
].join('|');
|
||||
})()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
str_val(&vm, r),
|
||||
"RangeError|RangeError|M04|M09|-271821-04-18[u-ca=iso8601]|2021-02-01[u-ca=iso8601]|RangeError|RangeError|RangeError|RangeError|0|RangeError|TypeError"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_year_month_getters() {
|
||||
let mut vm = Vm::new();
|
||||
// 槽值 getter + 日历直读 + ISO 日历无纪元(era/eraYear 恒 undefined)。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(() => {
|
||||
const ym = new Temporal.PlainYearMonth(-1, 8);
|
||||
return [
|
||||
ym.year, ym.month, ym.monthCode, ym.calendarId,
|
||||
ym.daysInMonth, ym.daysInYear, ym.monthsInYear, ym.inLeapYear,
|
||||
String(ym.era), String(ym.eraYear),
|
||||
].join('|');
|
||||
})()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "-1|8|M08|iso8601|31|365|12|false|undefined|undefined");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_month_day_to_string_and_to_json() {
|
||||
let mut vm = Vm::new();
|
||||
// 默认/auto/never 同形;critical 带 ! 注解;toJSON = 默认形且忽略参数;
|
||||
// 非法 calendarName → RangeError;options 非对象 → TypeError。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(() => {
|
||||
const md = new Temporal.PlainMonthDay(5, 2);
|
||||
const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } };
|
||||
return [
|
||||
md.toString(),
|
||||
md.toString({calendarName: 'auto'}),
|
||||
md.toString({calendarName: 'never'}),
|
||||
md.toString({calendarName: 'critical'}),
|
||||
md.toJSON(),
|
||||
kind(() => md.toString({calendarName: 'ALWAYS'})),
|
||||
kind(() => md.toString(1)),
|
||||
md.toString({}),
|
||||
].join('|');
|
||||
})()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
str_val(&vm, r),
|
||||
"05-02|05-02|05-02|1972-05-02[!u-ca=iso8601]|05-02|RangeError|TypeError|05-02"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_year_month_to_string_year_format() {
|
||||
let mut vm = Vm::new();
|
||||
// 年份格式化:-1 → -000001(6 位带符号);always 形补参考日。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(() => {
|
||||
const ym = new Temporal.PlainYearMonth(-1, 8);
|
||||
return [ym.toString(), ym.toString({calendarName: 'always'}), ym.toJSON()].join('|');
|
||||
})()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "-000001-08|-000001-08-01[u-ca=iso8601]|-000001-08");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_month_day_and_year_month_branding() {
|
||||
let mut vm = Vm::new();
|
||||
// receiver 非实例 → TypeError(toString / getter 均校验品牌)。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(() => {
|
||||
const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } };
|
||||
return [
|
||||
kind(() => Temporal.PlainMonthDay.prototype.toString.call(1)),
|
||||
kind(() => Temporal.PlainMonthDay.prototype.day.call({})),
|
||||
kind(() => Temporal.PlainYearMonth.prototype.toString.call('x')),
|
||||
kind(() => Temporal.PlainYearMonth.prototype.year.call(null)),
|
||||
].join('|');
|
||||
})()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "TypeError|TypeError|TypeError|TypeError");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user