Produce JS-visible string results as UTF-16 at their source, including numeric formatting, BigInt and BigFraction formatting, URI encoding, console formatting, parser errors, regular expression errors, Intl and Temporal records, LibUnicode locale boundaries, and LibWeb bindings. Handle fractional radix formatting through the UTF-16 builder view.
182 lines
6.8 KiB
C++
182 lines
6.8 KiB
C++
/*
|
|
* Copyright (c) 2021-2025, Tim Flynn <trflynn89@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <LibJS/Runtime/Intl/AbstractOperations.h>
|
|
#include <LibJS/Runtime/Intl/DisplayNames.h>
|
|
#include <LibJS/Runtime/VM.h>
|
|
|
|
namespace JS::Intl {
|
|
|
|
GC_DEFINE_ALLOCATOR(DisplayNames);
|
|
|
|
// 12 DisplayNames Objects, https://tc39.es/ecma402/#intl-displaynames-objects
|
|
DisplayNames::DisplayNames(Object& prototype)
|
|
: IntlObject(ConstructWithPrototypeTag::Tag, prototype)
|
|
{
|
|
}
|
|
|
|
// 12.2.3 Internal slots, https://tc39.es/ecma402/#sec-Intl.DisplayNames-internal-slots
|
|
ReadonlySpan<Utf16View> DisplayNames::relevant_extension_keys() const
|
|
{
|
|
// The value of the [[RelevantExtensionKeys]] internal slot is « ».
|
|
return {};
|
|
}
|
|
|
|
// 12.2.3 Internal slots, https://tc39.es/ecma402/#sec-Intl.DisplayNames-internal-slots
|
|
ReadonlySpan<ResolutionOptionDescriptor> DisplayNames::resolution_option_descriptors(VM&) const
|
|
{
|
|
// The value of the [[ResolutionOptionDescriptors]] internal slot is « ».
|
|
return {};
|
|
}
|
|
|
|
void DisplayNames::set_type(Utf16View type)
|
|
{
|
|
if (type == "language"sv)
|
|
m_type = Type::Language;
|
|
else if (type == "region"sv)
|
|
m_type = Type::Region;
|
|
else if (type == "script"sv)
|
|
m_type = Type::Script;
|
|
else if (type == "currency"sv)
|
|
m_type = Type::Currency;
|
|
else if (type == "calendar"sv)
|
|
m_type = Type::Calendar;
|
|
else if (type == "dateTimeField"sv)
|
|
m_type = Type::DateTimeField;
|
|
else
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
Utf16String DisplayNames::type_string() const
|
|
{
|
|
switch (m_type) {
|
|
case Type::Language:
|
|
return "language"_utf16;
|
|
case Type::Region:
|
|
return "region"_utf16;
|
|
case Type::Script:
|
|
return "script"_utf16;
|
|
case Type::Currency:
|
|
return "currency"_utf16;
|
|
case Type::Calendar:
|
|
return "calendar"_utf16;
|
|
case Type::DateTimeField:
|
|
return "dateTimeField"_utf16;
|
|
default:
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
}
|
|
|
|
void DisplayNames::set_fallback(Utf16View fallback)
|
|
{
|
|
if (fallback == "none"sv)
|
|
m_fallback = Fallback::None;
|
|
else if (fallback == "code"sv)
|
|
m_fallback = Fallback::Code;
|
|
else
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
Utf16String DisplayNames::fallback_string() const
|
|
{
|
|
switch (m_fallback) {
|
|
case Fallback::None:
|
|
return "none"_utf16;
|
|
case Fallback::Code:
|
|
return "code"_utf16;
|
|
default:
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
}
|
|
|
|
// 12.5.1 CanonicalCodeForDisplayNames ( type, code ), https://tc39.es/ecma402/#sec-canonicalcodefordisplaynames
|
|
ThrowCompletionOr<Value> canonical_code_for_display_names(VM& vm, DisplayNames::Type type, Utf16View code)
|
|
{
|
|
// 1. If type is "language", then
|
|
if (type == DisplayNames::Type::Language) {
|
|
// a. If code does not match the unicode_language_id production, throw a RangeError exception.
|
|
if (!Unicode::parse_unicode_language_id(code).has_value())
|
|
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "language"sv);
|
|
|
|
// b. If IsWellFormedLanguageTag(code) is false, throw a RangeError exception.
|
|
if (!is_well_formed_language_tag(code))
|
|
return vm.throw_completion<RangeError>(ErrorType::IntlInvalidLanguageTag, code);
|
|
|
|
// c. Return ! CanonicalizeUnicodeLocaleId(code).
|
|
auto canonicalized_tag = canonicalize_unicode_locale_id(code);
|
|
return PrimitiveString::create(vm, canonicalized_tag);
|
|
}
|
|
|
|
// 2. If type is "region", then
|
|
if (type == DisplayNames::Type::Region) {
|
|
// a. If code does not match the unicode_region_subtag production, throw a RangeError exception.
|
|
if (!Unicode::is_unicode_region_subtag(code))
|
|
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "region"sv);
|
|
|
|
// b. Return the ASCII-uppercase of code.
|
|
return PrimitiveString::create(vm, code.to_ascii_uppercase());
|
|
}
|
|
|
|
// 3. If type is "script", then
|
|
if (type == DisplayNames::Type::Script) {
|
|
// a. If code does not match the unicode_script_subtag production, throw a RangeError exception.
|
|
if (!Unicode::is_unicode_script_subtag(code))
|
|
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "script"sv);
|
|
|
|
// Assert: The length of code is 4, and every code unit of code represents an ASCII letter (0x0041 through 0x005A and 0x0061 through 0x007A, both inclusive).
|
|
VERIFY(code.length_in_code_units() == 4);
|
|
VERIFY(all_of(code, is_ascii_alpha));
|
|
|
|
// c. Let first be the ASCII-uppercase of the substring of code from 0 to 1.
|
|
// d. Let rest be the ASCII-lowercase of the substring of code from 1.
|
|
// e. Return the string-concatenation of first and rest.
|
|
return PrimitiveString::create(vm, code.to_ascii_titlecase());
|
|
}
|
|
|
|
// 4. If type is "calendar", then
|
|
if (type == DisplayNames::Type::Calendar) {
|
|
// a. If code does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
|
|
if (!Unicode::is_type_identifier(code))
|
|
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "calendar"sv);
|
|
|
|
// b. If code uses any of the backwards compatibility syntax described in Unicode Technical Standard #35 LDML § 3.3 BCP 47 Conformance, throw a RangeError exception.
|
|
if (code.contains(u"_"sv))
|
|
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "calendar"sv);
|
|
|
|
// c. Return the ASCII-lowercase of code.
|
|
return PrimitiveString::create(vm, code.to_ascii_lowercase());
|
|
}
|
|
|
|
// 5. If type is "dateTimeField", then
|
|
if (type == DisplayNames::Type::DateTimeField) {
|
|
// a. If the result of IsValidDateTimeFieldCode(code) is false, throw a RangeError exception.
|
|
if (!is_valid_date_time_field_code(code))
|
|
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "dateTimeField"sv);
|
|
|
|
// b. Return code.
|
|
return PrimitiveString::create(vm, Utf16String::from_utf16(code));
|
|
}
|
|
|
|
// 6. Assert: type is "currency".
|
|
VERIFY(type == DisplayNames::Type::Currency);
|
|
|
|
// 7. If ! IsWellFormedCurrencyCode(code) is false, throw a RangeError exception.
|
|
if (!is_well_formed_currency_code(code))
|
|
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "currency"sv);
|
|
|
|
// 8. Return the ASCII-uppercase of code.
|
|
return PrimitiveString::create(vm, code.to_ascii_uppercase());
|
|
}
|
|
|
|
// 12.5.2 IsValidDateTimeFieldCode ( field ), https://tc39.es/ecma402/#sec-isvaliddatetimefieldcode
|
|
bool is_valid_date_time_field_code(Utf16View field)
|
|
{
|
|
// 1. If field is listed in the Code column of Table 19, return true.
|
|
// 2. Return false.
|
|
return field.is_one_of("era"sv, "year"sv, "quarter"sv, "month"sv, "weekOfYear"sv, "weekday"sv, "day"sv, "dayPeriod"sv, "hour"sv, "minute"sv, "second"sv, "timeZoneName"sv);
|
|
}
|
|
|
|
}
|