diff --git a/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp b/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp index 486209ee3f2..73eb3fbde13 100644 --- a/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp +++ b/Libraries/LibJS/Runtime/Intl/AbstractOperations.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -610,7 +611,75 @@ ResolvedLocale resolve_locale(ReadonlySpan requested_locales, LocaleOpti return result; } -// 9.2.8 FilterLocales ( availableLocales, requestedLocales, options ), https://tc39.es/ecma402/#sec-lookupsupportedlocales +// 9.2.8 ResolveOptions ( constructor, localeData, locales, options [ , specialBehaviours [ , modifyResolutionOptions ] ] ), https://tc39.es/ecma402/#sec-resolveoptions +ThrowCompletionOr resolve_options(VM& vm, IntlObject& object, Value locales, Value options_value, SpecialBehaviors special_behaviours, Function modify_resolution_options) +{ + // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales). + auto requested_locales = TRY(canonicalize_locale_list(vm, locales)); + + // 2. If specialBehaviours is present and contains REQUIRE-OPTIONS and options is undefined, throw a TypeError exception. + if (has_flag(special_behaviours, SpecialBehaviors::RequireOptions) && options_value.is_undefined()) + return vm.throw_completion(ErrorType::IsUndefined, "options"sv); + + // 3. If specialBehaviours is present and contains COERCE-OPTIONS, set options to ? CoerceOptionsToObject(options). + // Otherwise, set options to ? GetOptionsObject(options). + GC::Ref options = has_flag(special_behaviours, SpecialBehaviors::CoerceOptions) + ? *TRY(coerce_options_to_object(vm, options_value)) + : *TRY(get_options_object(vm, options_value)); + + // 4. Let matcher be ? GetOption(options, "localeMatcher", STRING, « "lookup", "best fit" », "best fit"). + auto matcher = TRY(get_option(vm, options, vm.names.localeMatcher, OptionType::String, { "lookup"sv, "best fit"sv }, "best fit"sv)); + + // 5. Let opt be the Record { [[localeMatcher]]: matcher }. + LocaleOptions opt {}; + opt.locale_matcher = matcher; + + // 6. For each Resolution Option Descriptor desc of constructor.[[ResolutionOptionDescriptors]], do + for (auto const& descriptor : object.resolution_option_descriptors(vm)) { + // a. If desc has a [[Type]] field, let type be desc.[[Type]]. Otherwise, let type be STRING. + auto type = descriptor.type; + + // b. If desc has a [[Values]] field, let values be desc.[[Values]]. Otherwise, let values be EMPTY. + auto values = descriptor.values; + + // c. Let value be ? GetOption(options, desc.[[Property]], type, values, undefined). + auto value = TRY(get_option(vm, options, descriptor.property, type, values, Empty {})); + Optional locale_key; + + // d. If value is not undefined, then + if (!value.is_undefined()) { + // i. Set value to ! ToString(value). + auto value_string = MUST(value.to_string(vm)); + + // ii. If value cannot be matched by the type Unicode locale nonterminal, throw a RangeError exception. + if (!Unicode::is_type_identifier(value_string)) + return vm.throw_completion(ErrorType::OptionIsNotValidValue, value_string, descriptor.property); + + locale_key = move(value_string); + } + + // e. Let key be desc.[[Key]]. + auto key = descriptor.key; + + // f. Set opt.[[]] to value. + if (descriptor.property == vm.names.hour12) + opt.hour12 = value; + else + find_key_in_value(opt, key) = move(locale_key); + } + + // 7. If modifyResolutionOptions is present, perform ! modifyResolutionOptions(opt). + if (modify_resolution_options) + modify_resolution_options(opt); + + // 8. Let resolution be ResolveLocale(constructor.[[AvailableLocales]], requestedLocales, opt, constructor.[[RelevantExtensionKeys]], localeData). + auto resolution = resolve_locale(requested_locales, opt, object.relevant_extension_keys()); + + // 9. Return the Record { [[Options]]: options, [[ResolvedLocale]]: resolution, [[ResolutionOptions]]: opt }. + return ResolvedOptions { .options = options, .resolved_locale = move(resolution), .resolution_options = move(opt) }; +} + +// 9.2.9 FilterLocales ( availableLocales, requestedLocales, options ), https://tc39.es/ecma402/#sec-lookupsupportedlocales ThrowCompletionOr filter_locales(VM& vm, ReadonlySpan requested_locales, Value options) { auto& realm = *vm.current_realm(); @@ -648,7 +717,7 @@ ThrowCompletionOr filter_locales(VM& vm, ReadonlySpan requested_ return Array::create_from(realm, subset, [&vm](auto& locale) { return PrimitiveString::create(vm, move(locale)); }).ptr(); } -// 9.2.10 CoerceOptionsToObject ( options ), https://tc39.es/ecma402/#sec-coerceoptionstoobject +// 9.2.11 CoerceOptionsToObject ( options ), https://tc39.es/ecma402/#sec-coerceoptionstoobject ThrowCompletionOr coerce_options_to_object(VM& vm, Value options) { auto& realm = *vm.current_realm(); @@ -663,9 +732,9 @@ ThrowCompletionOr coerce_options_to_object(VM& vm, Value options) return TRY(options.to_object(vm)).ptr(); } -// NOTE: 9.2.11 GetOption has been removed and is being pulled in from ECMA-262 in the Temporal proposal. +// NOTE: 9.2.12 GetOption has been removed and is being pulled in from ECMA-262 in the Temporal proposal. -// 9.2.12 GetBooleanOrStringNumberFormatOption ( options, property, stringValues, fallback ), https://tc39.es/ecma402/#sec-getbooleanorstringnumberformatoption +// 9.2.13 GetBooleanOrStringNumberFormatOption ( options, property, stringValues, fallback ), https://tc39.es/ecma402/#sec-getbooleanorstringnumberformatoption ThrowCompletionOr get_boolean_or_string_number_format_option(VM& vm, Object const& options, PropertyKey const& property, ReadonlySpan string_values, StringOrBoolean fallback) { // 1. Let value be ? Get(options, property). @@ -695,7 +764,7 @@ ThrowCompletionOr get_boolean_or_string_number_format_option(VM return StringOrBoolean { *it }; } -// 9.2.13 DefaultNumberOption ( value, minimum, maximum, fallback ), https://tc39.es/ecma402/#sec-defaultnumberoption +// 9.2.14 DefaultNumberOption ( value, minimum, maximum, fallback ), https://tc39.es/ecma402/#sec-defaultnumberoption ThrowCompletionOr> default_number_option(VM& vm, Value value, int minimum, int maximum, Optional fallback) { // 1. If value is undefined, return fallback. @@ -713,7 +782,7 @@ ThrowCompletionOr> default_number_option(VM& vm, Value value, int return floor(value.as_double()); } -// 9.2.14 GetNumberOption ( options, property, minimum, maximum, fallback ), https://tc39.es/ecma402/#sec-getnumberoption +// 9.2.15 GetNumberOption ( options, property, minimum, maximum, fallback ), https://tc39.es/ecma402/#sec-getnumberoption ThrowCompletionOr> get_number_option(VM& vm, Object const& options, PropertyKey const& property, int minimum, int maximum, Optional fallback) { // 1. Assert: Type(options) is Object. diff --git a/Libraries/LibJS/Runtime/Intl/AbstractOperations.h b/Libraries/LibJS/Runtime/Intl/AbstractOperations.h index 1d0d3f9f644..e35fe92e535 100644 --- a/Libraries/LibJS/Runtime/Intl/AbstractOperations.h +++ b/Libraries/LibJS/Runtime/Intl/AbstractOperations.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -29,6 +30,7 @@ struct LocaleOptions { Optional kf; // [[CaseFirst]] Optional kn; // [[Numeric]] Optional nu; // [[NumberingSystem]] + Value hour12; }; struct MatchedLocale { @@ -47,6 +49,19 @@ struct ResolvedLocale { LocaleKey nu; // [[NumberingSystem]] }; +struct ResolvedOptions { + GC::Ref options; + ResolvedLocale resolved_locale; + LocaleOptions resolution_options; +}; + +enum class SpecialBehaviors : u8 { + None = 0, + RequireOptions = 1 << 1, + CoerceOptions = 1 << 2, +}; +AK_ENUM_BITWISE_OPERATORS(SpecialBehaviors); + using StringOrBoolean = Variant; bool is_structurally_valid_language_tag(StringView locale); @@ -60,6 +75,7 @@ Optional lookup_matching_locale_by_prefix(ReadonlySpan re Optional lookup_matching_locale_by_best_fit(ReadonlySpan requested_locales); String insert_unicode_extension_and_canonicalize(Unicode::LocaleID locale_id, Vector attributes, Vector keywords); ResolvedLocale resolve_locale(ReadonlySpan requested_locales, LocaleOptions const& options, ReadonlySpan relevant_extension_keys); +ThrowCompletionOr resolve_options(VM& vm, IntlObject& object, Value locales, Value options_value, SpecialBehaviors special_behaviours = SpecialBehaviors::None, Function modify_resolution_options = {}); ThrowCompletionOr filter_locales(VM& vm, ReadonlySpan requested_locales, Value options); ThrowCompletionOr coerce_options_to_object(VM&, Value options); ThrowCompletionOr get_boolean_or_string_number_format_option(VM& vm, Object const& options, PropertyKey const& property, ReadonlySpan string_values, StringOrBoolean fallback);