mirror of
https://github.com/Samsung/escargot.git
synced 2026-06-22 10:01:50 +00:00
Implement Intl.NumberFormat.prototype.formatRangeToParts function
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
This commit is contained in:
parent
7b71600aa7
commit
a264ee2026
9 changed files with 347 additions and 79 deletions
|
|
@ -470,6 +470,20 @@ static Value builtinIntlNumberFormatFormatRange(ExecutionState& state, Value thi
|
|||
}
|
||||
}
|
||||
|
||||
static Value builtinIntlNumberFormatFormatRangeToParts(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
|
||||
{
|
||||
if (!thisValue.isObject() || !thisValue.asObject()->hasInternalSlot() || !thisValue.asObject()->internalSlot()->hasOwnProperty(state, state.context()->staticStrings().lazyInitializedNumberFormat())) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Method called on incompatible receiver");
|
||||
}
|
||||
|
||||
if (argv[0].isUndefined() || argv[1].isUndefined()) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Invalid parameter: undefined");
|
||||
}
|
||||
|
||||
return IntlNumberFormat::formatRangeToParts(state, thisValue.asObject(),
|
||||
toIntlDecimalString(state, argv[0]), toIntlDecimalString(state, argv[1]));
|
||||
}
|
||||
|
||||
static Value builtinIntlNumberFormatSupportedLocalesOf(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
|
||||
{
|
||||
// If options is not provided, then let options be undefined.
|
||||
|
|
@ -1433,6 +1447,9 @@ void GlobalObject::installIntl(ExecutionState& state)
|
|||
m_intlNumberFormatPrototype->directDefineOwnProperty(state, state.context()->staticStrings().lazyFormatRange(),
|
||||
ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(strings->lazyFormatRange(), builtinIntlNumberFormatFormatRange, 2, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::ConfigurablePresent | ObjectPropertyDescriptor::WritablePresent)));
|
||||
|
||||
m_intlNumberFormatPrototype->directDefineOwnProperty(state, state.context()->staticStrings().lazyFormatRangeToParts(),
|
||||
ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(strings->lazyFormatRangeToParts(), builtinIntlNumberFormatFormatRangeToParts, 2, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::ConfigurablePresent | ObjectPropertyDescriptor::WritablePresent)));
|
||||
|
||||
m_intlNumberFormatPrototype->directDefineOwnProperty(state, state.context()->staticStrings().resolvedOptions,
|
||||
ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(strings->resolvedOptions, builtinIntlNumberFormatResolvedOptions, 0, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::ConfigurablePresent | ObjectPropertyDescriptor::WritablePresent)));
|
||||
|
||||
|
|
|
|||
|
|
@ -2521,7 +2521,7 @@ void Intl::convertICUNumberFieldToEcmaNumberField(std::vector<NumberFieldItem>&
|
|||
});
|
||||
}
|
||||
|
||||
String* Intl::icuNumberFieldToString(ExecutionState& state, int32_t fieldName, double d)
|
||||
String* Intl::icuNumberFieldToString(ExecutionState& state, int32_t fieldName, double d, String* style)
|
||||
{
|
||||
if (fieldName == -1) {
|
||||
return state.context()->staticStrings().lazyLiteral().string();
|
||||
|
|
@ -2545,7 +2545,11 @@ String* Intl::icuNumberFieldToString(ExecutionState& state, int32_t fieldName, d
|
|||
case UNUM_SIGN_FIELD:
|
||||
return std::signbit(d) ? state.context()->staticStrings().lazyMinusSign().string() : state.context()->staticStrings().lazyPlusSign().string();
|
||||
case UNUM_PERCENT_FIELD:
|
||||
return state.context()->staticStrings().lazyPercentSign().string();
|
||||
if (style->equals("unit")) {
|
||||
return state.context()->staticStrings().lazyUnit().string();
|
||||
} else {
|
||||
return state.context()->staticStrings().lazyPercentSign().string();
|
||||
}
|
||||
case UNUM_CURRENCY_FIELD:
|
||||
return state.context()->staticStrings().lazyCurrency().string();
|
||||
case UNUM_EXPONENT_SYMBOL_FIELD:
|
||||
|
|
@ -2564,9 +2568,13 @@ String* Intl::icuNumberFieldToString(ExecutionState& state, int32_t fieldName, d
|
|||
return state.context()->staticStrings().lazyUnit().string();
|
||||
case UNUM_COMPACT_FIELD:
|
||||
return state.context()->staticStrings().lazyCompact().string();
|
||||
#ifndef UNUM_APPROXIMATELY_SIGN_FIELD
|
||||
#define UNUM_APPROXIMATELY_SIGN_FIELD (UNUM_COMPACT_FIELD + 1)
|
||||
#endif
|
||||
case UNUM_APPROXIMATELY_SIGN_FIELD:
|
||||
return state.context()->staticStrings().lazyApproximatelySign().string();
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
return String::emptyString();
|
||||
return String::fromASCII("unknown");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ public:
|
|||
int32_t type;
|
||||
};
|
||||
static void convertICUNumberFieldToEcmaNumberField(std::vector<NumberFieldItem>& fields, double x, const UTF16StringDataNonGCStd& resultString);
|
||||
static String* icuNumberFieldToString(ExecutionState& state, int32_t fieldName, double d);
|
||||
static String* icuNumberFieldToString(ExecutionState& state, int32_t fieldName, double d, String* style);
|
||||
|
||||
enum class RoundingType {
|
||||
MorePrecision,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
#if defined(ENABLE_ICU) && defined(ENABLE_INTL)
|
||||
/*
|
||||
* Copyright (C) 2015 Andy VanWagoner (thetalecrafter@gmail.com)
|
||||
* Copyright (C) 2015 Sukolsak Sakshuwong (sukolsak@gmail.com)
|
||||
* Copyright (C) 2015 Andy VanWagoner (andy@vanwagoner.family)
|
||||
* Copyright (C) 2016 Sukolsak Sakshuwong (sukolsak@gmail.com)
|
||||
* Copyright (C) 2016-2023 Apple Inc. All rights reserved.
|
||||
* Copyright (C) 2020 Sony Interactive Entertainment Inc.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
|
|
@ -1019,6 +1021,12 @@ void IntlNumberFormat::initNumberFormatSkeleton(ExecutionState& state, const Int
|
|||
}
|
||||
}
|
||||
|
||||
#define THROW_TYPEERROR_IF_FAILED() \
|
||||
if (U_FAILURE(status)) { \
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Failed to format a number"); \
|
||||
}
|
||||
|
||||
|
||||
UTF16StringDataNonGCStd IntlNumberFormat::format(ExecutionState& state, Object* numberFormat, double x)
|
||||
{
|
||||
auto ud = reinterpret_cast<IntlNumberFormatData*>(numberFormat->internalSlot()->extraData());
|
||||
|
|
@ -1031,9 +1039,7 @@ UTF16StringDataNonGCStd IntlNumberFormat::format(ExecutionState& state, Object*
|
|||
|
||||
unumf_formatDouble(formatter, x, uresult.get(), &status);
|
||||
|
||||
if (U_FAILURE(status)) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Failed to format a number");
|
||||
}
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
|
||||
resultString.resize(32);
|
||||
auto length = unumf_resultToString(uresult.get(), (UChar*)resultString.data(), resultString.size(), &status);
|
||||
|
|
@ -1043,9 +1049,7 @@ UTF16StringDataNonGCStd IntlNumberFormat::format(ExecutionState& state, Object*
|
|||
status = U_ZERO_ERROR;
|
||||
unumf_resultToString(uresult.get(), (UChar*)resultString.data(), resultString.size(), &status);
|
||||
}
|
||||
if (U_FAILURE(status)) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Failed to format a number");
|
||||
}
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
|
||||
return resultString;
|
||||
}
|
||||
|
|
@ -1064,9 +1068,7 @@ UTF16StringDataNonGCStd IntlNumberFormat::format(ExecutionState& state, Object*
|
|||
|
||||
unumf_formatDecimal(formatter, s.data(), s.length(), uresult.get(), &status);
|
||||
|
||||
if (U_FAILURE(status)) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Failed to format a number");
|
||||
}
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
|
||||
resultString.resize(32);
|
||||
auto length = unumf_resultToString(uresult.get(), (UChar*)resultString.data(), resultString.size(), &status);
|
||||
|
|
@ -1076,15 +1078,14 @@ UTF16StringDataNonGCStd IntlNumberFormat::format(ExecutionState& state, Object*
|
|||
status = U_ZERO_ERROR;
|
||||
unumf_resultToString(uresult.get(), (UChar*)resultString.data(), resultString.size(), &status);
|
||||
}
|
||||
if (U_FAILURE(status)) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Failed to format a number");
|
||||
}
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
|
||||
return resultString;
|
||||
}
|
||||
|
||||
ArrayObject* IntlNumberFormat::formatToParts(ExecutionState& state, Object* numberFormat, double x)
|
||||
{
|
||||
auto style = numberFormat->internalSlot()->get(state, ObjectPropertyName(state.context()->staticStrings().lazyStyle())).value(state, numberFormat->internalSlot()).asString();
|
||||
auto ud = reinterpret_cast<IntlNumberFormatData*>(numberFormat->internalSlot()->extraData());
|
||||
UNumberFormatter* formatter = ud->numberFormatter;
|
||||
|
||||
|
|
@ -1097,9 +1098,7 @@ ArrayObject* IntlNumberFormat::formatToParts(ExecutionState& state, Object* numb
|
|||
|
||||
unumf_formatDouble(formatter, x, uresult.get(), &status);
|
||||
|
||||
if (U_FAILURE(status)) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Failed to format a number");
|
||||
}
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
|
||||
resultString.resize(32);
|
||||
auto length = unumf_resultToString(uresult.get(), (UChar*)resultString.data(), resultString.size(), &status);
|
||||
|
|
@ -1109,18 +1108,14 @@ ArrayObject* IntlNumberFormat::formatToParts(ExecutionState& state, Object* numb
|
|||
status = U_ZERO_ERROR;
|
||||
unumf_resultToString(uresult.get(), (UChar*)resultString.data(), resultString.size(), &status);
|
||||
}
|
||||
if (U_FAILURE(status)) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Failed to format a number");
|
||||
}
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
|
||||
LocalResourcePointer<UFieldPositionIterator> fpositer(ufieldpositer_open(&status), [](UFieldPositionIterator* f) { ufieldpositer_close(f); });
|
||||
ASSERT(U_SUCCESS(status));
|
||||
|
||||
unumf_resultGetAllFieldPositions(uresult.get(), fpositer.get(), &status);
|
||||
|
||||
if (U_FAILURE(status)) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Failed to format a number");
|
||||
}
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
|
||||
std::vector<Intl::NumberFieldItem> fields;
|
||||
|
||||
|
|
@ -1160,7 +1155,7 @@ ArrayObject* IntlNumberFormat::formatToParts(ExecutionState& state, Object* numb
|
|||
const Intl::NumberFieldItem& item = fields[i];
|
||||
|
||||
Object* o = new Object(state);
|
||||
o->defineOwnPropertyThrowsException(state, ObjectPropertyName(typeAtom), ObjectPropertyDescriptor(Intl::icuNumberFieldToString(state, item.type, x), ObjectPropertyDescriptor::AllPresent));
|
||||
o->defineOwnPropertyThrowsException(state, ObjectPropertyName(typeAtom), ObjectPropertyDescriptor(Intl::icuNumberFieldToString(state, item.type, x, style), ObjectPropertyDescriptor::AllPresent));
|
||||
auto sub = resultString.substr(item.start, item.end - item.start);
|
||||
o->defineOwnPropertyThrowsException(state, ObjectPropertyName(valueAtom), ObjectPropertyDescriptor(new UTF16String(sub.data(), sub.length()), ObjectPropertyDescriptor::AllPresent));
|
||||
|
||||
|
|
@ -1188,24 +1183,16 @@ String* IntlNumberFormat::formatRange(ExecutionState& state, Object* numberForma
|
|||
UErrorCode status = U_ZERO_ERROR;
|
||||
LocalResourcePointer<UFormattedNumberRange> uresult(unumrf_openResult(&status), [](UFormattedNumberRange* f) { unumrf_closeResult(f); });
|
||||
|
||||
if (U_FAILURE(status)) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Failed to format a number");
|
||||
}
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
|
||||
unumrf_formatDoubleRange(ud->numberRangeFormatter.value(), x, y, uresult.get(), &status);
|
||||
|
||||
if (U_FAILURE(status)) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Failed to format a number");
|
||||
}
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
auto* formattedValue = unumrf_resultAsValue(uresult.get(), &status);
|
||||
if (U_FAILURE(status)) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Failed to format a number");
|
||||
}
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
int32_t length = 0;
|
||||
const UChar* string = ufmtval_getString(formattedValue, &length, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Failed to format a number");
|
||||
}
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
|
||||
return new UTF16String(string, length);
|
||||
}
|
||||
|
|
@ -1225,28 +1212,292 @@ String* IntlNumberFormat::formatRange(ExecutionState& state, Object* numberForma
|
|||
UErrorCode status = U_ZERO_ERROR;
|
||||
LocalResourcePointer<UFormattedNumberRange> uresult(unumrf_openResult(&status), [](UFormattedNumberRange* f) { unumrf_closeResult(f); });
|
||||
|
||||
if (U_FAILURE(status)) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Failed to format a number");
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
|
||||
auto xs = x->toNonGCUTF8StringData();
|
||||
auto ys = y->toNonGCUTF8StringData();
|
||||
unumrf_formatDecimalRange(ud->numberRangeFormatter.value(), xs.data(), xs.length(), ys.data(), ys.length(), uresult.get(), &status);
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
auto* formattedValue = unumrf_resultAsValue(uresult.get(), &status);
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
int32_t length = 0;
|
||||
const UChar* string = ufmtval_getString(formattedValue, &length, &status);
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
|
||||
return new UTF16String(string, length);
|
||||
}
|
||||
|
||||
static bool numberFieldsPracticallyEqual(ExecutionState& state, const UFormattedValue* formattedValue)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
LocalResourcePointer<UConstrainedFieldPosition> uresult(ucfpos_open(&status), [](UConstrainedFieldPosition* f) { ucfpos_close(f); });
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
|
||||
// We only care about UFIELD_CATEGORY_NUMBER_RANGE_SPAN category.
|
||||
ucfpos_constrainCategory(uresult.get(), UFIELD_CATEGORY_NUMBER_RANGE_SPAN, &status);
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
|
||||
bool hasSpan = ufmtval_nextPosition(formattedValue, uresult.get(), &status);
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
|
||||
return !hasSpan;
|
||||
}
|
||||
|
||||
static std::vector<Intl::NumberFieldItem> flattenFields(std::vector<Intl::NumberFieldItem>&& fields, int32_t formattedStringLength)
|
||||
{
|
||||
// ICU generates sequence of nested fields, but ECMA402 requires non-overlapping sequence of parts.
|
||||
// This function flattens nested fields into sequence of non-overlapping parts.
|
||||
//
|
||||
// Formatted string: "100,000–20,000,000"
|
||||
// | | | | | | |
|
||||
// | B | | E F |
|
||||
// | | | |
|
||||
// +--C--+ +---G----+
|
||||
// +--A--+ +---D----+
|
||||
//
|
||||
// Ranges ICU generates:
|
||||
// A: (0, 7) UFIELD_CATEGORY_NUMBER_RANGE_SPAN startRange
|
||||
// B: (3, 4) UFIELD_CATEGORY_NUMBER group ","
|
||||
// C: (0, 7) UFIELD_CATEGORY_NUMBER integer
|
||||
// D: (8, 18) UFIELD_CATEGORY_NUMBER_RANGE_SPAN endRange
|
||||
// E: (10, 11) UFIELD_CATEGORY_NUMBER group ","
|
||||
// F: (14, 15) UFIELD_CATEGORY_NUMBER group ","
|
||||
// G: (8, 18) UFIELD_CATEGORY_NUMBER integer
|
||||
//
|
||||
// Then, we need to generate:
|
||||
// A: (0, 3) startRange integer
|
||||
// B: (3, 4) startRange group ","
|
||||
// C: (4, 7) startRange integer
|
||||
// D: (7, 8) shared literal "-"
|
||||
// E: (8, 10) endRange integer
|
||||
// F: (10, 11) endRange group ","
|
||||
// G: (11, 14) endRange integer
|
||||
// H: (14, 15) endRange group ","
|
||||
// I: (15, 18) endRange integer
|
||||
|
||||
std::sort(fields.begin(), fields.end(), [](Intl::NumberFieldItem& lhs, Intl::NumberFieldItem& rhs) {
|
||||
if (lhs.start < rhs.start)
|
||||
return true;
|
||||
if (lhs.start > rhs.start)
|
||||
return false;
|
||||
if (lhs.end < rhs.end)
|
||||
return false;
|
||||
if (lhs.end > rhs.end)
|
||||
return true;
|
||||
return lhs.type < rhs.type;
|
||||
});
|
||||
|
||||
std::vector<Intl::NumberFieldItem> flatten;
|
||||
std::vector<Intl::NumberFieldItem> stack;
|
||||
// Top-level field covers entire parts, which makes parts "literal".
|
||||
stack.push_back(Intl::NumberFieldItem{ 0, formattedStringLength, -1 });
|
||||
unsigned cursor = 0;
|
||||
int32_t begin = 0;
|
||||
while (cursor < fields.size()) {
|
||||
const auto& field = fields[cursor];
|
||||
|
||||
// If the new field is out of the current top-most field, roll up and insert a flatten field.
|
||||
// Because the top-level field in the stack covers all index range, this condition always becomes false
|
||||
// if stack size is 1.
|
||||
while (stack.back().end < field.start) {
|
||||
if (begin < stack.back().end) {
|
||||
Intl::NumberFieldItem flattenField{ begin, stack.back().end, stack.back().type };
|
||||
flatten.push_back(flattenField);
|
||||
begin = flattenField.end;
|
||||
}
|
||||
stack.erase(stack.end() - 1);
|
||||
}
|
||||
ASSERT(!stack.empty()); // At least, top-level field exists.
|
||||
|
||||
// If the new field is starting with the same index, diving into the new field by adding it into stack.
|
||||
if (begin == field.start) {
|
||||
stack.push_back(field);
|
||||
++cursor;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If there is a room between the current top-most field and the new field, insert a flatten field.
|
||||
if (begin < field.start) {
|
||||
Intl::NumberFieldItem flattenField{ begin, field.start, stack.back().type };
|
||||
flatten.push_back(flattenField);
|
||||
stack.push_back(field);
|
||||
begin = field.start;
|
||||
++cursor;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Roll up the nested field at the end of the formatted string sequence.
|
||||
// For example,
|
||||
//
|
||||
// <------------A-------------->
|
||||
// <--------B------------>
|
||||
// <---C---->
|
||||
//
|
||||
// Then, after C finishes, we should insert remaining B and A.
|
||||
while (!stack.empty()) {
|
||||
if (begin < stack.back().end) {
|
||||
Intl::NumberFieldItem flattenField{ begin, stack.back().end, stack.back().type };
|
||||
flatten.push_back(flattenField);
|
||||
begin = flattenField.end;
|
||||
}
|
||||
stack.erase(stack.end() - 1);
|
||||
}
|
||||
|
||||
return flatten;
|
||||
}
|
||||
|
||||
|
||||
ArrayObject* IntlNumberFormat::formatRangeToParts(ExecutionState& state, Object* numberFormat, String* x, String* y)
|
||||
{
|
||||
auto style = numberFormat->internalSlot()->get(state, ObjectPropertyName(state.context()->staticStrings().lazyStyle())).value(state, numberFormat->internalSlot()).asString();
|
||||
auto ud = reinterpret_cast<IntlNumberFormatData*>(numberFormat->internalSlot()->extraData());
|
||||
|
||||
#if defined(ENABLE_RUNTIME_ICU_BINDER)
|
||||
if (!ud->numberRangeFormatter) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Intl.NumberFormat.formatRange needs 68+ version of ICU");
|
||||
}
|
||||
#endif
|
||||
|
||||
UNumberFormatter* formatter = ud->numberFormatter;
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
LocalResourcePointer<UFormattedNumberRange> uresult(unumrf_openResult(&status), [](UFormattedNumberRange* f) { unumrf_closeResult(f); });
|
||||
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
|
||||
double xAsNumber = Value(x).toNumber(state);
|
||||
double yAsNumber = Value(y).toNumber(state);
|
||||
if (std::isnan(xAsNumber) || std::isnan(yAsNumber)) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Failed to format a number");
|
||||
}
|
||||
|
||||
auto xs = x->toNonGCUTF8StringData();
|
||||
auto ys = y->toNonGCUTF8StringData();
|
||||
unumrf_formatDecimalRange(ud->numberRangeFormatter.value(), xs.data(), xs.length(), ys.data(), ys.length(), uresult.get(), &status);
|
||||
|
||||
if (U_FAILURE(status)) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Failed to format a number");
|
||||
}
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
auto* formattedValue = unumrf_resultAsValue(uresult.get(), &status);
|
||||
if (U_FAILURE(status)) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Failed to format a number");
|
||||
}
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
int32_t length = 0;
|
||||
const UChar* string = ufmtval_getString(formattedValue, &length, &status);
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
|
||||
UVersionInfo versionArray;
|
||||
u_getVersion(versionArray);
|
||||
// After ICU 71, approximatelySign is supported. We use the old path only for < 71.
|
||||
if (versionArray[0] < 71) {
|
||||
bool equal = numberFieldsPracticallyEqual(state, formattedValue);
|
||||
if (equal) {
|
||||
return IntlNumberFormat::formatToParts(state, numberFormat, Value(x).toNumber(state));
|
||||
}
|
||||
}
|
||||
|
||||
// We care multiple categories (UFIELD_CATEGORY_DATE and UFIELD_CATEGORY_DATE_INTERVAL_SPAN).
|
||||
// So we do not constraint iterator.
|
||||
LocalResourcePointer<UConstrainedFieldPosition> iterator(ucfpos_open(&status), [](UConstrainedFieldPosition* f) { ucfpos_close(f); });
|
||||
if (U_FAILURE(status)) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Failed to format a number");
|
||||
}
|
||||
|
||||
return new UTF16String(string, length);
|
||||
std::vector<Intl::NumberFieldItem> fields;
|
||||
Intl::NumberFieldItem startField{ -1, -1, -1 };
|
||||
Intl::NumberFieldItem endField{ -1, -1, -1 };
|
||||
|
||||
while (true) {
|
||||
bool next = ufmtval_nextPosition(formattedValue, iterator.get(), &status);
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
if (!next) {
|
||||
break;
|
||||
}
|
||||
|
||||
int32_t category = ucfpos_getCategory(iterator.get(), &status);
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
|
||||
int32_t fieldType = ucfpos_getField(iterator.get(), &status);
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
|
||||
int32_t beginIndex = 0;
|
||||
int32_t endIndex = 0;
|
||||
ucfpos_getIndexes(iterator.get(), &beginIndex, &endIndex, &status);
|
||||
THROW_TYPEERROR_IF_FAILED();
|
||||
|
||||
if (category != UFIELD_CATEGORY_NUMBER && category != UFIELD_CATEGORY_NUMBER_RANGE_SPAN) {
|
||||
continue;
|
||||
}
|
||||
if (category == UFIELD_CATEGORY_NUMBER && fieldType < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (category == UFIELD_CATEGORY_NUMBER_RANGE_SPAN) {
|
||||
// > The special field category UFIELD_CATEGORY_NUMBER_RANGE_SPAN is used to indicate which number
|
||||
// > primitives came from which arguments: 0 means start, and 1 means end. The span category
|
||||
// > will always occur before the corresponding fields in UFIELD_CATEGORY_NUMBER in the nextPosition() iterator.
|
||||
// from ICU comment. So, field 0 is startRange, field 1 is endRange.
|
||||
if (!fieldType) {
|
||||
startField = { beginIndex, endIndex, -1 };
|
||||
} else {
|
||||
ASSERT(fieldType == 1);
|
||||
endField = { beginIndex, endIndex, -1 };
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
ASSERT(category == UFIELD_CATEGORY_NUMBER);
|
||||
Intl::NumberFieldItem item;
|
||||
item.start = beginIndex;
|
||||
item.end = endIndex;
|
||||
item.type = fieldType;
|
||||
fields.push_back(item);
|
||||
}
|
||||
|
||||
auto flatten = flattenFields(std::move(fields), length);
|
||||
|
||||
AtomicString typeAtom(state, "type", 4);
|
||||
AtomicString valueAtom = state.context()->staticStrings().value;
|
||||
AtomicString sourceAtom = state.context()->staticStrings().source;
|
||||
|
||||
String* sharedString = new ASCIIStringFromExternalMemory("shared");
|
||||
String* startRangeString = new ASCIIStringFromExternalMemory("startRange");
|
||||
String* endRangeString = new ASCIIStringFromExternalMemory("endRange");
|
||||
String* literalString = new ASCIIStringFromExternalMemory("literal");
|
||||
String* resultString = new UTF16String(string, length);
|
||||
|
||||
auto createPart = [&](String* type, int32_t beginIndex, int32_t endIndex) {
|
||||
auto sourceType = [&](int32_t index) -> String* {
|
||||
if (startField.start <= index && index < startField.end) {
|
||||
return startRangeString;
|
||||
}
|
||||
if (endField.start <= index && index < endField.end) {
|
||||
return endRangeString;
|
||||
}
|
||||
return sharedString;
|
||||
};
|
||||
|
||||
Object* part = new Object(state);
|
||||
part->defineOwnPropertyThrowsException(state, ObjectPropertyName(typeAtom), ObjectPropertyDescriptor(type, ObjectPropertyDescriptor::AllPresent));
|
||||
String* value = resultString->substring(beginIndex, endIndex);
|
||||
part->defineOwnPropertyThrowsException(state, ObjectPropertyName(valueAtom), ObjectPropertyDescriptor(value, ObjectPropertyDescriptor::AllPresent));
|
||||
part->defineOwnPropertyThrowsException(state, ObjectPropertyName(sourceAtom), ObjectPropertyDescriptor(sourceType(beginIndex), ObjectPropertyDescriptor::AllPresent));
|
||||
return part;
|
||||
};
|
||||
|
||||
ArrayObject* result = new ArrayObject(state);
|
||||
size_t resultIndex = 0;
|
||||
for (auto& field : flatten) {
|
||||
double d = xAsNumber;
|
||||
if (startField.start <= field.start && field.start < startField.end) {
|
||||
d = xAsNumber;
|
||||
} else {
|
||||
d = yAsNumber;
|
||||
}
|
||||
auto fieldType = field.type;
|
||||
auto partType = fieldType == -1 ? literalString : Intl::icuNumberFieldToString(state, fieldType, d, style);
|
||||
Object* part = createPart(partType, field.start, field.end);
|
||||
result->defineOwnPropertyThrowsException(state, ObjectPropertyName(state, resultIndex++), ObjectPropertyDescriptor(part, ObjectPropertyDescriptor::AllPresent));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Escargot
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ public:
|
|||
static String* formatRange(ExecutionState& state, Object* numberFormat, double x, double y);
|
||||
static String* formatRange(ExecutionState& state, Object* numberFormat, String* x, String* y);
|
||||
static ArrayObject* formatToParts(ExecutionState& state, Object* numberFormat, double x);
|
||||
static ArrayObject* formatRangeToParts(ExecutionState& state, Object* numberFormat, String* x, String* y);
|
||||
|
||||
private:
|
||||
static void initNumberFormatSkeleton(ExecutionState& state, const Intl::SetNumberFormatDigitOptionsResult& formatResult,
|
||||
|
|
|
|||
|
|
@ -343,7 +343,7 @@ ArrayObject* IntlRelativeTimeFormatObject::formatToParts(ExecutionState& state,
|
|||
const Intl::NumberFieldItem& item = fields[i];
|
||||
|
||||
Object* o = new Object(state);
|
||||
o->defineOwnPropertyThrowsException(state, ObjectPropertyName(typeAtom), ObjectPropertyDescriptor(Intl::icuNumberFieldToString(state, item.type, value), ObjectPropertyDescriptor::AllPresent));
|
||||
o->defineOwnPropertyThrowsException(state, ObjectPropertyName(typeAtom), ObjectPropertyDescriptor(Intl::icuNumberFieldToString(state, item.type, value, String::emptyString()), ObjectPropertyDescriptor::AllPresent));
|
||||
auto sub = resultString.substr(item.start, item.end - item.start);
|
||||
o->defineOwnPropertyThrowsException(state, ObjectPropertyName(valueAtom), ObjectPropertyDescriptor(new UTF16String(sub.data(), sub.length()), ObjectPropertyDescriptor::AllPresent));
|
||||
if (item.type != -1) {
|
||||
|
|
|
|||
|
|
@ -724,6 +724,7 @@ namespace Escargot {
|
|||
F(Accounting, "accounting") \
|
||||
F(Always, "always") \
|
||||
F(Any, "any") \
|
||||
F(ApproximatelySign, "approximatelySign") \
|
||||
F(Auto, "auto") \
|
||||
F(Base, "base") \
|
||||
F(Basic, "basic") \
|
||||
|
|
@ -760,6 +761,7 @@ namespace Escargot {
|
|||
F(ExponentSeparator, "exponentSeparator") \
|
||||
F(Fallback, "fallback") \
|
||||
F(FormatRange, "formatRange") \
|
||||
F(FormatRangeToParts, "formatRangeToParts") \
|
||||
F(FormatMatcher, "formatMatcher") \
|
||||
F(FormatToParts, "formatToParts") \
|
||||
F(Fraction, "fraction") \
|
||||
|
|
|
|||
34
third_party/runtime_icu_binder/ICUTypes.h
vendored
34
third_party/runtime_icu_binder/ICUTypes.h
vendored
|
|
@ -8003,6 +8003,7 @@ typedef enum UCurrNameStyle {
|
|||
typedef const void *UCurrRegistryKey;
|
||||
|
||||
// uformattedvalue.h
|
||||
|
||||
/**
|
||||
* All possible field categories in ICU. Every entry in this enum corresponds
|
||||
* to another enum that exists in ICU.
|
||||
|
|
@ -8013,70 +8014,67 @@ typedef const void *UCurrRegistryKey;
|
|||
* categories 2^28 and higher or below zero (with the highest bit turned on)
|
||||
* are private-use and will not be used by ICU in the future.
|
||||
*
|
||||
* @draft ICU 64
|
||||
* @stable ICU 64
|
||||
*/
|
||||
typedef enum UFieldCategory {
|
||||
/**
|
||||
* For an undefined field category.
|
||||
*
|
||||
* @draft ICU 64
|
||||
* @stable ICU 64
|
||||
*/
|
||||
UFIELD_CATEGORY_UNDEFINED = 0,
|
||||
|
||||
/**
|
||||
* For fields in UDateFormatField (udat.h), from ICU 3.0.
|
||||
*
|
||||
* @draft ICU 64
|
||||
* @stable ICU 64
|
||||
*/
|
||||
UFIELD_CATEGORY_DATE,
|
||||
|
||||
/**
|
||||
* For fields in UNumberFormatFields (unum.h), from ICU 49.
|
||||
*
|
||||
* @draft ICU 64
|
||||
* @stable ICU 64
|
||||
*/
|
||||
UFIELD_CATEGORY_NUMBER,
|
||||
|
||||
/**
|
||||
* For fields in UListFormatterField (ulistformatter.h), from ICU 63.
|
||||
*
|
||||
* @draft ICU 64
|
||||
* @stable ICU 64
|
||||
*/
|
||||
UFIELD_CATEGORY_LIST,
|
||||
|
||||
/**
|
||||
* For fields in URelativeDateTimeFormatterField (ureldatefmt.h), from ICU 64.
|
||||
*
|
||||
* @draft ICU 64
|
||||
* @stable ICU 64
|
||||
*/
|
||||
UFIELD_CATEGORY_RELATIVE_DATETIME,
|
||||
|
||||
/**
|
||||
* Reserved for possible future fields in UDateIntervalFormatField.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
UFIELD_CATEGORY_DATE_INTERVAL,
|
||||
|
||||
#ifndef U_HIDE_INTERNAL_API
|
||||
/** @internal */
|
||||
UFIELD_CATEGORY_COUNT,
|
||||
#endif /* U_HIDE_INTERNAL_API */
|
||||
|
||||
#endif /* U_HIDE_INTERNAL_API */
|
||||
/**
|
||||
* Category for spans in a list.
|
||||
*
|
||||
* @draft ICU 64
|
||||
* @stable ICU 64
|
||||
*/
|
||||
UFIELD_CATEGORY_LIST_SPAN = 0x1000 + UFIELD_CATEGORY_LIST,
|
||||
|
||||
/**
|
||||
* Category for spans in a date interval.
|
||||
*
|
||||
* @draft ICU 64
|
||||
* @stable ICU 64
|
||||
*/
|
||||
UFIELD_CATEGORY_DATE_INTERVAL_SPAN = 0x1000 + UFIELD_CATEGORY_DATE_INTERVAL,
|
||||
|
||||
/**
|
||||
* Category for spans in a number range.
|
||||
*
|
||||
* @stable ICU 69
|
||||
*/
|
||||
UFIELD_CATEGORY_NUMBER_RANGE_SPAN = 0x1000 + UFIELD_CATEGORY_NUMBER,
|
||||
} UFieldCategory;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4946,15 +4946,6 @@
|
|||
<test id="intl402/Locale/prototype/variants/name"><reason>TODO</reason></test>
|
||||
<test id="intl402/Locale/prototype/variants/prop-desc"><reason>TODO</reason></test>
|
||||
<test id="intl402/Locale/reject-duplicate-variants-in-tlang"><reason>TODO</reason></test>
|
||||
<test id="intl402/NumberFormat/prototype/formatRangeToParts/builtin"><reason>TODO</reason></test>
|
||||
<test id="intl402/NumberFormat/prototype/formatRangeToParts/en-US"><reason>TODO</reason></test>
|
||||
<test id="intl402/NumberFormat/prototype/formatRangeToParts/invoked-as-func"><reason>TODO</reason></test>
|
||||
<test id="intl402/NumberFormat/prototype/formatRangeToParts/length"><reason>TODO</reason></test>
|
||||
<test id="intl402/NumberFormat/prototype/formatRangeToParts/name"><reason>TODO</reason></test>
|
||||
<test id="intl402/NumberFormat/prototype/formatRangeToParts/nan-arguments-throws"><reason>TODO</reason></test>
|
||||
<test id="intl402/NumberFormat/prototype/formatRangeToParts/prop-desc"><reason>TODO</reason></test>
|
||||
<test id="intl402/NumberFormat/prototype/formatRangeToParts/x-greater-than-y-not-throws"><reason>TODO</reason></test>
|
||||
<test id="intl402/NumberFormat/prototype/formatToParts/percent-en-US"><reason>TODO</reason></test>
|
||||
<test id="intl402/PluralRules/constructor-option-read-order"><reason>TODO</reason></test>
|
||||
<test id="intl402/PluralRules/constructor-options-throwing-getters"><reason>TODO</reason></test>
|
||||
<test id="intl402/PluralRules/default-options-object-prototype"><reason>TODO</reason></test>
|
||||
|
|
@ -6460,4 +6451,4 @@
|
|||
<test id="staging/upsert/WeakMap/getOrInsertComputed/returns-value-if-key-is-not-present-symbol-key"><reason>TODO</reason></test>
|
||||
<test id="staging/upsert/WeakMap/getOrInsertComputed/returns-value-if-key-is-present-object-key"><reason>TODO</reason></test>
|
||||
<test id="staging/upsert/WeakMap/getOrInsertComputed/returns-value-if-key-is-present-symbol-key"><reason>TODO</reason></test>
|
||||
</excludeList>
|
||||
</excludeList>
|
||||
Loading…
Add table
Add a link
Reference in a new issue