mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-25 09:00:22 +00:00
f87041bf3a
Resulting in a massive rename across almost everywhere! Alongside the namespace change, we now have the following names: * JS::NonnullGCPtr -> GC::Ref * JS::GCPtr -> GC::Ptr * JS::HeapFunction -> GC::Function * JS::CellImpl -> GC::Cell * JS::Handle -> GC::Root
307 lines
12 KiB
C++
307 lines
12 KiB
C++
/*
|
|
* Copyright (c) 2021-2024, Tim Flynn <trflynn89@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Checked.h>
|
|
#include <AK/StringBuilder.h>
|
|
#include <AK/Utf8View.h>
|
|
#include <LibCrypto/BigInt/SignedBigInteger.h>
|
|
#include <LibJS/Runtime/AbstractOperations.h>
|
|
#include <LibJS/Runtime/Array.h>
|
|
#include <LibJS/Runtime/BigInt.h>
|
|
#include <LibJS/Runtime/GlobalObject.h>
|
|
#include <LibJS/Runtime/Intl/NumberFormat.h>
|
|
#include <LibJS/Runtime/Intl/NumberFormatFunction.h>
|
|
#include <LibJS/Runtime/Intl/PluralRules.h>
|
|
#include <LibJS/Runtime/ValueInlines.h>
|
|
#include <LibUnicode/CurrencyCode.h>
|
|
#include <LibUnicode/DisplayNames.h>
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
|
|
namespace JS::Intl {
|
|
|
|
GC_DEFINE_ALLOCATOR(NumberFormatBase);
|
|
GC_DEFINE_ALLOCATOR(NumberFormat);
|
|
|
|
NumberFormatBase::NumberFormatBase(Object& prototype)
|
|
: Object(ConstructWithPrototypeTag::Tag, prototype)
|
|
{
|
|
}
|
|
|
|
// 15 NumberFormat Objects, https://tc39.es/ecma402/#numberformat-objects
|
|
NumberFormat::NumberFormat(Object& prototype)
|
|
: NumberFormatBase(prototype)
|
|
{
|
|
}
|
|
|
|
void NumberFormat::visit_edges(Cell::Visitor& visitor)
|
|
{
|
|
Base::visit_edges(visitor);
|
|
if (m_bound_format)
|
|
visitor.visit(m_bound_format);
|
|
}
|
|
|
|
StringView NumberFormatBase::computed_rounding_priority_string() const
|
|
{
|
|
switch (m_computed_rounding_priority) {
|
|
case ComputedRoundingPriority::Auto:
|
|
return "auto"sv;
|
|
case ComputedRoundingPriority::MorePrecision:
|
|
return "morePrecision"sv;
|
|
case ComputedRoundingPriority::LessPrecision:
|
|
return "lessPrecision"sv;
|
|
default:
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
}
|
|
|
|
Value NumberFormat::use_grouping_to_value(VM& vm) const
|
|
{
|
|
switch (m_use_grouping) {
|
|
case Unicode::Grouping::Always:
|
|
case Unicode::Grouping::Auto:
|
|
case Unicode::Grouping::Min2:
|
|
return PrimitiveString::create(vm, Unicode::grouping_to_string(m_use_grouping));
|
|
case Unicode::Grouping::False:
|
|
return Value(false);
|
|
default:
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
}
|
|
|
|
void NumberFormat::set_use_grouping(StringOrBoolean const& use_grouping)
|
|
{
|
|
use_grouping.visit(
|
|
[this](StringView grouping) {
|
|
m_use_grouping = Unicode::grouping_from_string(grouping);
|
|
},
|
|
[this](bool grouping) {
|
|
VERIFY(!grouping);
|
|
m_use_grouping = Unicode::Grouping::False;
|
|
});
|
|
}
|
|
|
|
Unicode::RoundingOptions NumberFormatBase::rounding_options() const
|
|
{
|
|
return {
|
|
.type = m_rounding_type,
|
|
.mode = m_rounding_mode,
|
|
.trailing_zero_display = m_trailing_zero_display,
|
|
.min_significant_digits = m_min_significant_digits,
|
|
.max_significant_digits = m_max_significant_digits,
|
|
.min_fraction_digits = m_min_fraction_digits,
|
|
.max_fraction_digits = m_max_fraction_digits,
|
|
.min_integer_digits = m_min_integer_digits,
|
|
.rounding_increment = m_rounding_increment
|
|
};
|
|
}
|
|
|
|
Unicode::DisplayOptions NumberFormat::display_options() const
|
|
{
|
|
return {
|
|
.style = m_style,
|
|
.sign_display = m_sign_display,
|
|
.notation = m_notation,
|
|
.compact_display = m_compact_display,
|
|
.grouping = m_use_grouping,
|
|
.currency = m_currency,
|
|
.currency_display = m_currency_display,
|
|
.currency_sign = m_currency_sign,
|
|
.unit = m_unit,
|
|
.unit_display = m_unit_display,
|
|
};
|
|
}
|
|
|
|
// 15.5.1 CurrencyDigits ( currency ), https://tc39.es/ecma402/#sec-currencydigits
|
|
int currency_digits(StringView currency)
|
|
{
|
|
// 1. If the ISO 4217 currency and funds code list contains currency as an alphabetic code, return the minor
|
|
// unit value corresponding to the currency from the list; otherwise, return 2.
|
|
if (auto currency_code = Unicode::get_currency_code(currency); currency_code.has_value())
|
|
return currency_code->minor_unit.value_or(2);
|
|
return 2;
|
|
}
|
|
|
|
// 15.5.3 FormatNumericToString ( intlObject, x ), https://tc39.es/ecma402/#sec-formatnumberstring
|
|
String format_numeric_to_string(NumberFormatBase const& intl_object, MathematicalValue const& number)
|
|
{
|
|
return intl_object.formatter().format_to_decimal(number.to_value());
|
|
}
|
|
|
|
// 15.5.4 PartitionNumberPattern ( numberFormat, x ), https://tc39.es/ecma402/#sec-partitionnumberpattern
|
|
Vector<Unicode::NumberFormat::Partition> partition_number_pattern(NumberFormat const& number_format, MathematicalValue const& number)
|
|
{
|
|
return number_format.formatter().format_to_parts(number.to_value());
|
|
}
|
|
|
|
// 15.5.6 FormatNumeric ( numberFormat, x ), https://tc39.es/ecma402/#sec-formatnumber
|
|
String format_numeric(NumberFormat const& number_format, MathematicalValue const& number)
|
|
{
|
|
// 1. Let parts be ? PartitionNumberPattern(numberFormat, x).
|
|
// 2. Let result be the empty String.
|
|
// 3. For each Record { [[Type]], [[Value]] } part in parts, do
|
|
// a. Set result to the string-concatenation of result and part.[[Value]].
|
|
// 4. Return result.
|
|
return number_format.formatter().format(number.to_value());
|
|
}
|
|
|
|
// 15.5.7 FormatNumericToParts ( numberFormat, x ), https://tc39.es/ecma402/#sec-formatnumbertoparts
|
|
GC::Ref<Array> format_numeric_to_parts(VM& vm, NumberFormat const& number_format, MathematicalValue const& number)
|
|
{
|
|
auto& realm = *vm.current_realm();
|
|
|
|
// 1. Let parts be ? PartitionNumberPattern(numberFormat, x).
|
|
auto parts = partition_number_pattern(number_format, number);
|
|
|
|
// 2. Let result be ! ArrayCreate(0).
|
|
auto result = MUST(Array::create(realm, 0));
|
|
|
|
// 3. Let n be 0.
|
|
size_t n = 0;
|
|
|
|
// 4. For each Record { [[Type]], [[Value]] } part in parts, do
|
|
for (auto& part : parts) {
|
|
// a. Let O be OrdinaryObjectCreate(%Object.prototype%).
|
|
auto object = Object::create(realm, realm.intrinsics().object_prototype());
|
|
|
|
// b. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]).
|
|
MUST(object->create_data_property_or_throw(vm.names.type, PrimitiveString::create(vm, part.type)));
|
|
|
|
// c. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]).
|
|
MUST(object->create_data_property_or_throw(vm.names.value, PrimitiveString::create(vm, move(part.value))));
|
|
|
|
// d. Perform ! CreateDataPropertyOrThrow(result, ! ToString(n), O).
|
|
MUST(result->create_data_property_or_throw(n, object));
|
|
|
|
// e. Increment n by 1.
|
|
++n;
|
|
}
|
|
|
|
// 5. Return result.
|
|
return result;
|
|
}
|
|
|
|
// 15.5.16 ToIntlMathematicalValue ( value ), https://tc39.es/ecma402/#sec-tointlmathematicalvalue
|
|
ThrowCompletionOr<MathematicalValue> to_intl_mathematical_value(VM& vm, Value value)
|
|
{
|
|
// 1. Let primValue be ? ToPrimitive(value, number).
|
|
auto primitive_value = TRY(value.to_primitive(vm, Value::PreferredType::Number));
|
|
|
|
// 2. If Type(primValue) is BigInt, return the mathematical value of primValue.
|
|
if (primitive_value.is_bigint())
|
|
return MUST(value.as_bigint().big_integer().to_base(10));
|
|
|
|
// FIXME: The remaining steps are being refactored into a new Runtime Semantic, StringIntlMV.
|
|
// We short-circuit some of these steps to avoid known pitfalls.
|
|
// See: https://github.com/tc39/proposal-intl-numberformat-v3/pull/82
|
|
if (!primitive_value.is_string()) {
|
|
auto number = TRY(primitive_value.to_number(vm));
|
|
return number.as_double();
|
|
}
|
|
|
|
// 3. If Type(primValue) is String,
|
|
// a. Let str be primValue.
|
|
auto string = primitive_value.as_string().utf8_string();
|
|
|
|
// Step 4 handled separately by the FIXME above.
|
|
|
|
// 5. If the grammar cannot interpret str as an expansion of StringNumericLiteral, return not-a-number.
|
|
// 6. Let mv be the MV, a mathematical value, of ? ToNumber(str), as described in 7.1.4.1.1.
|
|
auto mathematical_value = TRY(primitive_value.to_number(vm)).as_double();
|
|
|
|
if (Value(mathematical_value).is_nan())
|
|
return MathematicalValue::Symbol::NotANumber;
|
|
|
|
// 7. If mv is 0 and the first non white space code point in str is -, return negative-zero.
|
|
if (mathematical_value == 0.0 && string.bytes_as_string_view().trim_whitespace(TrimMode::Left).starts_with('-'))
|
|
return MathematicalValue::Symbol::NegativeZero;
|
|
|
|
// 8. If mv is 10^10000 and str contains Infinity, return positive-infinity.
|
|
if (mathematical_value == pow(10, 10000) && string.contains("Infinity"sv))
|
|
return MathematicalValue::Symbol::PositiveInfinity;
|
|
|
|
// 9. If mv is -10^10000 and str contains Infinity, return negative-infinity.
|
|
if (mathematical_value == pow(-10, 10000) && string.contains("Infinity"sv))
|
|
return MathematicalValue::Symbol::NegativeInfinity;
|
|
|
|
// 10. Return mv.
|
|
return string;
|
|
}
|
|
|
|
// 15.5.19 PartitionNumberRangePattern ( numberFormat, x, y ), https://tc39.es/ecma402/#sec-partitionnumberrangepattern
|
|
ThrowCompletionOr<Vector<Unicode::NumberFormat::Partition>> partition_number_range_pattern(VM& vm, NumberFormat const& number_format, MathematicalValue const& start, MathematicalValue const& end)
|
|
{
|
|
// 1. If x is NaN or y is NaN, throw a RangeError exception.
|
|
if (start.is_nan())
|
|
return vm.throw_completion<RangeError>(ErrorType::NumberIsNaN, "start"sv);
|
|
if (end.is_nan())
|
|
return vm.throw_completion<RangeError>(ErrorType::NumberIsNaN, "end"sv);
|
|
|
|
return number_format.formatter().format_range_to_parts(start.to_value(), end.to_value());
|
|
}
|
|
|
|
// 15.5.22 FormatNumericRange ( numberFormat, x, y ), https://tc39.es/ecma402/#sec-formatnumericrange
|
|
ThrowCompletionOr<String> format_numeric_range(VM& vm, NumberFormat const& number_format, MathematicalValue const& start, MathematicalValue const& end)
|
|
{
|
|
// 1. Let parts be ? PartitionNumberRangePattern(numberFormat, x, y).
|
|
{
|
|
// NOTE: We short-circuit PartitionNumberRangePattern as we do not need individual partitions. But we must still
|
|
// perform the NaN sanity checks from its first step.
|
|
|
|
// 1. If x is NaN or y is NaN, throw a RangeError exception.
|
|
if (start.is_nan())
|
|
return vm.throw_completion<RangeError>(ErrorType::NumberIsNaN, "start"sv);
|
|
if (end.is_nan())
|
|
return vm.throw_completion<RangeError>(ErrorType::NumberIsNaN, "end"sv);
|
|
}
|
|
|
|
// 2. Let result be the empty String.
|
|
// 3. For each part in parts, do
|
|
// a. Set result to the string-concatenation of result and part.[[Value]].
|
|
// 4. Return result.
|
|
return number_format.formatter().format_range(start.to_value(), end.to_value());
|
|
}
|
|
|
|
// 15.5.23 FormatNumericRangeToParts ( numberFormat, x, y ), https://tc39.es/ecma402/#sec-formatnumericrangetoparts
|
|
ThrowCompletionOr<GC::Ref<Array>> format_numeric_range_to_parts(VM& vm, NumberFormat const& number_format, MathematicalValue const& start, MathematicalValue const& end)
|
|
{
|
|
auto& realm = *vm.current_realm();
|
|
|
|
// 1. Let parts be ? PartitionNumberRangePattern(numberFormat, x, y).
|
|
auto parts = TRY(partition_number_range_pattern(vm, number_format, start, end));
|
|
|
|
// 2. Let result be ! ArrayCreate(0).
|
|
auto result = MUST(Array::create(realm, 0));
|
|
|
|
// 3. Let n be 0.
|
|
size_t n = 0;
|
|
|
|
// 4. For each Record { [[Type]], [[Value]] } part in parts, do
|
|
for (auto& part : parts) {
|
|
// a. Let O be OrdinaryObjectCreate(%Object.prototype%).
|
|
auto object = Object::create(realm, realm.intrinsics().object_prototype());
|
|
|
|
// b. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]).
|
|
MUST(object->create_data_property_or_throw(vm.names.type, PrimitiveString::create(vm, part.type)));
|
|
|
|
// c. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]).
|
|
MUST(object->create_data_property_or_throw(vm.names.value, PrimitiveString::create(vm, move(part.value))));
|
|
|
|
// d. Perform ! CreateDataPropertyOrThrow(O, "source", part.[[Source]]).
|
|
MUST(object->create_data_property_or_throw(vm.names.source, PrimitiveString::create(vm, part.source)));
|
|
|
|
// e. Perform ! CreateDataPropertyOrThrow(result, ! ToString(n), O).
|
|
MUST(result->create_data_property_or_throw(n, object));
|
|
|
|
// f. Increment n by 1.
|
|
++n;
|
|
}
|
|
|
|
// 5. Return result.
|
|
return result;
|
|
}
|
|
|
|
}
|