/* * Copyright (c) 2021-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 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 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 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> 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(ErrorType::NumberIsNaN, "start"sv); if (end.is_nan()) return vm.throw_completion(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 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(ErrorType::NumberIsNaN, "start"sv); if (end.is_nan()) return vm.throw_completion(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> 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; } }