lib/Platform/Intl/PlatformIntlApple.mm (508 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include "hermes/Platform/Intl/BCP47Parser.h" #include "hermes/Platform/Intl/PlatformIntl.h" #import <Foundation/Foundation.h> namespace hermes { namespace platform_intl { namespace { NSString *u16StringToNSString(const std::u16string &src) { auto size = src.size(); const auto *cString = (const unichar *)src.c_str(); return [NSString stringWithCharacters:cString length:size]; } std::u16string nsStringToU16String(NSString *src) { auto size = src.length; std::u16string result; result.resize(size); [src getCharacters:(unichar *)&result[0] range:NSMakeRange(0, size)]; return result; } const std::vector<std::u16string> &getAvailableLocales() { static const std::vector<std::u16string> *availableLocales = [] { NSArray<NSString *> *availableLocales = [NSLocale availableLocaleIdentifiers]; // Intentionally leaked to avoid destruction order problems. auto *vec = new std::vector<std::u16string>(); for (id str in availableLocales) { auto u16str = nsStringToU16String(str); // NSLocale sometimes gives locale identifiers with an underscore instead // of a dash. We only consider dashes valid, so fix up any identifiers we // get from NSLocale. std::replace(u16str.begin(), u16str.end(), u'_', u'-'); // Some locales may still not be properly canonicalized (e.g. en_US_POSIX // should be en-US-posix). if (auto parsed = ParsedLocaleIdentifier::parse(u16str)) vec->push_back(parsed->canonicalize()); } return vec; }(); return *availableLocales; } const std::u16string &getDefaultLocale() { static const std::u16string *defLocale = new std::u16string([] { // Environment variable used for testing only const char *testLocale = std::getenv("_HERMES_TEST_LOCALE"); if (testLocale) { NSString *nsTestLocale = [NSString stringWithUTF8String:testLocale]; return nsStringToU16String(nsTestLocale); } NSString *nsDefLocale = [[NSLocale currentLocale] localeIdentifier]; auto defLocale = nsStringToU16String(nsDefLocale); // See the comment in getAvailableLocales. std::replace(defLocale.begin(), defLocale.end(), u'_', u'-'); if (auto parsed = ParsedLocaleIdentifier::parse(defLocale)) return parsed->canonicalize(); return std::u16string(u"und"); }()); return *defLocale; } /// https://402.ecma-international.org/8.0/#sec-bestavailablelocale std::optional<std::u16string> bestAvailableLocale( const std::vector<std::u16string> &availableLocales, const std::u16string &locale) { // 1. Let candidate be locale std::u16string candidate = locale; // 2. Repeat while (true) { // a. If availableLocales contains an element equal to candidate, return // candidate. if (llvh::find(availableLocales, candidate) != availableLocales.end()) return candidate; // b. Let pos be the character index of the last occurrence of "-" (U+002D) // within candidate. size_t pos = candidate.rfind(u'-'); // ...If that character does not occur, return undefined. if (pos == std::u16string::npos) return std::nullopt; // c. If pos ≥ 2 and the character "-" occurs at index pos-2 of candidate, // decrease pos by 2. if (pos >= 2 && candidate[pos - 2] == '-') pos -= 2; // d. Let candidate be the substring of candidate from position 0, // inclusive, to position pos, exclusive. candidate.resize(pos); } } struct LocaleMatch { std::u16string locale; std::map<std::u16string, std::u16string> extensions; }; /// https://402.ecma-international.org/8.0/#sec-lookupmatcher LocaleMatch lookupMatcher( const std::vector<std::u16string> &availableLocales, const std::vector<std::u16string> &requestedLocales) { // 1. Let result be a new Record. LocaleMatch result; // 2. For each element locale of requestedLocales, do for (const std::u16string &locale : requestedLocales) { // a. Let noExtensionsLocale be the String value that is locale with // any Unicode locale extension sequences removed. // In practice, we can skip this step because availableLocales never // contains any extensions, so bestAvailableLocale will trim away any // unicode extensions. // b. Let availableLocale be BestAvailableLocale(availableLocales, // noExtensionsLocale). std::optional<std::u16string> availableLocale = bestAvailableLocale(availableLocales, locale); // c. If availableLocale is not undefined, then if (availableLocale) { // i. Set result.[[locale]] to availableLocale. result.locale = std::move(*availableLocale); // ii. If locale and noExtensionsLocale are not the same String value, // then // 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. auto parsed = ParsedLocaleIdentifier::parse(locale); result.extensions = std::move(parsed->unicodeExtensionKeywords); // iii. Return result. return result; } } // availableLocale was undefined, so set result.[[locale]] to defLocale. result.locale = getDefaultLocale(); // 5. Return result. return result; } struct ResolvedLocale { std::u16string locale; std::u16string dataLocale; std::unordered_map<std::u16string, std::u16string> extensions; }; /// https://402.ecma-international.org/8.0/#sec-resolvelocale ResolvedLocale resolveLocale( const std::vector<std::u16string> &availableLocales, const std::vector<std::u16string> &requestedLocales, const std::unordered_map<std::u16string, std::u16string> &options, llvh::ArrayRef<std::u16string> relevantExtensionKeys) { // 1. Let matcher be options.[[localeMatcher]]. // 2. If matcher is "lookup", then // a. Let r be LookupMatcher(availableLocales, requestedLocales). // 3. Else, // a. Let r be BestFitMatcher(availableLocales, requestedLocales). auto r = lookupMatcher(availableLocales, requestedLocales); // 4. Let foundLocale be r.[[locale]]. auto foundLocale = r.locale; // 5. Let result be a new Record. ResolvedLocale result; // 6. Set result.[[dataLocale]] to foundLocale. result.dataLocale = foundLocale; // 7. If r has an [[extension]] field, then // a. Let components be ! UnicodeExtensionComponents(r.[[extension]]). // b. Let keywords be components.[[Keywords]]. // 8. Let supportedExtension be "-u". std::u16string supportedExtension = u"-u"; // 9. For each element key of relevantExtensionKeys, do for (const auto &key : relevantExtensionKeys) { // a. Let foundLocaleData be localeData.[[<foundLocale>]]. // NOTE: We don't actually have access to the underlying locale data, so we // accept everything and defer to NSLocale. // b. Assert: Type(foundLocaleData) is Record. // c. Let keyLocaleData be foundLocaleData.[[<key>]]. // d. Assert: Type(keyLocaleData) is List. // e. Let value be keyLocaleData[0]. // f. Assert: Type(value) is either String or Null. // g. Let supportedExtensionAddition be "". // h. If r has an [[extension]] field, then auto extIt = r.extensions.find(key); std::optional<std::u16string> value; std::u16string supportedExtensionAddition; // i. If keywords contains an element whose [[Key]] is the same as key, then if (extIt != r.extensions.end()) { // 1. Let entry be the element of keywords whose [[Key]] is the same as // key. // 2. Let requestedValue be entry.[[Value]]. // 3. If requestedValue is not the empty String, then // a. If keyLocaleData contains requestedValue, then // i. Let value be requestedValue. // ii. Let supportedExtensionAddition be the string-concatenation of "-", // key, "-", and value. // 4. Else if keyLocaleData contains "true", then // a. Let value be "true". // b. Let supportedExtensionAddition be the string-concatenation of "-" // and key. supportedExtensionAddition.append(u"-").append(key); if (extIt->second.empty()) value = u"true"; else { value = extIt->second; supportedExtensionAddition.append(u"-").append(*value); } } // i. If options has a field [[<key>]], then auto optIt = options.find(key); if (optIt != options.end()) { // i. Let optionsValue be options.[[<key>]]. std::u16string optionsValue = optIt->second; // ii. Assert: Type(optionsValue) is either String, Undefined, or Null. // iii. If Type(optionsValue) is String, then // 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. // 2. Let optionsValue be the string optionsValue after performing the // algorithm steps to replace Unicode extension values with their // canonical form per Technical Standard #35 LDML § 3.2.1 Canonical // Unicode Locale Identifiers, treating key as ukey and optionsValue as // uvalue productions // 3. If optionsValue is the empty String, then if (optionsValue.empty()) { // a. Let optionsValue be "true". optionsValue = u"true"; } // iv. If keyLocaleData contains optionsValue, then // 1. If SameValue(optionsValue, value) is false, then if (optionsValue != value) { // a. Let value be optionsValue. value = optionsValue; // b. Let supportedExtensionAddition be "". supportedExtensionAddition = u""; } } // j. Set result.[[<key>]] to value. if (value) result.extensions.emplace(key, std::move(*value)); // k. Append supportedExtensionAddition to supportedExtension. supportedExtension.append(supportedExtensionAddition); } // 10. If the number of elements in supportedExtension is greater than 2, then if (supportedExtension.size() > 2) { // a. Let foundLocale be InsertUnicodeExtensionAndCanonicalize(foundLocale, // supportedExtension). foundLocale.append(supportedExtension); } // 11. Set result.[[locale]] to foundLocale. result.locale = std::move(foundLocale); // 12. Return result. return result; } /// https://402.ecma-international.org/8.0/#sec-lookupsupportedlocales std::vector<std::u16string> lookupSupportedLocales( const std::vector<std::u16string> &availableLocales, const std::vector<std::u16string> &requestedLocales) { // 1. Let subset be a new empty List. std::vector<std::u16string> subset; // 2. For each element locale of requestedLocales in List order, do for (const std::u16string &locale : requestedLocales) { // a. Let noExtensionsLocale be the String value that is locale with all // Unicode locale extension sequences removed. // We can skip this step, see the comment in lookupMatcher. // b. Let availableLocale be BestAvailableLocale(availableLocales, // noExtensionsLocale). std::optional<std::u16string> availableLocale = bestAvailableLocale(availableLocales, locale); // c. If availableLocale is not undefined, append locale to the end of // subset. if (availableLocale) { subset.push_back(locale); } } // 3. Return subset. return subset; } /// https://402.ecma-international.org/8.0/#sec-supportedlocales std::vector<std::u16string> supportedLocales( const std::vector<std::u16string> &availableLocales, const std::vector<std::u16string> &requestedLocales) { // 1. Set options to ? CoerceOptionsToObject(options). // 2. Let matcher be ? GetOption(options, "localeMatcher", "string", « // "lookup", "best fit" », "best fit"). // 3. If matcher is "best fit", then // a. Let supportedLocales be BestFitSupportedLocales(availableLocales, // requestedLocales). // 4. Else, // a. Let supportedLocales be LookupSupportedLocales(availableLocales, // requestedLocales). // 5. Return CreateArrayFromList(supportedLocales). // We do not implement a BestFitMatcher, so we can just use LookupMatcher. return lookupSupportedLocales(availableLocales, requestedLocales); } /// https://402.ecma-international.org/8.0/#sec-canonicalizelocalelist vm::CallResult<std::vector<std::u16string>> canonicalizeLocaleList( vm::Runtime &runtime, const std::vector<std::u16string> &locales) { // 1. If locales is undefined, then // a. Return a new empty List // Not needed, this validation occurs closer to VM in 'normalizeLocales'. // 2. Let seen be a new empty List. std::vector<std::u16string> seen; // 3. If Type(locales) is String or Type(locales) is Object and locales has an // [[InitializedLocale]] internal slot, then // 4. Else // We don't yet support Locale object - // https://tc39.es/ecma402/#locale-objects As of now, 'locales' can only be a // string list/array. Validation occurs in normalizeLocaleList, so this // function just takes a vector of strings. // 5. Let len be ? ToLength(? Get(O, "length")). // 6. Let k be 0. // 7. Repeat, while k < len for (const auto &locale : locales) { // 7.c.vi. Let canonicalizedTag be CanonicalizeUnicodeLocaleId(tag). auto parsedOpt = ParsedLocaleIdentifier::parse(locale); if (!parsedOpt) return runtime.raiseRangeError( vm::TwineChar16("Invalid language tag: ") + vm::TwineChar16(locale.c_str())); auto canonicalizedTag = parsedOpt->canonicalize(); // 7.c.vii. If canonicalizedTag is not an element of seen, append // canonicalizedTag as the last element of seen. if (std::find(seen.begin(), seen.end(), canonicalizedTag) == seen.end()) { seen.push_back(std::move(canonicalizedTag)); } } return seen; } vm::CallResult<std::u16string> localeListToLocaleString( vm::Runtime &runtime, const std::vector<std::u16string> &locales) { // 3. Let requestedLocales be ? CanonicalizeLocaleList(locales). vm::CallResult<std::vector<std::u16string>> requestedLocales = canonicalizeLocaleList(runtime, locales); if (LLVM_UNLIKELY(requestedLocales == vm::ExecutionStatus::EXCEPTION)) { return vm::ExecutionStatus::EXCEPTION; } // 4. If requestedLocales is not an empty List, then // a. Let requestedLocale be requestedLocales[0]. // 5. Else, // a. Let requestedLocale be DefaultLocale(). std::u16string requestedLocale = requestedLocales->empty() ? getDefaultLocale() : std::move(requestedLocales->front()); // 6. Let noExtensionsLocale be the String value that is requestedLocale with // any Unicode locale extension sequences (6.2.1) removed. // We can skip this step, see the comment in lookupMatcher. // 7. Let availableLocales be a List with language tags that includes the // languages for which the Unicode Character Database contains language // sensitive case mappings. Implementations may add additional language tags // if they support case mapping for additional locales. // 8. Let locale be BestAvailableLocale(availableLocales, noExtensionsLocale). // Convert to C++ array for bestAvailableLocale function const std::vector<std::u16string> &availableLocales = getAvailableLocales(); std::optional<std::u16string> locale = bestAvailableLocale(availableLocales, requestedLocale); // 9. If locale is undefined, let locale be "und". return locale.value_or(u"und"); } /// https://402.ecma-international.org/8.0/#sec-getoption /// Split into getOptionString and getOptionBool to help readability vm::CallResult<std::optional<std::u16string>> getOptionString( vm::Runtime &runtime, const Options &options, const std::u16string &property, llvh::ArrayRef<std::u16string_view> values, std::optional<std::u16string_view> fallback) { // 1. Assert type(options) is object // 2. Let value be ? Get(options, property). auto valueIt = options.find(property); // 3. If value is undefined, return fallback. if (valueIt == options.end()) return std::optional<std::u16string>(fallback); const auto &value = valueIt->second.getString(); // 4. Assert: type is "boolean" or "string". // 5. If type is "boolean", then // a. Set value to ! ToBoolean(value). // 6. If type is "string", then // a. Set value to ? ToString(value). // 7. If values is not undefined and values does not contain an element equal // to value, throw a RangeError exception. if (!values.empty() && llvh::find(values, value) == values.end()) return runtime.raiseRangeError( vm::TwineChar16(property.c_str()) + vm::TwineChar16(" value is invalid.")); // 8. Return value. return std::optional<std::u16string>(value); } std::optional<bool> getOptionBool( vm::Runtime &runtime, const Options &options, const std::u16string &property, std::optional<bool> fallback) { // 1. Assert: Type(options) is Object. // 2. Let value be ? Get(options, property). auto value = options.find(property); // 3. If value is undefined, return fallback. if (value == options.end()) { return fallback; } // 8. Return value. return value->second.getBool(); } /// https://402.ecma-international.org/8.0/#sec-defaultnumberoption vm::CallResult<std::optional<uint8_t>> defaultNumberOption( vm::Runtime &runtime, const std::u16string &property, std::optional<Option> value, const std::uint8_t minimum, const std::uint8_t maximum, std::optional<uint8_t> fallback) { // 1. If value is undefined, return fallback. if (!value) { return fallback; } // 2. Set value to ? ToNumber(value). // 3. If value is NaN or less than minimum or greater than maximum, throw a // RangeError exception. if (std::isnan(value->getNumber()) || value->getNumber() < minimum || value->getNumber() > maximum) { return runtime.raiseRangeError( vm::TwineChar16(property.c_str()) + vm::TwineChar16(" value is invalid.")); } // 4. Return floor(value). return std::optional<uint8_t>(std::floor(value->getNumber())); } /// https://402.ecma-international.org/8.0/#sec-getnumberoption vm::CallResult<std::optional<uint8_t>> getNumberOption( vm::Runtime &runtime, const Options &options, const std::u16string &property, const std::uint8_t minimum, const std::uint8_t maximum, std::optional<uint8_t> fallback) { // 1. Assert: Type(options) is Object. // 2. Let value be ? Get(options, property). std::optional<Option> value; auto iter = options.find(property); if (iter != options.end()) { value = Option(iter->second); } // 3. Return ? DefaultNumberOption(value, minimum, maximum, fallback). auto defaultNumber = defaultNumberOption(runtime, property, value, minimum, maximum, fallback); if (defaultNumber == vm::ExecutionStatus::EXCEPTION) { return vm::ExecutionStatus::EXCEPTION; } return std::optional<uint8_t>(defaultNumber.getValue()); } } /// https://402.ecma-international.org/8.0/#sec-intl.getcanonicallocales vm::CallResult<std::vector<std::u16string>> getCanonicalLocales( vm::Runtime &runtime, const std::vector<std::u16string> &locales) { // 1. Let ll be ? CanonicalizeLocaleList(locales). // 2. Return CreateArrayFromList(ll). return canonicalizeLocaleList(runtime, locales); } /// https://402.ecma-international.org/8.0/#sup-string.prototype.tolocalelowercase vm::CallResult<std::u16string> toLocaleLowerCase( vm::Runtime &runtime, const std::vector<std::u16string> &locales, const std::u16string &str) { NSString *nsStr = u16StringToNSString(str); // Steps 3-9 in localeListToLocaleString() vm::CallResult<std::u16string> locale = localeListToLocaleString(runtime, locales); // 10. Let cpList be a List containing in order the code points of S as // defined in es2022, 6.1.4, starting at the first element of S. // 11. Let cuList be a List where the elements are the result of a lower case // transformation of the ordered code points in cpList according to the // Unicode Default Case Conversion algorithm or an implementation-defined // conversion algorithm. A conforming implementation's lower case // transformation algorithm must always yield the same cpList given the same // cuList and locale. // 12. Let L be a String whose elements are the UTF-16 Encoding (defined in // es2022, 6.1.4) of the code points of cuList. NSString *L = u16StringToNSString(locale.getValue()); // 13. Return L. return nsStringToU16String([nsStr lowercaseStringWithLocale:[[NSLocale alloc] initWithLocaleIdentifier:L]]); } /// https://402.ecma-international.org/8.0/#sup-string.prototype.tolocaleuppercase vm::CallResult<std::u16string> toLocaleUpperCase( vm::Runtime &runtime, const std::vector<std::u16string> &locales, const std::u16string &str) { NSString *nsStr = u16StringToNSString(str); // Steps 3-9 in localeListToLocaleString() vm::CallResult<std::u16string> locale = localeListToLocaleString(runtime, locales); // 10. Let cpList be a List containing in order the code points of S as // defined in es2022, 6.1.4, starting at the first element of S. // 11. Let cuList be a List where the elements are the result of a lower case // transformation of the ordered code points in cpList according to the // Unicode Default Case Conversion algorithm or an implementation-defined // conversion algorithm. A conforming implementation's lower case // transformation algorithm must always yield the same cpList given the same // cuList and locale. // 12. Let L be a String whose elements are the UTF-16 Encoding (defined in // es2022, 6.1.4) of the code points of cuList. NSString *L = u16StringToNSString(locale.getValue()); // 13. Return L. return nsStringToU16String([nsStr uppercaseStringWithLocale:[[NSLocale alloc] initWithLocaleIdentifier:L]]); } struct Collator::Impl { NSLocale *nsLocale; NSStringCompareOptions nsCompareOptions; std::u16string locale; std::u16string usage; std::u16string collation; std::u16string caseFirst; std::u16string sensitivity; bool numeric; bool ignorePunctuation; }; Collator::Collator() : impl_(std::make_unique<Impl>()) {} Collator::~Collator() {} /// https://402.ecma-international.org/8.0/#sec-intl.collator.supportedlocalesof vm::CallResult<std::vector<std::u16string>> Collator::supportedLocalesOf( vm::Runtime &runtime, const std::vector<std::u16string> &locales, const Options &options) noexcept { // 1. Let availableLocales be %Collator%.[[AvailableLocales]]. const auto &availableLocales = getAvailableLocales(); // 2. Let requestedLocales be ? CanonicalizeLocaleList(locales). auto requestedLocalesRes = canonicalizeLocaleList(runtime, locales); if (LLVM_UNLIKELY(requestedLocalesRes == vm::ExecutionStatus::EXCEPTION)) return vm::ExecutionStatus::EXCEPTION; // 3. Return ? SupportedLocales(availableLocales, requestedLocales, options) return supportedLocales(availableLocales, *requestedLocalesRes); } /// https://402.ecma-international.org/8.0/#sec-initializecollator vm::ExecutionStatus Collator::initialize( vm::Runtime &runtime, const std::vector<std::u16string> &locales, const Options &options) noexcept { // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales). auto requestedLocalesRes = canonicalizeLocaleList(runtime, locales); if (LLVM_UNLIKELY(requestedLocalesRes == vm::ExecutionStatus::EXCEPTION)) return vm::ExecutionStatus::EXCEPTION; // 2. Set options to ? CoerceOptionsToObject(options). // 3. Let usage be ? GetOption(options, "usage", "string", « "sort", "search" // », "sort"). auto usageRes = getOptionString( runtime, options, u"usage", {u"sort", u"search"}, {u"sort"}); if (LLVM_UNLIKELY(usageRes == vm::ExecutionStatus::EXCEPTION)) return vm::ExecutionStatus::EXCEPTION; // 4. Set collator.[[Usage]] to usage. impl_->usage = std::move(**usageRes); // 5. If usage is "sort", then // a. Let localeData be %Collator%.[[SortLocaleData]]. // 6. Else, // a. Let localeData be %Collator%.[[SearchLocaleData]]. // 7. Let opt be a new Record. std::unordered_map<std::u16string, std::u16string> opt; // 8. Let matcher be ? GetOption(options, "localeMatcher", "string", « // "lookup", "best fit" », "best fit"). // 9. Set opt.[[localeMatcher]] to matcher. // We only implement lookup matcher, so we don't need to record this. auto localeMatcherRes = getOptionString( runtime, options, u"localeMatcher", {u"lookup", u"best fit"}, u"best fit"); if (LLVM_UNLIKELY(localeMatcherRes == vm::ExecutionStatus::EXCEPTION)) return vm::ExecutionStatus::EXCEPTION; // 10. Let collation be ? GetOption(options, "collation", "string", undefined, // undefined). auto collationRes = getOptionString(runtime, options, u"collation", {}, {}); if (LLVM_UNLIKELY(collationRes == vm::ExecutionStatus::EXCEPTION)) return vm::ExecutionStatus::EXCEPTION; // 11. If collation is not undefined, then // a. If collation does not match the Unicode Locale Identifier type // nonterminal, throw a RangeError exception. // 12. Set opt.[[co]] to collation. if (auto &collationOpt = *collationRes) { if (!isUnicodeExtensionType(*collationOpt)) { return runtime.raiseRangeError( vm::TwineChar16("Invalid collation: ") + vm::TwineChar16(collationOpt->c_str())); } opt.emplace(u"co", std::move(*collationOpt)); } // 13. Let numeric be ? GetOption(options, "numeric", "boolean", undefined, // undefined). auto numericOpt = getOptionBool(runtime, options, u"numeric", {}); // 14. If numeric is not undefined, then // a. Let numeric be ! ToString(numeric). // 15. Set opt.[[kn]] to numeric. if (numericOpt) opt.emplace(u"kn", *numericOpt ? u"true" : u"false"); // 16. Let caseFirst be ? GetOption(options, "caseFirst", "string", « "upper", // "lower", "false" », undefined). auto caseFirstRes = getOptionString( runtime, options, u"caseFirst", {u"upper", u"lower", u"false"}, {}); if (LLVM_UNLIKELY(caseFirstRes == vm::ExecutionStatus::EXCEPTION)) return vm::ExecutionStatus::EXCEPTION; // 17. Set opt.[[kf]] to caseFirst. if (auto caseFirstOpt = *caseFirstRes) opt.emplace(u"kf", *caseFirstOpt); // 18. Let relevantExtensionKeys be %Collator%.[[RelevantExtensionKeys]]. std::vector<std::u16string> relevantExtensionKeys = {u"co", u"kn", u"kf"}; // 19. Let r be ResolveLocale(%Collator%.[[AvailableLocales]], // requestedLocales, opt,relevantExtensionKeys, localeData). auto r = resolveLocale( getAvailableLocales(), *requestedLocalesRes, opt, relevantExtensionKeys); // 20. Set collator.[[Locale]] to r.[[locale]]. impl_->locale = std::move(r.locale); // 21. Let collation be r.[[co]]. auto coIt = r.extensions.find(u"co"); // 22. If collation is null, let collation be "default". // 23. Set collator.[[Collation]] to collation. if (coIt == r.extensions.end()) impl_->collation = u"default"; else impl_->collation = std::move(coIt->second); // 24. If relevantExtensionKeys contains "kn", then // a. Set collator.[[Numeric]] to ! SameValue(r.[[kn]], "true"). auto knIt = r.extensions.find(u"kn"); if (knIt == r.extensions.end()) impl_->numeric = false; else impl_->numeric = (knIt->second == u"true"); // 25. If relevantExtensionKeys contains "kf", then // a. Set collator.[[CaseFirst]] to r.[[kf]]. auto kfIt = r.extensions.find(u"kf"); if (kfIt == r.extensions.end()) impl_->caseFirst = u"false"; else impl_->caseFirst = kfIt->second; // 26. Let sensitivity be ? GetOption(options, "sensitivity", "string", « // "base", "accent", "case", "variant" », undefined). auto sensitivityRes = getOptionString( runtime, options, u"sensitivity", {u"base", u"accent", u"case", u"variant"}, {}); if (LLVM_UNLIKELY(sensitivityRes == vm::ExecutionStatus::EXCEPTION)) return vm::ExecutionStatus::EXCEPTION; // 27. If sensitivity is undefined, then // a. If usage is "sort", then // i. Let sensitivity be "variant". // b. Else, // i. Let dataLocale be r.[[dataLocale]]. // ii. Let dataLocaleData be localeData.[[<dataLocale>]]. // iii. Let sensitivity be dataLocaleData.[[sensitivity]]. // 28. Set collator.[[Sensitivity]] to sensitivity. if (auto &sensitivityOpt = *sensitivityRes) impl_->sensitivity = std::move(*sensitivityOpt); else impl_->sensitivity = u"variant"; // 29. Let ignorePunctuation be ? GetOption(options, "ignorePunctuation", // "boolean", undefined,false). auto ignorePunctuationOpt = getOptionBool(runtime, options, u"ignorePunctuation", false); // 30. Set collator.[[IgnorePunctuation]] to ignorePunctuation. impl_->ignorePunctuation = *ignorePunctuationOpt; // Set up the state for calling into Obj-C APIs. NSStringCompareOptions cmpOpts = 0; if (impl_->numeric) cmpOpts |= NSNumericSearch; if (impl_->sensitivity == u"base") cmpOpts |= (NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch); else if (impl_->sensitivity == u"accent") cmpOpts |= NSCaseInsensitiveSearch; else if (impl_->sensitivity == u"case") cmpOpts |= NSDiacriticInsensitiveSearch; impl_->nsCompareOptions = cmpOpts; std::u16string nsLocaleExtensions; if (impl_->collation != u"default") nsLocaleExtensions.append(u"-co-").append(impl_->collation); else if (impl_->usage == u"search") nsLocaleExtensions.append(u"-co-search"); if (impl_->caseFirst != u"false") nsLocaleExtensions.append(u"-kf-").append(impl_->caseFirst); auto nsLocaleIdentifier = r.dataLocale; if (!nsLocaleExtensions.empty()) nsLocaleIdentifier.append(u"-u").append(nsLocaleExtensions); impl_->nsLocale = [NSLocale localeWithLocaleIdentifier:u16StringToNSString(nsLocaleIdentifier)]; // 31. Return collator. return vm::ExecutionStatus::RETURNED; } /// https://402.ecma-international.org/8.0/#sec-intl.collator.prototype.resolvedoptions Options Collator::resolvedOptions() noexcept { Options options; options.emplace(u"locale", Option(impl_->locale)); options.emplace(u"usage", Option(impl_->usage)); options.emplace(u"sensitivity", Option(impl_->sensitivity)); options.emplace(u"ignorePunctuation", Option(impl_->ignorePunctuation)); options.emplace(u"collation", Option(impl_->collation)); options.emplace(u"numeric", Option(impl_->numeric)); options.emplace(u"caseFirst", Option(impl_->caseFirst)); return options; } /// https://402.ecma-international.org/8.0/#sec-intl.collator.prototype.compare double Collator::compare( const std::u16string &x, const std::u16string &y) noexcept { NSString *nsX = u16StringToNSString(x); NSString *nsY = u16StringToNSString(y); if (impl_->ignorePunctuation) { // Unfortunately, NSLocale does not provide a way to specify alternate // handling, so we simulate it by manually stripping punctuation and // whitespace. auto *set = [NSMutableCharacterSet punctuationCharacterSet]; [set formUnionWithCharacterSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; auto removePunctuation = [&](NSString *nsStr) { auto *res = [NSMutableString new]; for (size_t i = 0; i < nsStr.length; ++i) if (![set characterIsMember:[nsStr characterAtIndex:i]]) [res appendFormat:@"%c", [nsStr characterAtIndex:i]]; return res; }; nsX = removePunctuation(nsX); nsY = removePunctuation(nsY); } return [nsX compare:nsY options:impl_->nsCompareOptions range:NSMakeRange(0, nsX.length) locale:impl_->nsLocale]; } struct DateTimeFormat::Impl { std::u16string locale; }; DateTimeFormat::DateTimeFormat() : impl_(std::make_unique<Impl>()) {} DateTimeFormat::~DateTimeFormat() {} vm::CallResult<std::vector<std::u16string>> DateTimeFormat::supportedLocalesOf( vm::Runtime &runtime, const std::vector<std::u16string> &locales, const Options &options) noexcept { return std::vector<std::u16string>{u"en-CA", u"de-DE"}; } vm::ExecutionStatus DateTimeFormat::initialize( vm::Runtime &runtime, const std::vector<std::u16string> &locales, const Options &options) noexcept { impl_->locale = u"en-US"; return vm::ExecutionStatus::RETURNED; } Options DateTimeFormat::resolvedOptions() noexcept { Options options; options.emplace(u"locale", Option(impl_->locale)); options.emplace(u"numeric", Option(false)); return options; } std::u16string DateTimeFormat::format(double jsTimeValue) noexcept { auto s = std::to_string(jsTimeValue); return std::u16string(s.begin(), s.end()); } std::vector<std::unordered_map<std::u16string, std::u16string>> DateTimeFormat::formatToParts(double jsTimeValue) noexcept { std::unordered_map<std::u16string, std::u16string> part; part[u"type"] = u"integer"; // This isn't right, but I didn't want to do more work for a stub. std::string s = std::to_string(jsTimeValue); part[u"value"] = {s.begin(), s.end()}; return std::vector<std::unordered_map<std::u16string, std::u16string>>{part}; } struct NumberFormat::Impl { std::u16string locale; }; NumberFormat::NumberFormat() : impl_(std::make_unique<Impl>()) {} NumberFormat::~NumberFormat() {} vm::CallResult<std::vector<std::u16string>> NumberFormat::supportedLocalesOf( vm::Runtime &runtime, const std::vector<std::u16string> &locales, const Options &options) noexcept { return std::vector<std::u16string>{u"en-CA", u"de-DE"}; } vm::ExecutionStatus NumberFormat::initialize( vm::Runtime &runtime, const std::vector<std::u16string> &locales, const Options &options) noexcept { impl_->locale = u"en-US"; return vm::ExecutionStatus::RETURNED; } Options NumberFormat::resolvedOptions() noexcept { Options options; options.emplace(u"locale", Option(impl_->locale)); options.emplace(u"numeric", Option(false)); return options; } std::u16string NumberFormat::format(double number) noexcept { auto s = std::to_string(number); return std::u16string(s.begin(), s.end()); } std::vector<std::unordered_map<std::u16string, std::u16string>> NumberFormat::formatToParts(double number) noexcept { std::unordered_map<std::u16string, std::u16string> part; part[u"type"] = u"integer"; // This isn't right, but I didn't want to do more work for a stub. std::string s = std::to_string(number); part[u"value"] = {s.begin(), s.end()}; return std::vector<std::unordered_map<std::u16string, std::u16string>>{part}; } } // namespace platform_intl } // namespace hermes