From 3e028ab9086f7587120f05f7bb34bf9239a01b4c Mon Sep 17 00:00:00 2001 From: Norbert Garfield Date: Sat, 14 May 2022 18:36:50 +0000 Subject: [PATCH 1/9] Implement InitializeDateTimeFormat --- Cargo.lock | 148 +- boa_engine/Cargo.toml | 2 + .../src/builtins/intl/date_time_format.rs | 1323 ++++++++++++++++- boa_engine/src/builtins/intl/mod.rs | 9 +- boa_engine/src/builtins/intl/tests.rs | 291 +++- boa_engine/src/object/mod.rs | 11 + 6 files changed, 1749 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c82789bd8e8..817f7897641 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -95,11 +95,13 @@ dependencies = [ "boa_profiler", "boa_unicode", "chrono", + "chrono-tz", "criterion", "dyn-clone", "fast-float", "float-cmp", "gc", + "icu", "icu_datetime", "icu_locale_canonicalizer", "icu_locid", @@ -202,7 +204,7 @@ checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ "lazy_static", "memchr", - "regex-automata", + "regex-automata 0.1.10", "serde", ] @@ -252,6 +254,28 @@ dependencies = [ "winapi", ] +[[package]] +name = "chrono-tz" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58549f1842da3080ce63002102d5bc954c7bc843d4f47818e642abdc36253552" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db058d493fb2f65f41861bfed7e3fe6335264a9f0f92710cab5bdf01fef09069" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + [[package]] name = "clap" version = "2.34.0" @@ -654,6 +678,24 @@ dependencies = [ "libc", ] +[[package]] +name = "icu" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15a4e90a2faa6719f4b3b1dac871d1f2794474d453b8e6ca1264062c4bf2c9da" +dependencies = [ + "fixed_decimal", + "icu_calendar", + "icu_datetime", + "icu_decimal", + "icu_list", + "icu_locale_canonicalizer", + "icu_locid", + "icu_plurals", + "icu_properties", + "writeable", +] + [[package]] name = "icu_calendar" version = "0.6.0" @@ -668,6 +710,19 @@ dependencies = [ "zerovec", ] +[[package]] +name = "icu_codepointtrie" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cdb6b96093158ec0031f9831283085cf897cf4bffc9a1a35a8360a777141058" +dependencies = [ + "displaydoc", + "icu_uniset", + "yoke", + "zerofrom", + "zerovec", +] + [[package]] name = "icu_datetime" version = "0.6.0" @@ -688,6 +743,33 @@ dependencies = [ "zerovec", ] +[[package]] +name = "icu_decimal" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614ff51266354e8c8d75bfc65f806fbc0d0177da079c2482402cfc20b72de47d" +dependencies = [ + "displaydoc", + "fixed_decimal", + "icu_locid", + "icu_provider", + "writeable", +] + +[[package]] +name = "icu_list" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c144d074b8de0f6adcb6941ac4544abf83483fa5681154dcc361253c3a122c5c" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider", + "regex-automata 0.2.0", + "writeable", + "zerovec", +] + [[package]] name = "icu_locale_canonicalizer" version = "0.6.0" @@ -730,6 +812,19 @@ dependencies = [ "zerovec", ] +[[package]] +name = "icu_properties" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ca8f26685a463ff47dc0d9f7b270e8d955d3c2dd9f748292af14940b659671" +dependencies = [ + "displaydoc", + "icu_codepointtrie", + "icu_provider", + "icu_uniset", + "zerovec", +] + [[package]] name = "icu_provider" version = "0.6.0" @@ -783,6 +878,19 @@ dependencies = [ "icu_provider_blob", ] +[[package]] +name = "icu_uniset" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecdc2859b6efd75ae22e6350e62f21c87cfe3cfdc11bef6f9565995e88d14ba9" +dependencies = [ + "displaydoc", + "tinystr", + "yoke", + "zerofrom", + "zerovec", +] + [[package]] name = "indexmap" version = "1.8.2" @@ -1072,6 +1180,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "parse-zoneinfo" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" +dependencies = [ + "regex", +] + [[package]] name = "perf-event-open-sys" version = "1.0.1" @@ -1092,6 +1209,16 @@ dependencies = [ "proc-macro-hack", ] +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator", + "phf_shared", +] + [[package]] name = "phf_generator" version = "0.10.0" @@ -1123,6 +1250,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" dependencies = [ "siphasher", + "uncased", ] [[package]] @@ -1324,6 +1452,15 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +[[package]] +name = "regex-automata" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9368763f5a9b804326f3af749e16f9abf378d227bcdee7634b13d8f17793782" +dependencies = [ + "memchr", +] + [[package]] name = "regex-syntax" version = "0.6.26" @@ -1701,6 +1838,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +[[package]] +name = "uncased" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-general-category" version = "0.5.1" diff --git a/boa_engine/Cargo.toml b/boa_engine/Cargo.toml index 3b20b2df0a3..8cf676f9b01 100644 --- a/boa_engine/Cargo.toml +++ b/boa_engine/Cargo.toml @@ -57,6 +57,8 @@ icu_plurals = { version = "0.6.0", features = ["serde"], optional = true } icu_provider = { version = "0.6.0", optional = true } icu_testdata = {version = "0.6.0", optional = true} sys-locale = { version = "0.2.0", optional = true } +icu = "0.6.0" +chrono-tz = "0.6.0" [dev-dependencies] criterion = "0.3.5" diff --git a/boa_engine/src/builtins/intl/date_time_format.rs b/boa_engine/src/builtins/intl/date_time_format.rs index 333c224bc41..902ae9edfef 100644 --- a/boa_engine/src/builtins/intl/date_time_format.rs +++ b/boa_engine/src/builtins/intl/date_time_format.rs @@ -8,6 +8,11 @@ //! [spec]: https://tc39.es/ecma402/#datetimeformat-objects use crate::{ + builtins::intl::{ + canonicalize_locale_list, default_locale, get_number_option, get_option, resolve_locale, + DateTimeFormatRecord, GetOptionType, LocaleDataRecord, + }, + builtins::JsArgs, context::intrinsics::StandardConstructors, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsFunction, JsObject, @@ -18,29 +23,44 @@ use crate::{ use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; +use chrono_tz::Tz; +use icu::{ + calendar::{buddhist::Buddhist, japanese::Japanese, Gregorian}, + datetime, + datetime::{ + options::{components, length, preferences}, + DateTimeFormatOptions, + }, + locid::Locale, +}; +use icu_provider::inv::InvariantDataProvider; +use rustc_hash::FxHashMap; +use std::cmp::{max, min}; /// JavaScript `Intl.DateTimeFormat` object. #[derive(Debug, Clone, Trace, Finalize)] pub struct DateTimeFormat { initialized_date_time_format: bool, locale: JsString, - calendar: JsString, - numbering_system: JsString, + calendar: JsValue, + numbering_system: JsValue, time_zone: JsString, - weekday: JsString, - era: JsString, - year: JsString, - month: JsString, - day: JsString, + weekday: JsValue, + era: JsValue, + year: JsValue, + month: JsValue, + day: JsValue, day_period: JsString, - hour: JsString, - minute: JsString, - second: JsString, + hour: JsValue, + minute: JsValue, + second: JsValue, fractional_second_digits: JsString, - time_zone_name: JsString, - hour_cycle: JsString, + time_zone_name: JsValue, + hour_cycle: JsValue, pattern: JsString, bound_format: JsString, + date_style: JsValue, + time_style: JsValue, } impl DateTimeFormat { @@ -67,7 +87,7 @@ impl DateTimeFormat { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat pub(crate) fn constructor( new_target: &JsValue, - _args: &[JsValue], + args: &[JsValue], context: &mut Context, ) -> JsResult { // 1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget. @@ -85,27 +105,34 @@ impl DateTimeFormat { ObjectData::date_time_format(Box::new(Self { initialized_date_time_format: true, locale: JsString::from("en-US"), - calendar: JsString::from("gregory"), - numbering_system: JsString::from("arab"), + calendar: JsValue::String(JsString::from("gregory")), + numbering_system: JsValue::String(JsString::from("arab")), time_zone: JsString::from("UTC"), - weekday: JsString::from("narrow"), - era: JsString::from("narrow"), - year: JsString::from("numeric"), - month: JsString::from("narrow"), - day: JsString::from("numeric"), + weekday: JsValue::String(JsString::from("narrow")), + era: JsValue::String(JsString::from("narrow")), + year: JsValue::String(JsString::from("numeric")), + month: JsValue::String(JsString::from("narrow")), + day: JsValue::String(JsString::from("numeric")), day_period: JsString::from("narrow"), - hour: JsString::from("numeric"), - minute: JsString::from("numeric"), - second: JsString::from("numeric"), + hour: JsValue::String(JsString::from("numeric")), + minute: JsValue::String(JsString::from("numeric")), + second: JsValue::String(JsString::from("numeric")), fractional_second_digits: JsString::from(""), - time_zone_name: JsString::from(""), - hour_cycle: JsString::from("h24"), + time_zone_name: JsValue::String(JsString::from("")), + hour_cycle: JsValue::String(JsString::from("h24")), pattern: JsString::from("{hour}:{minute}"), bound_format: JsString::from("undefined"), + date_style: JsValue::String(JsString::from("full")), + time_style: JsValue::String(JsString::from("full")), })), ); - // TODO 3. Perform ? InitializeDateTimeFormat(dateTimeFormat, locales, options). + // 3. Perform ? InitializeDateTimeFormat(dateTimeFormat, locales, options). + let maybe_locales = args.get_or_undefined(0); + let maybe_options = args.get_or_undefined(1); + let date_time_format = + initialize_date_time_format(&date_time_format, maybe_locales, maybe_options, context)?; + // TODO 4. If the implementation supports the normative optional constructor mode of 4.3 Note 1, then // TODO a. Let this be the this value. // TODO b. Return ? ChainDateTimeFormat(dateTimeFormat, NewTarget, this). @@ -120,7 +147,6 @@ impl DateTimeFormat { /// /// Since `required` and `defaults` differ only in the `any` and `all` variants, /// we combine both in a single variant `AnyAll`. -#[allow(unused)] #[derive(Debug, PartialEq)] pub(crate) enum DateTimeReqs { Date, @@ -135,7 +161,6 @@ pub(crate) enum DateTimeReqs { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma402/#sec-todatetimeoptions -#[allow(unused)] pub(crate) fn to_date_time_options( options: &JsValue, required: &DateTimeReqs, @@ -234,3 +259,1245 @@ pub(crate) fn to_date_time_options( // 13. Return options. Ok(options) } + +/// The abstract operation `is_terminal` determines whether `opt` `JsValue` contains a +/// nonterminal symbol. +/// +/// More information: +/// - [Unicode LDML reference][spec] +/// +/// [spec]: https://www.unicode.org/reports/tr35/#Unicode_locale_identifier +pub(crate) fn is_terminal(opt: &JsValue, context: &mut Context) -> bool { + let opt_str = opt + .to_string(context) + .unwrap_or_else(|_| JsString::empty()) + .to_string(); + if opt_str.is_empty() { + return true; + } + + // nonterminal = alphanum{3,8} (sep alphanum{3,8})* + // Any number of alphanumeric characters between 3 and 8, + // separated by dash, + // followed by any number of alphanumeric characters between 3 and 8 (repeated) + + // First, replace all underscores (legacy format) with dashes. + let opt_str = opt_str.replace('_', "-"); + + // Next, split the string by dashes. + let options_vec: Vec<&str> = opt_str.split('-').collect(); + + // If the vector contains less than 1 element, that cannot be a nonterminal. + if options_vec.is_empty() { + return true; + } + + // Check that each slice is has length between 3 and 8 and all characters are alphanumeric. + for option in options_vec { + if option.len() < 3 || option.len() > 8 { + return true; + } + + if option.chars().any(|character| !character.is_alphanumeric()) { + return true; + } + } + + false +} + +/// The value of the `LocaleData` internal slot is implementation-defined +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma402/#sec-intl.datetimeformat-internal-slots +fn build_locale_data(context: &Context) -> LocaleDataRecord { + let mut locale_data_entry = FxHashMap::default(); + let nu_values = vec![JsString::new("arab")]; + locale_data_entry.insert(JsString::new("nu"), nu_values); + + let hc_values = vec![ + JsString::new("h11"), + JsString::new("h12"), + JsString::new("h23"), + JsString::new("h24"), + ]; + locale_data_entry.insert(JsString::new("hc"), hc_values); + + let ca_values = vec![JsString::new("gregory")]; + locale_data_entry.insert(JsString::new("ca"), ca_values); + + let hour_cycle_values = vec![JsString::new("h24")]; + locale_data_entry.insert(JsString::new("hourCycle"), hour_cycle_values); + + let mut locale_data = FxHashMap::default(); + let default_locale_str = default_locale(context.icu().locale_canonicalizer()).to_string(); + locale_data.insert(JsString::new(default_locale_str), locale_data_entry); + + locale_data +} + +/// The value of the `RelevantExtensionKeys` internal slot is « "ca", "hc", "nu" ». +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma402/#sec-intl.datetimeformat-internal-slots +fn build_relevant_ext_keys() -> Vec { + vec![ + JsString::new("ca"), + JsString::new("hc"), + JsString::new("nu"), + ] +} + +/// `AvailableLocales` is a `List` that contains structurally valid and canonicalized Unicode +/// BCP 47 locale identifiers identifying the locales for which the implementation provides the +/// functionality of the constructed objects. Language tags on the list must not have a Unicode +/// locale extension sequence. The list must include the value returned by the `DefaultLocale` +/// abstract operation, and must not include duplicates. Implementations must include in +/// `AvailableLocales` locales that can serve as fallbacks in the algorithm used to resolve locales. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma402/#sec-internal-slots +fn build_available_locales(context: &Context) -> Vec { + let default_locale_str = default_locale(context.icu().locale_canonicalizer()).to_string(); + let canonicalized_locale = default_locale_str.replace('_', "-"); + let splitted_locale: Vec<&str> = canonicalized_locale.split('-').collect(); + let default_locale_fallback = splitted_locale + .get(0) + .expect("Failed to split default locale"); + let available_locales = vec![ + JsString::new(default_locale_str), + JsString::new(default_locale_fallback), + ]; + + available_locales +} + +/// The `DefaultTimeZone` abstract operation returns a String value representing the valid and +/// canonicalized time zone name for the host environment's current time zone. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma402/#sec-defaulttimezone +fn default_time_zone() -> JsString { + // FIXME fetch default time zone from the environment + JsString::new("UTC") +} + +/// The abstract operation `IsValidTimeZoneName` takes argument `timeZone`, a String value, and +/// verifies that it represents a valid Zone or Link name of the IANA Time Zone Database. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma402/#sec-isvalidtimezonename +pub(crate) fn is_valid_time_zone_name(time_zone: &JsString) -> bool { + let time_zone_str = time_zone.to_string(); + let maybe_time_zone: Result = time_zone_str.parse(); + maybe_time_zone.is_ok() +} + +/// The abstract operation `CanonicalizeTimeZoneName` takes argument `timeZone` (a String value +/// that is a valid time zone name as verified by `IsValidTimeZoneName`). It returns the canonical +/// and case-regularized form of timeZone. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma402/#sec-canonicalizetimezonename +pub(crate) fn canonicalize_time_zone_name(time_zone: &JsString) -> JsString { + let time_zone_str = time_zone.to_string(); + let canonical_tz: Tz = time_zone_str + .parse() + .expect("CanonicalizeTimeZoneName: time zone name is not supported"); + JsString::from(canonical_tz.name()) +} + +/// Converts `hour_cycle_str` to `preferences::HourCycle` +pub(crate) fn string_to_hour_cycle(hour_cycle_str: &JsString) -> preferences::HourCycle { + let mut string_to_hc_map = FxHashMap::::default(); + string_to_hc_map.insert(JsString::from("h11"), preferences::HourCycle::H11); + string_to_hc_map.insert(JsString::from("h12"), preferences::HourCycle::H12); + string_to_hc_map.insert(JsString::from("h23"), preferences::HourCycle::H23); + string_to_hc_map.insert(JsString::from("h24"), preferences::HourCycle::H24); + + let hour_cycle = string_to_hc_map + .get(hour_cycle_str) + .expect("Invalid hour cycle"); + *hour_cycle +} + +/// Converts `JsValue` to `length::Date` +pub(crate) fn value_to_date_style( + date_style_val: &JsValue, + context: &mut Context, +) -> Option { + if date_style_val.is_undefined() { + return None; + } + let date_style_str = date_style_val + .to_string(context) + .unwrap_or_else(|_| JsString::empty()); + let mut string_to_style_map = FxHashMap::>::default(); + string_to_style_map.insert(JsString::from("full"), Some(length::Date::Full)); + string_to_style_map.insert(JsString::from("long"), Some(length::Date::Long)); + string_to_style_map.insert(JsString::from("medium"), Some(length::Date::Medium)); + string_to_style_map.insert(JsString::from("short"), Some(length::Date::Short)); + + match string_to_style_map.get(&date_style_str) { + Some(date_style) => *date_style, + None => None, + } +} + +/// Converts `JsValue` to `length::Time` +pub(crate) fn value_to_time_style( + time_style_val: &JsValue, + context: &mut Context, +) -> Option { + if time_style_val.is_undefined() { + return None; + } + let time_style_str = time_style_val + .to_string(context) + .unwrap_or_else(|_| JsString::empty()); + let mut string_to_style_map = FxHashMap::>::default(); + string_to_style_map.insert(JsString::from("full"), Some(length::Time::Full)); + string_to_style_map.insert(JsString::from("long"), Some(length::Time::Long)); + string_to_style_map.insert(JsString::from("medium"), Some(length::Time::Medium)); + string_to_style_map.insert(JsString::from("short"), Some(length::Time::Short)); + + match string_to_style_map.get(&time_style_str) { + Some(time_style) => *time_style, + None => None, + } +} + +/// Converts `components::Text` to corresponding `JsString` +pub(crate) fn text_to_value(maybe_txt: Option) -> JsValue { + match maybe_txt { + None => JsValue::undefined(), + Some(txt) => match txt { + components::Text::Long => JsValue::String(JsString::from("long")), + components::Text::Short => JsValue::String(JsString::from("short")), + components::Text::Narrow => JsValue::String(JsString::from("narrow")), + _ => JsValue::undefined(), + }, + } +} +/// Converts `components::Year` to corresponding `JsString` +pub(crate) fn year_to_value(maybe_year: Option) -> JsValue { + match maybe_year { + None => JsValue::undefined(), + Some(year) => match year { + components::Year::Numeric => JsValue::String(JsString::from("numeric")), + components::Year::TwoDigit => JsValue::String(JsString::from("2-digit")), + components::Year::NumericWeekOf => JsValue::String(JsString::from("numericWeek")), + components::Year::TwoDigitWeekOf => JsValue::String(JsString::from("2-digitWeek")), + _ => JsValue::undefined(), + }, + } +} + +/// Converts `components::Month` to corresponding `JsString` +pub(crate) fn month_to_value(maybe_month: Option) -> JsValue { + match maybe_month { + None => JsValue::undefined(), + Some(month_val) => match month_val { + components::Month::Numeric => JsValue::String(JsString::from("numeric")), + components::Month::TwoDigit => JsValue::String(JsString::from("2-digit")), + components::Month::Long => JsValue::String(JsString::from("long")), + components::Month::Short => JsValue::String(JsString::from("short")), + components::Month::Narrow => JsValue::String(JsString::from("narrow")), + _ => JsValue::undefined(), + }, + } +} + +/// Converts `components::Day` to corresponding `JsString` +pub(crate) fn day_to_value(maybe_day: Option) -> JsValue { + match maybe_day { + None => JsValue::undefined(), + Some(day_val) => match day_val { + components::Day::NumericDayOfMonth => JsValue::String(JsString::from("numeric")), + components::Day::TwoDigitDayOfMonth => JsValue::String(JsString::from("2-digit")), + components::Day::DayOfWeekInMonth => JsValue::String(JsString::from("dayOfWeek")), + _ => JsValue::undefined(), + }, + } +} + +/// Converts `components::Numeric` to corresponding `JsString` +pub(crate) fn numeric_to_value(maybe_num: Option) -> JsValue { + match maybe_num { + None => JsValue::undefined(), + Some(num_val) => match num_val { + components::Numeric::Numeric => JsValue::String(JsString::from("numeric")), + components::Numeric::TwoDigit => JsValue::String(JsString::from("2-digit")), + _ => JsValue::undefined(), + }, + } +} + +/// Converts `components::TimeZoneName` to corresponding `JsString` +pub(crate) fn time_zone_to_value(maybe_tz: Option) -> JsValue { + match maybe_tz { + None => JsValue::undefined(), + Some(tz_val) => match tz_val { + components::TimeZoneName::ShortSpecific => JsValue::String(JsString::from("short")), + components::TimeZoneName::LongSpecific => JsValue::String(JsString::from("long")), + components::TimeZoneName::GmtOffset => JsValue::String(JsString::from("gmt")), + components::TimeZoneName::ShortGeneric => { + JsValue::String(JsString::from("shortGeneric")) + } + components::TimeZoneName::LongGeneric => JsValue::String(JsString::from("longGeneric")), + _ => JsValue::undefined(), + }, + } +} + +/// Fetches field with name `property` from `format` bag +fn get_format_field(format: &components::Bag, property: &str) -> JsValue { + if property == "weekday" { + text_to_value(format.weekday) + } else if property == "era" { + text_to_value(format.era) + } else if property == "year" { + year_to_value(format.year) + } else if property == "month" { + month_to_value(format.month) + } else if property == "day" { + day_to_value(format.day) + } else if property == "hour" { + numeric_to_value(format.hour) + } else if property == "minute" { + numeric_to_value(format.minute) + } else if property == "second" { + numeric_to_value(format.second) + } else if property == "timeZoneName" { + time_zone_to_value(format.time_zone_name) + } else { + JsValue::undefined() + } +} + +/// `FormatOptionsRecord` type aggregates `DateTimeFormatOptions` string and `components` map. +/// At this point, `DateTimeFormatOptions` does not support `components::Bag`. +#[derive(Debug)] +pub(crate) struct FormatOptionsRecord { + pub(crate) date_time_format_opts: DateTimeFormatOptions, + pub(crate) components: FxHashMap, +} + +/// `DateTimeComponents` type contains `property` map and list of `values` +#[derive(Debug)] +struct DateTimeComponents { + property: JsString, + values: Vec, +} + +/// Builds a list of `DateTimeComponents` which is commonly referred to as "Table 6" +fn build_date_time_components() -> Vec { + vec![ + DateTimeComponents { + property: JsString::new("weekday"), + values: vec![ + JsString::new("narrow"), + JsString::new("short"), + JsString::new("long"), + ], + }, + DateTimeComponents { + property: JsString::new("era"), + values: vec![ + JsString::new("narrow"), + JsString::new("short"), + JsString::new("long"), + ], + }, + DateTimeComponents { + property: JsString::new("year"), + values: vec![JsString::new("2-digit"), JsString::new("numeric")], + }, + DateTimeComponents { + property: JsString::new("month"), + values: vec![ + JsString::new("2-digit"), + JsString::new("numeric"), + JsString::new("narrow"), + JsString::new("short"), + JsString::new("long"), + ], + }, + DateTimeComponents { + property: JsString::new("day"), + values: vec![JsString::new("2-digit"), JsString::new("numeric")], + }, + DateTimeComponents { + property: JsString::new("dayPeriod"), + values: vec![ + JsString::new("narrow"), + JsString::new("short"), + JsString::new("long"), + ], + }, + DateTimeComponents { + property: JsString::new("hour"), + values: vec![ + JsString::new("narrow"), + JsString::new("short"), + JsString::new("long"), + ], + }, + DateTimeComponents { + property: JsString::new("minute"), + values: vec![ + JsString::new("narrow"), + JsString::new("short"), + JsString::new("long"), + ], + }, + DateTimeComponents { + property: JsString::new("second"), + values: vec![ + JsString::new("narrow"), + JsString::new("short"), + JsString::new("long"), + ], + }, + DateTimeComponents { + property: JsString::new("fractionalSecondDigits"), + values: vec![ + JsString::new("1.0"), + JsString::new("2.0"), + JsString::new("3.0"), + ], + }, + DateTimeComponents { + property: JsString::new("timeZoneName"), + values: vec![ + JsString::new("short"), + JsString::new("long"), + JsString::new("shortOffset"), + JsString::new("longOffset"), + JsString::new("shortGeneric"), + JsString::new("longGeneric"), + ], + }, + ] +} + +fn build_dtf_options( + maybe_date: Option, + maybe_time: Option, +) -> DateTimeFormatOptions { + let dtf_bag = match maybe_date { + Some(date_style) => match maybe_time { + Some(time_style) => length::Bag::from_date_time_style(date_style, time_style), + None => length::Bag::from_date_style(date_style), + }, + None => match maybe_time { + Some(time_style) => length::Bag::from_time_style(time_style), + None => length::Bag::empty(), + }, + }; + + DateTimeFormatOptions::Length(dtf_bag) +} + +/// Builds a list of `components::Bag` for all possible combinations of dateStyle and timeStyle +/// ("full", "medium", "short", "long", undefined) for specified `locale` and `calendar` +pub(crate) fn build_formats(locale: &JsString, calendar: &JsString) -> Vec { + let locale_str = locale.to_string(); + let locale = Locale::from_bytes(locale_str.as_bytes()).expect("Locale parsing failed"); + let provider = InvariantDataProvider; + let mut formats_vec = Vec::::new(); + for date_style in [ + Some(length::Date::Full), + Some(length::Date::Long), + Some(length::Date::Medium), + Some(length::Date::Short), + None, + ] { + for time_style in [ + Some(length::Time::Full), + Some(length::Time::Long), + Some(length::Time::Medium), + Some(length::Time::Short), + None, + ] { + let options = build_dtf_options(date_style, time_style); + + if calendar.eq(&JsString::from("buddhist")) { + let maybe_dtf = datetime::DateTimeFormat::::try_new( + locale.clone(), + &provider, + &options, + ); + match maybe_dtf { + Ok(dtf) => formats_vec.push(dtf.resolve_components()), + Err(_) => continue, + }; + } else if calendar.eq(&JsString::from("gregory")) { + let maybe_dtf = datetime::DateTimeFormat::::try_new( + locale.clone(), + &provider, + &options, + ); + match maybe_dtf { + Ok(dtf) => formats_vec.push(dtf.resolve_components()), + Err(_) => continue, + }; + } else if calendar.eq(&JsString::from("japanese")) { + let maybe_dtf = datetime::DateTimeFormat::::try_new( + locale.clone(), + &provider, + &options, + ); + match maybe_dtf { + Ok(dtf) => formats_vec.push(dtf.resolve_components()), + Err(_) => continue, + }; + } else { + continue; + } + } + } + + formats_vec +} + +#[derive(Debug)] +pub(crate) struct StylesRecord { + pub(crate) locale: JsString, + pub(crate) calendar: JsString, +} + +/// The `DateTimeStyleFormat` abstract operation accepts arguments `dateStyle` and `timeStyle`, +/// which are each either undefined, "full", "long", "medium", or "short", at least one of which +/// is not undefined, and styles, which is a record from +/// %`DateTimeFormat`%.[[`LocaleData`]].[[]].[[styles]].[[]] for some locale +/// `locale` and calendar `calendar`. It returns the appropriate format record for date time +/// formatting based on the parameters. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma402/#sec-date-time-style-format +pub(crate) fn date_time_style_format( + date_style: &JsValue, + time_style: &JsValue, + styles: &StylesRecord, + context: &mut Context, +) -> Option { + if date_style.is_undefined() && time_style.is_undefined() { + return None; + } + + let date_style = value_to_date_style(date_style, context); + let time_style = value_to_time_style(time_style, context); + + let options = build_dtf_options(date_style, time_style); + + let locale_str = styles.locale.to_string(); + let locale = Locale::from_bytes(locale_str.as_bytes()).expect("Locale parsing failed"); + let provider = InvariantDataProvider; + + if styles.calendar.eq(&JsString::from("buddhist")) { + let maybe_dtf = datetime::DateTimeFormat::::try_new(locale, &provider, &options); + match maybe_dtf { + Ok(dtf) => Some(dtf.resolve_components()), + Err(_) => None, + } + } else if styles.calendar.eq(&JsString::from("gregory")) { + let maybe_dtf = datetime::DateTimeFormat::::try_new(locale, &provider, &options); + match maybe_dtf { + Ok(dtf) => Some(dtf.resolve_components()), + Err(_) => None, + } + } else if styles.calendar.eq(&JsString::from("japanese")) { + let maybe_dtf = datetime::DateTimeFormat::::try_new(locale, &provider, &options); + match maybe_dtf { + Ok(dtf) => Some(dtf.resolve_components()), + Err(_) => None, + } + } else { + None + } +} + +/// `BasicFormatMatcher` abstract operation is called with two arguments `options` and `formats` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma402/#sec-basicformatmatcher +pub(crate) fn basic_format_matcher( + options: &FormatOptionsRecord, + formats: &[components::Bag], +) -> Option { + // 1. Let removalPenalty be 120. + let removal_penalty = 120; + + // 2. Let additionPenalty be 20. + let addition_penalty = 20; + + // 3. Let longLessPenalty be 8. + let long_less_penalty = 8; + + // 4. Let longMorePenalty be 6. + let long_more_penalty = 6; + + // 5. Let shortLessPenalty be 6. + let short_less_penalty = 6; + + // 6. Let shortMorePenalty be 3. + let short_more_penalty = 3; + + // 7. Let offsetPenalty be 1. + let offset_penalty = 1; + + // 8. Let bestScore be -Infinity. + let mut best_score = i32::MIN; + + // 9. Let bestFormat be undefined. + let mut best_format = None; + + // 10. Assert: Type(formats) is List. + // 11. For each element format of formats, do + for format in formats { + // a. Let score be 0. + let mut score = 0; + + // b. For each property name property shown in Table 6, do + let date_time_components = build_date_time_components(); + for table_row in date_time_components { + let property = table_row.property.to_string(); + // i. If options has a field [[]], + // let optionsProp be options.[[]]; + // else let optionsProp be undefined. + let options_prop = match options.components.get(&table_row.property) { + Some(opt) => opt.clone(), + None => JsValue::undefined(), + }; + + // ii. If format has a field [[]], + // let formatProp be format.[[]]; + // else let formatProp be undefined. + let format_prop = get_format_field(format, &property); + + // iii. If optionsProp is undefined and formatProp is not undefined, + // decrease score by additionPenalty. + if options_prop.is_undefined() && !format_prop.is_undefined() { + score -= addition_penalty; + + // iv. Else if optionsProp is not undefined and formatProp is undefined, + // decrease score by removalPenalty. + } else if !options_prop.is_undefined() && format_prop.is_undefined() { + score -= removal_penalty; + // v. Else if property is "timeZoneName", then + } else if property.eq("timeZoneName") { + // 1. If optionsProp is "short" or "shortGeneric", then + if options_prop.eq(&JsValue::String(JsString::new("short"))) + || options_prop.eq(&JsValue::String(JsString::new("shortGeneric"))) + { + // a. If formatProp is "shortOffset", decrease score by offsetPenalty. + // b. Else if formatProp is "longOffset", + // decrease score by (offsetPenalty + shortMorePenalty). + // c. Else if optionsProp is "short" and formatProp is "long", + // decrease score by shortMorePenalty. + // d. Else if optionsProp is "shortGeneric" and formatProp is "longGeneric", + // decrease score by shortMorePenalty. + // e. Else if optionsProp ≠ formatProp, decrease score by removalPenalty. + if format_prop.eq(&JsValue::String(JsString::new("shortOffset"))) { + // a. + score -= offset_penalty; + } else if format_prop.eq(&JsValue::String(JsString::new("longOffset"))) { + // b. + score -= offset_penalty + short_more_penalty; + } else if (options_prop.eq(&JsValue::String(JsString::new("short"))) + && format_prop.eq(&JsValue::String(JsString::new("long")))) + || (options_prop.eq(&JsValue::String(JsString::new("shortGeneric"))) + && format_prop.eq(&JsValue::String(JsString::new("longGeneric")))) + { + // c & d. + score -= short_more_penalty; + } else if options_prop.ne(&format_prop) { + // e. + score -= removal_penalty; + } + + // 2. Else if optionsProp is "shortOffset" and formatProp is "longOffset", + // decrease score by shortMorePenalty. + } else if options_prop.eq(&JsValue::String(JsString::new("shortOffset"))) + || format_prop.eq(&JsValue::String(JsString::new("longOffset"))) + { + score -= short_more_penalty; + + // 3. Else if optionsProp is "long" or "longGeneric", then + } else if options_prop.eq(&JsValue::String(JsString::new("long"))) + || options_prop.eq(&JsValue::String(JsString::new("longGeneric"))) + { + // a. If formatProp is "longOffset", decrease score by offsetPenalty. + // b. Else if formatProp is "shortOffset", + // decrease score by (offsetPenalty + longLessPenalty). + // c. Else if optionsProp is "long" and formatProp is "short", + // decrease score by longLessPenalty. + // d. Else if optionsProp is "longGeneric" and formatProp is "shortGeneric", + // decrease score by longLessPenalty. + // e. Else if optionsProp ≠ formatProp, decrease score by removalPenalty. + if format_prop.eq(&JsValue::String(JsString::new("longOffset"))) { + // a. + score -= offset_penalty; + } else if format_prop.eq(&JsValue::String(JsString::new("shortOffset"))) { + // b. + score -= offset_penalty + long_less_penalty; + } else if (options_prop.eq(&JsValue::String(JsString::new("long"))) + && format_prop.eq(&JsValue::String(JsString::new("short")))) + || (options_prop.eq(&JsValue::String(JsString::new("longGeneric"))) + && format_prop.eq(&JsValue::String(JsString::new("shortGeneric")))) + { + // c & d. + score -= long_less_penalty; + } else if options_prop.ne(&format_prop) { + // e. + score -= removal_penalty; + } + + // 4. Else if optionsProp is "longOffset" and formatProp is "shortOffset", + // decrease score by longLessPenalty. + } else if options_prop.eq(&JsValue::String(JsString::new("longOffset"))) + || format_prop.eq(&JsValue::String(JsString::new("shortOffset"))) + { + score -= long_less_penalty; + + // 5. Else if optionsProp ≠ formatProp, decrease score by removalPenalty. + } else if options_prop.ne(&format_prop) { + score -= removal_penalty; + } + + // vi. Else if optionsProp ≠ formatProp, then + } else if options_prop.ne(&format_prop) { + // 1. If property is "fractionalSecondDigits", then + // a. Let values be « 1𝔽, 2𝔽, 3𝔽 ». + // 2. Else, + // a. Let values be « "2-digit", "numeric", "narrow", "short", "long" ». + let values = if property.eq("fractionalSecondDigits") { + vec![JsValue::new(1.0), JsValue::new(2.0), JsValue::new(3.0)] + } else { + vec![ + JsValue::String(JsString::new("2-digit")), + JsValue::String(JsString::new("numeric")), + JsValue::String(JsString::new("narrow")), + JsValue::String(JsString::new("short")), + JsValue::String(JsString::new("long")), + ] + }; + + // 3. Let optionsPropIndex be the index of optionsProp within values. + let options_prop_index = values + .iter() + .position(|val| val.eq(&options_prop)) + .expect("Option not found") as i32; + + // 4. Let formatPropIndex be the index of formatProp within values. + let format_prop_index = values + .iter() + .position(|val| val.eq(&format_prop)) + .expect("Format not found") as i32; + + // 5. Let delta be max(min(formatPropIndex - optionsPropIndex, 2), -2). + let delta = max(min(format_prop_index - options_prop_index, 2), -2); + + // 6. If delta = 2, decrease score by longMorePenalty. + // 7. Else if delta = 1, decrease score by shortMorePenalty. + // 8. Else if delta = -1, decrease score by shortLessPenalty. + // 9. Else if delta = -2, decrease score by longLessPenalty. + if delta == 2 { + score -= long_more_penalty; + } else if delta == 1 { + score -= short_more_penalty; + } else if delta == -1 { + score -= short_less_penalty; + } else if delta == -2 { + score -= long_less_penalty; + } + } + } + + // c. If score > bestScore, then + if score > best_score { + // i. Let bestScore be score. + best_score = score; + + // ii. Let bestFormat be format. + best_format = Some(*format); + } + } + + // 12. Return bestFormat. + best_format +} + +/// When the `BestFitFormatMatcher` abstract operation is called with two arguments `options` and +/// `formats`, it performs implementation dependent steps, which should return a set of component +/// representations that a typical user of the selected locale would perceive as at least as good +/// as the one returned by `BasicFormatMatcher`. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma402/#sec-bestfitformatmatcher +fn best_fit_format_matcher( + options: &FormatOptionsRecord, + formats: &[components::Bag], +) -> Option { + basic_format_matcher(options, formats) +} + +/// The abstract operation `InitializeDateTimeFormat` accepts the arguments `dateTimeFormat` (which +/// must be an object), `locales`, and `options`. It initializes `dateTimeFormat` as a +/// `DateTimeFormat` object. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma402/#sec-initializedatetimeformat +fn initialize_date_time_format( + date_time_format: &JsObject, + locales: &JsValue, + options: &JsValue, + context: &mut Context, +) -> JsResult { + // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales). + let locales_arr = if locales.is_undefined() { + vec![JsValue::undefined()] + } else { + let locales_obj = locales + .to_object(context) + .unwrap_or_else(|_| JsObject::empty()); + let locales_len = locales_obj.length_of_array_like(context).unwrap_or(0); + let mut locales_acc = Vec::::new(); + for index in 0..locales_len as u32 { + let maybe_locale = locales_obj + .get(index, context) + .unwrap_or_else(|_| JsValue::undefined()); + locales_acc.push(maybe_locale); + } + locales_acc + }; + let requested_locales = canonicalize_locale_list(&locales_arr, context)?; + let requested_locales = requested_locales + .iter() + .map(|locale| JsString::new(locale.to_string())) + .collect::>(); + + // 2. Set options to ? ToDateTimeOptions(options, "any", "date"). + let options = + to_date_time_options(options, &DateTimeReqs::AnyAll, &DateTimeReqs::Date, context)?; + + // 3. Let opt be a new Record. + let mut opt = DateTimeFormatRecord { + locale_matcher: JsString::empty(), + properties: FxHashMap::default(), + }; + + // 4. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit"). + let matcher_values = vec![JsString::new("lookup"), JsString::new("best fit")]; + let matcher = get_option( + &options, + "localeMatcher", + &GetOptionType::String, + &matcher_values, + &JsValue::String(JsString::new("best fit")), + context, + )?; + + // 5. Set opt.[[localeMatcher]] to matcher. + opt.locale_matcher = matcher + .to_string(context) + .unwrap_or_else(|_| JsString::empty()); + + // 6. Let calendar be ? GetOption(options, "calendar", "string", undefined, undefined). + let calendar = get_option( + &options, + "calendar", + &GetOptionType::String, + &Vec::::new(), + &JsValue::undefined(), + context, + )?; + + // 7. If calendar is not undefined, then + if !calendar.is_undefined() { + // a. If calendar does not match the Unicode Locale Identifier type nonterminal, + // throw a RangeError exception. + if is_terminal(&calendar, context) { + return context.throw_range_error("calendar must be nonterminal"); + } + } + + // 8. Set opt.[[ca]] to calendar. + opt.properties.insert(JsString::new("ca"), calendar); + + // 9. Let numberingSystem be ? GetOption(options, "numberingSystem", "string", undefined, undefined). + let numbering_system = get_option( + &options, + "numberingSystem", + &GetOptionType::String, + &Vec::::new(), + &JsValue::undefined(), + context, + )?; + + // 10. If numberingSystem is not undefined, then + if !numbering_system.is_undefined() { + // a. If numberingSystem does not match the Unicode Locale Identifier type nonterminal, + // throw a RangeError exception. + if is_terminal(&numbering_system, context) { + return context.throw_range_error("numberingSystem must be nonterminal"); + } + } + + // 11. Set opt.[[nu]] to numberingSystem. + opt.properties.insert(JsString::new("nu"), numbering_system); + + // 12. Let hour12 be ? GetOption(options, "hour12", "boolean", undefined, undefined). + let hour_12 = get_option( + &options, + "hour12", + &GetOptionType::Boolean, + &Vec::::new(), + &JsValue::undefined(), + context, + )?; + + // 13. Let hourCycle be ? GetOption(options, "hourCycle", "string", « "h11", "h12", "h23", "h24" », undefined). + let hour_cycle_values = vec![ + JsString::new("h11"), + JsString::new("h12"), + JsString::new("h23"), + JsString::new("h24"), + ]; + let mut hour_cycle = get_option( + &options, + "hourCycle", + &GetOptionType::String, + &hour_cycle_values, + &JsValue::undefined(), + context, + )?; + + // 14. If hour12 is not undefined, then + if !hour_12.is_undefined() { + // a. Set hourCycle to null. + hour_cycle = JsValue::null(); + } + + // 15. Set opt.[[hc]] to hourCycle. + opt.properties.insert(JsString::new("hc"), hour_cycle); + + // 16. Let localeData be %DateTimeFormat%.[[LocaleData]]. + let locale_data = build_locale_data(context); + let relevant_ext_keys = build_relevant_ext_keys(); + let available_locales = build_available_locales(context); + + // 17. Let r be ResolveLocale(%DateTimeFormat%.[[AvailableLocales]], requestedLocales, opt, + // %DateTimeFormat%.[[RelevantExtensionKeys]], localeData). + let r = resolve_locale( + &available_locales, + &requested_locales, + &opt, + &relevant_ext_keys, + &locale_data, + context, + ); + + let mut date_time_fmt_borrow = date_time_format.borrow_mut(); + let date_time_fmt = date_time_fmt_borrow + .as_date_time_format_mut() + .expect("Cast to DateTimeFormat failed"); + + // 18. Set dateTimeFormat.[[Locale]] to r.[[locale]]. + date_time_fmt.locale = r.locale.clone(); + + // 19. Let resolvedCalendar be r.[[ca]]. + let resolved_calendar = r + .properties + .get(&JsString::new("ca")) + .expect("Failed to resolve calendar"); + + // 20. Set dateTimeFormat.[[Calendar]] to resolvedCalendar. + date_time_fmt.calendar = resolved_calendar.clone(); + + // 21. Set dateTimeFormat.[[NumberingSystem]] to r.[[nu]]. + let resolved_nu = r + .properties + .get(&JsString::new("nu")) + .expect("Failed to resolve numbering system"); + date_time_fmt.numbering_system = resolved_nu.clone(); + + // 22. Let dataLocale be r.[[dataLocale]]. + let data_locale = r.data_locale; + + // 23. Let dataLocaleData be localeData.[[]]. + let data_locale_data = locale_data + .get(&data_locale) + .expect("Failed to resolve data locale"); + + // 24. Let hcDefault be dataLocaleData.[[hourCycle]]. + let hc_default = data_locale_data + .get(&JsString::new("hourCycle")) + .expect("Failed to resolve hour cycle"); + + // 25. If hour12 is true, then + // a. If hcDefault is "h11" or "h23", let hc be "h11". Otherwise, let hc be "h12". + // 26. Else if hour12 is false, then + // a. If hcDefault is "h11" or "h23", let hc be "h23". Otherwise, let hc be "h24". + // 27. Else, + // a. Assert: hour12 is undefined. + // b. Let hc be r.[[hc]]. + // c. If hc is null, set hc to hcDefault. + let hc = if hour_12.is_boolean() { + if hour_12.to_boolean() { + if hc_default[0].eq(&JsString::new("h11")) || hc_default[0].eq(&JsString::new("h23")) { + JsString::new("h11") + } else { + JsString::new("h12") + } + } else if hc_default[0].eq(&JsString::new("h11")) || hc_default[0].eq(&JsString::new("h23")) + { + JsString::new("h23") + } else { + JsString::new("h24") + } + } else { + let hc_prop = r + .properties + .get(&JsString::new("hc")) + .expect("Failed to resolve hc"); + if hc_prop.is_null() { + hc_default[0].clone() + } else { + hc_prop.to_string(context)? + } + }; + + // 28. Set dateTimeFormat.[[HourCycle]] to hc. + date_time_fmt.hour_cycle = JsValue::String(hc.clone()); + + // 29. Let timeZone be ? Get(options, "timeZone"). + let time_zone = options.get("timeZone", context)?; + + // 30. If timeZone is undefined, then + // a. Set timeZone to ! DefaultTimeZone(). + // 31. Else, + // a. Set timeZone to ? ToString(timeZone). + // b. If the result of ! IsValidTimeZoneName(timeZone) is false, then + // i. Throw a RangeError exception. + // c. Set timeZone to ! CanonicalizeTimeZoneName(timeZone). + let time_zone_str = if time_zone.is_undefined() { + default_time_zone() + } else { + let time_zone = time_zone.to_string(context)?; + if !is_valid_time_zone_name(&time_zone) { + return context.throw_range_error("Invalid time zone name"); + } + + canonicalize_time_zone_name(&time_zone) + }; + + // 32. Set dateTimeFormat.[[TimeZone]] to timeZone. + date_time_fmt.time_zone = time_zone_str; + + // 33. Let formatOptions be a new Record. + let mut format_options = FormatOptionsRecord { + date_time_format_opts: DateTimeFormatOptions::default(), + components: FxHashMap::default(), + }; + + // 34. Set formatOptions.[[hourCycle]] to hc. + // TODO is it actually used anywhere? + let prefs = preferences::Bag::from_hour_cycle(string_to_hour_cycle(&hc)); + let mut hc_len_bag = length::Bag::empty(); + hc_len_bag.preferences = Some(prefs); + format_options.date_time_format_opts = DateTimeFormatOptions::Length(hc_len_bag); + + // 35. Let hasExplicitFormatComponents be false. + let mut has_explicit_format_components = false; + + // 36. For each row of Table 6, except the header row, in table order, do + let date_time_components = build_date_time_components(); + + for table_row in date_time_components { + // a. Let prop be the name given in the Property column of the row. + let prop = table_row.property; + + // b. If prop is "fractionalSecondDigits", then + // i. Let value be ? GetNumberOption(options, "fractionalSecondDigits", 1, 3, + // undefined). + // c. Else, + // i. Let values be a List whose elements are the strings given in the Values + // column of the row. + // ii. Let value be ? GetOption(options, prop, "string", values, undefined). + let value = if prop.eq("fractionalSecondDigits") { + let number_opt = + get_number_option(&options, "fractionalSecondDigits", 1.0, 3.0, None, context)?; + match number_opt { + Some(num) => JsValue::new(num), + None => JsValue::undefined(), + } + } else { + let values = table_row.values; + get_option( + &options, + &prop, + &GetOptionType::String, + &values, + &JsValue::undefined(), + context, + )? + }; + + // d. Set formatOptions.[[]] to value. + // e. If value is not undefined, then + if !value.is_undefined() { + // i. Set hasExplicitFormatComponents to true. + has_explicit_format_components = true; + } + format_options.components.insert(prop, value); + } + + // 37. Let matcher be ? GetOption(options, "formatMatcher", "string", « "basic", "best fit" », + // "best fit"). + let matcher_values = vec![JsString::new("basic"), JsString::new("best fit")]; + let matcher = get_option( + &options, + "formatMatcher", + &GetOptionType::String, + &matcher_values, + &JsValue::String(JsString::new("best fit")), + context, + )?; + + // 38. Let dateStyle be ? GetOption(options, "dateStyle", "string", + // « "full", "long", "medium", "short" », undefined). + let date_style_values = vec![ + JsString::new("full"), + JsString::new("long"), + JsString::new("medium"), + JsString::new("short"), + ]; + let date_style = get_option( + &options, + "dateStyle", + &GetOptionType::String, + &date_style_values, + &JsValue::undefined(), + context, + )?; + + // 39. Set dateTimeFormat.[[DateStyle]] to dateStyle. + date_time_fmt.date_style = date_style.clone(); + + // 40. Let timeStyle be ? GetOption(options, "timeStyle", "string", + // « "full", "long", "medium", "short" », undefined). + let time_style_values = vec![ + JsString::new("full"), + JsString::new("long"), + JsString::new("medium"), + JsString::new("short"), + ]; + let time_style = get_option( + &options, + "timeStyle", + &GetOptionType::String, + &time_style_values, + &JsValue::undefined(), + context, + )?; + + // 41. Set dateTimeFormat.[[TimeStyle]] to timeStyle. + date_time_fmt.time_style = time_style.clone(); + + // 42. If dateStyle is not undefined or timeStyle is not undefined, then + let best_format = if !date_style.is_undefined() || !time_style.is_undefined() { + // a. If hasExplicitFormatComponents is true, then + if has_explicit_format_components { + // i. Throw a TypeError exception. + return context.throw_type_error( + "dateStyle or timeStyle is defined, while components have explicit format", + ); + } + + // b. Let styles be dataLocaleData.[[styles]].[[]]. + let styles = StylesRecord { + locale: r.locale.clone(), + calendar: resolved_calendar + .to_string(context) + .unwrap_or_else(|_| JsString::empty()), + }; + // c. Let bestFormat be DateTimeStyleFormat(dateStyle, timeStyle, styles). + date_time_style_format(&date_style, &time_style, &styles, context) + .expect("DateTimeStyleFormat failed") + // 43. Else, + } else { + // a. Let formats be dataLocaleData.[[formats]].[[]]. + let formats = build_formats( + &r.locale, + &resolved_calendar + .to_string(context) + .unwrap_or_else(|_| JsString::empty()), + ); + + // b. If matcher is "basic", then + if matcher.eq(&JsValue::String(JsString::new("basic"))) { + // i. Let bestFormat be BasicFormatMatcher(formatOptions, formats). + basic_format_matcher(&format_options, &formats).expect("Failed to get basic format") + // c. Else, + } else { + // i. Let bestFormat be BestFitFormatMatcher(formatOptions, formats). + best_fit_format_matcher(&format_options, &formats) + .expect("Failed to get best fit format") + } + }; + + // 44. For each row in Table 6, except the header row, in table order, do + // a. Let prop be the name given in the Property column of the row. + // b. If bestFormat has a field [[]], then + date_time_fmt.weekday = get_format_field(&best_format, "weekday"); + date_time_fmt.era = get_format_field(&best_format, "era"); + date_time_fmt.year = get_format_field(&best_format, "year"); + date_time_fmt.month = get_format_field(&best_format, "month"); + date_time_fmt.day = get_format_field(&best_format, "day"); + date_time_fmt.hour = get_format_field(&best_format, "hour"); + date_time_fmt.minute = get_format_field(&best_format, "minute"); + date_time_fmt.second = get_format_field(&best_format, "second"); + date_time_fmt.time_zone_name = get_format_field(&best_format, "timeZoneName"); + + // 45. If dateTimeFormat.[[Hour]] is undefined, then + if date_time_fmt.hour.is_undefined() { + // a. Set dateTimeFormat.[[HourCycle]] to undefined. + date_time_fmt.hour_cycle = JsValue::undefined(); + } + + // 46. If dateTimeformat.[[HourCycle]] is "h11" or "h12", then + // a. Let pattern be bestFormat.[[pattern12]]. + // b. Let rangePatterns be bestFormat.[[rangePatterns12]]. + // 47. Else, + // a. Let pattern be bestFormat.[[pattern]]. + // b. Let rangePatterns be bestFormat.[[rangePatterns]]. + // 48. Set dateTimeFormat.[[Pattern]] to pattern. + // 49. Set dateTimeFormat.[[RangePatterns]] to rangePatterns. + + // FIXME icu::options::components::Bag does not provide patterns + + // 50. Return dateTimeFormat. + Ok(date_time_format.clone()) +} diff --git a/boa_engine/src/builtins/intl/mod.rs b/boa_engine/src/builtins/intl/mod.rs index d4a23ee12c3..8ae97f74f52 100644 --- a/boa_engine/src/builtins/intl/mod.rs +++ b/boa_engine/src/builtins/intl/mod.rs @@ -422,7 +422,10 @@ fn insert_unicode_extension_and_canonicalize( /// [spec]: https://tc39.es/ecma402/#sec-canonicalizelocalelist /// [bcp-47]: https://unicode.org/reports/tr35/#Unicode_locale_identifier /// [canon]: https://unicode.org/reports/tr35/#LocaleId_Canonicalization -fn canonicalize_locale_list(args: &[JsValue], context: &mut Context) -> JsResult> { +pub(crate) fn canonicalize_locale_list( + args: &[JsValue], + context: &mut Context, +) -> JsResult> { // 1. If locales is undefined, then let locales = args.get_or_undefined(0); if locales.is_undefined() { @@ -704,7 +707,6 @@ fn resolve_locale( result } -#[allow(unused)] pub(crate) enum GetOptionType { String, Boolean, @@ -721,7 +723,6 @@ pub(crate) enum GetOptionType { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma402/#sec-getoption -#[allow(unused)] pub(crate) fn get_option( options: &JsObject, property: &str, @@ -771,7 +772,6 @@ pub(crate) fn get_option( /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma402/#sec-getnumberoption -#[allow(unused)] pub(crate) fn get_number_option( options: &JsObject, property: &str, @@ -797,7 +797,6 @@ pub(crate) fn get_number_option( /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma402/#sec-defaultnumberoption -#[allow(unused)] pub(crate) fn default_number_option( value: &JsValue, minimum: f64, diff --git a/boa_engine/src/builtins/intl/tests.rs b/boa_engine/src/builtins/intl/tests.rs index 6abfb0c6002..b8aee73bdfe 100644 --- a/boa_engine/src/builtins/intl/tests.rs +++ b/boa_engine/src/builtins/intl/tests.rs @@ -1,5 +1,10 @@ use crate::{ - builtins::intl::date_time_format::{to_date_time_options, DateTimeReqs}, + builtins::intl::date_time_format::{ + basic_format_matcher, build_formats, canonicalize_time_zone_name, date_time_style_format, + is_valid_time_zone_name, month_to_value, numeric_to_value, string_to_hour_cycle, + text_to_value, time_zone_to_value, to_date_time_options, value_to_date_style, + value_to_time_style, year_to_value, DateTimeReqs, FormatOptionsRecord, StylesRecord, + }, builtins::intl::{ best_available_locale, best_fit_matcher, default_locale, default_number_option, get_number_option, get_option, insert_unicode_extension_and_canonicalize, lookup_matcher, @@ -9,6 +14,10 @@ use crate::{ Context, JsString, JsValue, }; +use icu::datetime::{ + options::{components, length, preferences}, + DateTimeFormatOptions, +}; use icu_locale_canonicalizer::LocaleCanonicalizer; use rustc_hash::FxHashMap; @@ -543,3 +552,283 @@ fn to_date_time_opts() { Ok(numeric_jsstring) ); } + +#[test] +fn nonterminals() { + let mut context = Context::default(); + + let nonterminal_calendar_options = vec![ + JsValue::String(JsString::new("")), + JsValue::String(JsString::new("a")), + JsValue::String(JsString::new("ab")), + JsValue::String(JsString::new("abcdefghi")), + JsValue::String(JsString::new("abc-abcdefghi")), + JsValue::String(JsString::new("!invalid!")), + JsValue::String(JsString::new("-gregory-")), + JsValue::String(JsString::new("gregory-")), + JsValue::String(JsString::new("gregory--")), + JsValue::String(JsString::new("gregory-nu")), + JsValue::String(JsString::new("gregory-nu-")), + JsValue::String(JsString::new("gregory-nu-latn")), + JsValue::String(JsString::new("gregoryé")), + JsValue::String(JsString::new("gregory역법")), + ]; + + for calendar_opt in nonterminal_calendar_options { + assert_eq!( + crate::builtins::intl::date_time_format::is_terminal(&calendar_opt, &mut context), + true + ); + } + + let terminal_calendar_options = vec![ + JsValue::String(JsString::new("buddhist")), + JsValue::String(JsString::new("chinese")), + JsValue::String(JsString::new("coptic")), + JsValue::String(JsString::new("dangi")), + JsValue::String(JsString::new("ethioaa")), + JsValue::String(JsString::new("ethiopic")), + JsValue::String(JsString::new("gregory")), + JsValue::String(JsString::new("hebrew")), + JsValue::String(JsString::new("indian")), + JsValue::String(JsString::new("islamic")), + JsValue::String(JsString::new("islamic-umalqura")), + JsValue::String(JsString::new("islamic-tbla")), + JsValue::String(JsString::new("islamic-civil")), + JsValue::String(JsString::new("islamic-rgsa")), + JsValue::String(JsString::new("iso8601")), + JsValue::String(JsString::new("japanese")), + JsValue::String(JsString::new("persian")), + JsValue::String(JsString::new("roc")), + ]; + + for calendar_opt in terminal_calendar_options { + assert_eq!( + crate::builtins::intl::date_time_format::is_terminal(&calendar_opt, &mut context), + false + ); + } +} + +#[test] +fn build_date_time_fmt() { + let mut context = Context::default(); + + let date_time_fmt_obj = crate::builtins::intl::date_time_format::DateTimeFormat::constructor( + &JsValue::undefined(), + &Vec::::new(), + &mut context, + ); + assert_eq!(date_time_fmt_obj.is_err(), false); +} + +#[test] +fn is_valid_tz() { + assert_eq!(is_valid_time_zone_name(&JsString::new("UTC")), true); + assert_eq!(is_valid_time_zone_name(&JsString::new("Israel")), true); + assert_eq!( + is_valid_time_zone_name(&JsString::new("Atlantic/Reykjavik")), + true + ); + assert_eq!(is_valid_time_zone_name(&JsString::new("Etc/Zulu")), true); + assert_eq!( + is_valid_time_zone_name(&JsString::new("Etc/Jamaica")), + false + ); + + println!( + "DEBUG: {}", + canonicalize_time_zone_name(&JsString::new("Brazil/West")).to_string() + ); +} + +#[test] +fn js_to_dtf() { + let mut context = Context::default(); + + assert_eq!( + string_to_hour_cycle(&JsString::new("h11")), + preferences::HourCycle::H11 + ); + assert_eq!( + string_to_hour_cycle(&JsString::new("h12")), + preferences::HourCycle::H12 + ); + assert_eq!( + string_to_hour_cycle(&JsString::new("h23")), + preferences::HourCycle::H23 + ); + assert_eq!( + string_to_hour_cycle(&JsString::new("h24")), + preferences::HourCycle::H24 + ); + + assert_eq!( + value_to_date_style(&JsValue::String(JsString::new("full")), &mut context), + Some(length::Date::Full) + ); + assert_eq!( + value_to_date_style(&JsValue::String(JsString::new("long")), &mut context), + Some(length::Date::Long) + ); + assert_eq!( + value_to_date_style(&JsValue::String(JsString::new("medium")), &mut context), + Some(length::Date::Medium) + ); + assert_eq!( + value_to_date_style(&JsValue::String(JsString::new("short")), &mut context), + Some(length::Date::Short) + ); + assert_eq!( + value_to_date_style(&JsValue::String(JsString::new("narrow")), &mut context), + None + ); + + assert_eq!( + value_to_time_style(&JsValue::String(JsString::new("full")), &mut context), + Some(length::Time::Full) + ); + assert_eq!( + value_to_time_style(&JsValue::String(JsString::new("long")), &mut context), + Some(length::Time::Long) + ); + assert_eq!( + value_to_time_style(&JsValue::String(JsString::new("medium")), &mut context), + Some(length::Time::Medium) + ); + assert_eq!( + value_to_time_style(&JsValue::String(JsString::new("short")), &mut context), + Some(length::Time::Short) + ); + assert_eq!( + value_to_time_style(&JsValue::String(JsString::new("narrow")), &mut context), + None + ); + + assert_eq!( + text_to_value(Some(components::Text::Long)), + JsValue::String(JsString::from("long")) + ); + assert_eq!( + text_to_value(Some(components::Text::Short)), + JsValue::String(JsString::from("short")) + ); + assert_eq!( + text_to_value(Some(components::Text::Narrow)), + JsValue::String(JsString::from("narrow")) + ); + assert_eq!(text_to_value(None), JsValue::undefined()); + + assert_eq!( + year_to_value(Some(components::Year::Numeric)), + JsValue::String(JsString::from("numeric")) + ); + assert_eq!( + year_to_value(Some(components::Year::TwoDigit)), + JsValue::String(JsString::from("2-digit")) + ); + assert_eq!( + year_to_value(Some(components::Year::NumericWeekOf)), + JsValue::String(JsString::from("numericWeek")) + ); + assert_eq!( + year_to_value(Some(components::Year::TwoDigitWeekOf)), + JsValue::String(JsString::from("2-digitWeek")) + ); + assert_eq!(year_to_value(None), JsValue::undefined()); + + assert_eq!( + month_to_value(Some(components::Month::Numeric)), + JsValue::String(JsString::from("numeric")) + ); + assert_eq!( + month_to_value(Some(components::Month::TwoDigit)), + JsValue::String(JsString::from("2-digit")) + ); + assert_eq!( + month_to_value(Some(components::Month::Long)), + JsValue::String(JsString::from("long")) + ); + assert_eq!( + month_to_value(Some(components::Month::Short)), + JsValue::String(JsString::from("short")) + ); + assert_eq!( + month_to_value(Some(components::Month::Narrow)), + JsValue::String(JsString::from("narrow")) + ); + assert_eq!(month_to_value(None), JsValue::undefined()); + + assert_eq!( + numeric_to_value(Some(components::Numeric::Numeric)), + JsValue::String(JsString::from("numeric")) + ); + assert_eq!( + numeric_to_value(Some(components::Numeric::TwoDigit)), + JsValue::String(JsString::from("2-digit")) + ); + assert_eq!(numeric_to_value(None), JsValue::undefined()); + + assert_eq!( + time_zone_to_value(Some(components::TimeZoneName::ShortSpecific)), + JsValue::String(JsString::from("short")) + ); + assert_eq!( + time_zone_to_value(Some(components::TimeZoneName::LongSpecific)), + JsValue::String(JsString::from("long")) + ); + assert_eq!( + time_zone_to_value(Some(components::TimeZoneName::GmtOffset)), + JsValue::String(JsString::from("gmt")) + ); + assert_eq!( + time_zone_to_value(Some(components::TimeZoneName::ShortGeneric)), + JsValue::String(JsString::from("shortGeneric")) + ); + assert_eq!( + time_zone_to_value(Some(components::TimeZoneName::LongGeneric)), + JsValue::String(JsString::from("longGeneric")) + ); + assert_eq!(time_zone_to_value(None), JsValue::undefined()); +} + +#[test] +fn build_fmts() { + let mut context = Context::default(); + + let formats = build_formats(&JsString::new("fr"), &JsString::new("gregory")); + assert_eq!(formats.is_empty(), false); + + let formats = build_formats(&JsString::new("de-DE"), &JsString::new("buddhist")); + assert_eq!(formats.is_empty(), false); + + let formats = build_formats(&JsString::new("ja-Kana-JP"), &JsString::new("japanese")); + assert_eq!(formats.is_empty(), false); + + let formats = build_formats(&JsString::new("it"), &JsString::new("julian")); + assert_eq!(formats.is_empty(), true); + + let formats = build_formats(&JsString::new("en-US"), &JsString::new("gregory")); + assert_eq!(formats.is_empty(), false); + + let format_options = FormatOptionsRecord { + date_time_format_opts: DateTimeFormatOptions::default(), + components: FxHashMap::default(), + }; + assert_eq!( + basic_format_matcher(&format_options, &formats).is_none(), + false + ); + + let styles = StylesRecord { + locale: JsString::new("en-US"), + calendar: JsString::new("gregory"), + }; + + let date_style = JsValue::String(JsString::from("full")); + let time_style = JsValue::String(JsString::from("full")); + assert_eq!( + date_time_style_format(&date_style, &time_style, &styles, &mut context).is_none(), + false + ); +} diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index 5eb4d6e9722..4eb089f64e4 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -1155,6 +1155,17 @@ impl Object { } } + #[inline] + pub fn as_date_time_format_mut(&mut self) -> Option<&mut DateTimeFormat> { + match self.data { + ObjectData { + kind: ObjectKind::DateTimeFormat(ref mut date_time_fmt), + .. + } => Some(date_time_fmt), + _ => None, + } + } + /// Gets the prototype instance of this object. #[inline] pub fn prototype(&self) -> &JsPrototype { From 04bb46e3ff8d3766ae1f359f59ebd8020269f971 Mon Sep 17 00:00:00 2001 From: Norbert Garfield Date: Sun, 15 May 2022 10:06:31 +0000 Subject: [PATCH 2/9] Fallback to fetch unicode extensions --- .../src/builtins/intl/date_time_format.rs | 37 ++++++-------- boa_engine/src/builtins/intl/mod.rs | 49 +++++++++++++++++-- 2 files changed, 60 insertions(+), 26 deletions(-) diff --git a/boa_engine/src/builtins/intl/date_time_format.rs b/boa_engine/src/builtins/intl/date_time_format.rs index 902ae9edfef..7499b11d35e 100644 --- a/boa_engine/src/builtins/intl/date_time_format.rs +++ b/boa_engine/src/builtins/intl/date_time_format.rs @@ -298,7 +298,10 @@ pub(crate) fn is_terminal(opt: &JsValue, context: &mut Context) -> bool { return true; } - if option.chars().any(|character| !character.is_alphanumeric()) { + if option + .chars() + .any(|character| !character.is_ascii_alphanumeric()) + { return true; } } @@ -312,7 +315,7 @@ pub(crate) fn is_terminal(opt: &JsValue, context: &mut Context) -> bool { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma402/#sec-intl.datetimeformat-internal-slots -fn build_locale_data(context: &Context) -> LocaleDataRecord { +fn build_locale_data(available_locales: &[JsString]) -> LocaleDataRecord { let mut locale_data_entry = FxHashMap::default(); let nu_values = vec![JsString::new("arab")]; locale_data_entry.insert(JsString::new("nu"), nu_values); @@ -332,8 +335,10 @@ fn build_locale_data(context: &Context) -> LocaleDataRecord { locale_data_entry.insert(JsString::new("hourCycle"), hour_cycle_values); let mut locale_data = FxHashMap::default(); - let default_locale_str = default_locale(context.icu().locale_canonicalizer()).to_string(); - locale_data.insert(JsString::new(default_locale_str), locale_data_entry); + + for avail_locale in available_locales { + locale_data.insert(avail_locale.clone(), locale_data_entry.clone()); + } locale_data } @@ -649,27 +654,15 @@ fn build_date_time_components() -> Vec { }, DateTimeComponents { property: JsString::new("hour"), - values: vec![ - JsString::new("narrow"), - JsString::new("short"), - JsString::new("long"), - ], + values: vec![JsString::new("2-digit"), JsString::new("numeric")], }, DateTimeComponents { property: JsString::new("minute"), - values: vec![ - JsString::new("narrow"), - JsString::new("short"), - JsString::new("long"), - ], + values: vec![JsString::new("2-digit"), JsString::new("numeric")], }, DateTimeComponents { property: JsString::new("second"), - values: vec![ - JsString::new("narrow"), - JsString::new("short"), - JsString::new("long"), - ], + values: vec![JsString::new("2-digit"), JsString::new("numeric")], }, DateTimeComponents { property: JsString::new("fractionalSecondDigits"), @@ -1078,6 +1071,8 @@ fn initialize_date_time_format( // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales). let locales_arr = if locales.is_undefined() { vec![JsValue::undefined()] + } else if locales.is_string() { + vec![locales.clone()] } else { let locales_obj = locales .to_object(context) @@ -1204,9 +1199,9 @@ fn initialize_date_time_format( opt.properties.insert(JsString::new("hc"), hour_cycle); // 16. Let localeData be %DateTimeFormat%.[[LocaleData]]. - let locale_data = build_locale_data(context); - let relevant_ext_keys = build_relevant_ext_keys(); let available_locales = build_available_locales(context); + let locale_data = build_locale_data(&available_locales); + let relevant_ext_keys = build_relevant_ext_keys(); // 17. Let r be ResolveLocale(%DateTimeFormat%.[[AvailableLocales]], requestedLocales, opt, // %DateTimeFormat%.[[RelevantExtensionKeys]], localeData). diff --git a/boa_engine/src/builtins/intl/mod.rs b/boa_engine/src/builtins/intl/mod.rs index 8ae97f74f52..40f44a6999d 100644 --- a/boa_engine/src/builtins/intl/mod.rs +++ b/boa_engine/src/builtins/intl/mod.rs @@ -151,6 +151,41 @@ fn best_available_locale(available_locales: &[JsString], locale: &JsString) -> O } } +/// Returns the position of the first found unicode locale extension in a given string. +/// +/// If no extensions found, return the length of requested locale +fn get_leftmost_unicode_extension_pos(requested_locale: &str) -> usize { + let ext_sep = "-u-"; + let src_locale = requested_locale.to_lowercase(); + let pos = src_locale.find(ext_sep); + match pos { + Some(idx) => idx, + None => src_locale.len(), + } +} + +/// Trims unciode locale extensions from a given string if any. +/// +/// For example: +/// +/// - `ja-Jpan-JP-u-ca-japanese-hc-h12` becomes `ja-Jpan-JP` +/// - `fr-FR` becomes `fr-FR` +fn trim_unicode_extensions(requested_locale: &str) -> JsString { + let trim_pos = get_leftmost_unicode_extension_pos(requested_locale); + JsString::new(&requested_locale[..trim_pos]) +} + +/// Extracts unciode locale extensions from a given string if any. +/// +/// For example: +/// +/// - `ja-Jpan-JP-u-ca-japanese-hc-h12` becomes `-u-ca-japanese-hc-h12` +/// - `en-US` becomes an empty string +fn extract_unicode_extensions(requested_locale: &str) -> JsString { + let trim_pos = get_leftmost_unicode_extension_pos(requested_locale); + JsString::new(&requested_locale[trim_pos..]) +} + /// Abstract operation `LookupMatcher ( availableLocales, requestedLocales )` /// /// Compares `requestedLocales`, which must be a `List` as returned by `CanonicalizeLocaleList`, @@ -171,9 +206,11 @@ fn lookup_matcher( for locale_str in requested_locales { // a. Let noExtensionsLocale be the String value that is locale with any Unicode locale // extension sequences removed. - let parsed_locale = - Locale::from_bytes(locale_str.as_bytes()).expect("Locale parsing failed"); - let no_extensions_locale = JsString::new(parsed_locale.id.to_string()); + let maybe_locale = Locale::from_bytes(locale_str.as_bytes()); + let no_extensions_locale = match &maybe_locale { + Ok(parsed_locale) => JsString::new(parsed_locale.id.to_string()), + Err(_) => trim_unicode_extensions(locale_str), + }; // b. Let availableLocale be ! BestAvailableLocale(availableLocales, noExtensionsLocale). let available_locale = best_available_locale(available_locales, &no_extensions_locale); @@ -189,7 +226,10 @@ fn lookup_matcher( // 1. Let extension be the String value consisting of the substring of the Unicode // locale extension sequence within locale. // 2. Set result.[[extension]] to extension. - JsString::new(parsed_locale.extensions.to_string()) + match maybe_locale { + Ok(parsed_locale) => JsString::new(parsed_locale.extensions.to_string()), + Err(_) => extract_unicode_extensions(locale_str), + } }; // iii. Return result. @@ -524,7 +564,6 @@ struct ResolveLocaleRecord { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma402/#sec-resolvelocale -#[allow(dead_code)] fn resolve_locale( available_locales: &[JsString], requested_locales: &[JsString], From 025c5c860ce17cf43102381f16085b482c9153ad Mon Sep 17 00:00:00 2001 From: Norbert Garfield Date: Mon, 16 May 2022 08:53:18 +0000 Subject: [PATCH 3/9] Replace maps with pattern matching --- .../src/builtins/intl/date_time_format.rs | 152 ++++++++---------- boa_engine/src/builtins/intl/tests.rs | 70 ++++---- 2 files changed, 102 insertions(+), 120 deletions(-) diff --git a/boa_engine/src/builtins/intl/date_time_format.rs b/boa_engine/src/builtins/intl/date_time_format.rs index 7499b11d35e..d4d7aa02187 100644 --- a/boa_engine/src/builtins/intl/date_time_format.rs +++ b/boa_engine/src/builtins/intl/date_time_format.rs @@ -267,11 +267,7 @@ pub(crate) fn to_date_time_options( /// - [Unicode LDML reference][spec] /// /// [spec]: https://www.unicode.org/reports/tr35/#Unicode_locale_identifier -pub(crate) fn is_terminal(opt: &JsValue, context: &mut Context) -> bool { - let opt_str = opt - .to_string(context) - .unwrap_or_else(|_| JsString::empty()) - .to_string(); +pub(crate) fn is_terminal(opt_str: &str) -> bool { if opt_str.is_empty() { return true; } @@ -298,9 +294,9 @@ pub(crate) fn is_terminal(opt: &JsValue, context: &mut Context) -> bool { return true; } - if option + if !option .chars() - .any(|character| !character.is_ascii_alphanumeric()) + .all(|character| character.is_ascii_alphanumeric()) { return true; } @@ -317,21 +313,21 @@ pub(crate) fn is_terminal(opt: &JsValue, context: &mut Context) -> bool { /// [spec]: https://tc39.es/ecma402/#sec-intl.datetimeformat-internal-slots fn build_locale_data(available_locales: &[JsString]) -> LocaleDataRecord { let mut locale_data_entry = FxHashMap::default(); - let nu_values = vec![JsString::new("arab")]; + let nu_values = Vec::from([JsString::new("arab")]); locale_data_entry.insert(JsString::new("nu"), nu_values); - let hc_values = vec![ + let hc_values = Vec::from([ JsString::new("h11"), JsString::new("h12"), JsString::new("h23"), JsString::new("h24"), - ]; + ]); locale_data_entry.insert(JsString::new("hc"), hc_values); - let ca_values = vec![JsString::new("gregory")]; + let ca_values = Vec::from([JsString::new("gregory")]); locale_data_entry.insert(JsString::new("ca"), ca_values); - let hour_cycle_values = vec![JsString::new("h24")]; + let hour_cycle_values = Vec::from([JsString::new("h24")]); locale_data_entry.insert(JsString::new("hourCycle"), hour_cycle_values); let mut locale_data = FxHashMap::default(); @@ -426,16 +422,13 @@ pub(crate) fn canonicalize_time_zone_name(time_zone: &JsString) -> JsString { /// Converts `hour_cycle_str` to `preferences::HourCycle` pub(crate) fn string_to_hour_cycle(hour_cycle_str: &JsString) -> preferences::HourCycle { - let mut string_to_hc_map = FxHashMap::::default(); - string_to_hc_map.insert(JsString::from("h11"), preferences::HourCycle::H11); - string_to_hc_map.insert(JsString::from("h12"), preferences::HourCycle::H12); - string_to_hc_map.insert(JsString::from("h23"), preferences::HourCycle::H23); - string_to_hc_map.insert(JsString::from("h24"), preferences::HourCycle::H24); - - let hour_cycle = string_to_hc_map - .get(hour_cycle_str) - .expect("Invalid hour cycle"); - *hour_cycle + match hour_cycle_str.as_str() { + "h11" => preferences::HourCycle::H11, + "h12" => preferences::HourCycle::H12, + "h23" => preferences::HourCycle::H23, + "h24" => preferences::HourCycle::H24, + _ => panic!("Invalid hour cycle"), + } } /// Converts `JsValue` to `length::Date` @@ -449,15 +442,12 @@ pub(crate) fn value_to_date_style( let date_style_str = date_style_val .to_string(context) .unwrap_or_else(|_| JsString::empty()); - let mut string_to_style_map = FxHashMap::>::default(); - string_to_style_map.insert(JsString::from("full"), Some(length::Date::Full)); - string_to_style_map.insert(JsString::from("long"), Some(length::Date::Long)); - string_to_style_map.insert(JsString::from("medium"), Some(length::Date::Medium)); - string_to_style_map.insert(JsString::from("short"), Some(length::Date::Short)); - - match string_to_style_map.get(&date_style_str) { - Some(date_style) => *date_style, - None => None, + match date_style_str.as_str() { + "full" => Some(length::Date::Full), + "long" => Some(length::Date::Long), + "medium" => Some(length::Date::Medium), + "short" => Some(length::Date::Short), + _ => None, } } @@ -472,15 +462,12 @@ pub(crate) fn value_to_time_style( let time_style_str = time_style_val .to_string(context) .unwrap_or_else(|_| JsString::empty()); - let mut string_to_style_map = FxHashMap::>::default(); - string_to_style_map.insert(JsString::from("full"), Some(length::Time::Full)); - string_to_style_map.insert(JsString::from("long"), Some(length::Time::Long)); - string_to_style_map.insert(JsString::from("medium"), Some(length::Time::Medium)); - string_to_style_map.insert(JsString::from("short"), Some(length::Time::Short)); - - match string_to_style_map.get(&time_style_str) { - Some(time_style) => *time_style, - None => None, + match time_style_str.as_str() { + "full" => Some(length::Time::Full), + "long" => Some(length::Time::Long), + "medium" => Some(length::Time::Medium), + "short" => Some(length::Time::Short), + _ => None, } } @@ -569,26 +556,17 @@ pub(crate) fn time_zone_to_value(maybe_tz: Option) -> /// Fetches field with name `property` from `format` bag fn get_format_field(format: &components::Bag, property: &str) -> JsValue { - if property == "weekday" { - text_to_value(format.weekday) - } else if property == "era" { - text_to_value(format.era) - } else if property == "year" { - year_to_value(format.year) - } else if property == "month" { - month_to_value(format.month) - } else if property == "day" { - day_to_value(format.day) - } else if property == "hour" { - numeric_to_value(format.hour) - } else if property == "minute" { - numeric_to_value(format.minute) - } else if property == "second" { - numeric_to_value(format.second) - } else if property == "timeZoneName" { - time_zone_to_value(format.time_zone_name) - } else { - JsValue::undefined() + match property { + "weekday" => text_to_value(format.weekday), + "era" => text_to_value(format.era), + "year" => year_to_value(format.year), + "month" => month_to_value(format.month), + "day" => day_to_value(format.day), + "hour" => numeric_to_value(format.hour), + "minute" => numeric_to_value(format.minute), + "second" => numeric_to_value(format.second), + "timeZoneName" => time_zone_to_value(format.time_zone_name), + _ => JsValue::undefined(), } } @@ -609,81 +587,81 @@ struct DateTimeComponents { /// Builds a list of `DateTimeComponents` which is commonly referred to as "Table 6" fn build_date_time_components() -> Vec { - vec![ + Vec::from([ DateTimeComponents { property: JsString::new("weekday"), - values: vec![ + values: Vec::from([ JsString::new("narrow"), JsString::new("short"), JsString::new("long"), - ], + ]), }, DateTimeComponents { property: JsString::new("era"), - values: vec![ + values: Vec::from([ JsString::new("narrow"), JsString::new("short"), JsString::new("long"), - ], + ]), }, DateTimeComponents { property: JsString::new("year"), - values: vec![JsString::new("2-digit"), JsString::new("numeric")], + values: Vec::from([JsString::new("2-digit"), JsString::new("numeric")]), }, DateTimeComponents { property: JsString::new("month"), - values: vec![ + values: Vec::from([ JsString::new("2-digit"), JsString::new("numeric"), JsString::new("narrow"), JsString::new("short"), JsString::new("long"), - ], + ]), }, DateTimeComponents { property: JsString::new("day"), - values: vec![JsString::new("2-digit"), JsString::new("numeric")], + values: Vec::from([JsString::new("2-digit"), JsString::new("numeric")]), }, DateTimeComponents { property: JsString::new("dayPeriod"), - values: vec![ + values: Vec::from([ JsString::new("narrow"), JsString::new("short"), JsString::new("long"), - ], + ]), }, DateTimeComponents { property: JsString::new("hour"), - values: vec![JsString::new("2-digit"), JsString::new("numeric")], + values: Vec::from([JsString::new("2-digit"), JsString::new("numeric")]), }, DateTimeComponents { property: JsString::new("minute"), - values: vec![JsString::new("2-digit"), JsString::new("numeric")], + values: Vec::from([JsString::new("2-digit"), JsString::new("numeric")]), }, DateTimeComponents { property: JsString::new("second"), - values: vec![JsString::new("2-digit"), JsString::new("numeric")], + values: Vec::from([JsString::new("2-digit"), JsString::new("numeric")]), }, DateTimeComponents { property: JsString::new("fractionalSecondDigits"), - values: vec![ + values: Vec::from([ JsString::new("1.0"), JsString::new("2.0"), JsString::new("3.0"), - ], + ]), }, DateTimeComponents { property: JsString::new("timeZoneName"), - values: vec![ + values: Vec::from([ JsString::new("short"), JsString::new("long"), JsString::new("shortOffset"), JsString::new("longOffset"), JsString::new("shortGeneric"), JsString::new("longGeneric"), - ], + ]), }, - ] + ]) } fn build_dtf_options( @@ -775,9 +753,9 @@ pub(crate) struct StylesRecord { /// The `DateTimeStyleFormat` abstract operation accepts arguments `dateStyle` and `timeStyle`, /// which are each either undefined, "full", "long", "medium", or "short", at least one of which /// is not undefined, and styles, which is a record from -/// %`DateTimeFormat`%.[[`LocaleData`]].[[]].[[styles]].[[]] for some locale -/// `locale` and calendar `calendar`. It returns the appropriate format record for date time -/// formatting based on the parameters. +/// `%DateTimeFormat%.[[LocaleData]].[[]].[[styles]].[[]]` for some locale and +/// calendar. It returns the appropriate format record for date time formatting based on the +/// parameters. /// /// More information: /// - [ECMAScript reference][spec] @@ -1133,7 +1111,10 @@ fn initialize_date_time_format( if !calendar.is_undefined() { // a. If calendar does not match the Unicode Locale Identifier type nonterminal, // throw a RangeError exception. - if is_terminal(&calendar, context) { + let calendar_str = calendar + .to_string(context) + .unwrap_or_else(|_| JsString::empty()); + if is_terminal(&calendar_str) { return context.throw_range_error("calendar must be nonterminal"); } } @@ -1155,7 +1136,10 @@ fn initialize_date_time_format( if !numbering_system.is_undefined() { // a. If numberingSystem does not match the Unicode Locale Identifier type nonterminal, // throw a RangeError exception. - if is_terminal(&numbering_system, context) { + let numbering_system_str = numbering_system + .to_string(context) + .unwrap_or_else(|_| JsString::empty()); + if is_terminal(&numbering_system_str) { return context.throw_range_error("numberingSystem must be nonterminal"); } } diff --git a/boa_engine/src/builtins/intl/tests.rs b/boa_engine/src/builtins/intl/tests.rs index b8aee73bdfe..f6310c23e59 100644 --- a/boa_engine/src/builtins/intl/tests.rs +++ b/boa_engine/src/builtins/intl/tests.rs @@ -555,56 +555,54 @@ fn to_date_time_opts() { #[test] fn nonterminals() { - let mut context = Context::default(); - let nonterminal_calendar_options = vec![ - JsValue::String(JsString::new("")), - JsValue::String(JsString::new("a")), - JsValue::String(JsString::new("ab")), - JsValue::String(JsString::new("abcdefghi")), - JsValue::String(JsString::new("abc-abcdefghi")), - JsValue::String(JsString::new("!invalid!")), - JsValue::String(JsString::new("-gregory-")), - JsValue::String(JsString::new("gregory-")), - JsValue::String(JsString::new("gregory--")), - JsValue::String(JsString::new("gregory-nu")), - JsValue::String(JsString::new("gregory-nu-")), - JsValue::String(JsString::new("gregory-nu-latn")), - JsValue::String(JsString::new("gregoryé")), - JsValue::String(JsString::new("gregory역법")), + JsString::new(""), + JsString::new("a"), + JsString::new("ab"), + JsString::new("abcdefghi"), + JsString::new("abc-abcdefghi"), + JsString::new("!invalid!"), + JsString::new("-gregory-"), + JsString::new("gregory-"), + JsString::new("gregory--"), + JsString::new("gregory-nu"), + JsString::new("gregory-nu-"), + JsString::new("gregory-nu-latn"), + JsString::new("gregoryé"), + JsString::new("gregory역법"), ]; for calendar_opt in nonterminal_calendar_options { assert_eq!( - crate::builtins::intl::date_time_format::is_terminal(&calendar_opt, &mut context), + crate::builtins::intl::date_time_format::is_terminal(&calendar_opt), true ); } let terminal_calendar_options = vec![ - JsValue::String(JsString::new("buddhist")), - JsValue::String(JsString::new("chinese")), - JsValue::String(JsString::new("coptic")), - JsValue::String(JsString::new("dangi")), - JsValue::String(JsString::new("ethioaa")), - JsValue::String(JsString::new("ethiopic")), - JsValue::String(JsString::new("gregory")), - JsValue::String(JsString::new("hebrew")), - JsValue::String(JsString::new("indian")), - JsValue::String(JsString::new("islamic")), - JsValue::String(JsString::new("islamic-umalqura")), - JsValue::String(JsString::new("islamic-tbla")), - JsValue::String(JsString::new("islamic-civil")), - JsValue::String(JsString::new("islamic-rgsa")), - JsValue::String(JsString::new("iso8601")), - JsValue::String(JsString::new("japanese")), - JsValue::String(JsString::new("persian")), - JsValue::String(JsString::new("roc")), + JsString::new("buddhist"), + JsString::new("chinese"), + JsString::new("coptic"), + JsString::new("dangi"), + JsString::new("ethioaa"), + JsString::new("ethiopic"), + JsString::new("gregory"), + JsString::new("hebrew"), + JsString::new("indian"), + JsString::new("islamic"), + JsString::new("islamic-umalqura"), + JsString::new("islamic-tbla"), + JsString::new("islamic-civil"), + JsString::new("islamic-rgsa"), + JsString::new("iso8601"), + JsString::new("japanese"), + JsString::new("persian"), + JsString::new("roc"), ]; for calendar_opt in terminal_calendar_options { assert_eq!( - crate::builtins::intl::date_time_format::is_terminal(&calendar_opt, &mut context), + crate::builtins::intl::date_time_format::is_terminal(&calendar_opt), false ); } From b530402f0105ef1bbb172ebd8334a5e8b680e703 Mon Sep 17 00:00:00 2001 From: Norbert Garfield Date: Sat, 21 May 2022 11:52:30 +0000 Subject: [PATCH 4/9] Update UnicodeExtensionComponents Support both '-u-' and 'u-' extension patterns. --- boa_engine/src/builtins/intl/mod.rs | 5 ++++- boa_engine/src/builtins/intl/tests.rs | 7 +++++++ boa_engine/src/context/mod.rs | 2 +- boa_engine/src/object/mod.rs | 1 + 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/boa_engine/src/builtins/intl/mod.rs b/boa_engine/src/builtins/intl/mod.rs index 40f44a6999d..e87a3c59e30 100644 --- a/boa_engine/src/builtins/intl/mod.rs +++ b/boa_engine/src/builtins/intl/mod.rs @@ -313,7 +313,10 @@ fn unicode_extension_components(extension: &JsString) -> UniExtRecord { let size = extension.len(); // 5. Let k be 3. - let mut k = 3; + // + // Actually, it has to be 3 when the extension begins with dash (-u-ca-gregory). + // When the extension begins with u (u-ca-gregory), start with 2. + let mut k = if extension.starts_with("u-") { 2 } else { 3 }; // 6. Repeat, while k < size, while k < size { diff --git a/boa_engine/src/builtins/intl/tests.rs b/boa_engine/src/builtins/intl/tests.rs index f6310c23e59..b642feffe17 100644 --- a/boa_engine/src/builtins/intl/tests.rs +++ b/boa_engine/src/builtins/intl/tests.rs @@ -163,6 +163,13 @@ fn uni_ext_comp() { assert_eq!(components.keywords.len(), 1); assert_eq!(components.keywords[0].key, "ca"); assert_eq!(components.keywords[0].value, "islamic-civil"); + + let ext = JsString::new("u-ca-islamic-civil"); + let components = unicode_extension_components(&ext); + assert!(components.attributes.is_empty()); + assert_eq!(components.keywords.len(), 1); + assert_eq!(components.keywords[0].key, "ca"); + assert_eq!(components.keywords[0].value, "islamic-civil"); } #[test] diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index e95bc92e09d..1f09f597668 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -30,7 +30,7 @@ use icu_provider::DataError; #[doc(inline)] #[cfg(all(feature = "intl", doc))] -pub use icu::BoaProvider; +pub use self::icu::BoaProvider; /// Javascript context. It is the primary way to interact with the runtime. /// diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index 4eb089f64e4..5b8d0c44e13 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -1155,6 +1155,7 @@ impl Object { } } + #[cfg(feature = "intl")] #[inline] pub fn as_date_time_format_mut(&mut self) -> Option<&mut DateTimeFormat> { match self.data { From 617c5f43e0589681570edea69535235b906ae75b Mon Sep 17 00:00:00 2001 From: Norbert Garfield Date: Tue, 31 May 2022 14:20:56 +0000 Subject: [PATCH 5/9] Implement bounds for dyn BoaProvider --- Cargo.lock | 453 +++++++++++++++++- boa_engine/Cargo.toml | 2 + .../src/builtins/intl/date_time_format.rs | 13 +- boa_engine/src/builtins/intl/tests.rs | 37 +- boa_engine/src/context/icu.rs | 99 ++++ boa_engine/src/context/mod.rs | 2 +- 6 files changed, 592 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 817f7897641..8409ede628a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "ahash" version = "0.7.6" @@ -46,6 +61,15 @@ dependencies = [ "nodrop", ] +[[package]] +name = "atomic-polyfill" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14bf7b4f565e5e717d7a7a65b2a05c0b8c96e4db636d6f780f03b15108cdd1b" +dependencies = [ + "critical-section", +] + [[package]] name = "atty" version = "0.2.14" @@ -63,6 +87,57 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version 0.2.3", +] + +[[package]] +name = "bare-metal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bit_field" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4" + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + [[package]] name = "bitflags" version = "1.3.2" @@ -102,6 +177,7 @@ dependencies = [ "float-cmp", "gc", "icu", + "icu_datagen", "icu_datetime", "icu_locale_canonicalizer", "icu_locid", @@ -226,7 +302,7 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a" dependencies = [ - "rustc_version", + "rustc_version 0.4.0", ] [[package]] @@ -352,6 +428,42 @@ dependencies = [ "winapi", ] +[[package]] +name = "cortex-m" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ff967e867ca14eba0c34ac25cd71ea98c678e741e3915d923999bb2fe7c826" +dependencies = [ + "bare-metal 0.2.5", + "bitfield", + "embedded-hal", + "volatile-register", +] + +[[package]] +name = "crabbake" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bf1a888c88f9bd2c4c4830e13b4f0f1aae7a1558ffb2b06bf81b2a6a6662b93" +dependencies = [ + "crabbake-derive", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "crabbake-derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6544515e9f66e2d1fd5ef43dd283e914396098b8251855f88b8dcc91d7327c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "criterion" version = "0.3.5" @@ -388,6 +500,24 @@ dependencies = [ "itertools", ] +[[package]] +name = "critical-section" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95da181745b56d4bd339530ec393508910c909c784e8962d15d722bacf0bcbcd" +dependencies = [ + "bare-metal 1.0.0", + "cfg-if", + "cortex-m", + "riscv", +] + +[[package]] +name = "crlify" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3bb45c3fd75ecfb6404201dd52d865622beab945d58c30b398d055e4b8246e5" + [[package]] name = "crossbeam-channel" version = "0.5.4" @@ -471,6 +601,30 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" +[[package]] +name = "deduplicating_array" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28824c71727770283344d341d212f885685c1799f8af5c7fcd2d7d20223c5d8" +dependencies = [ + "serde", +] + +[[package]] +name = "dhat" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47003dc9f6368a88e85956c3b2573a7e6872746a3e5d762a8885da3a136a0381" +dependencies = [ + "backtrace", + "lazy_static", + "parking_lot", + "rustc-hash", + "serde", + "serde_json", + "thousands", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -515,12 +669,40 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "elsa" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b4b5d23ed6b6948d68240aafa4ac98e568c9a020efd9d4201a6288bc3006e09" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + [[package]] name = "endian-type" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +[[package]] +name = "erased-serde" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad132dd8d0d0b546348d7d86cb3191aad14b34e5f979781fc005c80d4ac67ffd" +dependencies = [ + "serde", +] + [[package]] name = "errno" version = "0.2.8" @@ -639,12 +821,27 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" + [[package]] name = "half" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.11.2" @@ -654,6 +851,20 @@ dependencies = [ "ahash", ] +[[package]] +name = "heapless" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a08e755adbc0ad283725b29f4a4883deee15336f372d5f61fae59efec40f983" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version 0.4.0", + "serde", + "spin", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.3.3" @@ -718,11 +929,51 @@ checksum = "6cdb6b96093158ec0031f9831283085cf897cf4bffc9a1a35a8360a777141058" dependencies = [ "displaydoc", "icu_uniset", + "serde", "yoke", "zerofrom", "zerovec", ] +[[package]] +name = "icu_datagen" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34be4d7ea0d13bdc5c82f03d3d62656d43d7d9717558ea43a2f79ac5a8a04d6b" +dependencies = [ + "crabbake", + "crlify", + "displaydoc", + "elsa", + "icu_calendar", + "icu_codepointtrie", + "icu_datetime", + "icu_decimal", + "icu_list", + "icu_locale_canonicalizer", + "icu_locid", + "icu_plurals", + "icu_properties", + "icu_provider", + "icu_provider_adapters", + "icu_provider_blob", + "icu_provider_fs", + "icu_uniset", + "itertools", + "litemap", + "log", + "proc-macro2", + "quote", + "rayon", + "serde", + "serde-aux", + "serde_json", + "syn", + "tinystr", + "toml", + "zerovec", +] + [[package]] name = "icu_datetime" version = "0.6.0" @@ -753,6 +1004,7 @@ dependencies = [ "fixed_decimal", "icu_locid", "icu_provider", + "serde", "writeable", ] @@ -762,10 +1014,13 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c144d074b8de0f6adcb6941ac4544abf83483fa5681154dcc361253c3a122c5c" dependencies = [ + "crabbake", + "deduplicating_array", "displaydoc", "icu_locid", "icu_provider", "regex-automata 0.2.0", + "serde", "writeable", "zerovec", ] @@ -822,6 +1077,7 @@ dependencies = [ "icu_codepointtrie", "icu_provider", "icu_uniset", + "serde", "zerovec", ] @@ -831,10 +1087,14 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7fbd7ffd479fdbbc366334a82821dc50d9f80b758389393374e9b36ff159f1a" dependencies = [ + "crabbake", + "dhat", "displaydoc", + "erased-serde", "icu_locid", "icu_provider_macros", "litemap", + "log", "postcard", "serde", "writeable", @@ -843,13 +1103,27 @@ dependencies = [ "zerovec", ] +[[package]] +name = "icu_provider_adapters" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d14d64951b404a2a4eed07280af87f08637dd3616cda0bc53ad06a0356ed6321" +dependencies = [ + "icu_locid", + "icu_provider", + "yoke", +] + [[package]] name = "icu_provider_blob" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "474b884a565f7ec52a26754a8b57646c128195e7af629caa52317ef6674e3e0d" dependencies = [ + "erased-serde", "icu_provider", + "litemap", + "log", "postcard", "serde", "writeable", @@ -857,6 +1131,25 @@ dependencies = [ "zerovec", ] +[[package]] +name = "icu_provider_fs" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a94c6d630d7e625d77dae29ba531ea747654ad75d8ae64cbaadc0245db4fdf" +dependencies = [ + "bincode", + "crlify", + "displaydoc", + "erased-serde", + "icu_provider", + "log", + "postcard", + "serde", + "serde-json-core", + "serde_json", + "writeable", +] + [[package]] name = "icu_provider_macros" version = "0.6.0" @@ -885,6 +1178,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecdc2859b6efd75ae22e6350e62f21c87cfe3cfdc11bef6f9565995e88d14ba9" dependencies = [ "displaydoc", + "serde", "tinystr", "yoke", "zerofrom", @@ -998,6 +1292,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78d268a51abaaee3b8686e56396eb725b0da510bddd266a52e784aa1029dae73" dependencies = [ "serde", + "serde_json", "yoke", ] @@ -1058,6 +1353,30 @@ dependencies = [ "autocfg", ] +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.0.0", +] + +[[package]] +name = "nb" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae" + [[package]] name = "nibble_vec" version = "0.1.0" @@ -1137,6 +1456,15 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.28.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.12.0" @@ -1287,6 +1615,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a25c0b0ae06fcffe600ad392aabfa535696c8973f2253d9ac83171924c58a858" dependencies = [ + "heapless", "postcard-cobs", "serde", ] @@ -1459,6 +1788,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9368763f5a9b804326f3af749e16f9abf378d227bcdee7634b13d8f17793782" dependencies = [ "memchr", + "regex-syntax", ] [[package]] @@ -1476,19 +1806,55 @@ dependencies = [ "memchr", ] +[[package]] +name = "riscv" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6907ccdd7a31012b70faf2af85cd9e5ba97657cc3987c4f13f8e4d2c2a088aba" +dependencies = [ + "bare-metal 1.0.0", + "bit_field", + "riscv-target", +] + +[[package]] +name = "riscv-target" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88aa938cda42a0cf62a20cfe8d139ff1af20c2e681212b5b34adb5a58333f222" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + [[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver", + "semver 1.0.9", ] [[package]] @@ -1566,12 +1932,27 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + [[package]] name = "semver" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "serde" version = "1.0.137" @@ -1581,6 +1962,28 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-aux" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "907c320ef8f45ce134b28ca9567ec58ec0d51dcae4e1ffe7ee0cc15517243810" +dependencies = [ + "chrono", + "serde", + "serde_json", +] + +[[package]] +name = "serde-json-core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8014aeea272bca0f0779778d43253f2f3375b414185b30e6ecc4d3e4a9994781" +dependencies = [ + "heapless", + "ryu", + "serde", +] + [[package]] name = "serde_cbor" version = "0.11.2" @@ -1640,6 +2043,15 @@ dependencies = [ "serde", ] +[[package]] +name = "spin" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c530c2b0d0bf8b69304b39fe2001993e267461948b890cd037d8ad4293fa1a0d" +dependencies = [ + "lock_api", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -1791,6 +2203,12 @@ dependencies = [ "syn", ] +[[package]] +name = "thousands" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" + [[package]] name = "time" version = "0.1.44" @@ -1838,6 +2256,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + [[package]] name = "uncased" version = "0.9.7" @@ -1892,6 +2319,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + [[package]] name = "vec_map" version = "0.8.2" @@ -1904,6 +2337,21 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ee8f19f9d74293faf70901bc20ad067dc1ad390d2cbf1e3f75f721ffee908b6" +dependencies = [ + "vcell", +] + [[package]] name = "walkdir" version = "2.3.2" @@ -2126,6 +2574,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c1b475ff48237bf7281cfa1721a52f0ad7f95ede1a46385e555870a354afc45" dependencies = [ "serde", + "serde_json", "yoke", "zerofrom", "zerovec-derive", diff --git a/boa_engine/Cargo.toml b/boa_engine/Cargo.toml index 8cf676f9b01..a41478ec09d 100644 --- a/boa_engine/Cargo.toml +++ b/boa_engine/Cargo.toml @@ -21,6 +21,7 @@ intl = [ "dep:icu_plurals", "dep:icu_provider", "dep:icu_testdata", + "dep:icu_datagen", "dep:sys-locale" ] @@ -56,6 +57,7 @@ icu_datetime = { version = "0.6.0", features = ["serde"], optional = true } icu_plurals = { version = "0.6.0", features = ["serde"], optional = true } icu_provider = { version = "0.6.0", optional = true } icu_testdata = {version = "0.6.0", optional = true} +icu_datagen = {version = "0.6.0", optional = true} sys-locale = { version = "0.2.0", optional = true } icu = "0.6.0" chrono-tz = "0.6.0" diff --git a/boa_engine/src/builtins/intl/date_time_format.rs b/boa_engine/src/builtins/intl/date_time_format.rs index d4d7aa02187..7a784c9ac5a 100644 --- a/boa_engine/src/builtins/intl/date_time_format.rs +++ b/boa_engine/src/builtins/intl/date_time_format.rs @@ -13,7 +13,7 @@ use crate::{ DateTimeFormatRecord, GetOptionType, LocaleDataRecord, }, builtins::JsArgs, - context::intrinsics::StandardConstructors, + context::{icu::BoaProvider, intrinsics::StandardConstructors}, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsFunction, JsObject, ObjectData, @@ -33,7 +33,6 @@ use icu::{ }, locid::Locale, }; -use icu_provider::inv::InvariantDataProvider; use rustc_hash::FxHashMap; use std::cmp::{max, min}; @@ -684,10 +683,13 @@ fn build_dtf_options( /// Builds a list of `components::Bag` for all possible combinations of dateStyle and timeStyle /// ("full", "medium", "short", "long", undefined) for specified `locale` and `calendar` -pub(crate) fn build_formats(locale: &JsString, calendar: &JsString) -> Vec { +pub(crate) fn build_formats( + locale: &JsString, + calendar: &JsString, + provider: &dyn BoaProvider, +) -> Vec { let locale_str = locale.to_string(); let locale = Locale::from_bytes(locale_str.as_bytes()).expect("Locale parsing failed"); - let provider = InvariantDataProvider; let mut formats_vec = Vec::::new(); for date_style in [ Some(length::Date::Full), @@ -778,7 +780,7 @@ pub(crate) fn date_time_style_format( let locale_str = styles.locale.to_string(); let locale = Locale::from_bytes(locale_str.as_bytes()).expect("Locale parsing failed"); - let provider = InvariantDataProvider; + let provider = context.icu().provider(); if styles.calendar.eq(&JsString::from("buddhist")) { let maybe_dtf = datetime::DateTimeFormat::::try_new(locale, &provider, &options); @@ -1433,6 +1435,7 @@ fn initialize_date_time_format( &resolved_calendar .to_string(context) .unwrap_or_else(|_| JsString::empty()), + context.icu().provider(), ); // b. If matcher is "basic", then diff --git a/boa_engine/src/builtins/intl/tests.rs b/boa_engine/src/builtins/intl/tests.rs index b642feffe17..0c568e12d16 100644 --- a/boa_engine/src/builtins/intl/tests.rs +++ b/boa_engine/src/builtins/intl/tests.rs @@ -10,6 +10,7 @@ use crate::{ get_number_option, get_option, insert_unicode_extension_and_canonicalize, lookup_matcher, resolve_locale, unicode_extension_components, DateTimeFormatRecord, GetOptionType, }, + context::ContextBuilder, object::JsObject, Context, JsString, JsValue, }; @@ -799,21 +800,45 @@ fn js_to_dtf() { #[test] fn build_fmts() { - let mut context = Context::default(); + let provider = icu_testdata::get_provider(); + let context_builder = ContextBuilder::default() + .icu_provider(Box::new(provider)) + .expect("Failed to set a provider to ContextBuilder"); + let mut context = context_builder.build(); - let formats = build_formats(&JsString::new("fr"), &JsString::new("gregory")); + let formats = build_formats( + &JsString::new("fr"), + &JsString::new("gregory"), + context.icu().provider(), + ); assert_eq!(formats.is_empty(), false); - let formats = build_formats(&JsString::new("de-DE"), &JsString::new("buddhist")); + let formats = build_formats( + &JsString::new("de-DE"), + &JsString::new("buddhist"), + context.icu().provider(), + ); assert_eq!(formats.is_empty(), false); - let formats = build_formats(&JsString::new("ja-Kana-JP"), &JsString::new("japanese")); + let formats = build_formats( + &JsString::new("ja-Kana-JP"), + &JsString::new("japanese"), + context.icu().provider(), + ); assert_eq!(formats.is_empty(), false); - let formats = build_formats(&JsString::new("it"), &JsString::new("julian")); + let formats = build_formats( + &JsString::new("it"), + &JsString::new("julian"), + context.icu().provider(), + ); assert_eq!(formats.is_empty(), true); - let formats = build_formats(&JsString::new("en-US"), &JsString::new("gregory")); + let formats = build_formats( + &JsString::new("en-US"), + &JsString::new("gregory"), + context.icu().provider(), + ); assert_eq!(formats.is_empty(), false); let format_options = FormatOptionsRecord { diff --git a/boa_engine/src/context/icu.rs b/boa_engine/src/context/icu.rs index 880d9d01916..bd09a861fbf 100644 --- a/boa_engine/src/context/icu.rs +++ b/boa_engine/src/context/icu.rs @@ -1,3 +1,7 @@ +use icu_datagen::transform::cldr::{ + AliasesProvider, CommonDateProvider, LikelySubtagsProvider, PluralsProvider, WeekDataProvider, +}; +use icu_datagen::SourceData; use icu_datetime::provider::{ calendar::{DatePatternsV1Marker, DateSkeletonPatternsV1Marker, DateSymbolsV1Marker}, week_data::WeekDataV1Marker, @@ -7,6 +11,7 @@ use icu_locale_canonicalizer::{ LocaleCanonicalizer, }; use icu_plurals::provider::OrdinalV1Marker; +use icu_provider::inv::InvariantDataProvider; use icu_provider::prelude::*; /// Trait encompassing all the required implementations that define @@ -34,6 +39,100 @@ impl BoaProvider for T where { } +impl ResourceProvider for &dyn BoaProvider { + fn load_resource(&self, req: &DataRequest) -> Result, DataError> { + let provider = AliasesProvider::from(&SourceData::default()); + let response = provider.load_resource(req); + if response.is_ok() { + response + } else { + InvariantDataProvider.load_resource(req) + } + } +} + +impl ResourceProvider for &dyn BoaProvider { + fn load_resource( + &self, + req: &DataRequest, + ) -> Result, DataError> { + let provider = LikelySubtagsProvider::from(&SourceData::default()); + provider.load_resource(req) + } +} + +impl ResourceProvider for &dyn BoaProvider { + fn load_resource( + &self, + req: &DataRequest, + ) -> Result, DataError> { + let provider = CommonDateProvider::from(&SourceData::default()); + let response = provider.load_resource(req); + if response.is_ok() { + response + } else { + InvariantDataProvider.load_resource(req) + } + } +} + +impl ResourceProvider for &dyn BoaProvider { + fn load_resource( + &self, + req: &DataRequest, + ) -> Result, DataError> { + let provider = CommonDateProvider::from(&SourceData::default()); + let response = provider.load_resource(req); + if response.is_ok() { + response + } else { + InvariantDataProvider.load_resource(req) + } + } +} + +impl ResourceProvider for &dyn BoaProvider { + fn load_resource( + &self, + req: &DataRequest, + ) -> Result, DataError> { + let provider = CommonDateProvider::from(&SourceData::default()); + let response = provider.load_resource(req); + if response.is_ok() { + response + } else { + InvariantDataProvider.load_resource(req) + } + } +} + +impl ResourceProvider for &dyn BoaProvider { + fn load_resource(&self, req: &DataRequest) -> Result, DataError> { + let provider = PluralsProvider::from(&SourceData::default()); + let response = provider.load_resource(req); + if response.is_ok() { + response + } else { + InvariantDataProvider.load_resource(req) + } + } +} + +impl ResourceProvider for &dyn BoaProvider { + fn load_resource( + &self, + req: &DataRequest, + ) -> Result, DataError> { + let provider = WeekDataProvider::from(&SourceData::default()); + let response = provider.load_resource(req); + if response.is_ok() { + response + } else { + InvariantDataProvider.load_resource(req) + } + } +} + /// Collection of tools initialized from a [`BoaProvider`] that are used /// for the functionality of `Intl`. #[allow(unused)] diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index 1f09f597668..37d5cad56a0 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -3,7 +3,7 @@ pub mod intrinsics; #[cfg(feature = "intl")] -mod icu; +pub mod icu; use intrinsics::{IntrinsicObjects, Intrinsics}; From 93b7ea950e676950adb3504483708766008fb57f Mon Sep 17 00:00:00 2001 From: Norbert Garfield Date: Tue, 31 May 2022 16:46:32 +0000 Subject: [PATCH 6/9] Switch to ICU for extension parsing --- boa_engine/src/builtins/intl/mod.rs | 240 +++----------------------- boa_engine/src/builtins/intl/tests.rs | 70 +------- 2 files changed, 33 insertions(+), 277 deletions(-) diff --git a/boa_engine/src/builtins/intl/mod.rs b/boa_engine/src/builtins/intl/mod.rs index e87a3c59e30..213ba73c10a 100644 --- a/boa_engine/src/builtins/intl/mod.rs +++ b/boa_engine/src/builtins/intl/mod.rs @@ -21,6 +21,7 @@ pub mod date_time_format; mod tests; use boa_profiler::Profiler; +use icu::locid::extensions::unicode::Key; use icu_locale_canonicalizer::LocaleCanonicalizer; use icu_locid::{locale, Locale}; use indexmap::IndexSet; @@ -84,15 +85,6 @@ impl Intl { } } -/// `MatcherRecord` type aggregates unicode `locale` string and unicode locale `extension`. -/// -/// This is a return value for `lookup_matcher` and `best_fit_matcher` subroutines. -#[derive(Debug)] -struct MatcherRecord { - locale: JsString, - extension: JsString, -} - /// Abstract operation `DefaultLocale ( )` /// /// Returns a String value representing the structurally valid and canonicalized @@ -151,41 +143,6 @@ fn best_available_locale(available_locales: &[JsString], locale: &JsString) -> O } } -/// Returns the position of the first found unicode locale extension in a given string. -/// -/// If no extensions found, return the length of requested locale -fn get_leftmost_unicode_extension_pos(requested_locale: &str) -> usize { - let ext_sep = "-u-"; - let src_locale = requested_locale.to_lowercase(); - let pos = src_locale.find(ext_sep); - match pos { - Some(idx) => idx, - None => src_locale.len(), - } -} - -/// Trims unciode locale extensions from a given string if any. -/// -/// For example: -/// -/// - `ja-Jpan-JP-u-ca-japanese-hc-h12` becomes `ja-Jpan-JP` -/// - `fr-FR` becomes `fr-FR` -fn trim_unicode_extensions(requested_locale: &str) -> JsString { - let trim_pos = get_leftmost_unicode_extension_pos(requested_locale); - JsString::new(&requested_locale[..trim_pos]) -} - -/// Extracts unciode locale extensions from a given string if any. -/// -/// For example: -/// -/// - `ja-Jpan-JP-u-ca-japanese-hc-h12` becomes `-u-ca-japanese-hc-h12` -/// - `en-US` becomes an empty string -fn extract_unicode_extensions(requested_locale: &str) -> JsString { - let trim_pos = get_leftmost_unicode_extension_pos(requested_locale); - JsString::new(&requested_locale[trim_pos..]) -} - /// Abstract operation `LookupMatcher ( availableLocales, requestedLocales )` /// /// Compares `requestedLocales`, which must be a `List` as returned by `CanonicalizeLocaleList`, @@ -200,53 +157,36 @@ fn lookup_matcher( available_locales: &[JsString], requested_locales: &[JsString], canonicalizer: &LocaleCanonicalizer, -) -> MatcherRecord { +) -> Locale { // 1. Let result be a new Record. // 2. For each element locale of requestedLocales, do for locale_str in requested_locales { // a. Let noExtensionsLocale be the String value that is locale with any Unicode locale // extension sequences removed. - let maybe_locale = Locale::from_bytes(locale_str.as_bytes()); - let no_extensions_locale = match &maybe_locale { - Ok(parsed_locale) => JsString::new(parsed_locale.id.to_string()), - Err(_) => trim_unicode_extensions(locale_str), - }; + let parsed_locale = + Locale::from_bytes(locale_str.as_bytes()).expect("Failed to parse locale"); + let no_extensions_locale = JsString::new(parsed_locale.id.to_string()); // b. Let availableLocale be ! BestAvailableLocale(availableLocales, noExtensionsLocale). let available_locale = best_available_locale(available_locales, &no_extensions_locale); // c. If availableLocale is not undefined, then - if let Some(available_locale) = available_locale { + if available_locale.is_some() { // i. Set result.[[locale]] to availableLocale. // Assignment deferred. See return statement below. // ii. If locale and noExtensionsLocale are not the same String value, then - let maybe_ext = if locale_str.eq(&no_extensions_locale) { - JsString::empty() - } else { - // 1. Let extension be the String value consisting of the substring of the Unicode - // locale extension sequence within locale. - // 2. Set result.[[extension]] to extension. - match maybe_locale { - Ok(parsed_locale) => JsString::new(parsed_locale.extensions.to_string()), - Err(_) => extract_unicode_extensions(locale_str), - } - }; - + // 1. Let extension be the String value consisting of the substring of the Unicode + // locale extension sequence within locale. + // 2. Set result.[[extension]] to extension. // iii. Return result. - return MatcherRecord { - locale: available_locale, - extension: maybe_ext, - }; + return parsed_locale; } } // 3. Let defLocale be ! DefaultLocale(). // 4. Set result.[[locale]] to defLocale. // 5. Return result. - MatcherRecord { - locale: default_locale(canonicalizer).to_string().into(), - extension: JsString::empty(), - } + default_locale(canonicalizer) } /// Abstract operation `BestFitMatcher ( availableLocales, requestedLocales )` @@ -265,139 +205,10 @@ fn best_fit_matcher( available_locales: &[JsString], requested_locales: &[JsString], canonicalizer: &LocaleCanonicalizer, -) -> MatcherRecord { +) -> Locale { lookup_matcher(available_locales, requested_locales, canonicalizer) } -/// `Keyword` structure is a pair of keyword key and keyword value. -#[derive(Debug)] -struct Keyword { - key: JsString, - value: JsString, -} - -/// `UniExtRecord` structure represents unicode extension records. -/// -/// It contains the list of unicode `extension` attributes and the list of `keywords`. -/// -/// For example: -/// -/// - `-u-nu-thai` has no attributes and the list of keywords contains `(nu:thai)` pair. -#[allow(dead_code)] -#[derive(Debug)] -struct UniExtRecord { - attributes: Vec, // never read at this point - keywords: Vec, -} - -/// Abstract operation `UnicodeExtensionComponents ( extension )` -/// -/// Returns the attributes and keywords from `extension`, which must be a String -/// value whose contents are a `Unicode locale extension` sequence. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma402/#sec-unicode-extension-components -fn unicode_extension_components(extension: &JsString) -> UniExtRecord { - // 1. Let attributes be a new empty List. - let mut attributes = Vec::::new(); - - // 2. Let keywords be a new empty List. - let mut keywords = Vec::::new(); - - // 3. Let keyword be undefined. - let mut keyword: Option = None; - - // 4. Let size be the length of extension. - let size = extension.len(); - - // 5. Let k be 3. - // - // Actually, it has to be 3 when the extension begins with dash (-u-ca-gregory). - // When the extension begins with u (u-ca-gregory), start with 2. - let mut k = if extension.starts_with("u-") { 2 } else { 3 }; - - // 6. Repeat, while k < size, - while k < size { - // a. Let e be ! StringIndexOf(extension, "-", k). - let e = extension.index_of(&JsString::new("-"), k); - - // b. If e = -1, let len be size - k; else let len be e - k. - let len = match e { - Some(pos) => pos - k, - None => size - k, - }; - - // c. Let subtag be the String value equal to the substring of extension consisting of the - // code units at indices k (inclusive) through k + len (exclusive). - let subtag = JsString::new(&extension[k..k + len]); - - // d. If keyword is undefined and len ≠ 2, then - if keyword.is_none() && len != 2 { - // i. If subtag is not an element of attributes, then - if !attributes.contains(&subtag) { - // 1. Append subtag to attributes. - attributes.push(subtag); - } - // e. Else if len = 2, then - } else if len == 2 { - // i. If keyword is not undefined and keywords does not contain an element - // whose [[Key]] is the same as keyword.[[Key]], then - // 1. Append keyword to keywords. - if let Some(keyword_val) = keyword { - let has_key = keywords.iter().any(|elem| elem.key == keyword_val.key); - if !has_key { - keywords.push(keyword_val); - } - }; - - // ii. Set keyword to the Record { [[Key]]: subtag, [[Value]]: "" }. - keyword = Some(Keyword { - key: subtag, - value: JsString::empty(), - }); - // f. Else, - } else { - // i. If keyword.[[Value]] is the empty String, then - // 1. Set keyword.[[Value]] to subtag. - // ii. Else, - // 1. Set keyword.[[Value]] to the string-concatenation of keyword.[[Value]], "-", and subtag. - if let Some(keyword_val) = keyword { - let new_keyword_val = if keyword_val.value.is_empty() { - subtag - } else { - JsString::new(format!("{}-{subtag}", keyword_val.value)) - }; - - keyword = Some(Keyword { - key: keyword_val.key, - value: new_keyword_val, - }); - }; - } - - // g. Let k be k + len + 1. - k = k + len + 1; - } - - // 7. If keyword is not undefined and keywords does not contain an element whose [[Key]] is - // the same as keyword.[[Key]], then - // a. Append keyword to keywords. - if let Some(keyword_val) = keyword { - let has_key = keywords.iter().any(|elem| elem.key == keyword_val.key); - if !has_key { - keywords.push(keyword_val); - } - }; - - // 8. Return the Record { [[Attributes]]: attributes, [[Keywords]]: keywords }. - UniExtRecord { - attributes, - keywords, - } -} - /// Abstract operation `InsertUnicodeExtensionAndCanonicalize ( locale, extension )` /// /// Inserts `extension`, which must be a Unicode locale extension sequence, into @@ -596,7 +407,7 @@ fn resolve_locale( }; // 4. Let foundLocale be r.[[locale]]. - let mut found_locale = r.locale; + let mut found_locale = JsString::new(r.id.to_string()); // 5. Let result be a new Record. let mut result = ResolveLocaleRecord { @@ -609,14 +420,9 @@ fn resolve_locale( result.data_locale = found_locale.clone(); // 7. If r has an [[extension]] field, then - let keywords = if r.extension.is_empty() { - Vec::::new() - } else { - // a. Let components be ! UnicodeExtensionComponents(r.[[extension]]). - let components = unicode_extension_components(&r.extension); - // b. Let keywords be components.[[Keywords]]. - components.keywords - }; + // a. Let components be ! UnicodeExtensionComponents(r.[[extension]]). + // b. Let keywords be components.[[Keywords]]. + let keywords = r.extensions.unicode.keywords; // 8. Let supportedExtension be "-u". let mut supported_extension = JsString::new("-u"); @@ -648,24 +454,26 @@ fn resolve_locale( let mut supported_extension_addition = JsString::empty(); // h. If r has an [[extension]] field, then - if !r.extension.is_empty() { + if !keywords.is_empty() { // i. If keywords contains an element whose [[Key]] is the same as key, then // 1. Let entry be the element of keywords whose [[Key]] is the same as key. - let maybe_entry = keywords.iter().find(|elem| key.eq(&elem.key)); + let parsed_key: Key = key.parse().expect("Failed to parse key"); + + let maybe_entry = keywords.get(&parsed_key); if let Some(entry) = maybe_entry { // 2. Let requestedValue be entry.[[Value]]. - let requested_value = &entry.value; + let requested_value = JsString::new(entry.to_string()); // 3. If requestedValue is not the empty String, then if !requested_value.is_empty() { // a. If keyLocaleData contains requestedValue, then - if key_locale_data.contains(requested_value) { + if key_locale_data.contains(&requested_value) { // i. Let value be requestedValue. - value = JsValue::String(JsString::new(requested_value)); // ii. Let supportedExtensionAddition be the string-concatenation // of "-", key, "-", and value. supported_extension_addition = - JsString::concat_array(&["-", key, "-", requested_value]); + JsString::concat_array(&["-", key, "-", &requested_value]); + value = JsValue::String(requested_value); } // 4. Else if keyLocaleData contains "true", then } else if key_locale_data.contains(&JsString::new("true")) { diff --git a/boa_engine/src/builtins/intl/tests.rs b/boa_engine/src/builtins/intl/tests.rs index 0c568e12d16..2a66a973635 100644 --- a/boa_engine/src/builtins/intl/tests.rs +++ b/boa_engine/src/builtins/intl/tests.rs @@ -8,7 +8,7 @@ use crate::{ builtins::intl::{ best_available_locale, best_fit_matcher, default_locale, default_number_option, get_number_option, get_option, insert_unicode_extension_and_canonicalize, lookup_matcher, - resolve_locale, unicode_extension_components, DateTimeFormatRecord, GetOptionType, + resolve_locale, DateTimeFormatRecord, GetOptionType, }, context::ContextBuilder, object::JsObject, @@ -66,38 +66,32 @@ fn lookup_match() { let requested_locales = Vec::::new(); let matcher = lookup_matcher(&available_locales, &requested_locales, &canonicalizer); - assert_eq!( - matcher.locale, - default_locale(&canonicalizer).to_string().as_str() - ); - assert_eq!(matcher.extension, ""); + assert_eq!(matcher, default_locale(&canonicalizer)); + assert_eq!(matcher.extensions.is_empty(), true); // available: [de-DE], requested: [] let available_locales = vec![JsString::new("de-DE")]; let requested_locales = Vec::::new(); let matcher = lookup_matcher(&available_locales, &requested_locales, &canonicalizer); - assert_eq!( - matcher.locale, - default_locale(&canonicalizer).to_string().as_str() - ); - assert_eq!(matcher.extension, ""); + assert_eq!(matcher, default_locale(&canonicalizer)); + assert_eq!(matcher.extensions.is_empty(), true); // available: [fr-FR], requested: [fr-FR-u-hc-h12] let available_locales = vec![JsString::new("fr-FR")]; let requested_locales = vec![JsString::new("fr-FR-u-hc-h12")]; let matcher = lookup_matcher(&available_locales, &requested_locales, &canonicalizer); - assert_eq!(matcher.locale, "fr-FR"); - assert_eq!(matcher.extension, "u-hc-h12"); + assert_eq!(matcher.id.to_string(), "fr-FR"); + assert_eq!(matcher.extensions.unicode.to_string(), "-u-hc-h12"); // available: [es-ES], requested: [es-ES] let available_locales = vec![JsString::new("es-ES")]; let requested_locales = vec![JsString::new("es-ES")]; let matcher = best_fit_matcher(&available_locales, &requested_locales, &canonicalizer); - assert_eq!(matcher.locale, "es-ES"); - assert_eq!(matcher.extension, ""); + assert_eq!(matcher.id.to_string(), "es-ES"); + assert_eq!(matcher.extensions.is_empty(), true); } #[test] @@ -127,52 +121,6 @@ fn insert_unicode_ext() { ); } -#[test] -fn uni_ext_comp() { - let ext = JsString::new("-u-ca-japanese-hc-h12"); - let components = unicode_extension_components(&ext); - assert!(components.attributes.is_empty()); - assert_eq!(components.keywords.len(), 2); - assert_eq!(components.keywords[0].key, "ca"); - assert_eq!(components.keywords[0].value, "japanese"); - assert_eq!(components.keywords[1].key, "hc"); - assert_eq!(components.keywords[1].value, "h12"); - - let ext = JsString::new("-u-alias-co-phonebk-ka-shifted"); - let components = unicode_extension_components(&ext); - assert_eq!(components.attributes, vec![JsString::new("alias")]); - assert_eq!(components.keywords.len(), 2); - assert_eq!(components.keywords[0].key, "co"); - assert_eq!(components.keywords[0].value, "phonebk"); - assert_eq!(components.keywords[1].key, "ka"); - assert_eq!(components.keywords[1].value, "shifted"); - - let ext = JsString::new("-u-ca-buddhist-kk-nu-thai"); - let components = unicode_extension_components(&ext); - assert!(components.attributes.is_empty()); - assert_eq!(components.keywords.len(), 3); - assert_eq!(components.keywords[0].key, "ca"); - assert_eq!(components.keywords[0].value, "buddhist"); - assert_eq!(components.keywords[1].key, "kk"); - assert_eq!(components.keywords[1].value, ""); - assert_eq!(components.keywords[2].key, "nu"); - assert_eq!(components.keywords[2].value, "thai"); - - let ext = JsString::new("-u-ca-islamic-civil"); - let components = unicode_extension_components(&ext); - assert!(components.attributes.is_empty()); - assert_eq!(components.keywords.len(), 1); - assert_eq!(components.keywords[0].key, "ca"); - assert_eq!(components.keywords[0].value, "islamic-civil"); - - let ext = JsString::new("u-ca-islamic-civil"); - let components = unicode_extension_components(&ext); - assert!(components.attributes.is_empty()); - assert_eq!(components.keywords.len(), 1); - assert_eq!(components.keywords[0].key, "ca"); - assert_eq!(components.keywords[0].value, "islamic-civil"); -} - #[test] fn locale_resolution() { let mut context = Context::default(); From b2764e5f1fd3f5b7242d28d5eba3a122dca0922c Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Tue, 31 May 2022 12:16:27 -0500 Subject: [PATCH 7/9] Fix `ResourceProvider` impls for `&dyn BoaProvider` --- boa_engine/src/context/icu.rs | 56 +++++------------------------------ 1 file changed, 7 insertions(+), 49 deletions(-) diff --git a/boa_engine/src/context/icu.rs b/boa_engine/src/context/icu.rs index bd09a861fbf..2ea572e5a3b 100644 --- a/boa_engine/src/context/icu.rs +++ b/boa_engine/src/context/icu.rs @@ -1,7 +1,3 @@ -use icu_datagen::transform::cldr::{ - AliasesProvider, CommonDateProvider, LikelySubtagsProvider, PluralsProvider, WeekDataProvider, -}; -use icu_datagen::SourceData; use icu_datetime::provider::{ calendar::{DatePatternsV1Marker, DateSkeletonPatternsV1Marker, DateSymbolsV1Marker}, week_data::WeekDataV1Marker, @@ -11,7 +7,6 @@ use icu_locale_canonicalizer::{ LocaleCanonicalizer, }; use icu_plurals::provider::OrdinalV1Marker; -use icu_provider::inv::InvariantDataProvider; use icu_provider::prelude::*; /// Trait encompassing all the required implementations that define @@ -41,13 +36,7 @@ impl BoaProvider for T where impl ResourceProvider for &dyn BoaProvider { fn load_resource(&self, req: &DataRequest) -> Result, DataError> { - let provider = AliasesProvider::from(&SourceData::default()); - let response = provider.load_resource(req); - if response.is_ok() { - response - } else { - InvariantDataProvider.load_resource(req) - } + (*self).load_resource(req) } } @@ -56,8 +45,7 @@ impl ResourceProvider for &dyn BoaProvider { &self, req: &DataRequest, ) -> Result, DataError> { - let provider = LikelySubtagsProvider::from(&SourceData::default()); - provider.load_resource(req) + (*self).load_resource(req) } } @@ -66,13 +54,7 @@ impl ResourceProvider for &dyn BoaProvider { &self, req: &DataRequest, ) -> Result, DataError> { - let provider = CommonDateProvider::from(&SourceData::default()); - let response = provider.load_resource(req); - if response.is_ok() { - response - } else { - InvariantDataProvider.load_resource(req) - } + (*self).load_resource(req) } } @@ -81,13 +63,7 @@ impl ResourceProvider for &dyn BoaProvider { &self, req: &DataRequest, ) -> Result, DataError> { - let provider = CommonDateProvider::from(&SourceData::default()); - let response = provider.load_resource(req); - if response.is_ok() { - response - } else { - InvariantDataProvider.load_resource(req) - } + (*self).load_resource(req) } } @@ -96,25 +72,13 @@ impl ResourceProvider for &dyn BoaProvider { &self, req: &DataRequest, ) -> Result, DataError> { - let provider = CommonDateProvider::from(&SourceData::default()); - let response = provider.load_resource(req); - if response.is_ok() { - response - } else { - InvariantDataProvider.load_resource(req) - } + (*self).load_resource(req) } } impl ResourceProvider for &dyn BoaProvider { fn load_resource(&self, req: &DataRequest) -> Result, DataError> { - let provider = PluralsProvider::from(&SourceData::default()); - let response = provider.load_resource(req); - if response.is_ok() { - response - } else { - InvariantDataProvider.load_resource(req) - } + (*self).load_resource(req) } } @@ -123,13 +87,7 @@ impl ResourceProvider for &dyn BoaProvider { &self, req: &DataRequest, ) -> Result, DataError> { - let provider = WeekDataProvider::from(&SourceData::default()); - let response = provider.load_resource(req); - if response.is_ok() { - response - } else { - InvariantDataProvider.load_resource(req) - } + (*self).load_resource(req) } } From ae85769c384173cfb464237a21522a7b4950d7fe Mon Sep 17 00:00:00 2001 From: Norbert Garfield Date: Tue, 31 May 2022 17:57:35 +0000 Subject: [PATCH 8/9] Update test with supported styles --- boa_engine/src/builtins/intl/tests.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/boa_engine/src/builtins/intl/tests.rs b/boa_engine/src/builtins/intl/tests.rs index 2a66a973635..92da8fd38ee 100644 --- a/boa_engine/src/builtins/intl/tests.rs +++ b/boa_engine/src/builtins/intl/tests.rs @@ -590,9 +590,9 @@ fn is_valid_tz() { false ); - println!( - "DEBUG: {}", - canonicalize_time_zone_name(&JsString::new("Brazil/West")).to_string() + assert_eq!( + canonicalize_time_zone_name(&JsString::new("Brazil/West")).to_string(), + "Brazil/West" ); } @@ -803,8 +803,8 @@ fn build_fmts() { calendar: JsString::new("gregory"), }; - let date_style = JsValue::String(JsString::from("full")); - let time_style = JsValue::String(JsString::from("full")); + let date_style = JsValue::String(JsString::from("none")); + let time_style = JsValue::String(JsString::from("none")); assert_eq!( date_time_style_format(&date_style, &time_style, &styles, &mut context).is_none(), false From 90a7a25d39ea6a912ef4f72cbbf893341394faf5 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Wed, 1 Jun 2022 12:06:04 -0500 Subject: [PATCH 9/9] Adjust dependencies to improve compilation times Improve test semantics Fetch default timezone from the system --- Cargo.lock | 551 +----------------- boa_engine/Cargo.toml | 20 +- .../src/builtins/intl/date_time_format.rs | 82 ++- boa_engine/src/builtins/intl/mod.rs | 3 +- boa_engine/src/builtins/intl/tests.rs | 92 ++- 5 files changed, 110 insertions(+), 638 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8409ede628a..c163f946deb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "ahash" version = "0.7.6" @@ -61,15 +46,6 @@ dependencies = [ "nodrop", ] -[[package]] -name = "atomic-polyfill" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14bf7b4f565e5e717d7a7a65b2a05c0b8c96e4db636d6f780f03b15108cdd1b" -dependencies = [ - "critical-section", -] - [[package]] name = "atty" version = "0.2.14" @@ -87,57 +63,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "backtrace" -version = "0.3.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - -[[package]] -name = "bare-metal" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" -dependencies = [ - "rustc_version 0.2.3", -] - -[[package]] -name = "bare-metal" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603" - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bit_field" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4" - -[[package]] -name = "bitfield" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" - [[package]] name = "bitflags" version = "1.3.2" @@ -176,8 +101,8 @@ dependencies = [ "fast-float", "float-cmp", "gc", - "icu", - "icu_datagen", + "iana-time-zone", + "icu_calendar", "icu_datetime", "icu_locale_canonicalizer", "icu_locid", @@ -280,7 +205,7 @@ checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ "lazy_static", "memchr", - "regex-automata 0.1.10", + "regex-automata", "serde", ] @@ -302,7 +227,7 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a" dependencies = [ - "rustc_version 0.4.0", + "rustc_version", ] [[package]] @@ -429,40 +354,20 @@ dependencies = [ ] [[package]] -name = "cortex-m" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ff967e867ca14eba0c34ac25cd71ea98c678e741e3915d923999bb2fe7c826" -dependencies = [ - "bare-metal 0.2.5", - "bitfield", - "embedded-hal", - "volatile-register", -] - -[[package]] -name = "crabbake" -version = "0.4.0" +name = "core-foundation" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bf1a888c88f9bd2c4c4830e13b4f0f1aae7a1558ffb2b06bf81b2a6a6662b93" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" dependencies = [ - "crabbake-derive", - "proc-macro2", - "quote", - "syn", + "core-foundation-sys", + "libc", ] [[package]] -name = "crabbake-derive" -version = "0.4.0" +name = "core-foundation-sys" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b6544515e9f66e2d1fd5ef43dd283e914396098b8251855f88b8dcc91d7327c" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "criterion" @@ -500,24 +405,6 @@ dependencies = [ "itertools", ] -[[package]] -name = "critical-section" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95da181745b56d4bd339530ec393508910c909c784e8962d15d722bacf0bcbcd" -dependencies = [ - "bare-metal 1.0.0", - "cfg-if", - "cortex-m", - "riscv", -] - -[[package]] -name = "crlify" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3bb45c3fd75ecfb6404201dd52d865622beab945d58c30b398d055e4b8246e5" - [[package]] name = "crossbeam-channel" version = "0.5.4" @@ -601,30 +488,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" -[[package]] -name = "deduplicating_array" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28824c71727770283344d341d212f885685c1799f8af5c7fcd2d7d20223c5d8" -dependencies = [ - "serde", -] - -[[package]] -name = "dhat" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47003dc9f6368a88e85956c3b2573a7e6872746a3e5d762a8885da3a136a0381" -dependencies = [ - "backtrace", - "lazy_static", - "parking_lot", - "rustc-hash", - "serde", - "serde_json", - "thousands", -] - [[package]] name = "dirs-next" version = "2.0.0" @@ -669,40 +532,12 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" -[[package]] -name = "elsa" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b4b5d23ed6b6948d68240aafa4ac98e568c9a020efd9d4201a6288bc3006e09" -dependencies = [ - "stable_deref_trait", -] - -[[package]] -name = "embedded-hal" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" -dependencies = [ - "nb 0.1.3", - "void", -] - [[package]] name = "endian-type" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" -[[package]] -name = "erased-serde" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad132dd8d0d0b546348d7d86cb3191aad14b34e5f979781fc005c80d4ac67ffd" -dependencies = [ - "serde", -] - [[package]] name = "errno" version = "0.2.8" @@ -821,27 +656,12 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "gimli" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" - [[package]] name = "half" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" -[[package]] -name = "hash32" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" -dependencies = [ - "byteorder", -] - [[package]] name = "hashbrown" version = "0.11.2" @@ -851,20 +671,6 @@ dependencies = [ "ahash", ] -[[package]] -name = "heapless" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a08e755adbc0ad283725b29f4a4883deee15336f372d5f61fae59efec40f983" -dependencies = [ - "atomic-polyfill", - "hash32", - "rustc_version 0.4.0", - "serde", - "spin", - "stable_deref_trait", -] - [[package]] name = "heck" version = "0.3.3" @@ -890,21 +696,13 @@ dependencies = [ ] [[package]] -name = "icu" -version = "0.6.0" +name = "iana-time-zone" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15a4e90a2faa6719f4b3b1dac871d1f2794474d453b8e6ca1264062c4bf2c9da" +checksum = "df1b4d6691512a74af43bb6de0b7f546fbe70bfa3bac84da8454a00121e63201" dependencies = [ - "fixed_decimal", - "icu_calendar", - "icu_datetime", - "icu_decimal", - "icu_list", - "icu_locale_canonicalizer", - "icu_locid", - "icu_plurals", - "icu_properties", - "writeable", + "core-foundation", + "winapi", ] [[package]] @@ -921,59 +719,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_codepointtrie" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cdb6b96093158ec0031f9831283085cf897cf4bffc9a1a35a8360a777141058" -dependencies = [ - "displaydoc", - "icu_uniset", - "serde", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_datagen" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34be4d7ea0d13bdc5c82f03d3d62656d43d7d9717558ea43a2f79ac5a8a04d6b" -dependencies = [ - "crabbake", - "crlify", - "displaydoc", - "elsa", - "icu_calendar", - "icu_codepointtrie", - "icu_datetime", - "icu_decimal", - "icu_list", - "icu_locale_canonicalizer", - "icu_locid", - "icu_plurals", - "icu_properties", - "icu_provider", - "icu_provider_adapters", - "icu_provider_blob", - "icu_provider_fs", - "icu_uniset", - "itertools", - "litemap", - "log", - "proc-macro2", - "quote", - "rayon", - "serde", - "serde-aux", - "serde_json", - "syn", - "tinystr", - "toml", - "zerovec", -] - [[package]] name = "icu_datetime" version = "0.6.0" @@ -994,37 +739,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_decimal" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614ff51266354e8c8d75bfc65f806fbc0d0177da079c2482402cfc20b72de47d" -dependencies = [ - "displaydoc", - "fixed_decimal", - "icu_locid", - "icu_provider", - "serde", - "writeable", -] - -[[package]] -name = "icu_list" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c144d074b8de0f6adcb6941ac4544abf83483fa5681154dcc361253c3a122c5c" -dependencies = [ - "crabbake", - "deduplicating_array", - "displaydoc", - "icu_locid", - "icu_provider", - "regex-automata 0.2.0", - "serde", - "writeable", - "zerovec", -] - [[package]] name = "icu_locale_canonicalizer" version = "0.6.0" @@ -1067,34 +781,16 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_properties" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ca8f26685a463ff47dc0d9f7b270e8d955d3c2dd9f748292af14940b659671" -dependencies = [ - "displaydoc", - "icu_codepointtrie", - "icu_provider", - "icu_uniset", - "serde", - "zerovec", -] - [[package]] name = "icu_provider" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7fbd7ffd479fdbbc366334a82821dc50d9f80b758389393374e9b36ff159f1a" dependencies = [ - "crabbake", - "dhat", "displaydoc", - "erased-serde", "icu_locid", "icu_provider_macros", "litemap", - "log", "postcard", "serde", "writeable", @@ -1103,27 +799,13 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_provider_adapters" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d14d64951b404a2a4eed07280af87f08637dd3616cda0bc53ad06a0356ed6321" -dependencies = [ - "icu_locid", - "icu_provider", - "yoke", -] - [[package]] name = "icu_provider_blob" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "474b884a565f7ec52a26754a8b57646c128195e7af629caa52317ef6674e3e0d" dependencies = [ - "erased-serde", "icu_provider", - "litemap", - "log", "postcard", "serde", "writeable", @@ -1131,25 +813,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_provider_fs" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94a94c6d630d7e625d77dae29ba531ea747654ad75d8ae64cbaadc0245db4fdf" -dependencies = [ - "bincode", - "crlify", - "displaydoc", - "erased-serde", - "icu_provider", - "log", - "postcard", - "serde", - "serde-json-core", - "serde_json", - "writeable", -] - [[package]] name = "icu_provider_macros" version = "0.6.0" @@ -1171,20 +834,6 @@ dependencies = [ "icu_provider_blob", ] -[[package]] -name = "icu_uniset" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecdc2859b6efd75ae22e6350e62f21c87cfe3cfdc11bef6f9565995e88d14ba9" -dependencies = [ - "displaydoc", - "serde", - "tinystr", - "yoke", - "zerofrom", - "zerovec", -] - [[package]] name = "indexmap" version = "1.8.2" @@ -1292,7 +941,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78d268a51abaaee3b8686e56396eb725b0da510bddd266a52e784aa1029dae73" dependencies = [ "serde", - "serde_json", "yoke", ] @@ -1353,30 +1001,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "miniz_oxide" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" -dependencies = [ - "adler", -] - -[[package]] -name = "nb" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" -dependencies = [ - "nb 1.0.0", -] - -[[package]] -name = "nb" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae" - [[package]] name = "nibble_vec" version = "0.1.0" @@ -1456,15 +1080,6 @@ dependencies = [ "libc", ] -[[package]] -name = "object" -version = "0.28.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.12.0" @@ -1615,7 +1230,6 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a25c0b0ae06fcffe600ad392aabfa535696c8973f2253d9ac83171924c58a858" dependencies = [ - "heapless", "postcard-cobs", "serde", ] @@ -1781,16 +1395,6 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -[[package]] -name = "regex-automata" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9368763f5a9b804326f3af749e16f9abf378d227bcdee7634b13d8f17793782" -dependencies = [ - "memchr", - "regex-syntax", -] - [[package]] name = "regex-syntax" version = "0.6.26" @@ -1806,55 +1410,19 @@ dependencies = [ "memchr", ] -[[package]] -name = "riscv" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6907ccdd7a31012b70faf2af85cd9e5ba97657cc3987c4f13f8e4d2c2a088aba" -dependencies = [ - "bare-metal 1.0.0", - "bit_field", - "riscv-target", -] - -[[package]] -name = "riscv-target" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88aa938cda42a0cf62a20cfe8d139ff1af20c2e681212b5b34adb5a58333f222" -dependencies = [ - "lazy_static", - "regex", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" - [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc_version" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" -dependencies = [ - "semver 0.9.0", -] - [[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.9", + "semver", ] [[package]] @@ -1932,27 +1500,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[package]] -name = "semver" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" -dependencies = [ - "semver-parser", -] - [[package]] name = "semver" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" -[[package]] -name = "semver-parser" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" - [[package]] name = "serde" version = "1.0.137" @@ -1962,28 +1515,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-aux" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907c320ef8f45ce134b28ca9567ec58ec0d51dcae4e1ffe7ee0cc15517243810" -dependencies = [ - "chrono", - "serde", - "serde_json", -] - -[[package]] -name = "serde-json-core" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8014aeea272bca0f0779778d43253f2f3375b414185b30e6ecc4d3e4a9994781" -dependencies = [ - "heapless", - "ryu", - "serde", -] - [[package]] name = "serde_cbor" version = "0.11.2" @@ -2043,15 +1574,6 @@ dependencies = [ "serde", ] -[[package]] -name = "spin" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c530c2b0d0bf8b69304b39fe2001993e267461948b890cd037d8ad4293fa1a0d" -dependencies = [ - "lock_api", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2203,12 +1725,6 @@ dependencies = [ "syn", ] -[[package]] -name = "thousands" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" - [[package]] name = "time" version = "0.1.44" @@ -2256,15 +1772,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" -[[package]] -name = "toml" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" -dependencies = [ - "serde", -] - [[package]] name = "uncased" version = "0.9.7" @@ -2319,12 +1826,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" -[[package]] -name = "vcell" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" - [[package]] name = "vec_map" version = "0.8.2" @@ -2337,21 +1838,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - -[[package]] -name = "volatile-register" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ee8f19f9d74293faf70901bc20ad067dc1ad390d2cbf1e3f75f721ffee908b6" -dependencies = [ - "vcell", -] - [[package]] name = "walkdir" version = "2.3.2" @@ -2574,7 +2060,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c1b475ff48237bf7281cfa1721a52f0ad7f95ede1a46385e555870a354afc45" dependencies = [ "serde", - "serde_json", "yoke", "zerofrom", "zerovec-derive", diff --git a/boa_engine/Cargo.toml b/boa_engine/Cargo.toml index a41478ec09d..21dc4c3fcba 100644 --- a/boa_engine/Cargo.toml +++ b/boa_engine/Cargo.toml @@ -21,8 +21,10 @@ intl = [ "dep:icu_plurals", "dep:icu_provider", "dep:icu_testdata", - "dep:icu_datagen", - "dep:sys-locale" + "dep:icu_calendar", + "dep:sys-locale", + "dep:chrono-tz", + "dep:iana-time-zone", ] # Enable Boa's WHATWG console object implementation. @@ -51,16 +53,18 @@ unicode-normalization = "0.1.19" dyn-clone = "1.0.5" once_cell = "1.12.0" tap = "1.0.1" -icu_locale_canonicalizer = { version = "0.6.0", features = ["serde"], optional = true } -icu_locid = { version = "0.6.0", features = ["serde"], optional = true } +icu_locale_canonicalizer = { version = "0.6.0", features = [ + "serde", +], optional = true } +icu_locid = { version = "0.6.0", features = ["serde"], optional = true } icu_datetime = { version = "0.6.0", features = ["serde"], optional = true } icu_plurals = { version = "0.6.0", features = ["serde"], optional = true } icu_provider = { version = "0.6.0", optional = true } -icu_testdata = {version = "0.6.0", optional = true} -icu_datagen = {version = "0.6.0", optional = true} +icu_testdata = { version = "0.6.0", optional = true } +icu_calendar = { version = "0.6.0", optional = true } sys-locale = { version = "0.2.0", optional = true } -icu = "0.6.0" -chrono-tz = "0.6.0" +chrono-tz = { version = "0.6.1", optional = true } +iana-time-zone = { version = "0.1.33", optional = true } [dev-dependencies] criterion = "0.3.5" diff --git a/boa_engine/src/builtins/intl/date_time_format.rs b/boa_engine/src/builtins/intl/date_time_format.rs index 7a784c9ac5a..9c3bcf2a330 100644 --- a/boa_engine/src/builtins/intl/date_time_format.rs +++ b/boa_engine/src/builtins/intl/date_time_format.rs @@ -24,15 +24,12 @@ use crate::{ use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; use chrono_tz::Tz; -use icu::{ - calendar::{buddhist::Buddhist, japanese::Japanese, Gregorian}, - datetime, - datetime::{ - options::{components, length, preferences}, - DateTimeFormatOptions, - }, - locid::Locale, +use icu_calendar::{buddhist::Buddhist, japanese::Japanese, Gregorian}; +use icu_datetime::{ + options::{components, length, preferences}, + DateTimeFormatOptions, }; +use icu_locid::Locale; use rustc_hash::FxHashMap; use std::cmp::{max, min}; @@ -385,38 +382,32 @@ fn build_available_locales(context: &Context) -> Vec { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma402/#sec-defaulttimezone -fn default_time_zone() -> JsString { - // FIXME fetch default time zone from the environment - JsString::new("UTC") +fn default_time_zone() -> Tz { + iana_time_zone::get_timezone() + .ok() + .and_then(|tz| tz.parse().ok()) + .unwrap_or(Tz::UTC) } -/// The abstract operation `IsValidTimeZoneName` takes argument `timeZone`, a String value, and -/// verifies that it represents a valid Zone or Link name of the IANA Time Zone Database. -/// -/// More information: -/// - [ECMAScript reference][spec] +/// Combination of abstract operations `IsValidTimeZoneName ( timeZone )` and +/// `CanonicalizeTimeZoneName ( timeZone )` /// -/// [spec]: https://tc39.es/ecma402/#sec-isvalidtimezonename -pub(crate) fn is_valid_time_zone_name(time_zone: &JsString) -> bool { - let time_zone_str = time_zone.to_string(); - let maybe_time_zone: Result = time_zone_str.parse(); - maybe_time_zone.is_ok() -} - -/// The abstract operation `CanonicalizeTimeZoneName` takes argument `timeZone` (a String value -/// that is a valid time zone name as verified by `IsValidTimeZoneName`). It returns the canonical -/// and case-regularized form of timeZone. +/// This function takes the argument `timeZone` (a String value), then parses it, +/// throwing an error if the provided string is not a valid IANA timezone. /// /// More information: -/// - [ECMAScript reference][spec] +/// - [ECMAScript reference (`IsValidTimeZoneName`)][valid spec] +/// - [ECMAScript reference (`CanonicalizeTimeZoneName`)][canon spec] /// -/// [spec]: https://tc39.es/ecma402/#sec-canonicalizetimezonename -pub(crate) fn canonicalize_time_zone_name(time_zone: &JsString) -> JsString { - let time_zone_str = time_zone.to_string(); - let canonical_tz: Tz = time_zone_str +/// [valid spec]: https://tc39.es/ecma402/#sec-isvalidtimezonename +/// [canon spec]: https://tc39.es/ecma402/#sec-canonicalizetimezonename +pub(crate) fn parse_time_zone_name(time_zone: &JsString, context: &mut Context) -> JsResult { + // b. If the result of ! IsValidTimeZoneName(timeZone) is false, then + // i. Throw a RangeError exception. + // c. Set timeZone to ! CanonicalizeTimeZoneName(timeZone). + time_zone .parse() - .expect("CanonicalizeTimeZoneName: time zone name is not supported"); - JsString::from(canonical_tz.name()) + .map_err(|err| context.construct_range_error(format!("Invalid time zone name: {err}"))) } /// Converts `hour_cycle_str` to `preferences::HourCycle` @@ -708,7 +699,7 @@ pub(crate) fn build_formats( let options = build_dtf_options(date_style, time_style); if calendar.eq(&JsString::from("buddhist")) { - let maybe_dtf = datetime::DateTimeFormat::::try_new( + let maybe_dtf = icu_datetime::DateTimeFormat::::try_new( locale.clone(), &provider, &options, @@ -718,7 +709,7 @@ pub(crate) fn build_formats( Err(_) => continue, }; } else if calendar.eq(&JsString::from("gregory")) { - let maybe_dtf = datetime::DateTimeFormat::::try_new( + let maybe_dtf = icu_datetime::DateTimeFormat::::try_new( locale.clone(), &provider, &options, @@ -728,7 +719,7 @@ pub(crate) fn build_formats( Err(_) => continue, }; } else if calendar.eq(&JsString::from("japanese")) { - let maybe_dtf = datetime::DateTimeFormat::::try_new( + let maybe_dtf = icu_datetime::DateTimeFormat::::try_new( locale.clone(), &provider, &options, @@ -783,19 +774,22 @@ pub(crate) fn date_time_style_format( let provider = context.icu().provider(); if styles.calendar.eq(&JsString::from("buddhist")) { - let maybe_dtf = datetime::DateTimeFormat::::try_new(locale, &provider, &options); + let maybe_dtf = + icu_datetime::DateTimeFormat::::try_new(locale, &provider, &options); match maybe_dtf { Ok(dtf) => Some(dtf.resolve_components()), Err(_) => None, } } else if styles.calendar.eq(&JsString::from("gregory")) { - let maybe_dtf = datetime::DateTimeFormat::::try_new(locale, &provider, &options); + let maybe_dtf = + icu_datetime::DateTimeFormat::::try_new(locale, &provider, &options); match maybe_dtf { Ok(dtf) => Some(dtf.resolve_components()), Err(_) => None, } } else if styles.calendar.eq(&JsString::from("japanese")) { - let maybe_dtf = datetime::DateTimeFormat::::try_new(locale, &provider, &options); + let maybe_dtf = + icu_datetime::DateTimeFormat::::try_new(locale, &provider, &options); match maybe_dtf { Ok(dtf) => Some(dtf.resolve_components()), Err(_) => None, @@ -1283,19 +1277,15 @@ fn initialize_date_time_format( // b. If the result of ! IsValidTimeZoneName(timeZone) is false, then // i. Throw a RangeError exception. // c. Set timeZone to ! CanonicalizeTimeZoneName(timeZone). - let time_zone_str = if time_zone.is_undefined() { + let time_zone = if time_zone.is_undefined() { default_time_zone() } else { let time_zone = time_zone.to_string(context)?; - if !is_valid_time_zone_name(&time_zone) { - return context.throw_range_error("Invalid time zone name"); - } - - canonicalize_time_zone_name(&time_zone) + parse_time_zone_name(&time_zone, context)? }; // 32. Set dateTimeFormat.[[TimeZone]] to timeZone. - date_time_fmt.time_zone = time_zone_str; + date_time_fmt.time_zone = time_zone.name().into(); // 33. Let formatOptions be a new Record. let mut format_options = FormatOptionsRecord { diff --git a/boa_engine/src/builtins/intl/mod.rs b/boa_engine/src/builtins/intl/mod.rs index 213ba73c10a..fe8af52f45f 100644 --- a/boa_engine/src/builtins/intl/mod.rs +++ b/boa_engine/src/builtins/intl/mod.rs @@ -21,9 +21,8 @@ pub mod date_time_format; mod tests; use boa_profiler::Profiler; -use icu::locid::extensions::unicode::Key; use icu_locale_canonicalizer::LocaleCanonicalizer; -use icu_locid::{locale, Locale}; +use icu_locid::{extensions::unicode::Key, locale, Locale}; use indexmap::IndexSet; use rustc_hash::FxHashMap; use tap::{Conv, Pipe, TapOptional}; diff --git a/boa_engine/src/builtins/intl/tests.rs b/boa_engine/src/builtins/intl/tests.rs index 92da8fd38ee..84356790195 100644 --- a/boa_engine/src/builtins/intl/tests.rs +++ b/boa_engine/src/builtins/intl/tests.rs @@ -1,9 +1,9 @@ use crate::{ builtins::intl::date_time_format::{ - basic_format_matcher, build_formats, canonicalize_time_zone_name, date_time_style_format, - is_valid_time_zone_name, month_to_value, numeric_to_value, string_to_hour_cycle, - text_to_value, time_zone_to_value, to_date_time_options, value_to_date_style, - value_to_time_style, year_to_value, DateTimeReqs, FormatOptionsRecord, StylesRecord, + basic_format_matcher, build_formats, date_time_style_format, month_to_value, + numeric_to_value, parse_time_zone_name, string_to_hour_cycle, text_to_value, + time_zone_to_value, to_date_time_options, value_to_date_style, value_to_time_style, + year_to_value, DateTimeReqs, FormatOptionsRecord, StylesRecord, }, builtins::intl::{ best_available_locale, best_fit_matcher, default_locale, default_number_option, @@ -15,21 +15,20 @@ use crate::{ Context, JsString, JsValue, }; -use icu::datetime::{ +use icu_datetime::{ options::{components, length, preferences}, DateTimeFormatOptions, }; use icu_locale_canonicalizer::LocaleCanonicalizer; use rustc_hash::FxHashMap; +use chrono_tz::Tz; + #[test] fn best_avail_loc() { let no_extensions_locale = JsString::new("en-US"); let available_locales = Vec::::new(); - assert_eq!( - best_available_locale(&available_locales, &no_extensions_locale,), - None - ); + assert!(best_available_locale(&available_locales, &no_extensions_locale,).is_none()); let no_extensions_locale = JsString::new("de-DE"); let available_locales = vec![no_extensions_locale.clone()]; @@ -67,7 +66,7 @@ fn lookup_match() { let matcher = lookup_matcher(&available_locales, &requested_locales, &canonicalizer); assert_eq!(matcher, default_locale(&canonicalizer)); - assert_eq!(matcher.extensions.is_empty(), true); + assert!(matcher.extensions.is_empty()); // available: [de-DE], requested: [] let available_locales = vec![JsString::new("de-DE")]; @@ -75,7 +74,7 @@ fn lookup_match() { let matcher = lookup_matcher(&available_locales, &requested_locales, &canonicalizer); assert_eq!(matcher, default_locale(&canonicalizer)); - assert_eq!(matcher.extensions.is_empty(), true); + assert!(matcher.extensions.is_empty()); // available: [fr-FR], requested: [fr-FR-u-hc-h12] let available_locales = vec![JsString::new("fr-FR")]; @@ -91,7 +90,7 @@ fn lookup_match() { let matcher = best_fit_matcher(&available_locales, &requested_locales, &canonicalizer); assert_eq!(matcher.id.to_string(), "es-ES"); - assert_eq!(matcher.extensions.is_empty(), true); + assert!(matcher.extensions.is_empty()); } #[test] @@ -529,10 +528,9 @@ fn nonterminals() { ]; for calendar_opt in nonterminal_calendar_options { - assert_eq!( - crate::builtins::intl::date_time_format::is_terminal(&calendar_opt), - true - ); + assert!(crate::builtins::intl::date_time_format::is_terminal( + &calendar_opt + )); } let terminal_calendar_options = vec![ @@ -557,10 +555,9 @@ fn nonterminals() { ]; for calendar_opt in terminal_calendar_options { - assert_eq!( - crate::builtins::intl::date_time_format::is_terminal(&calendar_opt), - false - ); + assert!(!crate::builtins::intl::date_time_format::is_terminal( + &calendar_opt + ),); } } @@ -573,26 +570,32 @@ fn build_date_time_fmt() { &Vec::::new(), &mut context, ); - assert_eq!(date_time_fmt_obj.is_err(), false); + assert!(!date_time_fmt_obj.is_err()); } #[test] -fn is_valid_tz() { - assert_eq!(is_valid_time_zone_name(&JsString::new("UTC")), true); - assert_eq!(is_valid_time_zone_name(&JsString::new("Israel")), true); +fn parse_tz() { + let context = &mut Context::default(); assert_eq!( - is_valid_time_zone_name(&JsString::new("Atlantic/Reykjavik")), - true + parse_time_zone_name(&JsString::new("UTC"), context), + Ok(Tz::UTC) ); - assert_eq!(is_valid_time_zone_name(&JsString::new("Etc/Zulu")), true); assert_eq!( - is_valid_time_zone_name(&JsString::new("Etc/Jamaica")), - false + parse_time_zone_name(&JsString::new("Israel"), context), + Ok(Tz::Israel) + ); + assert_eq!( + parse_time_zone_name(&JsString::new("Atlantic/Reykjavik"), context), + Ok(Tz::Atlantic__Reykjavik) ); - assert_eq!( - canonicalize_time_zone_name(&JsString::new("Brazil/West")).to_string(), - "Brazil/West" + parse_time_zone_name(&JsString::new("Etc/Zulu"), context), + Ok(Tz::Etc__Zulu) + ); + assert!(parse_time_zone_name(&JsString::new("Etc/Jamaica"), context).is_err()); + assert_eq!( + parse_time_zone_name(&JsString::new("Brazil/West"), context), + Ok(Tz::Brazil__West) ); } @@ -633,10 +636,7 @@ fn js_to_dtf() { value_to_date_style(&JsValue::String(JsString::new("short")), &mut context), Some(length::Date::Short) ); - assert_eq!( - value_to_date_style(&JsValue::String(JsString::new("narrow")), &mut context), - None - ); + assert!(value_to_date_style(&JsValue::String(JsString::new("narrow")), &mut context).is_none()); assert_eq!( value_to_time_style(&JsValue::String(JsString::new("full")), &mut context), @@ -759,44 +759,41 @@ fn build_fmts() { &JsString::new("gregory"), context.icu().provider(), ); - assert_eq!(formats.is_empty(), false); + assert!(!formats.is_empty()); let formats = build_formats( &JsString::new("de-DE"), &JsString::new("buddhist"), context.icu().provider(), ); - assert_eq!(formats.is_empty(), false); + assert!(!formats.is_empty()); let formats = build_formats( &JsString::new("ja-Kana-JP"), &JsString::new("japanese"), context.icu().provider(), ); - assert_eq!(formats.is_empty(), false); + assert!(!formats.is_empty()); let formats = build_formats( &JsString::new("it"), &JsString::new("julian"), context.icu().provider(), ); - assert_eq!(formats.is_empty(), true); + assert!(formats.is_empty()); let formats = build_formats( &JsString::new("en-US"), &JsString::new("gregory"), context.icu().provider(), ); - assert_eq!(formats.is_empty(), false); + assert!(!formats.is_empty()); let format_options = FormatOptionsRecord { date_time_format_opts: DateTimeFormatOptions::default(), components: FxHashMap::default(), }; - assert_eq!( - basic_format_matcher(&format_options, &formats).is_none(), - false - ); + assert!(basic_format_matcher(&format_options, &formats).is_some()); let styles = StylesRecord { locale: JsString::new("en-US"), @@ -805,8 +802,5 @@ fn build_fmts() { let date_style = JsValue::String(JsString::from("none")); let time_style = JsValue::String(JsString::from("none")); - assert_eq!( - date_time_style_format(&date_style, &time_style, &styles, &mut context).is_none(), - false - ); + assert!(date_time_style_format(&date_style, &time_style, &styles, &mut context).is_some()); }