Dactyloidae/js/src/builtin/intl/CommonFunctions.js
Martok c10a29d96f Issue #1819 - Implement Intl.Locale proposal
This is according to spec for the mozilla71 cycle, a follow-up will further adjust to spec.

- Add Intl.Locale as native C++
- Port Unicode BCP 47 locale identifier parser to C++
- Port language tag parser to C++
- adjust make_intl_data to generate the data

Based-on: m-c 1433303, 1570370
2023-07-03 15:09:21 +08:00

987 lines
33 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* Portions Copyright Norbert Lindenberg 2011-2012. */
/**
* Shorthand for hasOwnProperty.
*/
function hasOwn(propName, object) {
return callFunction(std_Object_hasOwnProperty, object, propName);
}
#ifdef DEBUG
#define assertIsValidAndCanonicalLanguageTag(locale, desc) \
do { \
let canonical = intl_TryValidateAndCanonicalizeLanguageTag(locale); \
assert(canonical !== null, \
`${desc} is a structurally valid language tag`); \
assert(canonical === locale, \
`${desc} is a canonicalized language tag`); \
} while (false)
#else
#define assertIsValidAndCanonicalLanguageTag(locale, desc) ; // Elided assertion.
#endif
/**
* Returns the start index of a "Unicode locale extension sequence", which the
* specification defines as: "any substring of a language tag that starts with
* a separator '-' and the singleton 'u' and includes the maximum sequence of
* following non-singleton subtags and their preceding '-' separators."
*
* Alternatively, this may be defined as: the components of a language tag that
* match the `unicode_locale_extensions` production in UTS 35.
*
* Spec: ECMAScript Internationalization API Specification, 6.2.1.
*/
function startOfUnicodeExtensions(locale) {
assert(typeof locale === "string", "locale is a string");
// Search for "-u-" marking the start of a Unicode extension sequence.
var start = callFunction(std_String_indexOf, locale, "-u-");
if (start < 0)
return -1;
// And search for "-x-" marking the start of any privateuse component to
// handle the case when "-u-" was only found within a privateuse subtag.
var privateExt = callFunction(std_String_indexOf, locale, "-x-");
if (privateExt >= 0 && privateExt < start)
return -1;
return start;
}
/**
* Returns the end index of a Unicode locale extension sequence.
*/
function endOfUnicodeExtensions(locale, start) {
assert(typeof locale === "string", "locale is a string");
assert(0 <= start && start < locale.length, "start is an index into locale");
assert(Substring(locale, start, 3) === "-u-", "start points to Unicode extension sequence");
#define HYPHEN 0x2D
assert(std_String_fromCharCode(HYPHEN) === "-",
"code unit constant should match the expected character");
// Search for the start of the next singleton or privateuse subtag.
//
// Begin searching after the smallest possible Unicode locale extension
// sequence, namely |"-u-" 2alphanum|. End searching once the remaining
// characters can't fit the smallest possible singleton or privateuse
// subtag, namely |"-x-" alphanum|. Note the reduced end-limit means
// indexing inside the loop is always in-range.
for (var i = start + 5, end = locale.length - 4; i <= end; i++) {
if (callFunction(std_String_charCodeAt, locale, i) !== HYPHEN)
continue;
if (callFunction(std_String_charCodeAt, locale, i + 2) === HYPHEN)
return i;
// Skip over (i + 1) and (i + 2) because we've just verified they
// aren't "-", so the next possible delimiter can only be at (i + 3).
i += 2;
}
#undef HYPHEN
// If no singleton or privateuse subtag was found, the Unicode extension
// sequence extends until the end of the string.
return locale.length;
}
/**
* Removes Unicode locale extension sequences from the given language tag.
*/
function removeUnicodeExtensions(locale) {
var start = startOfUnicodeExtensions(locale);
if (start < 0)
return locale;
var end = endOfUnicodeExtensions(locale, start);
var left = Substring(locale, 0, start);
var right = Substring(locale, end, locale.length - end);
var combined = left + right;
assertIsValidAndCanonicalLanguageTag(combined, "the recombined locale");
assert(startOfUnicodeExtensions(combined) < 0,
"recombination failed to remove all Unicode locale extension sequences");
return combined;
}
/**
* Returns Unicode locale extension sequences from the given language tag.
*/
function getUnicodeExtensions(locale) {
var start = startOfUnicodeExtensions(locale);
assert(start >= 0, "start of Unicode extension sequence not found");
var end = endOfUnicodeExtensions(locale, start);
return Substring(locale, start, end - start);
}
/**
* Returns true if the input contains only ASCII alphabetical characters.
*/
function IsASCIIAlphaString(s) {
assert(typeof s === "string", "IsASCIIAlphaString");
for (var i = 0; i < s.length; i++) {
var c = callFunction(std_String_charCodeAt, s, i);
if (!((0x41 <= c && c <= 0x5A) || (0x61 <= c && c <= 0x7A)))
return false
}
return true;
}
// The last-ditch locale is used if none of the available locales satisfies a
// request. "en-GB" is used based on the assumptions that English is the most
// common second language, that both en-GB and en-US are normally available in
// an implementation, and that en-GB is more representative of the English used
// in other locales.
function lastDitchLocale() {
// Per bug 1177929, strings don't clone out of self-hosted code as atoms,
// breaking IonBuilder::constant. Put this in a function for now.
return "en-GB";
}
// Certain old, commonly-used language tags that lack a script, are expected to
// nonetheless imply one. This object maps these old-style tags to modern
// equivalents.
var oldStyleLanguageTagMappings = {
"pa-PK": "pa-Arab-PK",
"zh-CN": "zh-Hans-CN",
"zh-HK": "zh-Hant-HK",
"zh-SG": "zh-Hans-SG",
"zh-TW": "zh-Hant-TW",
};
var localeCandidateCache = {
runtimeDefaultLocale: undefined,
candidateDefaultLocale: undefined,
};
var localeCache = {
runtimeDefaultLocale: undefined,
defaultLocale: undefined,
};
/**
* Compute the candidate default locale: the locale *requested* to be used as
* the default locale. We'll use it if and only if ICU provides support (maybe
* fallback support, e.g. supporting "de-ZA" through "de" support implied by a
* "de-DE" locale).
*/
function DefaultLocaleIgnoringAvailableLocales() {
const runtimeDefaultLocale = RuntimeDefaultLocale();
if (runtimeDefaultLocale === localeCandidateCache.runtimeDefaultLocale)
return localeCandidateCache.candidateDefaultLocale;
// If we didn't get a cache hit, compute the candidate default locale and
// cache it. Fall back on the last-ditch locale when necessary.
var candidate = intl_TryValidateAndCanonicalizeLanguageTag(runtimeDefaultLocale);
if (candidate === null) {
candidate = lastDitchLocale();
} else {
// The default locale must be in [[availableLocales]], and that list
// must not contain any locales with Unicode extension sequences, so
// remove any present in the candidate.
candidate = removeUnicodeExtensions(candidate);
if (hasOwn(candidate, oldStyleLanguageTagMappings))
candidate = oldStyleLanguageTagMappings[candidate];
}
// Cache the candidate locale until the runtime default locale changes.
localeCandidateCache.candidateDefaultLocale = candidate;
localeCandidateCache.runtimeDefaultLocale = runtimeDefaultLocale;
assertIsValidAndCanonicalLanguageTag(candidate, "the candidate");
return candidate;
}
/**
* Returns the BCP 47 language tag for the host environment's current locale.
*
* Spec: ECMAScript Internationalization API Specification, 6.2.4.
*/
function DefaultLocale() {
const runtimeDefaultLocale = RuntimeDefaultLocale();
if (runtimeDefaultLocale === localeCache.runtimeDefaultLocale)
return localeCache.defaultLocale;
// If we didn't have a cache hit, compute the candidate default locale.
// Then use it as the actual default locale if ICU supports that locale
// (perhaps via fallback). Otherwise use the last-ditch locale.
var candidate = DefaultLocaleIgnoringAvailableLocales();
var locale;
if (BestAvailableLocaleIgnoringDefault(callFunction(collatorInternalProperties.availableLocales,
collatorInternalProperties),
candidate) &&
BestAvailableLocaleIgnoringDefault(callFunction(numberFormatInternalProperties.availableLocales,
numberFormatInternalProperties),
candidate) &&
BestAvailableLocaleIgnoringDefault(callFunction(dateTimeFormatInternalProperties.availableLocales,
dateTimeFormatInternalProperties),
candidate))
{
locale = candidate;
} else {
locale = lastDitchLocale();
}
assertIsValidAndCanonicalLanguageTag(locale, "the computed default locale");
assert(startOfUnicodeExtensions(locale) < 0,
"the computed default locale must not contain a Unicode extension sequence");
localeCache.defaultLocale = locale;
localeCache.runtimeDefaultLocale = runtimeDefaultLocale;
return locale;
}
/**
* Add old-style language tags without script code for locales that in current
* usage would include a script subtag. Also add an entry for the last-ditch
* locale, in case ICU doesn't directly support it (but does support it through
* fallback, e.g. supporting "en-GB" indirectly using "en" support).
*/
function addSpecialMissingLanguageTags(availableLocales) {
// Certain old-style language tags lack a script code, but in current usage
// they *would* include a script code. Map these over to modern forms.
var oldStyleLocales = std_Object_getOwnPropertyNames(oldStyleLanguageTagMappings);
for (var i = 0; i < oldStyleLocales.length; i++) {
var oldStyleLocale = oldStyleLocales[i];
if (availableLocales[oldStyleLanguageTagMappings[oldStyleLocale]])
availableLocales[oldStyleLocale] = true;
}
// Also forcibly provide the last-ditch locale.
var lastDitch = lastDitchLocale();
assert(lastDitch === "en-GB" && availableLocales["en"],
"shouldn't be a need to add every locale implied by the last-" +
"ditch locale, merely just the last-ditch locale");
availableLocales[lastDitch] = true;
}
/**
* Canonicalizes a locale list.
*
* Spec: ECMAScript Internationalization API Specification, 9.2.1.
*/
function CanonicalizeLocaleList(locales) {
// Step 1.
if (locales === undefined)
return [];
// Step 3 (and the remaining steps).
var tag = intl_ValidateAndCanonicalizeLanguageTag(locales, false);
if (tag !== null) {
assert(typeof tag === "string",
"intl_ValidateAndCanonicalizeLanguageTag returns a string value");
return [tag];
}
// Step 2.
var seen = [];
// Step 4.
var O = ToObject(locales);
// Step 5.
var len = ToLength(O.length);
// Step 6.
var k = 0;
// Step 7.
while (k < len) {
// Steps 7.a-c.
if (k in O) {
// Step 7.c.i.
var kValue = O[k];
// Step 7.c.ii.
if (!(typeof kValue === "string" || IsObject(kValue)))
ThrowTypeError(JSMSG_INVALID_LOCALES_ELEMENT);
// Steps 7.c.iii-iv.
var tag = intl_ValidateAndCanonicalizeLanguageTag(kValue, true);
assert(typeof tag === "string",
"ValidateAndCanonicalizeLanguageTag returns a string value");
// Step 7.c.v.
if (callFunction(ArrayIndexOf, seen, tag) === -1)
_DefineDataProperty(seen, seen.length, tag);
}
// Step 7.d.
k++;
}
// Step 8.
return seen;
}
function BestAvailableLocaleHelper(availableLocales, locale, considerDefaultLocale) {
assertIsValidAndCanonicalLanguageTag(locale, "BestAvailableLocale locale");
assert(startOfUnicodeExtensions(locale) < 0, "locale must contain no Unicode extensions");
// In the spec, [[availableLocales]] is formally a list of all available
// locales. But in our implementation, it's an *incomplete* list, not
// necessarily including the default locale (and all locales implied by it,
// e.g. "de" implied by "de-CH"), if that locale isn't in every
// [[availableLocales]] list (because that locale is supported through
// fallback, e.g. "de-CH" supported through "de").
//
// If we're considering the default locale, augment the spec loop with
// additional checks to also test whether the current prefix is a prefix of
// the default locale.
var defaultLocale;
if (considerDefaultLocale)
defaultLocale = DefaultLocale();
var candidate = locale;
while (true) {
if (availableLocales[candidate])
return candidate;
if (considerDefaultLocale && candidate.length <= defaultLocale.length) {
if (candidate === defaultLocale)
return candidate;
if (callFunction(std_String_startsWith, defaultLocale, candidate + "-"))
return candidate;
}
var pos = callFunction(std_String_lastIndexOf, candidate, "-");
if (pos === -1)
return undefined;
if (pos >= 2 && candidate[pos - 2] === "-")
pos -= 2;
candidate = callFunction(String_substring, candidate, 0, pos);
}
}
/**
* Compares a BCP 47 language tag against the locales in availableLocales
* and returns the best available match. Uses the fallback
* mechanism of RFC 4647, section 3.4.
*
* Spec: ECMAScript Internationalization API Specification, 9.2.2.
* Spec: RFC 4647, section 3.4.
*/
function BestAvailableLocale(availableLocales, locale) {
return BestAvailableLocaleHelper(availableLocales, locale, true);
}
/**
* Identical to BestAvailableLocale, but does not consider the default locale
* during computation.
*/
function BestAvailableLocaleIgnoringDefault(availableLocales, locale) {
return BestAvailableLocaleHelper(availableLocales, locale, false);
}
/**
* Compares a BCP 47 language priority list against the set of locales in
* availableLocales and determines the best available language to meet the
* request. Options specified through Unicode extension subsequences are
* ignored in the lookup, but information about such subsequences is returned
* separately.
*
* This variant is based on the Lookup algorithm of RFC 4647 section 3.4.
*
* Spec: ECMAScript Internationalization API Specification, 9.2.3.
* Spec: RFC 4647, section 3.4.
*/
function LookupMatcher(availableLocales, requestedLocales) {
// Step 1.
var result = new Record();
// Step 2.
for (var i = 0; i < requestedLocales.length; i++) {
var locale = requestedLocales[i];
// Step 2.a.
var noExtensionsLocale = removeUnicodeExtensions(locale);
// Step 2.b.
var availableLocale = BestAvailableLocale(availableLocales, noExtensionsLocale);
// Step 2.c.
if (availableLocale !== undefined) {
// Step 2.c.i.
result.locale = availableLocale;
// Step 2.c.ii.
if (locale !== noExtensionsLocale)
result.extension = getUnicodeExtensions(locale);
// Step 2.c.iii.
return result;
}
}
// Steps 3-4.
result.locale = DefaultLocale();
// Step 5.
return result;
}
/**
* Compares a BCP 47 language priority list against the set of locales in
* availableLocales and determines the best available language to meet the
* request. Options specified through Unicode extension subsequences are
* ignored in the lookup, but information about such subsequences is returned
* separately.
*
* Spec: ECMAScript Internationalization API Specification, 9.2.4.
*/
function BestFitMatcher(availableLocales, requestedLocales) {
// this implementation doesn't have anything better
return LookupMatcher(availableLocales, requestedLocales);
}
/**
* Returns the Unicode extension value subtags for the requested key subtag.
*
* Spec: ECMAScript Internationalization API Specification, 9.2.5.
*/
function UnicodeExtensionValue(extension, key) {
assert(typeof extension === "string", "extension is a string value");
assert(callFunction(std_String_startsWith, extension, "-u-") &&
getUnicodeExtensions("und" + extension) === extension,
"extension is a Unicode extension subtag");
assert(typeof key === "string", "key is a string value");
// Step 1.
assert(key.length === 2, "key is a Unicode extension key subtag");
// Step 2.
var size = extension.length;
// Step 3.
var searchValue = "-" + key + "-";
// Step 4.
var pos = callFunction(std_String_indexOf, extension, searchValue);
// Step 5.
if (pos !== -1) {
// Step 5.a.
var start = pos + 4;
// Step 5.b.
var end = start;
// Step 5.c.
var k = start;
// Steps 5.d-e.
while (true) {
// Step 5.e.i.
var e = callFunction(std_String_indexOf, extension, "-", k);
// Step 5.e.ii.
var len = e === -1 ? size - k : e - k;
// Step 5.e.iii.
if (len === 2)
break;
// Step 5.e.iv.
if (e === -1) {
end = size;
break;
}
// Step 5.e.v.
end = e;
k = e + 1;
}
// Step 5.f.
return callFunction(String_substring, extension, start, end);
}
// Step 6.
searchValue = "-" + key;
// Steps 7-8.
if (callFunction(std_String_endsWith, extension, searchValue))
return "";
// Step 9 (implicit).
}
/**
* Compares a BCP 47 language priority list against availableLocales and
* determines the best available language to meet the request. Options specified
* through Unicode extension subsequences are negotiated separately, taking the
* caller's relevant extensions and locale data as well as client-provided
* options into consideration.
*
* Spec: ECMAScript Internationalization API Specification, 9.2.6.
*/
function ResolveLocale(availableLocales, requestedLocales, options, relevantExtensionKeys, localeData) {
// Steps 1-3.
var matcher = options.localeMatcher;
var r = (matcher === "lookup")
? LookupMatcher(availableLocales, requestedLocales)
: BestFitMatcher(availableLocales, requestedLocales);
// Step 4.
var foundLocale = r.locale;
var extension = r.extension;
// Step 5.
var result = new Record();
// Step 6.
result.dataLocale = foundLocale;
// Step 7.
var supportedExtension = "-u";
// In this implementation, localeData is a function, not an object.
var localeDataProvider = localeData();
// Step 8.
for (var i = 0; i < relevantExtensionKeys.length; i++) {
var key = relevantExtensionKeys[i];
// Steps 8.a-h (The locale data is only computed when needed).
var keyLocaleData = undefined;
var value = undefined;
// Locale tag may override.
// Step 8.g.
var supportedExtensionAddition = "";
// Step 8.h.
if (extension !== undefined) {
// Step 8.h.i.
var requestedValue = UnicodeExtensionValue(extension, key);
// Step 8.h.ii.
if (requestedValue !== undefined) {
// Steps 8.a-d.
keyLocaleData = callFunction(localeDataProvider[key], null, foundLocale);
// Step 8.h.ii.1.
if (requestedValue !== "") {
// Step 8.h.ii.1.a.
if (callFunction(ArrayIndexOf, keyLocaleData, requestedValue) !== -1) {
value = requestedValue;
supportedExtensionAddition = "-" + key + "-" + value;
}
} else {
// Step 8.h.ii.2.
// According to the LDML spec, if there's no type value,
// and true is an allowed value, it's used.
if (callFunction(ArrayIndexOf, keyLocaleData, "true") !== -1) {
value = "true";
supportedExtensionAddition = "-" + key;
}
}
}
}
// Options override all.
// Step 8.i.i.
var optionsValue = options[key];
// Step 8.i.ii.
assert(typeof optionsValue === "string" ||
optionsValue === undefined ||
optionsValue === null,
"unexpected type for options value");
// Steps 8.i, 8.i.iii.1.
if (optionsValue !== undefined && optionsValue !== value) {
// Steps 8.a-d.
if (keyLocaleData === undefined)
keyLocaleData = callFunction(localeDataProvider[key], null, foundLocale);
// Step 8.i.iii.
if (callFunction(ArrayIndexOf, keyLocaleData, optionsValue) !== -1) {
value = optionsValue;
supportedExtensionAddition = "";
}
}
// Locale data provides default value.
if (value === undefined) {
// Steps 8.a-f.
value = keyLocaleData === undefined
? callFunction(localeDataProvider.default[key], null, foundLocale)
: keyLocaleData[0];
}
// Step 8.j.
assert(typeof value === "string" || value === null, "unexpected locale data value");
result[key] = value;
// Step 8.k.
supportedExtension += supportedExtensionAddition;
}
// Step 9.
if (supportedExtension.length > 2) {
assert(!callFunction(std_String_startsWith, foundLocale, "x-"),
"unexpected privateuse-only locale returned from ICU");
// Step 9.a.
var privateIndex = callFunction(std_String_indexOf, foundLocale, "-x-");
// Steps 9.b-c.
if (privateIndex === -1) {
foundLocale += supportedExtension;
} else {
var preExtension = callFunction(String_substring, foundLocale, 0, privateIndex);
var postExtension = callFunction(String_substring, foundLocale, privateIndex);
foundLocale = preExtension + supportedExtension + postExtension;
}
// Step 9.d-e (Step 9.e is not required in this implementation, because we don't
// canonicalize Unicode extension subtags).
assertIsValidAndCanonicalLanguageTag(foundLocale, "same locale with extension");
}
// Step 10.
result.locale = foundLocale;
// Step 11.
return result;
}
/**
* Returns the subset of requestedLocales for which availableLocales has a
* matching (possibly fallback) locale. Locales appear in the same order in the
* returned list as in the input list.
*
* Spec: ECMAScript Internationalization API Specification, 9.2.7.
*/
function LookupSupportedLocales(availableLocales, requestedLocales) {
// Step 1.
var subset = [];
// Step 2.
for (var i = 0; i < requestedLocales.length; i++) {
var locale = requestedLocales[i];
// Step 2.a.
var noExtensionsLocale = removeUnicodeExtensions(locale);
// Step 2.b.
var availableLocale = BestAvailableLocale(availableLocales, noExtensionsLocale);
// Step 2.c.
if (availableLocale !== undefined)
_DefineDataProperty(subset, subset.length, locale);
}
// Step 3.
return subset;
}
/**
* Returns the subset of requestedLocales for which availableLocales has a
* matching (possibly fallback) locale. Locales appear in the same order in the
* returned list as in the input list.
*
* Spec: ECMAScript Internationalization API Specification, 9.2.8.
*/
function BestFitSupportedLocales(availableLocales, requestedLocales) {
// don't have anything better
return LookupSupportedLocales(availableLocales, requestedLocales);
}
/**
* Returns the subset of requestedLocales for which availableLocales has a
* matching (possibly fallback) locale. Locales appear in the same order in the
* returned list as in the input list.
*
* Spec: ECMAScript Internationalization API Specification, 9.2.9.
*/
function SupportedLocales(availableLocales, requestedLocales, options) {
// Step 1.
var matcher;
if (options !== undefined) {
// Step 1.a.
options = ToObject(options);
// Step 1.b
matcher = options.localeMatcher;
if (matcher !== undefined) {
matcher = ToString(matcher);
if (matcher !== "lookup" && matcher !== "best fit")
ThrowRangeError(JSMSG_INVALID_LOCALE_MATCHER, matcher);
}
}
// Steps 2-5.
var subset = (matcher === undefined || matcher === "best fit")
? BestFitSupportedLocales(availableLocales, requestedLocales)
: LookupSupportedLocales(availableLocales, requestedLocales);
// Steps 6-7.
for (var i = 0; i < subset.length; i++) {
_DefineDataProperty(subset, i, subset[i],
ATTR_ENUMERABLE | ATTR_NONCONFIGURABLE | ATTR_NONWRITABLE);
}
_DefineDataProperty(subset, "length", subset.length,
ATTR_NONENUMERABLE | ATTR_NONCONFIGURABLE | ATTR_NONWRITABLE);
// Step 8.
return subset;
}
/**
* Extracts a property value from the provided options object, converts it to
* the required type, checks whether it is one of a list of allowed values,
* and fills in a fallback value if necessary.
*
* Spec: ECMAScript Internationalization API Specification, 9.2.10.
*/
function GetOption(options, property, type, values, fallback) {
// Step 1.
var value = options[property];
// Step 2.
if (value !== undefined) {
// Steps 2.a-c.
if (type === "boolean")
value = ToBoolean(value);
else if (type === "string")
value = ToString(value);
else
assert(false, "GetOption");
// Step 2.d.
if (values !== undefined && callFunction(ArrayIndexOf, values, value) === -1)
ThrowRangeError(JSMSG_INVALID_OPTION_VALUE, property, value);
// Step 2.e.
return value;
}
// Step 3.
return fallback;
}
/**
* Extracts a property value from the provided options object, converts it to a
* Number value, checks whether it is in the allowed range, and fills in a
* fallback value if necessary.
*
* Spec: ECMAScript Internationalization API Specification, 9.2.10.
*/
function GetNumberOption(options, property, minimum, maximum, fallback) {
assert(typeof minimum === "number", "GetNumberOption");
assert(typeof maximum === "number", "GetNumberOption");
assert(fallback === undefined || (fallback >= minimum && fallback <= maximum), "GetNumberOption");
// Step 1.
var value = options[property];
// Step 2.
if (value !== undefined) {
value = ToNumber(value);
if (Number_isNaN(value) || value < minimum || value > maximum)
ThrowRangeError(JSMSG_INVALID_DIGITS_VALUE, value);
return std_Math_floor(value);
}
// Step 3.
return fallback;
}
// Symbols in the self-hosting compartment can't be cloned, use a separate
// object to hold the actual symbol value.
// TODO: Can we add support to clone symbols?
var intlFallbackSymbolHolder = { value: undefined };
/**
* The [[FallbackSymbol]] symbol of the %Intl% intrinsic object.
*
* This symbol is used to implement the legacy constructor semantics for
* Intl.DateTimeFormat and Intl.NumberFormat.
*/
function intlFallbackSymbol() {
var fallbackSymbol = intlFallbackSymbolHolder.value;
if (!fallbackSymbol)
intlFallbackSymbolHolder.value = fallbackSymbol = std_Symbol();
return fallbackSymbol;
}
/**
* Initializes the INTL_INTERNALS_OBJECT_SLOT of the given object.
*/
function initializeIntlObject(obj, type, lazyData) {
assert(IsObject(obj), "Non-object passed to initializeIntlObject");
assert((type === "Collator" && IsCollator(obj)) ||
(type === "DateTimeFormat" && IsDateTimeFormat(obj)) ||
(type === "NumberFormat" && IsNumberFormat(obj)) ||
(type === "PluralRules" && IsPluralRules(obj)) ||
(type === "RelativeTimeFormat" && IsRelativeTimeFormat(obj)),
"type must match the object's class");
assert(IsObject(lazyData), "non-object lazy data");
// Intl-initialized objects are weird. They have [[initializedIntlObject]]
// set on them, but they don't *necessarily* have any other properties.
var internals = std_Object_create(null);
// The meaning of an internals object for an object |obj| is as follows.
//
// The .type property indicates the type of Intl object that |obj| is:
// "Collator", "DateTimeFormat", "NumberFormat", or "PluralRules" (likely
// with more coming in future Intl specs).
//
// The .lazyData property stores information needed to compute -- without
// observable side effects -- the actual internal Intl properties of
// |obj|. If it is non-null, then the actual internal properties haven't
// been computed, and .lazyData must be processed by
// |setInternalProperties| before internal Intl property values are
// available. If it is null, then the .internalProps property contains an
// object whose properties are the internal Intl properties of |obj|.
var internals = std_Object_create(null);
internals.type = type;
internals.lazyData = lazyData;
internals.internalProps = null;
assert(UnsafeGetReservedSlot(obj, INTL_INTERNALS_OBJECT_SLOT) === null,
"Internal slot already initialized?");
UnsafeSetReservedSlot(obj, INTL_INTERNALS_OBJECT_SLOT, internals);
}
/**
* Set the internal properties object for an |internals| object previously
* associated with lazy data.
*/
function setInternalProperties(internals, internalProps) {
assert(IsObject(internals.lazyData), "lazy data must exist already");
assert(IsObject(internalProps), "internalProps argument should be an object");
// Set in reverse order so that the .lazyData nulling is a barrier.
internals.internalProps = internalProps;
internals.lazyData = null;
}
/**
* Get the existing internal properties out of a non-newborn |internals|, or
* null if none have been computed.
*/
function maybeInternalProperties(internals) {
assert(IsObject(internals), "non-object passed to maybeInternalProperties");
var lazyData = internals.lazyData;
if (lazyData)
return null;
assert(IsObject(internals.internalProps), "missing lazy data and computed internals");
return internals.internalProps;
}
/**
* Return |obj|'s internals object (*not* the object holding its internal
* properties!), with structure specified above.
*
* Spec: ECMAScript Internationalization API Specification, 10.3.
* Spec: ECMAScript Internationalization API Specification, 11.3.
* Spec: ECMAScript Internationalization API Specification, 12.3.
*/
function getIntlObjectInternals(obj) {
assert(IsObject(obj), "getIntlObjectInternals called with non-Object");
assert(IsCollator(obj) || IsDateTimeFormat(obj) || IsNumberFormat(obj) ||
IsPluralRules(obj) || IsRelativeTimeFormat(obj),
"getIntlObjectInternals called with non-Intl object");
var internals = UnsafeGetReservedSlot(obj, INTL_INTERNALS_OBJECT_SLOT);
assert(IsObject(internals), "internals not an object");
assert(hasOwn("type", internals), "missing type");
assert((internals.type === "Collator" && IsCollator(obj)) ||
(internals.type === "DateTimeFormat" && IsDateTimeFormat(obj)) ||
(internals.type === "NumberFormat" && IsNumberFormat(obj)) ||
(internals.type === "PluralRules" && IsPluralRules(obj)) ||
(internals.type === "RelativeTimeFormat" && IsRelativeTimeFormat(obj)),
"type must match the object's class");
assert(hasOwn("lazyData", internals),
"missing lazyData");
assert(hasOwn("internalProps", internals),
"missing internalProps");
return internals;
}
/**
* Get the internal properties of known-Intl object |obj|. For use only by
* C++ code that knows what it's doing!
*/
function getInternals(obj) {
var internals = getIntlObjectInternals(obj);
// If internal properties have already been computed, use them.
var internalProps = maybeInternalProperties(internals);
if (internalProps)
return internalProps;
// Otherwise it's time to fully create them.
var type = internals.type;
switch (type) {
case "Collator":
internalProps = resolveCollatorInternals(internals.lazyData);
break;
case "DateTimeFormat":
internalProps = resolveDateTimeFormatInternals(internals.lazyData);
break;
case "PluralRules":
internalProps = resolvePluralRulesInternals(internals.lazyData);
break;
case "NumberFormat":
internalProps = resolveNumberFormatInternals(internals.lazyData);
break;
default: // type === "RelativeTimeFormat"
internalProps = resolveRelativeTimeFormatInternals(internals.lazyData);
break;
}
setInternalProperties(internals, internalProps);
return internalProps;
}