AbstractOperations.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  1. /*
  2. * Copyright (c) 2021, Tim Flynn <trflynn89@pm.me>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/AllOf.h>
  7. #include <AK/AnyOf.h>
  8. #include <AK/Array.h>
  9. #include <AK/CharacterTypes.h>
  10. #include <AK/Find.h>
  11. #include <AK/Function.h>
  12. #include <AK/QuickSort.h>
  13. #include <AK/TypeCasts.h>
  14. #include <LibJS/Runtime/Array.h>
  15. #include <LibJS/Runtime/GlobalObject.h>
  16. #include <LibJS/Runtime/Intl/AbstractOperations.h>
  17. #include <LibJS/Runtime/Intl/Locale.h>
  18. #include <LibUnicode/Locale.h>
  19. namespace JS::Intl {
  20. // 6.2.2 IsStructurallyValidLanguageTag ( locale ), https://tc39.es/ecma402/#sec-isstructurallyvalidlanguagetag
  21. Optional<Unicode::LocaleID> is_structurally_valid_language_tag(StringView locale)
  22. {
  23. auto contains_duplicate_variant = [](auto& variants) {
  24. if (variants.is_empty())
  25. return false;
  26. quick_sort(variants);
  27. for (size_t i = 0; i < variants.size() - 1; ++i) {
  28. if (variants[i].equals_ignoring_case(variants[i + 1]))
  29. return true;
  30. }
  31. return false;
  32. };
  33. // IsStructurallyValidLanguageTag returns true if all of the following conditions hold, false otherwise:
  34. // locale can be generated from the EBNF grammar for unicode_locale_id in Unicode Technical Standard #35 LDML § 3.2 Unicode Locale Identifier;
  35. auto locale_id = Unicode::parse_unicode_locale_id(locale);
  36. if (!locale_id.has_value())
  37. return {};
  38. // locale does not use any of the backwards compatibility syntax described in Unicode Technical Standard #35 LDML § 3.3 BCP 47 Conformance;
  39. // https://unicode.org/reports/tr35/#BCP_47_Conformance
  40. if (locale.contains('_') || locale_id->language_id.is_root || !locale_id->language_id.language.has_value())
  41. return {};
  42. // the unicode_language_id within locale contains no duplicate unicode_variant_subtag subtags; and
  43. if (contains_duplicate_variant(locale_id->language_id.variants))
  44. return {};
  45. // if locale contains an extensions* component, that component
  46. Vector<char> unique_keys;
  47. for (auto& extension : locale_id->extensions) {
  48. // does not contain any other_extensions components with duplicate [alphanum-[tTuUxX]] subtags,
  49. // contains at most one unicode_locale_extensions component,
  50. // contains at most one transformed_extensions component, and
  51. char key = extension.visit(
  52. [](Unicode::LocaleExtension const&) { return 'u'; },
  53. [](Unicode::TransformedExtension const&) { return 't'; },
  54. [](Unicode::OtherExtension const& ext) { return static_cast<char>(to_ascii_lowercase(ext.key)); });
  55. if (unique_keys.contains_slow(key))
  56. return {};
  57. unique_keys.append(key);
  58. // if a transformed_extensions component that contains a tlang component is present, then
  59. // the tlang component contains no duplicate unicode_variant_subtag subtags.
  60. if (auto* transformed = extension.get_pointer<Unicode::TransformedExtension>()) {
  61. auto& language = transformed->language;
  62. if (language.has_value() && contains_duplicate_variant(language->variants))
  63. return {};
  64. }
  65. }
  66. return locale_id;
  67. }
  68. // 6.2.3 CanonicalizeUnicodeLocaleId ( locale ), https://tc39.es/ecma402/#sec-canonicalizeunicodelocaleid
  69. String canonicalize_unicode_locale_id(Unicode::LocaleID& locale)
  70. {
  71. // Note: This implementation differs from the spec in how Step 3 is implemented. The spec assumes
  72. // the input to this method is a string, and is written such that operations are performed on parts
  73. // of that string. LibUnicode gives us the parsed locale in a structure, so we can mutate that
  74. // structure directly. From a footnote in the spec:
  75. //
  76. // The third step of this algorithm ensures that a Unicode locale extension sequence in the
  77. // returned language tag contains:
  78. // * only the first instance of any attribute duplicated in the input, and
  79. // * only the first keyword for a given key in the input.
  80. for (auto& extension : locale.extensions) {
  81. if (!extension.has<Unicode::LocaleExtension>())
  82. continue;
  83. auto& locale_extension = extension.get<Unicode::LocaleExtension>();
  84. auto attributes = move(locale_extension.attributes);
  85. for (auto& attribute : attributes) {
  86. if (!locale_extension.attributes.contains_slow(attribute))
  87. locale_extension.attributes.append(move(attribute));
  88. }
  89. auto keywords = move(locale_extension.keywords);
  90. for (auto& keyword : keywords) {
  91. if (!any_of(locale_extension.keywords, [&](auto const& k) { return k.key == keyword.key; }))
  92. locale_extension.keywords.append(move(keyword));
  93. }
  94. break;
  95. }
  96. // 1. Let localeId be the string locale after performing the algorithm to transform it to canonical syntax per Unicode Technical Standard #35 LDML § 3.2.1 Canonical Unicode Locale Identifiers.
  97. // 2. Let localeId be the string localeId after performing the algorithm to transform it to canonical form.
  98. auto locale_id = Unicode::canonicalize_unicode_locale_id(locale);
  99. VERIFY(locale_id.has_value());
  100. // 4. Return localeId.
  101. return locale_id.release_value();
  102. }
  103. // 6.3.1 IsWellFormedCurrencyCode ( currency ), https://tc39.es/ecma402/#sec-canonicalcodefordisplaynames
  104. bool is_well_formed_currency_code(StringView currency)
  105. {
  106. // 1. Let normalized be the result of mapping currency to upper case as described in 6.1.
  107. // 2. If the number of elements in normalized is not 3, return false.
  108. if (currency.length() != 3)
  109. return false;
  110. // 3. If normalized contains any character that is not in the range "A" to "Z" (U+0041 to U+005A), return false.
  111. if (!all_of(currency, is_ascii_alpha))
  112. return false;
  113. // 4. Return true.
  114. return true;
  115. }
  116. // 6.5.1 IsWellFormedUnitIdentifier ( unitIdentifier ), https://tc39.es/ecma402/#sec-iswellformedunitidentifier
  117. bool is_well_formed_unit_identifier(StringView unit_identifier)
  118. {
  119. // 6.5.2 IsSanctionedSimpleUnitIdentifier ( unitIdentifier ), https://tc39.es/ecma402/#sec-iswellformedunitidentifier
  120. constexpr auto is_sanctioned_simple_unit_identifier = [](StringView unit_identifier) {
  121. // 1. If unitIdentifier is listed in Table 2 below, return true.
  122. // 2. Else, return false.
  123. static constexpr auto sanctioned_units = AK::Array { "acre"sv, "bit"sv, "byte"sv, "celsius"sv, "centimeter"sv, "day"sv, "degree"sv, "fahrenheit"sv, "fluid-ounce"sv, "foot"sv, "gallon"sv, "gigabit"sv, "gigabyte"sv, "gram"sv, "hectare"sv, "hour"sv, "inch"sv, "kilobit"sv, "kilobyte"sv, "kilogram"sv, "kilometer"sv, "liter"sv, "megabit"sv, "megabyte"sv, "meter"sv, "mile"sv, "mile-scandinavian"sv, "milliliter"sv, "millimeter"sv, "millisecond"sv, "minute"sv, "month"sv, "ounce"sv, "percent"sv, "petabyte"sv, "pound"sv, "second"sv, "stone"sv, "terabit"sv, "terabyte"sv, "week"sv, "yard"sv, "year"sv };
  124. return find(sanctioned_units.begin(), sanctioned_units.end(), unit_identifier) != sanctioned_units.end();
  125. };
  126. // 1. If the result of IsSanctionedSimpleUnitIdentifier(unitIdentifier) is true, then
  127. if (is_sanctioned_simple_unit_identifier(unit_identifier)) {
  128. // a. Return true.
  129. return true;
  130. }
  131. auto indices = unit_identifier.find_all("-per-"sv);
  132. // 2. If the substring "-per-" does not occur exactly once in unitIdentifier, then
  133. if (indices.size() != 1) {
  134. // a. Return false.
  135. return false;
  136. }
  137. // 3. Let numerator be the substring of unitIdentifier from the beginning to just before "-per-".
  138. auto numerator = unit_identifier.substring_view(0, indices[0]);
  139. // 4. If the result of IsSanctionedSimpleUnitIdentifier(numerator) is false, then
  140. if (!is_sanctioned_simple_unit_identifier(numerator)) {
  141. // a. Return false.
  142. return false;
  143. }
  144. // 5. Let denominator be the substring of unitIdentifier from just after "-per-" to the end.
  145. auto denominator = unit_identifier.substring_view(indices[0] + 5);
  146. // 6. If the result of IsSanctionedSimpleUnitIdentifier(denominator) is false, then
  147. if (!is_sanctioned_simple_unit_identifier(denominator)) {
  148. // a. Return false.
  149. return false;
  150. }
  151. // 7. Return true.
  152. return true;
  153. }
  154. // 9.2.1 CanonicalizeLocaleList ( locales ), https://tc39.es/ecma402/#sec-canonicalizelocalelist
  155. ThrowCompletionOr<Vector<String>> canonicalize_locale_list(GlobalObject& global_object, Value locales)
  156. {
  157. auto& vm = global_object.vm();
  158. // 1. If locales is undefined, then
  159. if (locales.is_undefined()) {
  160. // a. Return a new empty List.
  161. return Vector<String> {};
  162. }
  163. // 2. Let seen be a new empty List.
  164. Vector<String> seen;
  165. Object* object = nullptr;
  166. // 3. If Type(locales) is String or Type(locales) is Object and locales has an [[InitializedLocale]] internal slot, then
  167. if (locales.is_string() || (locales.is_object() && is<Locale>(locales.as_object()))) {
  168. // a. Let O be CreateArrayFromList(« locales »).
  169. object = Array::create_from(global_object, { locales });
  170. }
  171. // 4. Else,
  172. else {
  173. // a. Let O be ? ToObject(locales).
  174. object = locales.to_object(global_object);
  175. if (auto* exception = vm.exception())
  176. return throw_completion(exception->value());
  177. }
  178. // 5. Let len be ? ToLength(? Get(O, "length")).
  179. auto length_value = object->get(vm.names.length);
  180. if (auto* exception = vm.exception())
  181. return throw_completion(exception->value());
  182. auto length = length_value.to_length(global_object);
  183. if (auto* exception = vm.exception())
  184. return throw_completion(exception->value());
  185. // 6. Let k be 0.
  186. // 7. Repeat, while k < len,
  187. for (size_t k = 0; k < length; ++k) {
  188. // a. Let Pk be ToString(k).
  189. auto property_key = PropertyName { k };
  190. // b. Let kPresent be ? HasProperty(O, Pk).
  191. auto key_present = object->has_property(property_key);
  192. if (auto* exception = vm.exception())
  193. return throw_completion(exception->value());
  194. // c. If kPresent is true, then
  195. if (key_present) {
  196. // i. Let kValue be ? Get(O, Pk).
  197. auto key_value = object->get(property_key);
  198. if (auto* exception = vm.exception())
  199. return throw_completion(exception->value());
  200. // ii. If Type(kValue) is not String or Object, throw a TypeError exception.
  201. if (!key_value.is_string() && !key_value.is_object())
  202. return vm.throw_completion<TypeError>(global_object, ErrorType::NotAnObjectOrString, key_value.to_string_without_side_effects());
  203. String tag;
  204. // iii. If Type(kValue) is Object and kValue has an [[InitializedLocale]] internal slot, then
  205. if (key_value.is_object() && is<Locale>(key_value.as_object())) {
  206. // 1. Let tag be kValue.[[Locale]].
  207. tag = static_cast<Locale const&>(key_value.as_object()).locale();
  208. }
  209. // iv. Else,
  210. else {
  211. // 1. Let tag be ? ToString(kValue).
  212. tag = key_value.to_string(global_object);
  213. if (auto* exception = vm.exception())
  214. return throw_completion(exception->value());
  215. }
  216. // v. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError exception.
  217. auto locale_id = is_structurally_valid_language_tag(tag);
  218. if (!locale_id.has_value())
  219. return vm.throw_completion<RangeError>(global_object, ErrorType::IntlInvalidLanguageTag, tag);
  220. // vi. Let canonicalizedTag be CanonicalizeUnicodeLocaleId(tag).
  221. auto canonicalized_tag = JS::Intl::canonicalize_unicode_locale_id(*locale_id);
  222. // vii. If canonicalizedTag is not an element of seen, append canonicalizedTag as the last element of seen.
  223. if (!seen.contains_slow(canonicalized_tag))
  224. seen.append(move(canonicalized_tag));
  225. }
  226. // d. Increase k by 1.
  227. }
  228. return seen;
  229. }
  230. // 9.2.2 BestAvailableLocale ( availableLocales, locale ), https://tc39.es/ecma402/#sec-bestavailablelocale
  231. Optional<String> best_available_locale(StringView const& locale)
  232. {
  233. // 1. Let candidate be locale.
  234. StringView candidate = locale;
  235. // 2. Repeat,
  236. while (true) {
  237. // a. If availableLocales contains an element equal to candidate, return candidate.
  238. if (Unicode::is_locale_available(candidate))
  239. return candidate;
  240. // b. Let pos be the character index of the last occurrence of "-" (U+002D) within candidate. If that character does not occur, return undefined.
  241. auto pos = candidate.find_last('-');
  242. if (!pos.has_value())
  243. return {};
  244. // c. If pos ≥ 2 and the character "-" occurs at index pos-2 of candidate, decrease pos by 2.
  245. if ((*pos >= 2) && (candidate[*pos - 2] == '-'))
  246. pos = *pos - 2;
  247. // d. Let candidate be the substring of candidate from position 0, inclusive, to position pos, exclusive.
  248. candidate = candidate.substring_view(0, *pos);
  249. }
  250. }
  251. struct MatcherResult {
  252. String locale;
  253. Vector<Unicode::Extension> extensions {};
  254. };
  255. // 9.2.3 LookupMatcher ( availableLocales, requestedLocales ), https://tc39.es/ecma402/#sec-lookupmatcher
  256. static MatcherResult lookup_matcher(Vector<String> const& requested_locales)
  257. {
  258. // 1. Let result be a new Record.
  259. MatcherResult result {};
  260. // 2. For each element locale of requestedLocales, do
  261. for (auto const& locale : requested_locales) {
  262. auto locale_id = Unicode::parse_unicode_locale_id(locale);
  263. VERIFY(locale_id.has_value());
  264. // a. Let noExtensionsLocale be the String value that is locale with any Unicode locale extension sequences removed.
  265. auto extensions = locale_id->remove_extension_type<Unicode::LocaleExtension>();
  266. auto no_extensions_locale = locale_id->to_string();
  267. // b. Let availableLocale be BestAvailableLocale(availableLocales, noExtensionsLocale).
  268. auto available_locale = best_available_locale(no_extensions_locale);
  269. // c. If availableLocale is not undefined, then
  270. if (available_locale.has_value()) {
  271. // i. Set result.[[locale]] to availableLocale.
  272. result.locale = available_locale.release_value();
  273. // ii. If locale and noExtensionsLocale are not the same String value, then
  274. if (locale != no_extensions_locale) {
  275. // 1. Let extension be the String value consisting of the substring of the Unicode locale extension sequence within locale.
  276. // 2. Set result.[[extension]] to extension.
  277. result.extensions.extend(move(extensions));
  278. }
  279. // iii. Return result.
  280. return result;
  281. }
  282. }
  283. // 3. Let defLocale be DefaultLocale().
  284. // 4. Set result.[[locale]] to defLocale.
  285. result.locale = Unicode::default_locale();
  286. // 5. Return result.
  287. return result;
  288. }
  289. // 9.2.4 BestFitMatcher ( availableLocales, requestedLocales ), https://tc39.es/ecma402/#sec-bestfitmatcher
  290. static MatcherResult best_fit_matcher(Vector<String> const& requested_locales)
  291. {
  292. // The algorithm is implementation dependent, but should produce results that a typical user of the requested locales would
  293. // perceive as at least as good as those produced by the LookupMatcher abstract operation.
  294. return lookup_matcher(requested_locales);
  295. }
  296. // 9.2.6 InsertUnicodeExtensionAndCanonicalize ( locale, extension ), https://tc39.es/ecma402/#sec-insert-unicode-extension-and-canonicalize
  297. String insert_unicode_extension_and_canonicalize(Unicode::LocaleID locale, Unicode::LocaleExtension extension)
  298. {
  299. // Note: This implementation differs from the spec in how the extension is inserted. The spec assumes
  300. // the input to this method is a string, and is written such that operations are performed on parts
  301. // of that string. LibUnicode gives us the parsed locale in a structure, so we can mutate that
  302. // structure directly.
  303. locale.extensions.append(move(extension));
  304. return JS::Intl::canonicalize_unicode_locale_id(locale);
  305. }
  306. template<typename T>
  307. static auto& find_key_in_value(T& value, StringView key)
  308. {
  309. if (key == "nu"sv)
  310. return value.nu;
  311. // If you hit this point, you must add any missing keys from [[RelevantExtensionKeys]] to LocaleOptions and LocaleResult.
  312. VERIFY_NOT_REACHED();
  313. }
  314. // 9.2.7 ResolveLocale ( availableLocales, requestedLocales, options, relevantExtensionKeys, localeData ), https://tc39.es/ecma402/#sec-resolvelocale
  315. LocaleResult resolve_locale(Vector<String> const& requested_locales, LocaleOptions const& options, Vector<StringView> const& relevant_extension_keys)
  316. {
  317. // 1. Let matcher be options.[[localeMatcher]].
  318. auto const& matcher = options.locale_matcher;
  319. MatcherResult matcher_result;
  320. // 2. If matcher is "lookup", then
  321. if (matcher.is_string() && (matcher.as_string().string() == "lookup"sv)) {
  322. // a. Let r be LookupMatcher(availableLocales, requestedLocales).
  323. matcher_result = lookup_matcher(requested_locales);
  324. }
  325. // 3. Else,
  326. else {
  327. // a. Let r be BestFitMatcher(availableLocales, requestedLocales).
  328. matcher_result = best_fit_matcher(requested_locales);
  329. }
  330. // 4. Let foundLocale be r.[[locale]].
  331. auto found_locale = move(matcher_result.locale);
  332. // 5. Let result be a new Record.
  333. LocaleResult result {};
  334. // 6. Set result.[[dataLocale]] to foundLocale.
  335. result.data_locale = found_locale;
  336. // 7. If r has an [[extension]] field, then
  337. Vector<Unicode::Keyword> keywords;
  338. for (auto& extension : matcher_result.extensions) {
  339. if (!extension.has<Unicode::LocaleExtension>())
  340. continue;
  341. // a. Let components be ! UnicodeExtensionComponents(r.[[extension]]).
  342. auto& components = extension.get<Unicode::LocaleExtension>();
  343. // b. Let keywords be components.[[Keywords]].
  344. keywords = move(components.keywords);
  345. break;
  346. }
  347. // 8. Let supportedExtension be "-u".
  348. Unicode::LocaleExtension supported_extension {};
  349. // 9. For each element key of relevantExtensionKeys, do
  350. for (auto const& key : relevant_extension_keys) {
  351. // a. Let foundLocaleData be localeData.[[<foundLocale>]].
  352. // b. Assert: Type(foundLocaleData) is Record.
  353. // c. Let keyLocaleData be foundLocaleData.[[<key>]].
  354. // d. Assert: Type(keyLocaleData) is List.
  355. auto key_locale_data = Unicode::get_locale_key_mapping(found_locale, key);
  356. // e. Let value be keyLocaleData[0].
  357. // f. Assert: Type(value) is either String or Null.
  358. Optional<String> value;
  359. if (!key_locale_data.is_empty())
  360. value = key_locale_data[0];
  361. // g. Let supportedExtensionAddition be "".
  362. Optional<Unicode::Keyword> supported_extension_addition {};
  363. // h. If r has an [[extension]] field, then
  364. for (auto& entry : keywords) {
  365. // i. If keywords contains an element whose [[Key]] is the same as key, then
  366. if (entry.key != key)
  367. continue;
  368. // 1. Let entry be the element of keywords whose [[Key]] is the same as key.
  369. // 2. Let requestedValue be entry.[[Value]].
  370. auto requested_value = entry.value;
  371. // 3. If requestedValue is not the empty String, then
  372. if (!requested_value.is_empty()) {
  373. // a. If keyLocaleData contains requestedValue, then
  374. if (key_locale_data.contains_slow(requested_value)) {
  375. // i. Let value be requestedValue.
  376. value = move(requested_value);
  377. // ii. Let supportedExtensionAddition be the string-concatenation of "-", key, "-", and value.
  378. supported_extension_addition = Unicode::Keyword { key, move(entry.value) };
  379. }
  380. }
  381. // 4. Else if keyLocaleData contains "true", then
  382. else if (key_locale_data.contains_slow("true"sv)) {
  383. // a. Let value be "true".
  384. value = "true"sv;
  385. // b. Let supportedExtensionAddition be the string-concatenation of "-" and key.
  386. supported_extension_addition = Unicode::Keyword { key, {} };
  387. }
  388. break;
  389. }
  390. // i. If options has a field [[<key>]], then
  391. // i. Let optionsValue be options.[[<key>]].
  392. // ii. Assert: Type(optionsValue) is either String, Undefined, or Null.
  393. auto options_value = find_key_in_value(options, key);
  394. // iii. If Type(optionsValue) is String, then
  395. if (options_value.has_value()) {
  396. // 1. Let optionsValue be the string optionsValue after performing the algorithm steps to transform Unicode extension values to canonical syntax per Unicode Technical Standard #35 LDML § 3.2.1 Canonical Unicode Locale Identifiers, treating key as ukey and optionsValue as uvalue productions.
  397. // 2. Let optionsValue be the string optionsValue after performing the algorithm steps to replace Unicode extension values with their canonical form per Unicode Technical Standard #35 LDML § 3.2.1 Canonical Unicode Locale Identifiers, treating key as ukey and optionsValue as uvalue productions.
  398. Unicode::canonicalize_unicode_extension_values(key, *options_value, true);
  399. // 3. If optionsValue is the empty String, then
  400. if (options_value->is_empty()) {
  401. // a. Let optionsValue be "true".
  402. options_value = "true"sv;
  403. }
  404. }
  405. // iv. If keyLocaleData contains optionsValue, then
  406. if (options_value.has_value() && key_locale_data.contains_slow(*options_value)) {
  407. // 1. If SameValue(optionsValue, value) is false, then
  408. if (options_value != value) {
  409. // a. Let value be optionsValue.
  410. value = move(options_value);
  411. // b. Let supportedExtensionAddition be "".
  412. supported_extension_addition.clear();
  413. }
  414. }
  415. // j. Set result.[[<key>]] to value.
  416. find_key_in_value(result, key) = move(value);
  417. // k. Append supportedExtensionAddition to supportedExtension.
  418. if (supported_extension_addition.has_value())
  419. supported_extension.keywords.append(supported_extension_addition.release_value());
  420. }
  421. // 10. If the number of elements in supportedExtension is greater than 2, then
  422. if (!supported_extension.keywords.is_empty()) {
  423. auto locale_id = Unicode::parse_unicode_locale_id(found_locale);
  424. VERIFY(locale_id.has_value());
  425. // a. Let foundLocale be InsertUnicodeExtensionAndCanonicalize(foundLocale, supportedExtension).
  426. found_locale = insert_unicode_extension_and_canonicalize(locale_id.release_value(), move(supported_extension));
  427. }
  428. // 11. Set result.[[locale]] to foundLocale.
  429. result.locale = move(found_locale);
  430. // 12. Return result.
  431. return result;
  432. }
  433. // 9.2.8 LookupSupportedLocales ( availableLocales, requestedLocales ), https://tc39.es/ecma402/#sec-lookupsupportedlocales
  434. Vector<String> lookup_supported_locales(Vector<String> const& requested_locales)
  435. {
  436. // 1. Let subset be a new empty List.
  437. Vector<String> subset;
  438. // 2. For each element locale of requestedLocales, do
  439. for (auto const& locale : requested_locales) {
  440. auto locale_id = Unicode::parse_unicode_locale_id(locale);
  441. VERIFY(locale_id.has_value());
  442. // a. Let noExtensionsLocale be the String value that is locale with any Unicode locale extension sequences removed.
  443. locale_id->remove_extension_type<Unicode::LocaleExtension>();
  444. auto no_extensions_locale = locale_id->to_string();
  445. // b. Let availableLocale be BestAvailableLocale(availableLocales, noExtensionsLocale).
  446. auto available_locale = best_available_locale(no_extensions_locale);
  447. // c. If availableLocale is not undefined, append locale to the end of subset.
  448. if (available_locale.has_value())
  449. subset.append(locale);
  450. }
  451. // 3. Return subset.
  452. return subset;
  453. }
  454. // 9.2.9 BestFitSupportedLocales ( availableLocales, requestedLocales ), https://tc39.es/ecma402/#sec-bestfitsupportedlocales
  455. Vector<String> best_fit_supported_locales(Vector<String> const& requested_locales)
  456. {
  457. // The BestFitSupportedLocales abstract operation returns the subset of the provided BCP 47
  458. // language priority list requestedLocales for which availableLocales has a matching locale
  459. // when using the Best Fit Matcher algorithm. Locales appear in the same order in the returned
  460. // list as in requestedLocales. The steps taken are implementation dependent.
  461. // :yakbrain:
  462. return lookup_supported_locales(requested_locales);
  463. }
  464. // 9.2.10 SupportedLocales ( availableLocales, requestedLocales, options ), https://tc39.es/ecma402/#sec-supportedlocales
  465. ThrowCompletionOr<Array*> supported_locales(GlobalObject& global_object, Vector<String> const& requested_locales, Value options)
  466. {
  467. auto& vm = global_object.vm();
  468. // 1. Set options to ? CoerceOptionsToObject(options).
  469. auto* options_object = TRY(coerce_options_to_object(global_object, options));
  470. // 2. Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
  471. auto matcher = TRY(get_option(global_object, *options_object, vm.names.localeMatcher, Value::Type::String, { "lookup"sv, "best fit"sv }, "best fit"sv));
  472. Vector<String> supported_locales;
  473. // 3. If matcher is "best fit", then
  474. if (matcher.as_string().string() == "best fit"sv) {
  475. // a. Let supportedLocales be BestFitSupportedLocales(availableLocales, requestedLocales).
  476. supported_locales = best_fit_supported_locales(requested_locales);
  477. }
  478. // 4. Else,
  479. else {
  480. // a. Let supportedLocales be LookupSupportedLocales(availableLocales, requestedLocales).
  481. supported_locales = lookup_supported_locales(requested_locales);
  482. }
  483. // 5. Return CreateArrayFromList(supportedLocales).
  484. return Array::create_from<String>(global_object, supported_locales, [&vm](auto& locale) { return js_string(vm, locale); });
  485. }
  486. // 9.2.12 CoerceOptionsToObject ( options ), https://tc39.es/ecma402/#sec-coerceoptionstoobject
  487. ThrowCompletionOr<Object*> coerce_options_to_object(GlobalObject& global_object, Value options)
  488. {
  489. auto& vm = global_object.vm();
  490. // 1. If options is undefined, then
  491. if (options.is_undefined()) {
  492. // a. Return ! OrdinaryObjectCreate(null).
  493. return Object::create(global_object, nullptr);
  494. }
  495. // 2. Return ? ToObject(options).
  496. auto result = options.to_object(global_object);
  497. if (auto* exception = vm.exception())
  498. return throw_completion(exception->value());
  499. return result;
  500. }
  501. // 9.2.13 GetOption ( options, property, type, values, fallback ), https://tc39.es/ecma402/#sec-getoption
  502. ThrowCompletionOr<Value> get_option(GlobalObject& global_object, Object const& options, PropertyName const& property, Value::Type type, Vector<StringView> const& values, Fallback fallback)
  503. {
  504. auto& vm = global_object.vm();
  505. // 1. Assert: Type(options) is Object.
  506. // 2. Let value be ? Get(options, property).
  507. auto value = options.get(property);
  508. if (auto* exception = vm.exception())
  509. return throw_completion(exception->value());
  510. // 3. If value is undefined, return fallback.
  511. if (value.is_undefined()) {
  512. return fallback.visit(
  513. [](Empty) { return js_undefined(); },
  514. [](bool f) { return Value(f); },
  515. [&vm](StringView f) { return Value(js_string(vm, f)); });
  516. }
  517. // 4. Assert: type is "boolean" or "string".
  518. VERIFY((type == Value::Type::Boolean) || (type == Value::Type::String));
  519. // 5. If type is "boolean", then
  520. if (type == Value::Type::Boolean) {
  521. // a. Let value be ! ToBoolean(value).
  522. value = Value(value.to_boolean());
  523. }
  524. // 6. If type is "string", then
  525. else {
  526. // a. Let value be ? ToString(value).
  527. value = value.to_primitive_string(global_object);
  528. if (auto* exception = vm.exception())
  529. return throw_completion(exception->value());
  530. }
  531. // 7. If values is not undefined and values does not contain an element equal to value, throw a RangeError exception.
  532. if (!values.is_empty()) {
  533. // Note: Every location in the spec that invokes GetOption with type=boolean also has values=undefined.
  534. VERIFY(value.is_string());
  535. if (!values.contains_slow(value.as_string().string()))
  536. return vm.throw_completion<RangeError>(global_object, ErrorType::OptionIsNotValidValue, value.to_string_without_side_effects(), property.as_string());
  537. }
  538. // 8. Return value.
  539. return value;
  540. }
  541. // 9.2.14 DefaultNumberOption ( value, minimum, maximum, fallback ), https://tc39.es/ecma402/#sec-defaultnumberoption
  542. ThrowCompletionOr<Optional<int>> default_number_option(GlobalObject& global_object, Value value, int minimum, int maximum, Optional<int> fallback)
  543. {
  544. auto& vm = global_object.vm();
  545. // 1. If value is undefined, return fallback.
  546. if (value.is_undefined())
  547. return fallback;
  548. // 2. Let value be ? ToNumber(value).
  549. value = value.to_number(global_object);
  550. if (auto* exception = vm.exception())
  551. return throw_completion(exception->value());
  552. // 3. If value is NaN or less than minimum or greater than maximum, throw a RangeError exception.
  553. if (value.is_nan() || (value.as_double() < minimum) || (value.as_double() > maximum))
  554. return vm.throw_completion<RangeError>(global_object, ErrorType::IntlNumberIsNaNOrOutOfRange, value, minimum, maximum);
  555. // 4. Return floor(value).
  556. return { floor(value.as_double()) };
  557. }
  558. // 9.2.15 GetNumberOption ( options, property, minimum, maximum, fallback ), https://tc39.es/ecma402/#sec-getnumberoption
  559. Optional<int> get_number_option(GlobalObject& global_object, Object const& options, PropertyName const& property, int minimum, int maximum, Optional<int> fallback)
  560. {
  561. auto& vm = global_object.vm();
  562. // 1. Assert: Type(options) is Object.
  563. // 2. Let value be ? Get(options, property).
  564. auto value = options.get(property);
  565. if (vm.exception())
  566. return {};
  567. // 3. Return ? DefaultNumberOption(value, minimum, maximum, fallback).
  568. return TRY_OR_DISCARD(default_number_option(global_object, value, minimum, maximum, move(fallback)));
  569. }
  570. // 9.2.16 PartitionPattern ( pattern ), https://tc39.es/ecma402/#sec-partitionpattern
  571. Vector<PatternPartition> partition_pattern(StringView pattern)
  572. {
  573. // 1. Let result be a new empty List.
  574. Vector<PatternPartition> result;
  575. // 2. Let beginIndex be ! StringIndexOf(pattern, "{", 0).
  576. auto begin_index = pattern.find('{', 0);
  577. // 3. Let endIndex be 0.
  578. size_t end_index = 0;
  579. // 4. Let nextIndex be 0.
  580. size_t next_index = 0;
  581. // 5. Let length be the number of code units in pattern.
  582. // 6. Repeat, while beginIndex is an integer index into pattern,
  583. while (begin_index.has_value()) {
  584. // a. Set endIndex to ! StringIndexOf(pattern, "}", beginIndex).
  585. end_index = pattern.find('}', *begin_index).value();
  586. // b. Assert: endIndex is greater than beginIndex.
  587. VERIFY(end_index > *begin_index);
  588. // c. If beginIndex is greater than nextIndex, then
  589. if (*begin_index > next_index) {
  590. // i. Let literal be a substring of pattern from position nextIndex, inclusive, to position beginIndex, exclusive.
  591. auto literal = pattern.substring_view(next_index, *begin_index - next_index);
  592. // ii. Append a new Record { [[Type]]: "literal", [[Value]]: literal } as the last element of the list result.
  593. result.append({ "literal"sv, literal });
  594. }
  595. // d. Let p be the substring of pattern from position beginIndex, exclusive, to position endIndex, exclusive.
  596. auto partition = pattern.substring_view(*begin_index + 1, end_index - *begin_index - 1);
  597. // e. Append a new Record { [[Type]]: p, [[Value]]: undefined } as the last element of the list result.
  598. result.append({ partition, {} });
  599. // f. Set nextIndex to endIndex + 1.
  600. next_index = end_index + 1;
  601. // g. Set beginIndex to ! StringIndexOf(pattern, "{", nextIndex).
  602. begin_index = pattern.find('{', next_index);
  603. }
  604. // 7. If nextIndex is less than length, then
  605. if (next_index < pattern.length()) {
  606. // a. Let literal be the substring of pattern from position nextIndex, inclusive, to position length, exclusive.
  607. auto literal = pattern.substring_view(next_index);
  608. // b. Append a new Record { [[Type]]: "literal", [[Value]]: literal } as the last element of the list result.
  609. result.append({ "literal"sv, literal });
  610. }
  611. // 8. Return result.
  612. return result;
  613. }
  614. }