Bug 1431957 - Move String-based Intl-dependent functionality out of Intl.cpp, into jsstr.cpp. It may *depend* on Intl, but it *lives* on String, so it should be defined in String code for easiest searching. And with newly-slimmed builtin/intl/*.h headers, it's no real compile overhead to define this outside of Intl code. r=anba

--HG--
extra : rebase_source : 6471c2d3028dba1f6ac9348d8b40c20f07e50cbd
This commit is contained in:
Jeff Walden 2018-01-19 21:02:38 -08:00
parent d0f152b2e0
commit 9999e83d84
4 changed files with 205 additions and 151 deletions

View File

@ -63,123 +63,6 @@ using js::intl::IcuLocale;
using js::intl::INITIAL_CHAR_BUFFER_SIZE;
using js::intl::StringsAreEqual;
/******************** String ********************/
static const char*
CaseMappingLocale(JSContext* cx, JSString* str)
{
JSLinearString* locale = str->ensureLinear(cx);
if (!locale)
return nullptr;
MOZ_ASSERT(locale->length() >= 2, "locale is a valid language tag");
// Lithuanian, Turkish, and Azeri have language dependent case mappings.
static const char languagesWithSpecialCasing[][3] = { "lt", "tr", "az" };
// All strings in |languagesWithSpecialCasing| are of length two, so we
// only need to compare the first two characters to find a matching locale.
// ES2017 Intl, §9.2.2 BestAvailableLocale
if (locale->length() == 2 || locale->latin1OrTwoByteChar(2) == '-') {
for (const auto& language : languagesWithSpecialCasing) {
if (locale->latin1OrTwoByteChar(0) == language[0] &&
locale->latin1OrTwoByteChar(1) == language[1])
{
return language;
}
}
}
return ""; // ICU root locale
}
bool
js::intl_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(args[0].isString());
MOZ_ASSERT(args[1].isString());
RootedString string(cx, args[0].toString());
const char* locale = CaseMappingLocale(cx, args[1].toString());
if (!locale)
return false;
// Call String.prototype.toLowerCase() for language independent casing.
if (StringsAreEqual(locale, "")) {
JSString* str = js::StringToLowerCase(cx, string);
if (!str)
return false;
args.rval().setString(str);
return true;
}
AutoStableStringChars inputChars(cx);
if (!inputChars.initTwoByte(cx, string))
return false;
mozilla::Range<const char16_t> input = inputChars.twoByteRange();
// Maximum case mapping length is three characters.
static_assert(JSString::MAX_LENGTH < INT32_MAX / 3,
"Case conversion doesn't overflow int32_t indices");
JSString* str = CallICU(cx, [&input, locale](UChar* chars, int32_t size, UErrorCode* status) {
return u_strToLower(chars, size, input.begin().get(), input.length(), locale, status);
});
if (!str)
return false;
args.rval().setString(str);
return true;
}
bool
js::intl_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(args[0].isString());
MOZ_ASSERT(args[1].isString());
RootedString string(cx, args[0].toString());
const char* locale = CaseMappingLocale(cx, args[1].toString());
if (!locale)
return false;
// Call String.prototype.toUpperCase() for language independent casing.
if (StringsAreEqual(locale, "")) {
JSString* str = js::StringToUpperCase(cx, string);
if (!str)
return false;
args.rval().setString(str);
return true;
}
AutoStableStringChars inputChars(cx);
if (!inputChars.initTwoByte(cx, string))
return false;
mozilla::Range<const char16_t> input = inputChars.twoByteRange();
// Maximum case mapping length is three characters.
static_assert(JSString::MAX_LENGTH < INT32_MAX / 3,
"Case conversion doesn't overflow int32_t indices");
JSString* str = CallICU(cx, [&input, locale](UChar* chars, int32_t size, UErrorCode* status) {
return u_strToUpper(chars, size, input.begin().get(), input.length(), locale, status);
});
if (!str)
return false;
args.rval().setString(str);
return true;
}
/******************** Intl ********************/
bool

View File

