2021-08-24 22:58:45 -04:00
/*
2024-06-08 11:22:05 -04:00
* Copyright ( c ) 2021 - 2024 , Tim Flynn < trflynn89 @ serenityos . org >
2021-08-24 22:58:45 -04:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# include <AK/AllOf.h>
# include <AK/CharacterTypes.h>
2021-09-10 11:04:54 -04:00
# include <AK/Find.h>
2021-08-24 22:58:45 -04:00
# include <AK/QuickSort.h>
# include <AK/TypeCasts.h>
# include <LibJS/Runtime/Array.h>
# include <LibJS/Runtime/GlobalObject.h>
# include <LibJS/Runtime/Intl/AbstractOperations.h>
2021-09-01 22:26:06 -04:00
# include <LibJS/Runtime/Intl/Locale.h>
2024-06-17 16:24:58 -04:00
# include <LibJS/Runtime/Intl/SingleUnitIdentifiers.h>
# include <LibJS/Runtime/VM.h>
2023-10-06 17:54:21 +02:00
# include <LibJS/Runtime/ValueInlines.h>
2024-06-23 09:14:27 -04:00
# include <LibUnicode/UnicodeKeywords.h>
2021-08-24 22:58:45 -04:00
namespace JS : : Intl {
2024-06-16 15:36:34 -04:00
Optional < LocaleKey > locale_key_from_value ( Value value )
{
if ( value . is_undefined ( ) )
return OptionalNone { } ;
if ( value . is_null ( ) )
return Empty { } ;
if ( value . is_string ( ) )
return value . as_string ( ) . utf8_string ( ) ;
VERIFY_NOT_REACHED ( ) ;
}
2024-06-25 10:07:35 -04:00
// 6.2.1 IsStructurallyValidLanguageTag ( locale ), https://tc39.es/ecma402/#sec-isstructurallyvalidlanguagetag
2024-06-08 11:22:05 -04:00
bool is_structurally_valid_language_tag ( StringView locale )
2021-08-24 22:58:45 -04:00
{
2023-08-22 15:39:18 -04:00
auto contains_duplicate_variant = [ & ] ( auto & variants ) {
2021-08-28 14:46:36 -04:00
if ( variants . is_empty ( ) )
return false ;
quick_sort ( variants ) ;
for ( size_t i = 0 ; i < variants . size ( ) - 1 ; + + i ) {
2023-03-08 13:03:04 -05:00
if ( variants [ i ] . equals_ignoring_case ( variants [ i + 1 ] ) )
2021-08-28 14:46:36 -04:00
return true ;
}
return false ;
} ;
2024-06-08 11:22:05 -04:00
// 1. Let lowerLocale be the ASCII-lowercase of locale.
2024-06-23 09:14:27 -04:00
// NOTE: LibUnicode's parsing is case-insensitive.
2021-08-24 22:58:45 -04:00
2024-06-08 11:22:05 -04:00
// 2. If lowerLocale cannot be matched by the unicode_locale_id Unicode locale nonterminal, return false.
2024-06-23 09:14:27 -04:00
auto locale_id = Unicode : : parse_unicode_locale_id ( locale ) ;
2021-08-24 22:58:45 -04:00
if ( ! locale_id . has_value ( ) )
2024-06-08 11:22:05 -04:00
return false ;
2021-08-24 22:58:45 -04:00
2024-06-08 11:22:05 -04:00
// 3. If lowerLocale uses any of the backwards compatibility syntax described in Unicode Technical Standard #35 Part 1 Core,
// Section 3.3 BCP 47 Conformance, return false.
// https://unicode.org/reports/tr35/#BCP_47_Conformance
2021-08-24 22:58:45 -04:00
if ( locale . contains ( ' _ ' ) | | locale_id - > language_id . is_root | | ! locale_id - > language_id . language . has_value ( ) )
2024-06-08 11:22:05 -04:00
return false ;
// 4. Let languageId be the longest prefix of lowerLocale matched by the unicode_language_id Unicode locale nonterminal.
auto & language_id = locale_id - > language_id ;
// 5. Let variants be GetLocaleVariants(languageId).
// 6. If variants is not undefined, then
if ( auto & variants = language_id . variants ; ! variants . is_empty ( ) ) {
// a. If variants contains any duplicate subtags, return false.
if ( contains_duplicate_variant ( variants ) )
return false ;
}
2021-08-24 22:58:45 -04:00
2024-06-08 11:22:05 -04:00
HashTable < char > unique_keys ;
2021-08-24 22:58:45 -04:00
2024-06-08 11:22:05 -04:00
// 7. Let allExtensions be the suffix of lowerLocale following languageId.
// 8. If allExtensions contains a substring matched by the pu_extensions Unicode locale nonterminal, let extensions be
// the prefix of allExtensions preceding the longest such substring. Otherwise, let extensions be allExtensions.
// 9. If extensions is not the empty String, then
2021-08-28 14:46:36 -04:00
for ( auto & extension : locale_id - > extensions ) {
char key = extension . visit (
2024-06-23 09:14:27 -04:00
[ ] ( Unicode : : LocaleExtension const & ) { return ' u ' ; } ,
[ ] ( Unicode : : TransformedExtension const & ) { return ' t ' ; } ,
[ ] ( Unicode : : OtherExtension const & ext ) { return static_cast < char > ( to_ascii_lowercase ( ext . key ) ) ; } ) ;
2021-08-28 14:46:36 -04:00
2024-06-08 11:22:05 -04:00
// a. If extensions contains any duplicate singleton subtags, return false.
if ( unique_keys . set ( key ) ! = HashSetResult : : InsertedNewEntry )
return false ;
2021-08-28 14:46:36 -04:00
2024-06-08 11:22:05 -04:00
// b. Let transformExtension be the longest substring of extensions matched by the transformed_extensions Unicode
// locale nonterminal. If there is no such substring, return true.
2024-06-23 09:14:27 -04:00
if ( auto * transformed = extension . get_pointer < Unicode : : TransformedExtension > ( ) ) {
2024-06-08 11:22:05 -04:00
// c. Assert: The substring of transformExtension from 0 to 3 is "-t-".
// d. Let tPrefix be the substring of transformExtension from 3.
// e. Let tlang be the longest prefix of tPrefix matched by the tlang Unicode locale nonterminal. If there is
// no such prefix, return true.
auto & transformed_language = transformed - > language ;
if ( ! transformed_language . has_value ( ) )
continue ;
// f. Let tlangRefinements be the longest suffix of tlang following a non-empty prefix matched by the
// unicode_language_subtag Unicode locale nonterminal.
auto & transformed_refinements = transformed_language - > variants ;
// g. If tlangRefinements contains any duplicate substrings matched greedily by the unicode_variant_subtag
// Unicode locale nonterminal, return false.
if ( contains_duplicate_variant ( transformed_refinements ) )
return false ;
2021-08-24 22:58:45 -04:00
}
}
2024-06-08 11:22:05 -04:00
// 10. Return true.
return true ;
2021-08-24 22:58:45 -04:00
}
2024-06-25 10:07:35 -04:00
// 6.2.2 CanonicalizeUnicodeLocaleId ( locale ), https://tc39.es/ecma402/#sec-canonicalizeunicodelocaleid
2024-06-08 11:22:05 -04:00
String canonicalize_unicode_locale_id ( StringView locale )
2021-08-24 22:58:45 -04:00
{
2024-06-23 09:14:27 -04:00
return Unicode : : canonicalize_unicode_locale_id ( locale ) ;
2021-08-24 22:58:45 -04:00
}
2022-03-28 09:53:06 -04:00
// 6.3.1 IsWellFormedCurrencyCode ( currency ), https://tc39.es/ecma402/#sec-iswellformedcurrencycode
2021-09-10 11:04:54 -04:00
bool is_well_formed_currency_code ( StringView currency )
2021-08-24 22:58:45 -04:00
{
2022-03-28 09:53:06 -04:00
// 1. If the length of currency is not 3, return false.
2021-08-24 22:58:45 -04:00
if ( currency . length ( ) ! = 3 )
return false ;
2022-03-28 09:53:06 -04:00
// 2. Let normalized be the ASCII-uppercase of currency.
// 3. If normalized contains any code unit outside of 0x0041 through 0x005A (corresponding to Unicode characters LATIN CAPITAL LETTER A through LATIN CAPITAL LETTER Z), return false.
2021-08-24 22:58:45 -04:00
if ( ! all_of ( currency , is_ascii_alpha ) )
return false ;
// 4. Return true.
return true ;
}
2024-06-25 10:07:35 -04:00
// 6.6.1 IsWellFormedUnitIdentifier ( unitIdentifier ), https://tc39.es/ecma402/#sec-iswellformedunitidentifier
2021-09-10 11:04:54 -04:00
bool is_well_formed_unit_identifier ( StringView unit_identifier )
{
2024-06-25 10:07:35 -04:00
// 6.6.2 IsSanctionedSingleUnitIdentifier ( unitIdentifier ), https://tc39.es/ecma402/#sec-issanctionedsingleunitidentifier
2022-03-28 12:05:18 -04:00
constexpr auto is_sanctioned_single_unit_identifier = [ ] ( StringView unit_identifier ) {
2021-09-10 11:04:54 -04:00
// 1. If unitIdentifier is listed in Table 2 below, return true.
// 2. Else, return false.
2022-03-28 12:05:18 -04:00
static constexpr auto sanctioned_units = sanctioned_single_unit_identifiers ( ) ;
2021-09-10 11:04:54 -04:00
return find ( sanctioned_units . begin ( ) , sanctioned_units . end ( ) , unit_identifier ) ! = sanctioned_units . end ( ) ;
} ;
2022-03-28 12:05:18 -04:00
// 1. If ! IsSanctionedSingleUnitIdentifier(unitIdentifier) is true, then
if ( is_sanctioned_single_unit_identifier ( unit_identifier ) ) {
2021-09-10 11:04:54 -04:00
// a. Return true.
return true ;
}
2022-05-02 20:54:39 +02:00
// 2. Let i be StringIndexOf(unitIdentifier, "-per-", 0).
2021-09-10 11:04:54 -04:00
auto indices = unit_identifier . find_all ( " -per- " sv ) ;
2022-05-02 20:54:39 +02:00
// 3. If i is -1 or StringIndexOf(unitIdentifier, "-per-", i + 1) is not -1, then
2021-09-10 11:04:54 -04:00
if ( indices . size ( ) ! = 1 ) {
// a. Return false.
return false ;
}
2022-03-28 12:05:18 -04:00
// 4. Assert: The five-character substring "-per-" occurs exactly once in unitIdentifier, at index i.
// NOTE: We skip this because the indices vector being of size 1 already verifies this invariant.
2021-09-10 11:04:54 -04:00
2022-03-28 12:05:18 -04:00
// 5. Let numerator be the substring of unitIdentifier from 0 to i.
auto numerator = unit_identifier . substring_view ( 0 , indices [ 0 ] ) ;
2021-09-10 11:04:54 -04:00
2022-03-28 12:05:18 -04:00
// 6. Let denominator be the substring of unitIdentifier from i + 5.
2021-09-10 11:04:54 -04:00
auto denominator = unit_identifier . substring_view ( indices [ 0 ] + 5 ) ;
2022-03-28 12:05:18 -04:00
// 7. If ! IsSanctionedSingleUnitIdentifier(numerator) and ! IsSanctionedSingleUnitIdentifier(denominator) are both true, then
if ( is_sanctioned_single_unit_identifier ( numerator ) & & is_sanctioned_single_unit_identifier ( denominator ) ) {
// a. Return true.
return true ;
2021-09-10 11:04:54 -04:00
}
2022-03-28 12:05:18 -04:00
// 8. Return false.
return false ;
2021-09-10 11:04:54 -04:00
}
2021-08-24 22:58:45 -04:00
// 9.2.1 CanonicalizeLocaleList ( locales ), https://tc39.es/ecma402/#sec-canonicalizelocalelist
2023-01-19 13:13:57 -05:00
ThrowCompletionOr < Vector < String > > canonicalize_locale_list ( VM & vm , Value locales )
2021-08-24 22:58:45 -04:00
{
2022-08-20 08:25:24 +01:00
auto & realm = * vm . current_realm ( ) ;
2021-08-24 22:58:45 -04:00
// 1. If locales is undefined, then
if ( locales . is_undefined ( ) ) {
// a. Return a new empty List.
2023-01-19 13:13:57 -05:00
return Vector < String > { } ;
2021-08-24 22:58:45 -04:00
}
// 2. Let seen be a new empty List.
2023-01-19 13:13:57 -05:00
Vector < String > seen ;
2021-08-24 22:58:45 -04:00
Object * object = nullptr ;
// 3. If Type(locales) is String or Type(locales) is Object and locales has an [[InitializedLocale]] internal slot, then
2021-09-01 22:26:06 -04:00
if ( locales . is_string ( ) | | ( locales . is_object ( ) & & is < Locale > ( locales . as_object ( ) ) ) ) {
2022-05-02 20:54:39 +02:00
// a. Let O be CreateArrayFromList(« locales »).
2022-08-16 00:20:49 +01:00
object = Array : : create_from ( realm , { locales } ) ;
2021-08-24 22:58:45 -04:00
}
// 4. Else,
else {
// a. Let O be ? ToObject(locales).
2022-08-21 14:00:56 +01:00
object = TRY ( locales . to_object ( vm ) ) ;
2021-08-24 22:58:45 -04:00
}
// 5. Let len be ? ToLength(? Get(O, "length")).
2021-10-17 23:57:37 +03:00
auto length_value = TRY ( object - > get ( vm . names . length ) ) ;
2022-08-21 14:00:56 +01:00
auto length = TRY ( length_value . to_length ( vm ) ) ;
2021-08-24 22:58:45 -04:00
// 6. Let k be 0.
// 7. Repeat, while k < len,
for ( size_t k = 0 ; k < length ; + + k ) {
// a. Let Pk be ToString(k).
2021-10-24 16:01:24 +02:00
auto property_key = PropertyKey { k } ;
2021-08-24 22:58:45 -04:00
// b. Let kPresent be ? HasProperty(O, Pk).
2021-10-03 02:00:39 +01:00
auto key_present = TRY ( object - > has_property ( property_key ) ) ;
2021-08-24 22:58:45 -04:00
// c. If kPresent is true, then
if ( key_present ) {
// i. Let kValue be ? Get(O, Pk).
2021-10-02 23:52:27 +01:00
auto key_value = TRY ( object - > get ( property_key ) ) ;
2021-08-24 22:58:45 -04:00
// ii. If Type(kValue) is not String or Object, throw a TypeError exception.
2021-09-18 19:24:28 +03:00
if ( ! key_value . is_string ( ) & & ! key_value . is_object ( ) )
2023-01-19 13:13:57 -05:00
return vm . throw_completion < TypeError > ( ErrorType : : NotAnObjectOrString , key_value ) ;
2021-08-24 22:58:45 -04:00
2023-01-19 13:13:57 -05:00
String tag ;
2021-09-01 22:26:06 -04:00
2021-08-24 22:58:45 -04:00
// iii. If Type(kValue) is Object and kValue has an [[InitializedLocale]] internal slot, then
2021-09-01 22:26:06 -04:00
if ( key_value . is_object ( ) & & is < Locale > ( key_value . as_object ( ) ) ) {
// 1. Let tag be kValue.[[Locale]].
tag = static_cast < Locale const & > ( key_value . as_object ( ) ) . locale ( ) ;
}
2021-08-24 22:58:45 -04:00
// iv. Else,
2021-09-01 22:26:06 -04:00
else {
// 1. Let tag be ? ToString(kValue).
2023-01-19 13:13:57 -05:00
tag = TRY ( key_value . to_string ( vm ) ) ;
2021-09-01 22:26:06 -04:00
}
2021-08-24 22:58:45 -04:00
2022-03-28 08:30:48 -04:00
// v. If ! IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
2024-06-08 11:22:05 -04:00
if ( ! is_structurally_valid_language_tag ( tag ) )
2022-08-16 20:33:17 +01:00
return vm . throw_completion < RangeError > ( ErrorType : : IntlInvalidLanguageTag , tag ) ;
2021-08-24 22:58:45 -04:00
2022-03-28 08:30:48 -04:00
// vi. Let canonicalizedTag be ! CanonicalizeUnicodeLocaleId(tag).
2024-06-08 11:22:05 -04:00
auto canonicalized_tag = canonicalize_unicode_locale_id ( tag ) ;
2021-08-24 22:58:45 -04:00
// vii. If canonicalizedTag is not an element of seen, append canonicalizedTag as the last element of seen.
if ( ! seen . contains_slow ( canonicalized_tag ) )
2023-08-30 11:08:15 -04:00
seen . append ( move ( canonicalized_tag ) ) ;
2021-08-24 22:58:45 -04:00
}
// d. Increase k by 1.
}
return seen ;
}
2024-06-18 10:13:30 -04:00
// 9.2.3 LookupMatchingLocaleByPrefix ( availableLocales, requestedLocales ), https://tc39.es/ecma402/#sec-lookupmatchinglocalebyprefix
Optional < MatchedLocale > lookup_matching_locale_by_prefix ( ReadonlySpan < String > requested_locales )
2021-08-24 22:58:45 -04:00
{
2024-06-18 10:13:30 -04:00
// 1. For each element locale of requestedLocales, do
for ( auto locale : requested_locales ) {
2024-06-23 09:14:27 -04:00
auto locale_id = Unicode : : parse_unicode_locale_id ( locale ) ;
2024-06-18 10:13:30 -04:00
VERIFY ( locale_id . has_value ( ) ) ;
2021-08-24 22:58:45 -04:00
2024-06-18 10:13:30 -04:00
// a. Let extension be empty.
2024-06-23 09:14:27 -04:00
Optional < Unicode : : Extension > extension ;
2024-06-18 10:13:30 -04:00
String locale_without_extension ;
2021-08-30 08:51:52 -04:00
2024-06-18 10:13:30 -04:00
// b. If locale contains a Unicode locale extension sequence, then
2024-06-23 09:14:27 -04:00
if ( auto extensions = locale_id - > remove_extension_type < Unicode : : LocaleExtension > ( ) ; ! extensions . is_empty ( ) ) {
2024-06-18 10:13:30 -04:00
VERIFY ( extensions . size ( ) = = 1 ) ;
2021-08-24 22:58:45 -04:00
2024-06-18 10:13:30 -04:00
// i. Set extension to the Unicode locale extension sequence of locale.
extension = extensions . take_first ( ) ;
2021-08-30 08:51:52 -04:00
2024-06-18 10:13:30 -04:00
// ii. Set locale to the String value that is locale with any Unicode locale extension sequences removed.
locale = locale_id - > to_string ( ) ;
}
2021-08-24 22:58:45 -04:00
2024-06-18 10:13:30 -04:00
// c. Let prefix be locale.
StringView prefix { locale } ;
2021-08-24 22:58:45 -04:00
2024-06-18 10:13:30 -04:00
// d. Repeat, while prefix is not the empty String,
while ( ! prefix . is_empty ( ) ) {
// i. If availableLocales contains prefix, return the Record { [[locale]]: prefix, [[extension]]: extension }.
2024-06-23 09:14:27 -04:00
if ( Unicode : : is_locale_available ( prefix ) )
2024-06-18 10:13:30 -04:00
return MatchedLocale { MUST ( String : : from_utf8 ( prefix ) ) , move ( extension ) } ;
2021-08-24 22:58:45 -04:00
2024-06-18 10:13:30 -04:00
// ii. If prefix contains "-" (code unit 0x002D HYPHEN-MINUS), let pos be the index into prefix of the last
// occurrence of "-"; else let pos be 0.
auto position = prefix . find_last ( ' - ' ) . value_or ( 0 ) ;
// iii. Repeat, while pos ≥ 2 and the substring of prefix from pos - 2 to pos - 1 is "-",
while ( position > = 2 & & prefix . substring_view ( position - 2 , 1 ) = = ' - ' ) {
// 1. Set pos to pos - 2.
position - = 2 ;
2021-08-30 08:51:52 -04:00
}
2021-08-24 22:58:45 -04:00
2024-06-18 10:13:30 -04:00
// iv. Set prefix to the substring of prefix from 0 to pos.
prefix = prefix . substring_view ( 0 , position ) ;
2021-08-24 22:58:45 -04:00
}
}
2024-06-18 10:13:30 -04:00
// 2. Return undefined.
return { } ;
2021-08-24 22:58:45 -04:00
}
2024-06-18 10:13:30 -04:00
// 9.2.4 LookupMatchingLocaleByBestFit ( availableLocales, requestedLocales ), https://tc39.es/ecma402/#sec-lookupmatchinglocalebybestfit
Optional < MatchedLocale > lookup_matching_locale_by_best_fit ( ReadonlySpan < String > requested_locales )
2021-08-24 22:58:45 -04:00
{
2024-06-18 10:13:30 -04:00
// The algorithm is implementation dependent, but should produce results that a typical user of the requested locales
// would consider at least as good as those produced by the LookupMatchingLocaleByPrefix algorithm.
return lookup_matching_locale_by_prefix ( requested_locales ) ;
2021-08-24 22:58:45 -04:00
}
2024-06-18 10:13:30 -04:00
// 9.2.6 InsertUnicodeExtensionAndCanonicalize ( locale, attributes, keywords ), https://tc39.es/ecma402/#sec-insert-unicode-extension-and-canonicalize
2024-06-23 09:14:27 -04:00
String insert_unicode_extension_and_canonicalize ( Unicode : : LocaleID locale , Vector < String > attributes , Vector < Unicode : : Keyword > keywords )
2021-09-01 22:08:15 -04:00
{
// Note: This implementation differs from the spec in how the extension is inserted. The spec assumes
// the input to this method is a string, and is written such that operations are performed on parts
2024-06-23 09:14:27 -04:00
// of that string. LibUnicode gives us the parsed locale in a structure, so we can mutate that
2021-09-01 22:08:15 -04:00
// structure directly.
2024-06-23 09:14:27 -04:00
locale . extensions . append ( Unicode : : LocaleExtension { move ( attributes ) , move ( keywords ) } ) ;
2023-08-30 11:08:15 -04:00
2024-06-18 10:13:30 -04:00
// 10. Return CanonicalizeUnicodeLocaleId(newLocale).
2024-06-08 11:22:05 -04:00
return JS : : Intl : : canonicalize_unicode_locale_id ( locale . to_string ( ) ) ;
2021-09-01 22:08:15 -04:00
}
2021-09-10 10:01:41 -04:00
template < typename T >
static auto & find_key_in_value ( T & value , StringView key )
{
2021-11-28 17:55:47 -05:00
if ( key = = " ca " sv )
return value . ca ;
2022-01-29 11:30:46 -05:00
if ( key = = " co " sv )
return value . co ;
2021-11-28 17:55:47 -05:00
if ( key = = " hc " sv )
return value . hc ;
2022-01-29 11:30:46 -05:00
if ( key = = " kf " sv )
return value . kf ;
if ( key = = " kn " sv )
return value . kn ;
2021-09-10 11:04:54 -04:00
if ( key = = " nu " sv )
return value . nu ;
2024-06-18 10:13:30 -04:00
// If you hit this point, you must add any missing keys from [[RelevantExtensionKeys]] to LocaleOptions and ResolvedLocale.
2021-09-10 10:01:41 -04:00
VERIFY_NOT_REACHED ( ) ;
}
2024-06-16 15:36:34 -04:00
static Vector < LocaleKey > available_keyword_values ( StringView locale , StringView key )
{
2024-06-23 09:14:27 -04:00
auto key_locale_data = Unicode : : available_keyword_values ( locale , key ) ;
2024-06-16 15:36:34 -04:00
Vector < LocaleKey > result ;
result . ensure_capacity ( key_locale_data . size ( ) ) ;
for ( auto & keyword : key_locale_data )
result . unchecked_append ( move ( keyword ) ) ;
if ( key = = " hc " sv ) {
// https://tc39.es/ecma402/#sec-intl.datetimeformat-internal-slots
// [[LocaleData]].[[<locale>]].[[hc]] must be « null, "h11", "h12", "h23", "h24" ».
result . prepend ( Empty { } ) ;
}
return result ;
}
2021-08-24 22:58:45 -04:00
// 9.2.7 ResolveLocale ( availableLocales, requestedLocales, options, relevantExtensionKeys, localeData ), https://tc39.es/ecma402/#sec-resolvelocale
2024-06-18 10:13:30 -04:00
ResolvedLocale resolve_locale ( ReadonlySpan < String > requested_locales , LocaleOptions const & options , ReadonlySpan < StringView > relevant_extension_keys )
2021-08-24 22:58:45 -04:00
{
2024-06-16 15:36:34 -04:00
static auto true_string = " true " _string ;
2021-08-24 22:58:45 -04:00
// 1. Let matcher be options.[[localeMatcher]].
auto const & matcher = options . locale_matcher ;
2024-06-18 10:13:30 -04:00
Optional < MatchedLocale > matcher_result ;
2021-08-24 22:58:45 -04:00
// 2. If matcher is "lookup", then
2024-06-18 10:13:30 -04:00
if ( matcher . is_string ( ) & & matcher . as_string ( ) . utf8_string_view ( ) = = " lookup " sv ) {
// a. Let r be LookupMatchingLocaleByPrefix(availableLocales, requestedLocales).
matcher_result = lookup_matching_locale_by_prefix ( requested_locales ) ;
2021-08-24 22:58:45 -04:00
}
// 3. Else,
else {
2024-06-18 10:13:30 -04:00
// a. Let r be LookupMatchingLocaleByBestFit(availableLocales, requestedLocales).
matcher_result = lookup_matching_locale_by_best_fit ( requested_locales ) ;
2021-08-24 22:58:45 -04:00
}
2021-08-30 08:51:52 -04:00
2024-06-18 10:13:30 -04:00
// 4. If r is undefined, set r to the Record { [[locale]]: DefaultLocale(), [[extension]]: empty }.
if ( ! matcher_result . has_value ( ) )
2024-06-23 09:14:27 -04:00
matcher_result = MatchedLocale { MUST ( String : : from_utf8 ( Unicode : : default_locale ( ) ) ) , { } } ;
2024-06-18 10:13:30 -04:00
// 5. Let foundLocale be r.[[locale]].
auto found_locale = move ( matcher_result - > locale ) ;
2021-08-30 08:51:52 -04:00
2024-06-18 10:13:30 -04:00
// 6. Let foundLocaleData be localeData.[[<foundLocale>]].
// 7. Assert: Type(foundLocaleData) is Record.
2021-08-30 08:51:52 -04:00
2024-06-18 10:13:30 -04:00
// 8. Let result be a new Record.
// 9. Set result.[[LocaleData]] to foundLocaleData.
ResolvedLocale result { } ;
2021-08-24 22:58:45 -04:00
2024-06-23 09:14:27 -04:00
Vector < Unicode : : Keyword > keywords ;
2021-09-10 10:01:41 -04:00
2024-06-18 10:13:30 -04:00
// 10. If r.[[extension]] is not empty, then
if ( matcher_result - > extension . has_value ( ) ) {
// a. Let components be UnicodeExtensionComponents(r.[[extension]]).
2024-06-23 09:14:27 -04:00
auto & components = matcher_result - > extension - > get < Unicode : : LocaleExtension > ( ) ;
2024-06-18 10:13:30 -04:00
2021-09-10 10:01:41 -04:00
// b. Let keywords be components.[[Keywords]].
keywords = move ( components . keywords ) ;
}
2024-06-18 10:13:30 -04:00
// 11. Else,
// a. Let keywords be a new empty List.
2021-09-10 10:01:41 -04:00
2024-06-18 10:13:30 -04:00
// 12. Let supportedKeywords be a new empty List.
2024-06-23 09:14:27 -04:00
Vector < Unicode : : Keyword > supported_keywords ;
2021-09-10 10:01:41 -04:00
2024-06-18 10:13:30 -04:00
// 13. For each element key of relevantExtensionKeys, do
2021-09-10 10:01:41 -04:00
for ( auto const & key : relevant_extension_keys ) {
2024-06-18 10:13:30 -04:00
// a. Let keyLocaleData be foundLocaleData.[[<key>]].
// b. Assert: keyLocaleData is a List.
2024-06-16 15:36:34 -04:00
auto key_locale_data = available_keyword_values ( found_locale , key ) ;
2021-09-10 10:01:41 -04:00
2024-06-18 10:13:30 -04:00
// c. Let value be keyLocaleData[0].
// d. Assert: value is a String or value is null.
2024-06-15 20:23:53 -04:00
auto value = key_locale_data [ 0 ] ;
2021-09-10 10:01:41 -04:00
2024-06-18 10:13:30 -04:00
// e. Let supportedKeyword be empty.
2024-06-23 09:14:27 -04:00
Optional < Unicode : : Keyword > supported_keyword ;
2021-09-10 10:01:41 -04:00
2024-06-18 10:13:30 -04:00
// f. If keywords contains an element whose [[Key]] is key, then
if ( auto entry = keywords . find_if ( [ & ] ( auto const & entry ) { return entry . key = = key ; } ) ; entry ! = keywords . end ( ) ) {
// i. Let entry be the element of keywords whose [[Key]] is key.
// ii. Let requestedValue be entry.[[Value]].
auto requested_value = entry - > value ;
2021-09-10 10:01:41 -04:00
2024-06-18 10:13:30 -04:00
// iii. If requestedValue is not the empty String, then
2021-09-10 10:01:41 -04:00
if ( ! requested_value . is_empty ( ) ) {
2024-06-18 10:13:30 -04:00
// 1. If keyLocaleData contains requestedValue, then
2021-09-10 10:01:41 -04:00
if ( key_locale_data . contains_slow ( requested_value ) ) {
2024-06-18 10:13:30 -04:00
// a. Set value to requestedValue.
2023-01-19 13:13:57 -05:00
value = move ( requested_value ) ;
2021-09-10 10:01:41 -04:00
2024-06-18 10:13:30 -04:00
// b. Set supportedKeyword to the Record { [[Key]]: key, [[Value]]: value }.
2024-06-23 09:14:27 -04:00
supported_keyword = Unicode : : Keyword { MUST ( String : : from_utf8 ( key ) ) , move ( entry - > value ) } ;
2021-09-10 10:01:41 -04:00
}
}
2024-06-18 10:13:30 -04:00
// iv. Else if keyLocaleData contains "true", then
2024-06-16 15:36:34 -04:00
else if ( key_locale_data . contains_slow ( true_string ) ) {
2024-06-18 10:13:30 -04:00
// 1. Set value to "true".
2024-06-16 15:36:34 -04:00
value = true_string ;
2021-09-10 10:01:41 -04:00
2024-06-18 10:13:30 -04:00
// 2. Set supportedKeyword to the Record { [[Key]]: key, [[Value]]: "" }.
2024-06-23 09:14:27 -04:00
supported_keyword = Unicode : : Keyword { MUST ( String : : from_utf8 ( key ) ) , { } } ;
2021-09-10 10:01:41 -04:00
}
}
2024-06-18 10:13:30 -04:00
// g. Assert: options has a field [[<key>]].
// h. Let optionsValue be options.[[<key>]].
// i. Assert: optionsValue is a String, or optionsValue is either undefined or null.
2021-09-10 10:01:41 -04:00
auto options_value = find_key_in_value ( options , key ) ;
2024-06-18 10:13:30 -04:00
// j. If optionsValue is a String, then
2024-06-16 15:36:34 -04:00
if ( auto * options_string = options_value . has_value ( ) ? options_value - > get_pointer < String > ( ) : nullptr ) {
2024-06-18 10:13:30 -04:00
// i. Let ukey be the ASCII-lowercase of key.
// NOTE: `key` is always lowercase, and this step is likely to be removed:
// https://github.com/tc39/ecma402/pull/846#discussion_r1428263375
// ii. Set optionsValue to CanonicalizeUValue(ukey, optionsValue).
2024-06-23 09:14:27 -04:00
* options_string = Unicode : : canonicalize_unicode_extension_values ( key , * options_string ) ;
2021-09-10 10:01:41 -04:00
2024-06-18 10:13:30 -04:00
// iii. If optionsValue is the empty String, then
2024-06-16 15:36:34 -04:00
if ( options_string - > is_empty ( ) ) {
2024-06-18 10:13:30 -04:00
// 1. Set optionsValue to "true".
2024-06-16 15:36:34 -04:00
* options_string = true_string ;
2021-09-10 10:01:41 -04:00
}
}
2024-06-18 10:13:30 -04:00
// k. If SameValue(optionsValue, value) is false and keyLocaleData contains optionsValue, then
2023-01-12 10:53:04 -05:00
if ( options_value . has_value ( ) & & ( options_value ! = value ) & & key_locale_data . contains_slow ( * options_value ) ) {
2024-06-18 10:13:30 -04:00
// i. Set value to optionsValue.
2024-06-15 20:23:53 -04:00
value = options_value . release_value ( ) ;
2023-01-12 10:53:04 -05:00
2024-06-18 10:13:30 -04:00
// ii. Set supportedKeyword to empty.
supported_keyword . clear ( ) ;
2021-09-10 10:01:41 -04:00
}
2024-06-18 10:13:30 -04:00
// l. If supportedKeyword is not empty, append supportedKeyword to supportedKeywords.
if ( supported_keyword . has_value ( ) )
supported_keywords . append ( supported_keyword . release_value ( ) ) ;
2021-09-10 10:01:41 -04:00
2024-06-18 10:13:30 -04:00
// m. Set result.[[<key>]] to value.
find_key_in_value ( result , key ) = move ( value ) ;
2021-09-10 10:01:41 -04:00
}
2024-06-18 10:13:30 -04:00
// 14. If supportedKeywords is not empty, then
if ( ! supported_keywords . is_empty ( ) ) {
2024-06-23 09:14:27 -04:00
auto locale_id = Unicode : : parse_unicode_locale_id ( found_locale ) ;
2021-09-10 10:01:41 -04:00
VERIFY ( locale_id . has_value ( ) ) ;
2024-06-18 10:13:30 -04:00
// a. Let supportedAttributes be a new empty List.
// b. Set foundLocale to InsertUnicodeExtensionAndCanonicalize(foundLocale, supportedAttributes, supportedKeywords).
found_locale = insert_unicode_extension_and_canonicalize ( locale_id . release_value ( ) , { } , move ( supported_keywords ) ) ;
2021-09-10 10:01:41 -04:00
}
2021-08-30 08:51:52 -04:00
2024-06-18 10:13:30 -04:00
// 15. Set result.[[locale]] to foundLocale.
2021-08-30 08:51:52 -04:00
result . locale = move ( found_locale ) ;
2021-08-24 22:58:45 -04:00
2024-06-18 10:13:30 -04:00
// 16. Return result.
2021-08-24 22:58:45 -04:00
return result ;
}
2024-06-18 10:13:30 -04:00
// 9.2.8 FilterLocales ( availableLocales, requestedLocales, options ), https://tc39.es/ecma402/#sec-lookupsupportedlocales
ThrowCompletionOr < Array * > filter_locales ( VM & vm , ReadonlySpan < String > requested_locales , Value options )
2021-09-04 17:08:57 +01:00
{
2024-06-18 10:13:30 -04:00
auto & realm = * vm . current_realm ( ) ;
// 1. Set options to ? CoerceOptionsToObject(options).
auto * options_object = TRY ( coerce_options_to_object ( vm , options ) ) ;
// 2. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit").
auto matcher = TRY ( get_option ( vm , * options_object , vm . names . localeMatcher , OptionType : : String , { " lookup " sv , " best fit " sv } , " best fit " sv ) ) ;
// 3. Let subset be a new empty List.
2023-01-19 13:13:57 -05:00
Vector < String > subset ;
2021-09-04 17:08:57 +01:00
2024-06-18 10:13:30 -04:00
// 4. For each element locale of requestedLocales, do
2021-09-04 17:08:57 +01:00
for ( auto const & locale : requested_locales ) {
2024-06-23 09:14:27 -04:00
auto locale_id = Unicode : : parse_unicode_locale_id ( locale ) ;
2021-09-04 17:08:57 +01:00
VERIFY ( locale_id . has_value ( ) ) ;
// a. Let noExtensionsLocale be the String value that is locale with any Unicode locale extension sequences removed.
2024-06-23 09:14:27 -04:00
locale_id - > remove_extension_type < Unicode : : LocaleExtension > ( ) ;
2023-08-22 15:39:18 -04:00
auto no_extensions_locale = locale_id - > to_string ( ) ;
2021-09-04 17:08:57 +01:00
2024-06-18 10:13:30 -04:00
Optional < MatchedLocale > match ;
2021-09-04 17:08:57 +01:00
2024-06-18 10:13:30 -04:00
// b. If matcher is "lookup", then
if ( matcher . as_string ( ) . utf8_string_view ( ) = = " lookup " sv ) {
// i. Let match be LookupMatchingLocaleByPrefix(availableLocales, noExtensionsLocale).
match = lookup_matching_locale_by_prefix ( { { no_extensions_locale } } ) ;
}
// c. Else,
else {
// i. Let match be LookupMatchingLocaleByBestFit(availableLocales, noExtensionsLocale).
match = lookup_matching_locale_by_best_fit ( { { no_extensions_locale } } ) ;
}
2021-09-04 17:08:57 +01:00
2024-06-18 10:13:30 -04:00
// d. If match is not undefined, append locale to subset.
if ( match . has_value ( ) )
subset . append ( locale ) ;
2021-09-04 17:08:57 +01:00
}
2024-06-18 10:13:30 -04:00
// 5. Return CreateArrayFromList(subset).
return Array : : create_from < String > ( realm , subset , [ & vm ] ( auto & locale ) { return PrimitiveString : : create ( vm , move ( locale ) ) ; } ) . ptr ( ) ;
2021-09-04 17:08:57 +01:00
}
2024-06-18 10:13:30 -04:00
// 9.2.10 CoerceOptionsToObject ( options ), https://tc39.es/ecma402/#sec-coerceoptionstoobject
2022-08-20 08:25:24 +01:00
ThrowCompletionOr < Object * > coerce_options_to_object ( VM & vm , Value options )
2021-09-01 22:08:15 -04:00
{
2022-08-20 08:25:24 +01:00
auto & realm = * vm . current_realm ( ) ;
2022-08-16 00:20:49 +01:00
2021-09-01 22:08:15 -04:00
// 1. If options is undefined, then
if ( options . is_undefined ( ) ) {
2022-05-02 20:54:39 +02:00
// a. Return OrdinaryObjectCreate(null).
2022-12-13 20:49:50 +00:00
return Object : : create ( realm , nullptr ) . ptr ( ) ;
2021-09-01 22:08:15 -04:00
}
// 2. Return ? ToObject(options).
2023-04-13 15:26:41 +02:00
return TRY ( options . to_object ( vm ) ) . ptr ( ) ;
2021-09-01 22:08:15 -04:00
}
2024-06-18 10:13:30 -04:00
// NOTE: 9.2.11 GetOption has been removed and is being pulled in from ECMA-262 in the Temporal proposal.
2021-08-24 22:58:45 -04:00
2024-06-18 10:13:30 -04:00
// 9.2.12 GetBooleanOrStringNumberFormatOption ( options, property, stringValues, fallback ), https://tc39.es/ecma402/#sec-getbooleanorstringnumberformatoption
2023-02-05 19:02:54 +00:00
ThrowCompletionOr < StringOrBoolean > get_boolean_or_string_number_format_option ( VM & vm , Object const & options , PropertyKey const & property , ReadonlySpan < StringView > string_values , StringOrBoolean fallback )
2022-07-12 13:18:23 -04:00
{
// 1. Let value be ? Get(options, property).
auto value = TRY ( options . get ( property ) ) ;
// 2. If value is undefined, return fallback.
if ( value . is_undefined ( ) )
return fallback ;
2023-01-30 10:36:47 -05:00
// 3. If value is true, return true.
2022-07-12 13:18:23 -04:00
if ( value . is_boolean ( ) & & value . as_bool ( ) )
2023-01-30 10:36:47 -05:00
return StringOrBoolean { true } ;
2022-07-12 13:18:23 -04:00
2023-01-30 10:36:47 -05:00
// 4. If ToBoolean(value) is false, return false.
if ( ! value . to_boolean ( ) )
return StringOrBoolean { false } ;
2022-07-12 13:18:23 -04:00
2023-01-30 10:36:47 -05:00
// 5. Let value be ? ToString(value).
2023-01-15 10:31:39 -05:00
auto value_string = TRY ( value . to_string ( vm ) ) ;
2022-07-12 13:18:23 -04:00
2023-01-30 10:36:47 -05:00
// 6. If stringValues does not contain value, throw a RangeError exception.
auto it = find ( string_values . begin ( ) , string_values . end ( ) , value_string . bytes_as_string_view ( ) ) ;
if ( it = = string_values . end ( ) )
2022-09-13 11:00:58 -04:00
return vm . throw_completion < RangeError > ( ErrorType : : OptionIsNotValidValue , value_string , property . as_string ( ) ) ;
2022-07-12 13:18:23 -04:00
2023-01-30 10:36:47 -05:00
// 7. Return value.
2022-07-12 13:18:23 -04:00
return StringOrBoolean { * it } ;
}
2024-06-18 10:13:30 -04:00
// 9.2.13 DefaultNumberOption ( value, minimum, maximum, fallback ), https://tc39.es/ecma402/#sec-defaultnumberoption
2022-08-20 08:25:24 +01:00
ThrowCompletionOr < Optional < int > > default_number_option ( VM & vm , Value value , int minimum , int maximum , Optional < int > fallback )
2021-09-10 11:04:54 -04:00
{
// 1. If value is undefined, return fallback.
if ( value . is_undefined ( ) )
return fallback ;
2021-11-24 08:51:03 -05:00
// 2. Set value to ? ToNumber(value).
2022-08-21 14:00:56 +01:00
value = TRY ( value . to_number ( vm ) ) ;
2021-09-10 11:04:54 -04:00
// 3. If value is NaN or less than minimum or greater than maximum, throw a RangeError exception.
2021-09-18 19:37:39 +03:00
if ( value . is_nan ( ) | | ( value . as_double ( ) < minimum ) | | ( value . as_double ( ) > maximum ) )
2022-08-16 20:33:17 +01:00
return vm . throw_completion < RangeError > ( ErrorType : : IntlNumberIsNaNOrOutOfRange , value , minimum , maximum ) ;
2021-09-10 11:04:54 -04:00
// 4. Return floor(value).
2021-10-20 19:17:45 +01:00
return floor ( value . as_double ( ) ) ;
2021-09-10 11:04:54 -04:00
}
2024-06-18 10:13:30 -04:00
// 9.2.14 GetNumberOption ( options, property, minimum, maximum, fallback ), https://tc39.es/ecma402/#sec-getnumberoption
2022-08-20 08:25:24 +01:00
ThrowCompletionOr < Optional < int > > get_number_option ( VM & vm , Object const & options , PropertyKey const & property , int minimum , int maximum , Optional < int > fallback )
2021-09-10 11:04:54 -04:00
{
// 1. Assert: Type(options) is Object.
2021-10-02 23:52:27 +01:00
2021-09-10 11:04:54 -04:00
// 2. Let value be ? Get(options, property).
2021-10-02 23:52:27 +01:00
auto value = TRY ( options . get ( property ) ) ;
2021-09-10 11:04:54 -04:00
// 3. Return ? DefaultNumberOption(value, minimum, maximum, fallback).
2022-08-20 08:25:24 +01:00
return default_number_option ( vm , value , minimum , maximum , move ( fallback ) ) ;
2021-09-10 11:04:54 -04:00
}
2021-08-24 22:58:45 -04:00
}