#ifndef V8_INTL_SUPPORT
#error Internationalization is expected to be enabled.
#endif
#include "src/objects/js-number-format.h"
#include <set>
#include <string>
#include "src/execution/isolate.h"
#include "src/numbers/conversions.h"
#include "src/objects/intl-objects.h"
#include "src/objects/js-number-format-inl.h"
#include "src/objects/managed-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/option-utils.h"
#include "src/strings/char-predicates-inl.h"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wshadow"
#include "unicode/currunit.h"
#include "unicode/locid.h"
#include "unicode/numberformatter.h"
#include "unicode/numberrangeformatter.h"
#include "unicode/numsys.h"
#include "unicode/ucurr.h"
#include "unicode/uloc.h"
#include "unicode/unumberformatter.h"
#include "unicode/uvernum.h"
#pragma GCC diagnostic pop
namespace v8 {
namespace internal {
namespace {
#define AVOID_AMBIGUOUS_OP_WARNING(x) *static_cast<icu::UObject*>(&x)
enum class Style { DECIMAL, PERCENT, CURRENCY, UNIT };
enum class CurrencyDisplay {
CODE,
SYMBOL,
NAME,
NARROW_SYMBOL,
};
enum class CurrencySign {
STANDARD,
ACCOUNTING,
};
enum class UnitDisplay {
SHORT,
NARROW,
LONG,
};
enum class SignDisplay {
AUTO,
ALWAYS,
NEVER,
EXCEPT_ZERO,
NEGATIVE,
};
enum class UseGrouping {
OFF,
MIN2,
AUTO,
ALWAYS,
};
UNumberUnitWidth ToUNumberUnitWidth(CurrencyDisplay currency_display) {
switch (currency_display) {
case CurrencyDisplay::SYMBOL:
return UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT;
case CurrencyDisplay::CODE:
return UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE;
case CurrencyDisplay::NAME:
return UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME;
case CurrencyDisplay::NARROW_SYMBOL:
return UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW;
}
}
UNumberUnitWidth ToUNumberUnitWidth(UnitDisplay unit_display) {
switch (unit_display) {
case UnitDisplay::SHORT:
return UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT;
case UnitDisplay::LONG:
return UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME;
case UnitDisplay::NARROW:
return UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW;
}
}
UNumberSignDisplay ToUNumberSignDisplay(SignDisplay sign_display,
CurrencySign currency_sign) {
switch (sign_display) {
case SignDisplay::AUTO:
if (currency_sign == CurrencySign::ACCOUNTING) {
return UNumberSignDisplay::UNUM_SIGN_ACCOUNTING;
}
DCHECK(currency_sign == CurrencySign::STANDARD);
return UNumberSignDisplay::UNUM_SIGN_AUTO;
case SignDisplay::NEVER:
return UNumberSignDisplay::UNUM_SIGN_NEVER;
case SignDisplay::ALWAYS:
if (currency_sign == CurrencySign::ACCOUNTING) {
return UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_ALWAYS;
}
DCHECK(currency_sign == CurrencySign::STANDARD);
return UNumberSignDisplay::UNUM_SIGN_ALWAYS;
case SignDisplay::EXCEPT_ZERO:
if (currency_sign == CurrencySign::ACCOUNTING) {
return UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO;
}
DCHECK(currency_sign == CurrencySign::STANDARD);
return UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO;
case SignDisplay::NEGATIVE:
if (currency_sign == CurrencySign::ACCOUNTING) {
return UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_NEGATIVE;
}
DCHECK(currency_sign == CurrencySign::STANDARD);
return UNumberSignDisplay::UNUM_SIGN_NEGATIVE;
}
}
}
icu::number::Notation Intl::ToICUNotation(
Intl::Notation notation, Intl::CompactDisplay compact_display) {
switch (notation) {
case Intl::Notation::STANDARD:
return icu::number::Notation::simple();
case Intl::Notation::SCIENTIFIC:
return icu::number::Notation::scientific();
case Intl::Notation::ENGINEERING:
return icu::number::Notation::engineering();
case Intl::Notation::COMPACT:
if (compact_display == Intl::CompactDisplay::SHORT) {
return icu::number::Notation::compactShort();
}
DCHECK(compact_display == Intl::CompactDisplay::LONG);
return icu::number::Notation::compactLong();
}
}
namespace {
UNumberFormatRoundingMode ToUNumberFormatRoundingMode(
Intl::RoundingMode rounding_mode) {
switch (rounding_mode) {
case Intl::RoundingMode::kCeil:
return UNumberFormatRoundingMode::UNUM_ROUND_CEILING;
case Intl::RoundingMode::kFloor:
return UNumberFormatRoundingMode::UNUM_ROUND_FLOOR;
case Intl::RoundingMode::kExpand:
return UNumberFormatRoundingMode::UNUM_ROUND_UP;
case Intl::RoundingMode::kTrunc:
return UNumberFormatRoundingMode::UNUM_ROUND_DOWN;
case Intl::RoundingMode::kHalfCeil:
return UNumberFormatRoundingMode::UNUM_ROUND_HALF_CEILING;
case Intl::RoundingMode::kHalfFloor:
return UNumberFormatRoundingMode::UNUM_ROUND_HALF_FLOOR;
case Intl::RoundingMode::kHalfExpand:
return UNumberFormatRoundingMode::UNUM_ROUND_HALFUP;
case Intl::RoundingMode::kHalfTrunc:
return UNumberFormatRoundingMode::UNUM_ROUND_HALFDOWN;
case Intl::RoundingMode::kHalfEven:
return UNumberFormatRoundingMode::UNUM_ROUND_HALFEVEN;
}
}
UNumberGroupingStrategy ToUNumberGroupingStrategy(UseGrouping use_grouping) {
switch (use_grouping) {
case UseGrouping::OFF:
return UNumberGroupingStrategy::UNUM_GROUPING_OFF;
case UseGrouping::MIN2:
return UNumberGroupingStrategy::UNUM_GROUPING_MIN2;
case UseGrouping::AUTO:
return UNumberGroupingStrategy::UNUM_GROUPING_AUTO;
case UseGrouping::ALWAYS:
return UNumberGroupingStrategy::UNUM_GROUPING_ON_ALIGNED;
}
}
std::map<const std::string, icu::MeasureUnit, std::less<>> CreateUnitMap() {
UErrorCode status = U_ZERO_ERROR;
int32_t total = icu::MeasureUnit::getAvailable(nullptr, 0, status);
DCHECK(U_FAILURE(status));
status = U_ZERO_ERROR;
std::vector<icu::MeasureUnit> units(total);
total = icu::MeasureUnit::getAvailable(units.data(), total, status);
DCHECK(U_SUCCESS(status));
std::map<const std::string, icu::MeasureUnit, std::less<>> map;
std::set<std::string> sanctioned(Intl::SanctionedSimpleUnits());
for (auto it = units.begin(); it != units.end(); ++it) {
if (sanctioned.count(it->getSubtype()) > 0 &&
strcmp("none", it->getType()) != 0) {
map[it->getSubtype()] = *it;
}
}
return map;
}
class UnitFactory {
public:
UnitFactory() : map_(CreateUnitMap()) {}
virtual ~UnitFactory() = default;
icu::MeasureUnit create(const std::string_view unitIdentifier) {
auto found = map_.find(unitIdentifier);
if (found != map_.end()) {
return found->second;
}
return icu::MeasureUnit();
}
private:
std::map<const std::string, icu::MeasureUnit, std::less<>> map_;
};
icu::MeasureUnit IsSanctionedUnitIdentifier(const std::string_view unit) {
static base::LazyInstance<UnitFactory>::type factory =
LAZY_INSTANCE_INITIALIZER;
return factory.Pointer()->create(unit);
}
Maybe<std::pair<icu::MeasureUnit, icu::MeasureUnit>> IsWellFormedUnitIdentifier(
Isolate* isolate, const std::string_view unit) {
icu::MeasureUnit result = IsSanctionedUnitIdentifier(unit);
icu::MeasureUnit none = icu::MeasureUnit();
if (result != AVOID_AMBIGUOUS_OP_WARNING(none)) {
std::pair<icu::MeasureUnit, icu::MeasureUnit> pair(result, none);
return Just(pair);
}
size_t first_per = unit.find("-per-");
if (first_per == std::string::npos ||
unit.find("-per-", first_per + 5) != std::string::npos) {
return Nothing<std::pair<icu::MeasureUnit, icu::MeasureUnit>>();
}
std::string_view numerator = unit.substr(0, first_per);
result = IsSanctionedUnitIdentifier(numerator);
if (result == AVOID_AMBIGUOUS_OP_WARNING(none)) {
return Nothing<std::pair<icu::MeasureUnit, icu::MeasureUnit>>();
}
std::string_view denominator = unit.substr(first_per + 5);
icu::MeasureUnit den_result = IsSanctionedUnitIdentifier(denominator);
if (den_result == AVOID_AMBIGUOUS_OP_WARNING(none)) {
return Nothing<std::pair<icu::MeasureUnit, icu::MeasureUnit>>();
}
std::pair<icu::MeasureUnit, icu::MeasureUnit> pair(result, den_result);
return Just(pair);
}
int CurrencyDigits(const icu::UnicodeString& currency) {
UErrorCode status = U_ZERO_ERROR;
uint32_t fraction_digits = ucurr_getDefaultFractionDigits(
reinterpret_cast<const UChar*>(currency.getBuffer()), &status);
return U_SUCCESS(status) ? fraction_digits : 2;
}
bool IsAToZ(char ch) {
return base::IsInRange(AsciiAlphaToLower(ch), 'a', 'z');
}
bool IsWellFormedCurrencyCode(const std::string& currency) {
if (currency.length() != 3) return false;
return (IsAToZ(currency[0]) && IsAToZ(currency[1]) && IsAToZ(currency[2]));
}
DirectHandle<String> StyleAsString(Isolate* isolate, Style style) {
switch (style) {
case Style::PERCENT:
return isolate->factory()->percent_string();
case Style::CURRENCY:
return isolate->factory()->currency_string();
case Style::UNIT:
return isolate->factory()->unit_string();
case Style::DECIMAL:
return isolate->factory()->decimal_string();
}
UNREACHABLE();
}
DirectHandle<String> CurrencyDisplayString(Isolate* isolate,
const icu::UnicodeString& skeleton) {
if (skeleton.indexOf("unit-width-iso-code") >= 0) {
return isolate->factory()->code_string();
}
if (skeleton.indexOf("unit-width-full-name") >= 0) {
return isolate->factory()->name_string();
}
if (skeleton.indexOf("unit-width-narrow") >= 0) {
return isolate->factory()->narrowSymbol_string();
}
return isolate->factory()->symbol_string();
}
DirectHandle<Object> UseGroupingFromSkeleton(
Isolate* isolate, const icu::UnicodeString& skeleton) {
Factory* factory = isolate->factory();
static const char* group = "group-";
int32_t start = skeleton.indexOf(group);
if (start >= 0) {
DCHECK_EQ(6, strlen(group));
icu::UnicodeString check = skeleton.tempSubString(start + 6);
if (check.startsWith("off")) {
return factory->false_value();
}
if (check.startsWith("min2")) {
return isolate->factory()->min2_string();
}
if (check.startsWith("on-aligned")) {
return isolate->factory()->always_string();
}
}
return isolate->factory()->auto_string();
}
const icu::UnicodeString CurrencyFromSkeleton(
const icu::UnicodeString& skeleton) {
const char currency[] = "currency/";
int32_t index = skeleton.indexOf(currency);
if (index < 0) return "";
index += static_cast<int32_t>(std::strlen(currency));
return skeleton.tempSubString(index, 3);
}
}
const icu::UnicodeString JSNumberFormat::NumberingSystemFromSkeleton(
const icu::UnicodeString& skeleton) {
const char numbering_system[] = "numbering-system/";
int32_t index = skeleton.indexOf(numbering_system);
if (index < 0) return "latn";
index += static_cast<int32_t>(std::strlen(numbering_system));
const icu::UnicodeString res = skeleton.tempSubString(index);
index = res.indexOf(" ");
if (index < 0) return res;
return res.tempSubString(0, index);
}
namespace {
DirectHandle<String> CurrencySignString(Isolate* isolate,
const icu::UnicodeString& skeleton) {
if (skeleton.indexOf("sign-accounting") >= 0) {
return isolate->factory()->accounting_string();
}
return isolate->factory()->standard_string();
}
DirectHandle<String> UnitDisplayString(Isolate* isolate,
const icu::UnicodeString& skeleton) {
if (skeleton.indexOf("unit-width-full-name") >= 0) {
return isolate->factory()->long_string();
}
if (skeleton.indexOf("unit-width-narrow") >= 0) {
return isolate->factory()->narrow_string();
}
return isolate->factory()->short_string();
}
}
Intl::Notation Intl::NotationFromSkeleton(const icu::UnicodeString& skeleton) {
if (skeleton.indexOf("scientific") >= 0) {
return Intl::Notation::SCIENTIFIC;
}
if (skeleton.indexOf("engineering") >= 0) {
return Intl::Notation::ENGINEERING;
}
if (skeleton.indexOf("compact-") >= 0) {
return Intl::Notation::COMPACT;
}
return Intl::Notation::STANDARD;
}
DirectHandle<String> Intl::NotationAsString(Isolate* isolate,
Intl::Notation notation) {
switch (notation) {
case Intl::Notation::SCIENTIFIC:
return isolate->factory()->scientific_string();
case Intl::Notation::ENGINEERING:
return isolate->factory()->engineering_string();
case Intl::Notation::COMPACT:
return isolate->factory()->compact_string();
case Intl::Notation::STANDARD:
return isolate->factory()->standard_string();
}
UNREACHABLE();
}
DirectHandle<String> Intl::CompactDisplayString(
Isolate* isolate, const icu::UnicodeString& skeleton) {
if (skeleton.indexOf("compact-long") >= 0) {
return isolate->factory()->long_string();
}
DCHECK_GE(skeleton.indexOf("compact-short"), 0);
return isolate->factory()->short_string();
}
namespace {
DirectHandle<String> SignDisplayString(Isolate* isolate,
const icu::UnicodeString& skeleton) {
if (skeleton.indexOf("sign-never") >= 0) {
return isolate->factory()->never_string();
}
if (skeleton.indexOf("sign-always") >= 0 ||
skeleton.indexOf("sign-accounting-always") >= 0) {
return isolate->factory()->always_string();
}
if (skeleton.indexOf("sign-accounting-except-zero") >= 0 ||
skeleton.indexOf("sign-except-zero") >= 0) {
return isolate->factory()->exceptZero_string();
}
if (skeleton.indexOf("sign-accounting-negative") >= 0 ||
skeleton.indexOf("sign-negative") >= 0) {
return isolate->factory()->negative_string();
}
return isolate->factory()->auto_string();
}
}
DirectHandle<String> JSNumberFormat::RoundingModeString(
Isolate* isolate, const icu::UnicodeString& skeleton) {
static const char* rounding_mode = "rounding-mode-";
int32_t start = skeleton.indexOf(rounding_mode);
if (start >= 0) {
DCHECK_EQ(14, strlen(rounding_mode));
icu::UnicodeString check = skeleton.tempSubString(start + 14);
if (check.startsWith("ceiling")) {
return isolate->factory()->ceil_string();
}
if (check.startsWith("down")) {
return isolate->factory()->trunc_string();
}
if (check.startsWith("floor")) {
return isolate->factory()->floor_string();
}
if (check.startsWith("half-ceiling")) {
return isolate->factory()->halfCeil_string();
}
if (check.startsWith("half-down")) {
return isolate->factory()->halfTrunc_string();
}
if (check.startsWith("half-floor")) {
return isolate->factory()->halfFloor_string();
}
if (check.startsWith("half-up")) {
return isolate->factory()->halfExpand_string();
}
if (check.startsWith("up")) {
return isolate->factory()->expand_string();
}
}
return isolate->factory()->halfEven_string();
}
DirectHandle<Object> JSNumberFormat::RoundingIncrement(
Isolate* isolate, const icu::UnicodeString& skeleton) {
int32_t cur = skeleton.indexOf(u"precision-increment/");
if (cur < 0) return isolate->factory()->NewNumberFromInt(1);
cur += 20;
int32_t increment = 0;
while (cur < skeleton.length()) {
char16_t c = skeleton[cur++];
if (c == u'.') continue;
if (!IsDecimalDigit(c)) break;
increment = increment * 10 + (c - '0');
}
return isolate->factory()->NewNumberFromInt(increment);
}
DirectHandle<String> JSNumberFormat::RoundingPriorityString(
Isolate* isolate, const icu::UnicodeString& skeleton) {
int32_t found;
if ((found = skeleton.indexOf("#r")) >= 0 ||
(found = skeleton.indexOf("@r")) >= 0) {
if (found + 2 == skeleton.length() || skeleton[found + 2] == ' ') {
return isolate->factory()->morePrecision_string();
}
}
if ((found = skeleton.indexOf("#s")) >= 0 ||
(found = skeleton.indexOf("@s")) >= 0) {
if (found + 2 == skeleton.length() || skeleton[found + 2] == ' ') {
return isolate->factory()->lessPrecision_string();
}
}
return isolate->factory()->auto_string();
}
DirectHandle<String> JSNumberFormat::TrailingZeroDisplayString(
Isolate* isolate, const icu::UnicodeString& skeleton) {
int32_t found;
if ((found = skeleton.indexOf("/w")) >= 0) {
if (found + 2 == skeleton.length() || skeleton[found + 2] == ' ') {
return isolate->factory()->stripIfInteger_string();
}
}
return isolate->factory()->auto_string();
}
int32_t JSNumberFormat::MinimumIntegerDigitsFromSkeleton(
const icu::UnicodeString& skeleton) {
icu::UnicodeString search("integer-width/*");
int32_t index = skeleton.indexOf(search);
if (index < 0) return 1;
index += search.length();
int32_t matched = 0;
while (index < skeleton.length() && skeleton[index] == '0') {
matched++;
index++;
}
DCHECK_GT(matched, 0);
return matched;
}
bool JSNumberFormat::FractionDigitsFromSkeleton(
const icu::UnicodeString& skeleton, int32_t* minimum, int32_t* maximum) {
int32_t index = skeleton.indexOf(".");
if (index < 0) {
if (skeleton.indexOf("precision-integer") >= 0 ||
skeleton.indexOf("precision-increment/") >= 0) {
*minimum = *maximum = 0;
return true;
}
return false;
}
*minimum = 0;
index++;
while (index < skeleton.length() && IsDecimalDigit(skeleton[index])) {
(*minimum)++;
index++;
}
*maximum = *minimum;
while (index < skeleton.length() && skeleton[index] == '#') {
(*maximum)++;
index++;
}
return true;
}
bool JSNumberFormat::SignificantDigitsFromSkeleton(
const icu::UnicodeString& skeleton, int32_t* minimum, int32_t* maximum) {
int32_t index = skeleton.indexOf("@");
if (index < 0) return false;
*minimum = 1;
index++;
while (index < skeleton.length() && skeleton[index] == '@') {
(*minimum)++;
index++;
}
*maximum = *minimum;
while (index < skeleton.length() && skeleton[index] == '#') {
(*maximum)++;
index++;
}
return true;
}
namespace {
std::string UnitFromSkeleton(const icu::UnicodeString& skeleton) {
std::string str;
str = skeleton.toUTF8String<std::string>(str);
std::string search("unit/");
size_t begin = str.find(search);
if (begin == str.npos) {
if (str.find("percent") != str.npos) {
return "percent";
}
return "";
}
begin += search.size();
if (begin == str.npos) {
return "";
}
size_t end = str.find(' ', begin);
if (end == str.npos) {
end = str.size();
}
return str.substr(begin, end - begin);
}
Style StyleFromSkeleton(const icu::UnicodeString& skeleton) {
if (skeleton.indexOf("currency/") >= 0) {
return Style::CURRENCY;
}
if (skeleton.indexOf("percent") >= 0) {
if (skeleton.indexOf("scale/100") >= 0) {
return Style::PERCENT;
} else {
return Style::UNIT;
}
}
if (skeleton.indexOf("unit/") >= 0) {
return Style::UNIT;
}
return Style::DECIMAL;
}
}
icu::number::UnlocalizedNumberFormatter
JSNumberFormat::SetDigitOptionsToFormatter(
const icu::number::UnlocalizedNumberFormatter& settings,
const Intl::NumberFormatDigitOptions& digit_options) {
icu::number::UnlocalizedNumberFormatter result = settings.roundingMode(
ToUNumberFormatRoundingMode(digit_options.rounding_mode));
if (digit_options.minimum_integer_digits > 1) {
result = result.integerWidth(icu::number::IntegerWidth::zeroFillTo(
digit_options.minimum_integer_digits));
}
icu::number::Precision precision = icu::number::Precision::unlimited();
bool relaxed = false;
switch (digit_options.rounding_type) {
case Intl::RoundingType::kSignificantDigits:
precision = icu::number::Precision::minMaxSignificantDigits(
digit_options.minimum_significant_digits,
digit_options.maximum_significant_digits);
break;
case Intl::RoundingType::kFractionDigits:
precision = icu::number::Precision::minMaxFraction(
digit_options.minimum_fraction_digits,
digit_options.maximum_fraction_digits);
break;
case Intl::RoundingType::kMorePrecision:
relaxed = true;
[[fallthrough]];
case Intl::RoundingType::kLessPrecision:
precision =
icu::number::Precision::minMaxFraction(
digit_options.minimum_fraction_digits,
digit_options.maximum_fraction_digits)
.withSignificantDigits(digit_options.minimum_significant_digits,
digit_options.maximum_significant_digits,
relaxed ? UNUM_ROUNDING_PRIORITY_RELAXED
: UNUM_ROUNDING_PRIORITY_STRICT);
break;
}
if (digit_options.rounding_increment != 1) {
precision = ::icu::number::Precision::incrementExact(
digit_options.rounding_increment,
-digit_options.maximum_fraction_digits)
.withMinFraction(digit_options.minimum_fraction_digits);
}
if (digit_options.trailing_zero_display ==
Intl::TrailingZeroDisplay::kStripIfInteger) {
precision = precision.trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE);
}
return result.precision(precision);
}
DirectHandle<JSObject> JSNumberFormat::ResolvedOptions(
Isolate* isolate, DirectHandle<JSNumberFormat> number_format) {
Factory* factory = isolate->factory();
UErrorCode status = U_ZERO_ERROR;
icu::number::LocalizedNumberFormatter* fmt =
number_format->icu_number_formatter()->raw();
icu::UnicodeString skeleton = fmt->toSkeleton(status);
DCHECK(U_SUCCESS(status));
DirectHandle<JSObject> options =
factory->NewJSObject(isolate->object_function());
DirectHandle<String> locale =
DirectHandle<String>(number_format->locale(), isolate);
const icu::UnicodeString numberingSystem_ustr =
JSNumberFormat::NumberingSystemFromSkeleton(skeleton);
CHECK(JSReceiver::CreateDataProperty(isolate, options,
factory->locale_string(), locale,
Just(kDontThrow))
.FromJust());
DirectHandle<String> numberingSystem_string;
CHECK(Intl::ToString(isolate, numberingSystem_ustr)
.ToHandle(&numberingSystem_string));
CHECK(JSReceiver::CreateDataProperty(isolate, options,
factory->numberingSystem_string(),
numberingSystem_string, Just(kDontThrow))
.FromJust());
Style style = StyleFromSkeleton(skeleton);
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->style_string(),
StyleAsString(isolate, style), Just(kDontThrow))
.FromJust());
const icu::UnicodeString currency_ustr = CurrencyFromSkeleton(skeleton);
if (!currency_ustr.isEmpty()) {
DirectHandle<String> currency_string;
CHECK(Intl::ToString(isolate, currency_ustr).ToHandle(¤cy_string));
CHECK(JSReceiver::CreateDataProperty(isolate, options,
factory->currency_string(),
currency_string, Just(kDontThrow))
.FromJust());
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->currencyDisplay_string(),
CurrencyDisplayString(isolate, skeleton), Just(kDontThrow))
.FromJust());
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->currencySign_string(),
CurrencySignString(isolate, skeleton), Just(kDontThrow))
.FromJust());
}
if (style == Style::UNIT) {
std::string unit = UnitFromSkeleton(skeleton);
if (!unit.empty()) {
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->unit_string(),
isolate->factory()->NewStringFromAsciiChecked(unit.c_str()),
Just(kDontThrow))
.FromJust());
}
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->unitDisplay_string(),
UnitDisplayString(isolate, skeleton), Just(kDontThrow))
.FromJust());
}
CHECK(
JSReceiver::CreateDataProperty(
isolate, options, factory->minimumIntegerDigits_string(),
factory->NewNumberFromInt(MinimumIntegerDigitsFromSkeleton(skeleton)),
Just(kDontThrow))
.FromJust());
int32_t mnsd = 0, mxsd = 0, mnfd = 0, mxfd = 0;
if (FractionDigitsFromSkeleton(skeleton, &mnfd, &mxfd)) {
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->minimumFractionDigits_string(),
factory->NewNumberFromInt(mnfd), Just(kDontThrow))
.FromJust());
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->maximumFractionDigits_string(),
factory->NewNumberFromInt(mxfd), Just(kDontThrow))
.FromJust());
}
if (SignificantDigitsFromSkeleton(skeleton, &mnsd, &mxsd)) {
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->minimumSignificantDigits_string(),
factory->NewNumberFromInt(mnsd), Just(kDontThrow))
.FromJust());
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->maximumSignificantDigits_string(),
factory->NewNumberFromInt(mxsd), Just(kDontThrow))
.FromJust());
}
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->useGrouping_string(),
UseGroupingFromSkeleton(isolate, skeleton), Just(kDontThrow))
.FromJust());
Intl::Notation notation = Intl::NotationFromSkeleton(skeleton);
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->notation_string(),
Intl::NotationAsString(isolate, notation), Just(kDontThrow))
.FromJust());
if (notation == Intl::Notation::COMPACT) {
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->compactDisplay_string(),
Intl::CompactDisplayString(isolate, skeleton), Just(kDontThrow))
.FromJust());
}
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->signDisplay_string(),
SignDisplayString(isolate, skeleton), Just(kDontThrow))
.FromJust());
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->roundingIncrement_string(),
RoundingIncrement(isolate, skeleton), Just(kDontThrow))
.FromJust());
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->roundingMode_string(),
RoundingModeString(isolate, skeleton), Just(kDontThrow))
.FromJust());
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->roundingPriority_string(),
RoundingPriorityString(isolate, skeleton), Just(kDontThrow))
.FromJust());
CHECK(JSReceiver::CreateDataProperty(
isolate, options, factory->trailingZeroDisplay_string(),
TrailingZeroDisplayString(isolate, skeleton), Just(kDontThrow))
.FromJust());
return options;
}
MaybeDirectHandle<JSNumberFormat> JSNumberFormat::UnwrapNumberFormat(
Isolate* isolate, DirectHandle<JSReceiver> format_holder) {
DirectHandle<Context> native_context(isolate->context()->native_context(),
isolate);
DirectHandle<JSFunction> constructor(
Cast<JSFunction>(native_context->intl_number_format_function()), isolate);
DirectHandle<Object> object;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, object,
Intl::LegacyUnwrapReceiver(isolate, format_holder, constructor,
IsJSNumberFormat(*format_holder)));
if (!IsJSNumberFormat(*object)) {
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kIncompatibleMethodReceiver,
isolate->factory()->NewStringFromAsciiChecked(
"UnwrapNumberFormat")));
}
return Cast<JSNumberFormat>(object);
}
MaybeDirectHandle<JSNumberFormat> JSNumberFormat::New(
Isolate* isolate, DirectHandle<Map> map, DirectHandle<Object> locales,
DirectHandle<Object> options_obj, const char* service) {
Factory* factory = isolate->factory();
Maybe<std::vector<std::string>> maybe_requested_locales =
Intl::CanonicalizeLocaleList(isolate, locales);
MAYBE_RETURN(maybe_requested_locales, DirectHandle<JSNumberFormat>());
std::vector<std::string> requested_locales =
maybe_requested_locales.FromJust();
DirectHandle<JSReceiver> options;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, options, CoerceOptionsToObject(isolate, options_obj, service));
Maybe<Intl::MatcherOption> maybe_locale_matcher =
Intl::GetLocaleMatcher(isolate, options, service);
MAYBE_RETURN(maybe_locale_matcher, MaybeDirectHandle<JSNumberFormat>());
Intl::MatcherOption matcher = maybe_locale_matcher.FromJust();
std::string numbering_system_str;
Maybe<bool> maybe_numberingSystem =
Intl::GetNumberingSystem(isolate, options, service, numbering_system_str);
MAYBE_RETURN(maybe_numberingSystem, MaybeDirectHandle<JSNumberFormat>());
Intl::ResolvedLocale r;
if (!Intl::ResolveLocale(isolate, JSNumberFormat::GetAvailableLocales(),
requested_locales, matcher, {"nu"})
.To(&r)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError));
}
icu::Locale icu_locale = r.icu_locale;
UErrorCode status = U_ZERO_ERROR;
if (maybe_numberingSystem.FromJust()) {
auto nu_extension_it = r.extensions.find("nu");
if (nu_extension_it != r.extensions.end() &&
nu_extension_it->second != numbering_system_str &&
Intl::IsValidNumberingSystem(numbering_system_str)) {
icu_locale.setUnicodeKeywordValue("nu", nullptr, status);
DCHECK(U_SUCCESS(status));
}
}
Maybe<std::string> maybe_locale_str = Intl::ToLanguageTag(icu_locale);
MAYBE_RETURN(maybe_locale_str, MaybeDirectHandle<JSNumberFormat>());
DirectHandle<String> locale_str =
isolate->factory()->NewStringFromAsciiChecked(
maybe_locale_str.FromJust().c_str());
if (maybe_numberingSystem.FromJust() &&
Intl::IsValidNumberingSystem(numbering_system_str)) {
icu_locale.setUnicodeKeywordValue("nu", numbering_system_str, status);
DCHECK(U_SUCCESS(status));
}
std::string numbering_system = Intl::GetNumberingSystem(icu_locale);
icu::number::UnlocalizedNumberFormatter settings =
icu::number::UnlocalizedNumberFormatter().roundingMode(UNUM_ROUND_HALFUP);
if (!numbering_system.empty() && numbering_system != "latn") {
settings = settings.adoptSymbols(icu::NumberingSystem::createInstanceByName(
numbering_system.c_str(), status));
DCHECK(U_SUCCESS(status));
}
Maybe<Style> maybe_style = GetStringOption<Style>(
isolate, options, isolate->factory()->style_string(), service,
std::to_array<const std::string_view>(
{"decimal", "percent", "currency", "unit"}),
std::array{Style::DECIMAL, Style::PERCENT, Style::CURRENCY, Style::UNIT},
Style::DECIMAL);
MAYBE_RETURN(maybe_style, MaybeDirectHandle<JSNumberFormat>());
Style style = maybe_style.FromJust();
DirectHandle<String> currency_str;
Maybe<bool> found_currency =
GetStringOption(isolate, options, isolate->factory()->currency_string(),
service, ¤cy_str);
MAYBE_RETURN(found_currency, MaybeDirectHandle<JSNumberFormat>());
std::string currency;
if (found_currency.FromJust()) {
currency = currency_str->ToStdString();
if (!IsWellFormedCurrencyCode(currency)) {
THROW_NEW_ERROR(
isolate,
NewRangeError(MessageTemplate::kInvalid,
factory->NewStringFromStaticChars("currency code"),
currency_str));
}
} else {
if (style == Style::CURRENCY) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kCurrencyCode));
}
}
Maybe<CurrencyDisplay> maybe_currency_display =
GetStringOption<CurrencyDisplay>(
isolate, options, isolate->factory()->currencyDisplay_string(),
service,
std::to_array<const std::string_view>(
{"code", "symbol", "name", "narrowSymbol"}),
std::array{CurrencyDisplay::CODE, CurrencyDisplay::SYMBOL,
CurrencyDisplay::NAME, CurrencyDisplay::NARROW_SYMBOL},
CurrencyDisplay::SYMBOL);
MAYBE_RETURN(maybe_currency_display, MaybeDirectHandle<JSNumberFormat>());
CurrencyDisplay currency_display = maybe_currency_display.FromJust();
CurrencySign currency_sign = CurrencySign::STANDARD;
Maybe<CurrencySign> maybe_currency_sign = GetStringOption<CurrencySign>(
isolate, options, isolate->factory()->currencySign_string(), service,
std::to_array<const std::string_view>({"standard", "accounting"}),
std::array{CurrencySign::STANDARD, CurrencySign::ACCOUNTING},
CurrencySign::STANDARD);
MAYBE_RETURN(maybe_currency_sign, MaybeDirectHandle<JSNumberFormat>());
currency_sign = maybe_currency_sign.FromJust();
DirectHandle<String> unit_str;
Maybe<bool> found_unit = GetStringOption(
isolate, options, isolate->factory()->unit_string(), service, &unit_str);
MAYBE_RETURN(found_unit, MaybeDirectHandle<JSNumberFormat>());
std::pair<icu::MeasureUnit, icu::MeasureUnit> unit_pair;
if (found_unit.FromJust()) {
std::string unit_stdstr = unit_str->ToStdString();
Maybe<std::pair<icu::MeasureUnit, icu::MeasureUnit>> maybe_wellformed_unit =
IsWellFormedUnitIdentifier(isolate, unit_stdstr);
if (maybe_wellformed_unit.IsNothing()) {
THROW_NEW_ERROR(
isolate,
NewRangeError(MessageTemplate::kInvalidUnit,
factory->NewStringFromAsciiChecked(service), unit_str));
}
unit_pair = maybe_wellformed_unit.FromJust();
} else {
if (style == Style::UNIT) {
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kInvalidUnit,
factory->NewStringFromAsciiChecked(service),
factory->empty_string()));
}
}
Maybe<UnitDisplay> maybe_unit_display = GetStringOption<UnitDisplay>(
isolate, options, isolate->factory()->unitDisplay_string(), service,
std::to_array<const std::string_view>({"short", "narrow", "long"}),
std::array{UnitDisplay::SHORT, UnitDisplay::NARROW, UnitDisplay::LONG},
UnitDisplay::SHORT);
MAYBE_RETURN(maybe_unit_display, MaybeDirectHandle<JSNumberFormat>());
UnitDisplay unit_display = maybe_unit_display.FromJust();
icu::UnicodeString currency_ustr;
if (style == Style::CURRENCY) {
if (!found_currency.FromJust()) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kCurrencyCode));
}
std::transform(currency.begin(), currency.end(), currency.begin(), toupper);
currency_ustr = currency.c_str();
if (!currency_ustr.isEmpty()) {
DirectHandle<String> currency_string;
ASSIGN_RETURN_ON_EXCEPTION(isolate, currency_string,
Intl::ToString(isolate, currency_ustr));
settings =
settings.unit(icu::CurrencyUnit(currency_ustr.getBuffer(), status));
DCHECK(U_SUCCESS(status));
if (currency_display != CurrencyDisplay::SYMBOL) {
settings = settings.unitWidth(ToUNumberUnitWidth(currency_display));
}
DCHECK(U_SUCCESS(status));
}
}
if (style == Style::UNIT) {
isolate->CountUsage(v8::Isolate::UseCounterFeature::kNumberFormatStyleUnit);
icu::MeasureUnit none = icu::MeasureUnit();
if (unit_pair.first != AVOID_AMBIGUOUS_OP_WARNING(none)) {
settings = settings.unit(unit_pair.first);
}
if (unit_pair.second != AVOID_AMBIGUOUS_OP_WARNING(none)) {
settings = settings.perUnit(unit_pair.second);
}
if (unit_display != UnitDisplay::SHORT) {
settings = settings.unitWidth(ToUNumberUnitWidth(unit_display));
}
}
if (style == Style::PERCENT) {
settings = settings.unit(icu::MeasureUnit::getPercent())
.scale(icu::number::Scale::powerOfTen(2));
}
Intl::Notation notation = Intl::Notation::STANDARD;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, notation,
GetStringOption<Intl::Notation>(
isolate, options, isolate->factory()->notation_string(), service,
std::to_array<const std::string_view>(
{"standard", "scientific", "engineering", "compact"}),
std::array{Intl::Notation::STANDARD, Intl::Notation::SCIENTIFIC,
Intl::Notation::ENGINEERING, Intl::Notation::COMPACT},
Intl::Notation::STANDARD));
int mnfd_default, mxfd_default;
if (style == Style::CURRENCY && notation == Intl::Notation::STANDARD) {
int c_digits = CurrencyDigits(currency_ustr);
mnfd_default = c_digits;
mxfd_default = c_digits;
} else {
mnfd_default = 0;
if (style == Style::PERCENT) {
mxfd_default = 0;
} else {
mxfd_default = 3;
}
}
Maybe<Intl::NumberFormatDigitOptions> maybe_digit_options =
Intl::SetNumberFormatDigitOptions(
isolate, options, mnfd_default, mxfd_default,
notation == Intl::Notation::COMPACT, service);
MAYBE_RETURN(maybe_digit_options, DirectHandle<JSNumberFormat>());
Intl::NumberFormatDigitOptions digit_options = maybe_digit_options.FromJust();
if (digit_options.rounding_increment != 1) {
mxfd_default = mnfd_default;
}
settings = SetDigitOptionsToFormatter(settings, digit_options);
Maybe<Intl::CompactDisplay> maybe_compact_display =
GetStringOption<Intl::CompactDisplay>(
isolate, options, isolate->factory()->compactDisplay_string(),
service, std::to_array<const std::string_view>({"short", "long"}),
std::array{Intl::CompactDisplay::SHORT, Intl::CompactDisplay::LONG},
Intl::CompactDisplay::SHORT);
MAYBE_RETURN(maybe_compact_display, MaybeDirectHandle<JSNumberFormat>());
Intl::CompactDisplay compact_display = maybe_compact_display.FromJust();
if (notation != Intl::Notation::STANDARD) {
settings =
settings.notation(Intl::ToICUNotation(notation, compact_display));
}
UseGrouping default_use_grouping = UseGrouping::AUTO;
if (notation == Intl::Notation::COMPACT) {
default_use_grouping = UseGrouping::MIN2;
}
if (v8_flags.icu_default_italian_number_grouping_always) {
if (strcmp(icu_locale.getLanguage(), "it") == 0) {
default_use_grouping = UseGrouping::ALWAYS;
}
}
Maybe<UseGrouping> maybe_use_grouping = GetStringOrBooleanOption<UseGrouping>(
isolate, options, "useGrouping", service,
std::to_array<const std::string_view>({"min2", "auto", "always"}),
std::array{UseGrouping::MIN2, UseGrouping::AUTO, UseGrouping::ALWAYS},
UseGrouping::ALWAYS,
UseGrouping::OFF,
default_use_grouping);
MAYBE_RETURN(maybe_use_grouping, MaybeDirectHandle<JSNumberFormat>());
UseGrouping use_grouping = maybe_use_grouping.FromJust();
if (use_grouping != UseGrouping::AUTO) {
settings = settings.grouping(ToUNumberGroupingStrategy(use_grouping));
}
Maybe<SignDisplay> maybe_sign_display = Nothing<SignDisplay>();
maybe_sign_display = GetStringOption<SignDisplay>(
isolate, options, isolate->factory()->signDisplay_string(), service,
std::to_array<const std::string_view>(
{"auto", "never", "always", "exceptZero", "negative"}),
std::array{SignDisplay::AUTO, SignDisplay::NEVER, SignDisplay::ALWAYS,
SignDisplay::EXCEPT_ZERO, SignDisplay::NEGATIVE},
SignDisplay::AUTO);
MAYBE_RETURN(maybe_sign_display, MaybeDirectHandle<JSNumberFormat>());
SignDisplay sign_display = maybe_sign_display.FromJust();
if (sign_display != SignDisplay::AUTO ||
currency_sign != CurrencySign::STANDARD) {
settings = settings.sign(ToUNumberSignDisplay(sign_display, currency_sign));
}
icu::number::LocalizedNumberFormatter fmt = settings.locale(icu_locale);
DirectHandle<Managed<icu::number::LocalizedNumberFormatter>>
managed_number_formatter =
Managed<icu::number::LocalizedNumberFormatter>::From(
isolate, 0,
std::make_shared<icu::number::LocalizedNumberFormatter>(fmt));
DirectHandle<JSNumberFormat> number_format = Cast<JSNumberFormat>(
isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
DisallowGarbageCollection no_gc;
number_format->set_locale(*locale_str);
number_format->set_icu_number_formatter(*managed_number_formatter);
number_format->set_bound_format(*factory->undefined_value());
return number_format;
}
namespace {
template <typename StringHandle>
int32_t SignedStringLength(StringHandle string) {
uint32_t unsigned_length = string->length();
static_assert(String::kMaxLength < std::numeric_limits<int32_t>::max());
SBXCHECK_LE(unsigned_length, String::kMaxLength);
return static_cast<int32_t>(unsigned_length);
}
icu::number::FormattedNumber FormatDecimalString(
Isolate* isolate,
std::shared_ptr<icu::number::LocalizedNumberFormatter> lfmt,
Handle<String> string, UErrorCode& status) {
string = String::Flatten(isolate, string);
DisallowGarbageCollection no_gc;
const String::FlatContent& flat = string->GetFlatContent(no_gc);
int32_t length = SignedStringLength(string);
if (flat.IsOneByte()) {
const char* char_buffer =
reinterpret_cast<const char*>(flat.ToOneByteVector().begin());
return lfmt->formatDecimal({char_buffer, length}, status);
}
return lfmt->formatDecimal({string->ToCString().get(), length}, status);
}
}
bool IntlMathematicalValue::IsNaN() const { return i::IsNaN(*value_); }
MaybeHandle<String> IntlMathematicalValue::ToString(Isolate* isolate) const {
DirectHandle<String> string;
if (IsNumber(*value_)) {
return isolate->factory()->NumberToString(value_);
}
if (IsBigInt(*value_)) {
return BigInt::ToString(isolate, Cast<BigInt>(value_));
}
DCHECK(IsString(*value_));
return Cast<String>(value_);
}
namespace {
Maybe<icu::number::FormattedNumber> IcuFormatNumber(
Isolate* isolate,
std::shared_ptr<icu::number::LocalizedNumberFormatter> lfmt,
Handle<Object> numeric_obj) {
icu::number::FormattedNumber formatted;
UErrorCode status = U_ZERO_ERROR;
if (IsBigInt(*numeric_obj)) {
auto big_int = Cast<BigInt>(numeric_obj);
Handle<String> big_int_string;
ASSIGN_RETURN_ON_EXCEPTION(isolate, big_int_string,
BigInt::ToString(isolate, big_int));
big_int_string = String::Flatten(isolate, big_int_string);
DisallowGarbageCollection no_gc;
const String::FlatContent& flat = big_int_string->GetFlatContent(no_gc);
int32_t length = SignedStringLength(big_int_string);
DCHECK(flat.IsOneByte());
const char* char_buffer =
reinterpret_cast<const char*>(flat.ToOneByteVector().begin());
formatted = lfmt->formatDecimal({char_buffer, length}, status);
} else {
if (IsString(*numeric_obj)) {
DirectHandle<String> string =
String::Flatten(isolate, Cast<String>(numeric_obj));
DisallowGarbageCollection no_gc;
const String::FlatContent& flat = string->GetFlatContent(no_gc);
int32_t length = SignedStringLength(string);
if (flat.IsOneByte()) {
const char* char_buffer =
reinterpret_cast<const char*>(flat.ToOneByteVector().begin());
formatted = lfmt->formatDecimal({char_buffer, length}, status);
} else {
formatted = lfmt->formatDecimal({string->ToCString().get(), length}, status);
}
} else {
double number = IsNaN(*numeric_obj)
? std::numeric_limits<double>::quiet_NaN()
: Object::NumberValue(*numeric_obj);
formatted = lfmt->formatDouble(number, status);
}
}
if (U_FAILURE(status)) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError));
}
return Just(std::move(formatted));
}
}
Maybe<icu::number::FormattedNumber> IntlMathematicalValue::FormatNumeric(
Isolate* isolate,
std::shared_ptr<icu::number::LocalizedNumberFormatter> lfmt,
const IntlMathematicalValue& x) {
if (IsString(*x.value_)) {
Handle<String> string;
ASSIGN_RETURN_ON_EXCEPTION(isolate, string, x.ToString(isolate));
UErrorCode status = U_ZERO_ERROR;
icu::number::FormattedNumber result =
FormatDecimalString(isolate, std::move(lfmt), string, status);
if (U_FAILURE(status)) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError));
}
return Just(std::move(result));
}
CHECK(IsNumber(*x.value_) || IsBigInt(*x.value_));
return IcuFormatNumber(isolate, std::move(lfmt), x.value_);
}
Maybe<icu::number::FormattedNumberRange> IntlMathematicalValue::FormatRange(
Isolate* isolate,
const icu::number::LocalizedNumberRangeFormatter& number_range_format,
const IntlMathematicalValue& x, const IntlMathematicalValue& y) {
icu::Formattable x_formatable;
ASSIGN_RETURN_ON_EXCEPTION(isolate, x_formatable, x.ToFormattable(isolate));
icu::Formattable y_formatable;
ASSIGN_RETURN_ON_EXCEPTION(isolate, y_formatable, y.ToFormattable(isolate));
UErrorCode status = U_ZERO_ERROR;
icu::number::FormattedNumberRange result =
number_range_format.formatFormattableRange(x_formatable, y_formatable,
status);
if (U_FAILURE(status)) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError));
}
return Just(std::move(result));
}
namespace {
template <typename Char>
std::pair<int, int> FindLeadingAndTrailingWhiteSpaceOrLineTerminator(
base::Vector<const Char> src) {
size_t leading_end = 0;
while (leading_end < src.size() &&
IsWhiteSpaceOrLineTerminator(
static_cast<uint16_t>(src.at(leading_end)))) {
leading_end++;
}
size_t trailing_start = src.size();
while (trailing_start > leading_end &&
IsWhiteSpaceOrLineTerminator(
static_cast<uint16_t>(src.at(trailing_start - 1)))) {
trailing_start--;
}
return std::make_pair(leading_end, trailing_start);
}
Handle<String> TrimWhiteSpaceOrLineTerminator(Isolate* isolate,
Handle<String> string) {
string = String::Flatten(isolate, string);
std::pair<int, uint32_t> whitespace_offsets;
{
DisallowGarbageCollection no_gc;
String::FlatContent flat = string->GetFlatContent(no_gc);
if (flat.IsOneByte()) {
whitespace_offsets = FindLeadingAndTrailingWhiteSpaceOrLineTerminator(
flat.ToOneByteVector());
} else {
whitespace_offsets =
FindLeadingAndTrailingWhiteSpaceOrLineTerminator(flat.ToUC16Vector());
}
}
if (whitespace_offsets.first == 0 &&
string->length() == whitespace_offsets.second) {
return string;
}
return isolate->factory()->NewSubString(string, whitespace_offsets.first,
whitespace_offsets.second);
}
}
Maybe<IntlMathematicalValue> IntlMathematicalValue::From(Isolate* isolate,
Handle<Object> value) {
Factory* factory = isolate->factory();
Handle<Object> prim_value;
if (IsJSReceiver(*value)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, prim_value,
JSReceiver::ToPrimitive(isolate, Cast<JSReceiver>(value),
ToPrimitiveHint::kNumber));
} else {
prim_value = value;
}
IntlMathematicalValue result;
if (IsBigInt(*prim_value)) {
result.value_ = prim_value;
result.approx_ = Cast<BigInt>(prim_value)->AsInt64();
return Just(result);
}
if (IsOddball(*prim_value)) {
prim_value = Oddball::ToNumber(isolate, Cast<Oddball>(prim_value));
}
if (IsNumber(*prim_value)) {
result.value_ = prim_value;
result.approx_ = Object::NumberValue(*prim_value);
return Just(result);
}
if (!IsString(*prim_value)) {
ASSIGN_RETURN_ON_EXCEPTION(isolate, result.value_,
Object::ToNumber(isolate, prim_value));
result.approx_ = Object::NumberValue(*result.value_);
return Just(result);
}
Handle<String> string = Cast<String>(prim_value);
string = TrimWhiteSpaceOrLineTerminator(isolate, string);
if (string->length() == 0) {
result.value_ = handle(Smi::zero(), isolate);
result.approx_ = 0;
return Just(result);
}
if (2 < string->length() && string->Get(0) == '0') {
uint16_t ch = string->Get(1);
if (ch == 'b' || ch == 'B' || ch == 'o' || ch == 'O' || ch == 'x' ||
ch == 'X') {
result.approx_ =
StringToDouble(isolate, string, ALLOW_NON_DECIMAL_PREFIX, 0);
if (result.approx_ < kMaxSafeInteger) {
result.value_ = isolate->factory()->NewNumber(result.approx_);
return Just(result);
}
MaybeHandle<BigInt> maybe_bigint = StringToBigInt(isolate, string);
if (maybe_bigint.is_null()) {
isolate->clear_exception();
result.value_ = factory->nan_value();
return Just(result);
}
result.value_ = maybe_bigint.ToHandleChecked();
return Just(result);
}
}
result.approx_ = StringToDouble(isolate, string, NO_CONVERSION_FLAG, 0);
if (std::isnan(result.approx_)) {
result.value_ = factory->nan_value();
return Just(result);
}
if (!std::isfinite(result.approx_)) {
if (result.approx_ < 0) {
result.value_ = factory->minus_infinity_value();
} else {
result.value_ = factory->infinity_value();
}
return Just(result);
}
result.value_ = string;
return Just(result);
}
Maybe<icu::Formattable> IntlMathematicalValue::ToFormattable(
Isolate* isolate) const {
if (IsNumber(*value_)) {
return Just(icu::Formattable(approx_));
}
DirectHandle<String> string;
ASSIGN_RETURN_ON_EXCEPTION(isolate, string, ToString(isolate));
UErrorCode status = U_ZERO_ERROR;
{
DisallowGarbageCollection no_gc;
const String::FlatContent& flat = string->GetFlatContent(no_gc);
int32_t length = SignedStringLength(string);
if (flat.IsOneByte()) {
icu::Formattable result(
{reinterpret_cast<const char*>(flat.ToOneByteVector().begin()),
length},
status);
if (U_SUCCESS(status)) return Just(result);
} else {
auto converted = string->ToStdString();
icu::Formattable result(converted, status);
if (U_SUCCESS(status)) return Just(result);
}
}
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError));
}
namespace {
bool cmp_NumberFormatSpan(const NumberFormatSpan& a,
const NumberFormatSpan& b) {
if (a.begin_pos < b.begin_pos) return true;
if (a.begin_pos > b.begin_pos) return false;
if (a.end_pos < b.end_pos) return false;
if (a.end_pos > b.end_pos) return true;
return a.field_id < b.field_id;
}
}
std::vector<NumberFormatSpan> FlattenRegionsToParts(
std::vector<NumberFormatSpan>* regions) {
std::sort(regions->begin(), regions->end(), cmp_NumberFormatSpan);
std::vector<size_t> overlapping_region_index_stack;
overlapping_region_index_stack.push_back(0);
NumberFormatSpan top_region = regions->at(0);
size_t region_iterator = 1;
int32_t entire_size = top_region.end_pos;
std::vector<NumberFormatSpan> out_parts;
int32_t climber = 0;
while (climber < entire_size) {
int32_t next_region_begin_pos;
if (region_iterator < regions->size()) {
next_region_begin_pos = regions->at(region_iterator).begin_pos;
} else {
next_region_begin_pos = entire_size;
}
if (climber < next_region_begin_pos) {
while (top_region.end_pos < next_region_begin_pos) {
if (climber < top_region.end_pos) {
out_parts.push_back(NumberFormatSpan(top_region.field_id, climber,
top_region.end_pos));
climber = top_region.end_pos;
} else {
}
overlapping_region_index_stack.pop_back();
top_region = regions->at(overlapping_region_index_stack.back());
}
if (climber < next_region_begin_pos) {
out_parts.push_back(NumberFormatSpan(top_region.field_id, climber,
next_region_begin_pos));
climber = next_region_begin_pos;
}
}
if (region_iterator < regions->size()) {
overlapping_region_index_stack.push_back(region_iterator++);
top_region = regions->at(overlapping_region_index_stack.back());
}
}
return out_parts;
}
namespace {
Maybe<int> ConstructParts(Isolate* isolate,
const icu::FormattedValue& formatted,
DirectHandle<JSArray> result, int start_index,
bool style_is_unit, bool is_nan, bool output_source,
bool output_unit, DirectHandle<String> unit) {
UErrorCode status = U_ZERO_ERROR;
icu::UnicodeString formatted_text = formatted.toString(status);
if (U_FAILURE(status)) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError));
}
int32_t length = formatted_text.length();
int index = start_index;
if (length == 0) return Just(index);
std::vector<NumberFormatSpan> regions;
regions.push_back(NumberFormatSpan(-1, 0, formatted_text.length()));
Intl::FormatRangeSourceTracker tracker;
{
icu::ConstrainedFieldPosition cfpos;
while (formatted.nextPosition(cfpos, status)) {
int32_t category = cfpos.getCategory();
int32_t field = cfpos.getField();
int32_t start = cfpos.getStart();
int32_t limit = cfpos.getLimit();
if (category == UFIELD_CATEGORY_NUMBER_RANGE_SPAN) {
DCHECK_LE(field, 2);
tracker.Add(field, start, limit);
} else {
regions.push_back(NumberFormatSpan(field, start, limit));
}
}
}
std::vector<NumberFormatSpan> parts = FlattenRegionsToParts(®ions);
for (auto it = parts.begin(); it < parts.end(); it++) {
NumberFormatSpan part = *it;
DirectHandle<String> field_type_string =
isolate->factory()->literal_string();
if (part.field_id != -1) {
if (style_is_unit && static_cast<UNumberFormatFields>(part.field_id) ==
UNUM_PERCENT_FIELD) {
field_type_string = isolate->factory()->unit_string();
} else {
field_type_string =
Intl::NumberFieldToType(isolate, part, formatted_text, is_nan);
}
}
DirectHandle<String> substring;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, substring,
Intl::ToString(isolate, formatted_text, part.begin_pos, part.end_pos));
if (output_source) {
Intl::AddElement(
isolate, result, index, field_type_string, substring,
isolate->factory()->source_string(),
Intl::SourceString(isolate,
tracker.GetSource(part.begin_pos, part.end_pos)));
} else {
if (output_unit) {
Intl::AddElement(isolate, result, index, field_type_string, substring,
isolate->factory()->unit_string(), unit);
} else {
Intl::AddElement(isolate, result, index, field_type_string, substring);
}
}
++index;
}
JSObject::ValidateElements(isolate, *result);
return Just(index);
}
}
Maybe<int> Intl::AddNumberElements(Isolate* isolate,
const icu::FormattedValue& formatted,
DirectHandle<JSArray> result,
int start_index, DirectHandle<String> unit) {
return ConstructParts(isolate, formatted, result, start_index, true, false,
false, true, unit);
}
namespace {
template <typename T, MaybeHandle<T> (*F)(
Isolate*, const icu::FormattedValue&,
std::shared_ptr<icu::number::LocalizedNumberFormatter>, bool)>
MaybeHandle<T> PartitionNumberRangePattern(
Isolate* isolate, DirectHandle<JSNumberFormat> number_format,
Handle<Object> start, Handle<Object> end, const char* func_name) {
Factory* factory = isolate->factory();
IntlMathematicalValue x;
ASSIGN_RETURN_ON_EXCEPTION(isolate, x,
IntlMathematicalValue::From(isolate, start));
IntlMathematicalValue y;
ASSIGN_RETURN_ON_EXCEPTION(isolate, y,
IntlMathematicalValue::From(isolate, end));
if (x.IsNaN()) {
THROW_NEW_ERROR(
isolate,
NewRangeError(MessageTemplate::kInvalid,
factory->NewStringFromStaticChars("start"), start));
}
if (y.IsNaN()) {
THROW_NEW_ERROR(
isolate, NewRangeError(MessageTemplate::kInvalid,
factory->NewStringFromStaticChars("end"), end));
}
Maybe<icu::number::LocalizedNumberRangeFormatter> maybe_range_formatter =
JSNumberFormat::GetRangeFormatter(
isolate, number_format->locale(),
*number_format->icu_number_formatter()->raw());
MAYBE_RETURN(maybe_range_formatter, MaybeHandle<T>());
icu::number::LocalizedNumberRangeFormatter nrfmt =
maybe_range_formatter.FromJust();
Maybe<icu::number::FormattedNumberRange> maybe_formatted =
IntlMathematicalValue::FormatRange(isolate, nrfmt, x, y);
MAYBE_RETURN(maybe_formatted, MaybeHandle<T>());
icu::number::FormattedNumberRange formatted =
std::move(maybe_formatted).FromJust();
return F(isolate, formatted, number_format->icu_number_formatter()->get(),
false );
}
MaybeHandle<String> FormatToString(Isolate* isolate,
const icu::FormattedValue& formatted) {
UErrorCode status = U_ZERO_ERROR;
icu::UnicodeString result = formatted.toString(status);
if (U_FAILURE(status)) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError));
}
return Intl::ToString(isolate, result);
}
MaybeHandle<String> FormatToString(
Isolate* isolate, const icu::FormattedValue& formatted,
std::shared_ptr<icu::number::LocalizedNumberFormatter>, bool) {
return FormatToString(isolate, formatted);
}
MaybeHandle<JSArray> FormatToJSArray(
Isolate* isolate, const icu::FormattedValue& formatted,
std::shared_ptr<icu::number::LocalizedNumberFormatter> lfmt, bool is_nan,
bool output_source) {
UErrorCode status = U_ZERO_ERROR;
bool is_unit = Style::UNIT == StyleFromSkeleton(lfmt->toSkeleton(status));
CHECK(U_SUCCESS(status));
Factory* factory = isolate->factory();
Handle<JSArray> result = factory->NewJSArray(0);
int format_to_parts;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, format_to_parts,
ConstructParts(isolate, formatted, result, 0, is_unit, is_nan,
output_source, false, Handle<String>()));
USE(format_to_parts);
return result;
}
MaybeHandle<JSArray> FormatRangeToJSArray(
Isolate* isolate, const icu::FormattedValue& formatted,
std::shared_ptr<icu::number::LocalizedNumberFormatter> lfmt, bool is_nan) {
return FormatToJSArray(isolate, formatted, std::move(lfmt), is_nan, true);
}
}
Maybe<icu::number::LocalizedNumberRangeFormatter>
JSNumberFormat::GetRangeFormatter(
Isolate* isolate, Tagged<String> locale,
const icu::number::LocalizedNumberFormatter& number_formatter) {
UErrorCode status = U_ZERO_ERROR;
UParseError perror;
auto locale_str = locale->ToStdString();
icu::number::LocalizedNumberRangeFormatter range_formatter =
icu::number::UnlocalizedNumberRangeFormatter()
.numberFormatterBoth(icu::number::NumberFormatter::forSkeleton(
number_formatter.toSkeleton(status), perror, status))
.locale(icu::Locale::forLanguageTag(locale_str, status));
if (U_FAILURE(status)) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError));
}
return Just(range_formatter);
}
MaybeDirectHandle<String> JSNumberFormat::FormatNumeric(
Isolate* isolate,
std::shared_ptr<icu::number::LocalizedNumberFormatter> lfmt,
Handle<Object> numeric_obj) {
Maybe<icu::number::FormattedNumber> maybe_format =
IcuFormatNumber(isolate, std::move(lfmt), numeric_obj);
MAYBE_RETURN(maybe_format, Handle<String>());
icu::number::FormattedNumber formatted = std::move(maybe_format).FromJust();
return FormatToString(isolate, formatted);
}
MaybeDirectHandle<String> JSNumberFormat::NumberFormatFunction(
Isolate* isolate, DirectHandle<JSNumberFormat> number_format,
Handle<Object> value) {
IntlMathematicalValue x;
ASSIGN_RETURN_ON_EXCEPTION(isolate, x,
IntlMathematicalValue::From(isolate, value));
Maybe<icu::number::FormattedNumber> maybe_formatted =
IntlMathematicalValue::FormatNumeric(
isolate, number_format->icu_number_formatter()->get(), x);
MAYBE_RETURN(maybe_formatted, Handle<String>());
icu::number::FormattedNumber formatted =
std::move(maybe_formatted).FromJust();
return FormatToString(isolate, formatted);
}
MaybeDirectHandle<JSArray> JSNumberFormat::FormatToParts(
Isolate* isolate, DirectHandle<JSNumberFormat> number_format,
Handle<Object> numeric_obj) {
IntlMathematicalValue value;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, value, IntlMathematicalValue::From(isolate, numeric_obj));
std::shared_ptr<icu::number::LocalizedNumberFormatter> lfmt(
number_format->icu_number_formatter()->get());
Maybe<icu::number::FormattedNumber> maybe_formatted =
IntlMathematicalValue::FormatNumeric(isolate, lfmt, value);
MAYBE_RETURN(maybe_formatted, Handle<JSArray>());
icu::number::FormattedNumber formatted =
std::move(maybe_formatted).FromJust();
return FormatToJSArray(isolate, formatted, std::move(lfmt), value.IsNaN(),
false);
}
MaybeDirectHandle<String> JSNumberFormat::FormatNumericRange(
Isolate* isolate, DirectHandle<JSNumberFormat> number_format,
Handle<Object> x_obj, Handle<Object> y_obj) {
return PartitionNumberRangePattern<String, FormatToString>(
isolate, number_format, x_obj, y_obj,
"Intl.NumberFormat.prototype.formatRange");
}
MaybeDirectHandle<JSArray> JSNumberFormat::FormatNumericRangeToParts(
Isolate* isolate, DirectHandle<JSNumberFormat> number_format,
Handle<Object> x_obj, Handle<Object> y_obj) {
return PartitionNumberRangePattern<JSArray, FormatRangeToJSArray>(
isolate, number_format, x_obj, y_obj,
"Intl.NumberFormat.prototype.formatRangeToParts");
}
namespace {
struct CheckNumberElements {
static const char* key() { return "NumberElements"; }
static const char* path() { return nullptr; }
};
}
const std::set<std::string>& JSNumberFormat::GetAvailableLocales() {
static base::LazyInstance<Intl::AvailableLocales<CheckNumberElements>>::type
available_locales = LAZY_INSTANCE_INITIALIZER;
return available_locales.Pointer()->Get();
}
}
}