@ -125,28 +125,6 @@ intl_GetLocaleInfo(JSContext* cx, unsigned argc, Value* vp);
extern MOZ_MUST_USE bool
intl_ComputeDisplayNames(JSContext* cx, unsigned argc, Value* vp);
/******************** String ********************/
/**
* Returns the input string converted to lower case based on the language
* specific case mappings for the input locale.
*
* Usage: lowerCase = intl_toLocaleLowerCase(string, locale)
*/
extern MOZ_MUST_USE bool
intl_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns the input string converted to upper case based on the language
* specific case mappings for the input locale.
*
* Usage: upperCase = intl_toLocaleUpperCase(string, locale)
*/
extern MOZ_MUST_USE bool
intl_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp);
} // namespace js
#endif /* builtin_Intl_h */

View File

@ -31,6 +31,8 @@
#include "jstypes.h"
#include "jsutil.h"
#include "builtin/intl/CommonFunctions.h"
#include "builtin/intl/ICUStubs.h"
#include "builtin/RegExp.h"
#include "jit/InlinableNatives.h"
#include "js/Conversions.h"
@ -1011,7 +1013,85 @@ js::str_toLowerCase(JSContext* cx, unsigned argc, Value* vp)
return true;
}
#if !EXPOSE_INTL_API
static const char*
CaseMappingLocale(JSContext* cx, JSString* str)
{
JSLinearString* locale = str->ensureLinear(cx);
if (!locale)
return nullptr;
MOZ_ASSERT(locale->length() >= 2, "locale is a valid language tag");
// Lithuanian, Turkish, and Azeri have language dependent case mappings.
static const char languagesWithSpecialCasing[][3] = { "lt", "tr", "az" };
// All strings in |languagesWithSpecialCasing| are of length two, so we
// only need to compare the first two characters to find a matching locale.
// ES2017 Intl, §9.2.2 BestAvailableLocale
if (locale->length() == 2 || locale->latin1OrTwoByteChar(2) == '-') {
for (const auto& language : languagesWithSpecialCasing) {
if (locale->latin1OrTwoByteChar(0) == language[0] &&
locale->latin1OrTwoByteChar(1) == language[1])
{
return language;
}
}
}
return ""; // ICU root locale
}
bool
js::intl_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(args[0].isString());
MOZ_ASSERT(args[1].isString());
RootedString string(cx, args[0].toString());
const char* locale = CaseMappingLocale(cx, args[1].toString());
if (!locale)
return false;
// Call String.prototype.toLowerCase() for language independent casing.
if (intl::StringsAreEqual(locale, "")) {
JSString* str = StringToLowerCase(cx, string);
if (!str)
return false;
args.rval().setString(str);
return true;
}
AutoStableStringChars inputChars(cx);
if (!inputChars.initTwoByte(cx, string))
return false;
mozilla::Range<const char16_t> input = inputChars.twoByteRange();
// Maximum case mapping length is three characters.
static_assert(JSString::MAX_LENGTH < INT32_MAX / 3,
"Case conversion doesn't overflow int32_t indices");
JSString* str =
intl::CallICU(cx, [&input, locale](UChar* chars, int32_t size, UErrorCode* status) {
return u_strToLower(chars, size, input.begin().get(), input.length(), locale, status);
});
if (!str)
return false;
args.rval().setString(str);
return true;
}
#if EXPOSE_INTL_API
// String.prototype.toLocaleLowerCase is self-hosted when Intl is exposed,
// with core functionality performed by the intrinsic above.
#else
bool
js::str_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp)
{
@ -1045,7 +1125,8 @@ js::str_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp)
args.rval().setString(result);
return true;
}
#endif /* !EXPOSE_INTL_API */
#endif // EXPOSE_INTL_API
static inline bool
CanUpperCaseSpecialCasing(Latin1Char charCode)
@ -1348,7 +1429,57 @@ js::str_toUpperCase(JSContext* cx, unsigned argc, Value* vp)
return true;
}
#if !EXPOSE_INTL_API
bool
js::intl_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(args[0].isString());
MOZ_ASSERT(args[1].isString());
RootedString string(cx, args[0].toString());
const char* locale = CaseMappingLocale(cx, args[1].toString());
if (!locale)
return false;
// Call String.prototype.toUpperCase() for language independent casing.
if (intl::StringsAreEqual(locale, "")) {
JSString* str = js::StringToUpperCase(cx, string);
if (!str)
return false;
args.rval().setString(str);
return true;
}
AutoStableStringChars inputChars(cx);
if (!inputChars.initTwoByte(cx, string))
return false;
mozilla::Range<const char16_t> input = inputChars.twoByteRange();
// Maximum case mapping length is three characters.
static_assert(JSString::MAX_LENGTH < INT32_MAX / 3,
"Case conversion doesn't overflow int32_t indices");
JSString* str =
intl::CallICU(cx, [&input, locale](UChar* chars, int32_t size, UErrorCode* status) {
return u_strToUpper(chars, size, input.begin().get(), input.length(), locale, status);
});
if (!str)
return false;
args.rval().setString(str);
return true;
}
#if EXPOSE_INTL_API
// String.prototype.toLocaleLowerCase is self-hosted when Intl is exposed,
// with core functionality performed by the intrinsic above.
#else
bool
js::str_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp)
{
@ -1382,9 +1513,15 @@ js::str_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp)
args.rval().setString(result);
return true;
}
#endif /* !EXPOSE_INTL_API */
#if !EXPOSE_INTL_API
#endif // EXPOSE_INTL_API
#if EXPOSE_INTL_API
// String.prototype.localeCompare is self-hosted when Intl is exposed.
#else
bool
js::str_localeCompare(JSContext* cx, unsigned argc, Value* vp)
{
@ -1413,9 +1550,11 @@ js::str_localeCompare(JSContext* cx, unsigned argc, Value* vp)
args.rval().setInt32(result);
return true;
}
#endif
#endif // EXPOSE_INTL_API
#if EXPOSE_INTL_API
// ES2017 draft rev 45e890512fd77add72cc0ee742785f9f6f6482de
// 21.1.3.12 String.prototype.normalize ( [ form ] )
bool
@ -1549,7 +1688,8 @@ js::str_normalize(JSContext* cx, unsigned argc, Value* vp)
args.rval().setString(ns);
return true;
}
#endif
#endif // EXPOSE_INTL_API
bool
js::str_charAt(JSContext* cx, unsigned argc, Value* vp)

View File

@ -344,19 +344,72 @@ str_trimLeft(JSContext* cx, unsigned argc, Value* vp);
extern bool
str_trimRight(JSContext* cx, unsigned argc, Value* vp);
#if !EXPOSE_INTL_API
/**
* Returns the input string converted to lower case based on the language
* specific case mappings for the input locale.
*
* This function only works #if EXPOSE_INTL_API; if not, it will *crash*.
* Govern yourself accordingly.
*
* Usage: lowerCase = intl_toLocaleLowerCase(string, locale)
*/
extern MOZ_MUST_USE bool
intl_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp);
/**
* Returns the input string converted to upper case based on the language
* specific case mappings for the input locale.
*
* This function only works #if EXPOSE_INTL_API; if not, it will *crash*.
* Govern yourself accordingly.
*
* Usage: upperCase = intl_toLocaleUpperCase(string, locale)
*/
extern MOZ_MUST_USE bool
intl_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp);
#if EXPOSE_INTL_API
// When the Intl API is exposed, String.prototype.to{Lower,Upper}Case is
// self-hosted. The core functionality is provided by the intrinsics above.
#else
// When the Intl API is not exposed, String.prototype.to{Lower,Upper}Case are
// implemented in C++.
extern bool
str_toLocaleLowerCase(JSContext* cx, unsigned argc, Value* vp);
extern bool
str_toLocaleUpperCase(JSContext* cx, unsigned argc, Value* vp);
extern bool
str_localeCompare(JSContext* cx, unsigned argc, Value* vp);
#else
#endif // EXPOSE_INTL_API
#if EXPOSE_INTL_API
// String.prototype.normalize is only implementable if ICU's normalization
// functionality is available.
extern bool
str_normalize(JSContext* cx, unsigned argc, Value* vp);
#endif
#endif // EXPOSE_INTL_API
#if EXPOSE_INTL_API
// String.prototype.localeCompare is self-hosted when Intl functionality is
// exposed, and the only intrinsics it requires are provided in the
// implementation of Intl.Collator.
#else
// String.prototype.localeCompare is implemented in C++ (delegating to
// JSLocaleCallbacks) when Intl functionality is not exposed.
extern bool
str_localeCompare(JSContext* cx, unsigned argc, Value* vp);
#endif // EXPOSE_INTL_API
extern bool
str_concat(JSContext* cx, unsigned argc, Value* vp);