Libraries: Parse JS strings from UTF-16

Thread UTF-16 string input through JSON, script parsing, Date parsing,
Intl option parsing, Temporal parsing, and the helper library boundaries
that feed those parsers. Preserve ASCII fast paths where the source data
is known to be ASCII.
This commit is contained in:
Andreas Kling 2026-06-21 19:03:06 +02:00 committed by Andreas Kling
parent ee37bb5a9c
commit 7025dd1fa7
131 changed files with 1498 additions and 670 deletions

View file

@ -16,6 +16,13 @@ size_t size_required_to_decode_base64(StringView input)
return simdutf::maximal_binary_length_from_base64(input.characters_without_null_termination(), input.length());
}
size_t size_required_to_decode_base64(Utf16View input)
{
if (input.has_ascii_storage())
return size_required_to_decode_base64(StringView { input.bytes() });
return simdutf::maximal_binary_length_from_base64(input.utf16_span().data(), input.length_in_code_units());
}
static constexpr simdutf::last_chunk_handling_options to_simdutf_last_chunk_handling(LastChunkHandling last_chunk_handling)
{
switch (last_chunk_handling) {
@ -30,6 +37,29 @@ static constexpr simdutf::last_chunk_handling_options to_simdutf_last_chunk_hand
VERIFY_NOT_REACHED();
}
static Optional<InvalidBase64> base64_error_from_result(simdutf::result result, ByteBuffer& output)
{
if (result.error == simdutf::SUCCESS || result.error == simdutf::OUTPUT_BUFFER_TOO_SMALL)
return {};
output.resize((result.count / 4) * 3);
auto error = [&]() {
switch (result.error) {
case simdutf::BASE64_EXTRA_BITS:
return Error::from_string_literal("Extra bits found at end of chunk");
case simdutf::BASE64_INPUT_REMAINDER:
return Error::from_string_literal("Invalid trailing data");
case simdutf::INVALID_BASE64_CHARACTER:
return Error::from_string_literal("Invalid base64 character");
default:
return Error::from_string_literal("Invalid base64-encoded data");
}
}();
return InvalidBase64 { .error = move(error), .valid_input_bytes = result.count };
}
static ErrorOr<size_t, InvalidBase64> decode_base64_into_impl(StringView input, ByteBuffer& output, LastChunkHandling last_chunk_handling, simdutf::base64_options options)
{
static constexpr auto decode_up_to_bad_character = true;
@ -44,24 +74,34 @@ static ErrorOr<size_t, InvalidBase64> decode_base64_into_impl(StringView input,
to_simdutf_last_chunk_handling(last_chunk_handling),
decode_up_to_bad_character);
if (result.error != simdutf::SUCCESS && result.error != simdutf::OUTPUT_BUFFER_TOO_SMALL) {
output.resize((result.count / 4) * 3);
if (auto error = base64_error_from_result(result, output); error.has_value())
return error.release_value();
auto error = [&]() {
switch (result.error) {
case simdutf::BASE64_EXTRA_BITS:
return Error::from_string_literal("Extra bits found at end of chunk");
case simdutf::BASE64_INPUT_REMAINDER:
return Error::from_string_literal("Invalid trailing data");
case simdutf::INVALID_BASE64_CHARACTER:
return Error::from_string_literal("Invalid base64 character");
default:
return Error::from_string_literal("Invalid base64-encoded data");
}
}();
VERIFY(output_length <= output.size());
output.resize(output_length);
return InvalidBase64 { .error = move(error), .valid_input_bytes = result.count };
}
return result.count;
}
static ErrorOr<size_t, InvalidBase64> decode_base64_into_impl(Utf16View input, ByteBuffer& output, LastChunkHandling last_chunk_handling, simdutf::base64_options options)
{
if (input.has_ascii_storage())
return decode_base64_into_impl(StringView { input.bytes() }, output, last_chunk_handling, options);
static constexpr auto decode_up_to_bad_character = true;
auto output_length = output.size();
auto result = simdutf::base64_to_binary_safe(
input.utf16_span().data(),
input.length_in_code_units(),
reinterpret_cast<char*>(output.data()),
output_length,
options,
to_simdutf_last_chunk_handling(last_chunk_handling),
decode_up_to_bad_character);
if (auto error = base64_error_from_result(result, output); error.has_value())
return error.release_value();
VERIFY(output_length <= output.size());
output.resize(output_length);
@ -118,6 +158,16 @@ ErrorOr<size_t, InvalidBase64> decode_base64url_into(StringView input, ByteBuffe
return decode_base64_into_impl(input, output, last_chunk_handling, simdutf::base64_url);
}
ErrorOr<size_t, InvalidBase64> decode_base64_into(Utf16View input, ByteBuffer& output, LastChunkHandling last_chunk_handling)
{
return decode_base64_into_impl(input, output, last_chunk_handling, simdutf::base64_default);
}
ErrorOr<size_t, InvalidBase64> decode_base64url_into(Utf16View input, ByteBuffer& output, LastChunkHandling last_chunk_handling)
{
return decode_base64_into_impl(input, output, last_chunk_handling, simdutf::base64_url);
}
ErrorOr<String> encode_base64(ReadonlyBytes input, OmitPadding omit_padding)
{
auto options = omit_padding == OmitPadding::Yes

View file

@ -10,10 +10,12 @@
#include <AK/Error.h>
#include <AK/String.h>
#include <AK/StringView.h>
#include <AK/Utf16View.h>
namespace AK {
size_t size_required_to_decode_base64(StringView);
size_t size_required_to_decode_base64(Utf16View);
enum class LastChunkHandling {
Loose,
@ -33,6 +35,8 @@ struct InvalidBase64 {
// string length if the output buffer was not large enough.
ErrorOr<size_t, InvalidBase64> decode_base64_into(StringView, ByteBuffer&, LastChunkHandling = LastChunkHandling::Loose);
ErrorOr<size_t, InvalidBase64> decode_base64url_into(StringView, ByteBuffer&, LastChunkHandling = LastChunkHandling::Loose);
ErrorOr<size_t, InvalidBase64> decode_base64_into(Utf16View, ByteBuffer&, LastChunkHandling = LastChunkHandling::Loose);
ErrorOr<size_t, InvalidBase64> decode_base64url_into(Utf16View, ByteBuffer&, LastChunkHandling = LastChunkHandling::Loose);
enum class OmitPadding {
No,

View file

@ -198,7 +198,7 @@ template<>
struct Formatter<Utf16FlyString> : Formatter<Utf16String> {
ErrorOr<void> format(FormatBuilder& builder, Utf16FlyString const& string)
{
return Formatter<Utf16String>::format(builder, string.to_utf16_string());
return Formatter<Utf16String> {}.format(builder, string.to_utf16_string());
}
};

View file

@ -127,6 +127,33 @@ ErrorOr<SignedBigInteger> SignedBigInteger::from_base(u16 N, StringView str)
return result;
}
ErrorOr<SignedBigInteger> SignedBigInteger::from_base(u16 N, Utf16View str)
{
VERIFY(N <= 36);
if (str.is_empty())
return SignedBigInteger(0);
auto buffer = TRY(ByteBuffer::create_zeroed(str.length_in_code_units() + 1));
size_t idx = 0;
for (size_t i = 0; i < str.length_in_code_units(); ++i) {
auto code_unit = str.code_unit_at(i);
if (code_unit > NumericLimits<u8>::max())
return Error::from_string_literal("Invalid number");
if (code_unit == '_') {
// Skip underscores
continue;
}
buffer[idx++] = static_cast<u8>(code_unit);
}
SignedBigInteger result;
if (mp_read_radix(&result.m_mp, reinterpret_cast<char const*>(buffer.data()), N) != MP_OKAY)
return Error::from_string_literal("Invalid number");
return result;
}
ErrorOr<String> SignedBigInteger::to_base(u16 N) const
{
VERIFY(N <= 36);

View file

@ -42,6 +42,7 @@ public:
[[nodiscard]] Bytes export_data(Bytes) const;
[[nodiscard]] static ErrorOr<SignedBigInteger> from_base(u16 N, StringView str);
[[nodiscard]] static ErrorOr<SignedBigInteger> from_base(u16 N, Utf16View str);
[[nodiscard]] ErrorOr<String> to_base(u16 N) const;
[[nodiscard]] i64 to_i64() const;
@ -117,5 +118,5 @@ struct AK::Formatter<Crypto::SignedBigInteger> : AK::Formatter<Crypto::UnsignedB
inline Crypto::SignedBigInteger
operator""_sbigint(char const* string, size_t length)
{
return MUST(Crypto::SignedBigInteger::from_base(10, { string, length }));
return MUST(Crypto::SignedBigInteger::from_base(10, StringView { string, length }));
}

View file

@ -129,6 +129,33 @@ ErrorOr<UnsignedBigInteger> UnsignedBigInteger::from_base(u16 N, StringView str)
return result;
}
ErrorOr<UnsignedBigInteger> UnsignedBigInteger::from_base(u16 N, Utf16View str)
{
VERIFY(N <= 36);
if (str.is_empty())
return UnsignedBigInteger(0);
auto buffer = TRY(ByteBuffer::create_zeroed(str.length_in_code_units() + 1));
size_t idx = 0;
for (size_t i = 0; i < str.length_in_code_units(); ++i) {
auto code_unit = str.code_unit_at(i);
if (code_unit > NumericLimits<u8>::max())
return Error::from_string_literal("Invalid number");
if (code_unit == '_') {
// Skip underscores
continue;
}
buffer[idx++] = static_cast<u8>(code_unit);
}
UnsignedBigInteger result;
if (mp_read_radix(&result.m_mp, reinterpret_cast<char const*>(buffer.data()), N) != MP_OKAY)
return Error::from_string_literal("Invalid number");
return result;
}
ErrorOr<String> UnsignedBigInteger::to_base(u16 N) const
{
VERIFY(N <= 36);

View file

@ -10,6 +10,7 @@
#pragma once
#include <AK/String.h>
#include <AK/Utf16View.h>
#include <LibCrypto/BigInt/TommathForward.h>
namespace Crypto {
@ -44,6 +45,7 @@ public:
[[nodiscard]] Bytes export_data(Bytes) const;
[[nodiscard]] static ErrorOr<UnsignedBigInteger> from_base(u16 N, StringView str);
[[nodiscard]] static ErrorOr<UnsignedBigInteger> from_base(u16 N, Utf16View str);
[[nodiscard]] ErrorOr<String> to_base(u16 N) const;
[[nodiscard]] size_t count_digits_in_base(u16 base) const;
@ -125,7 +127,7 @@ struct AK::Formatter<Crypto::UnsignedBigInteger> : Formatter<StringView> {
inline Crypto::UnsignedBigInteger operator""_bigint(char const* string, size_t length)
{
return MUST(Crypto::UnsignedBigInteger::from_base(10, { string, length }));
return MUST(Crypto::UnsignedBigInteger::from_base(10, StringView { string, length }));
}
inline Crypto::UnsignedBigInteger operator""_bigint(unsigned long long value)

View file

@ -401,7 +401,7 @@ inline ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value thi
if (!succeeded && strict == Strict::Yes) [[unlikely]] {
if (base.is_object())
return vm.throw_completion<TypeError>(ErrorType::ReferenceNullishSetProperty, name, base);
return vm.throw_completion<TypeError>(ErrorType::ReferencePrimitiveSetProperty, name, base.typeof_(vm)->utf16_string_view().to_utf8_but_should_be_ported_to_utf16(), base);
return vm.throw_completion<TypeError>(ErrorType::ReferencePrimitiveSetProperty, name, base.typeof_(vm)->utf16_string_view(), base);
}
break;
}

View file

@ -446,11 +446,11 @@ ThrowCompletionOr<Value> Console::dirxml()
return js_undefined();
}
static ThrowCompletionOr<String> label_or_fallback(VM& vm, StringView fallback)
static ThrowCompletionOr<Utf16String> label_or_fallback(VM& vm, Utf16View fallback)
{
return vm.argument_count() > 0 && !vm.argument(0).is_undefined()
? TRY(vm.argument(0).to_utf16_string(vm)).to_utf8_but_should_be_ported_to_utf16()
: TRY_OR_THROW_OOM(vm, String::from_utf8(fallback));
? TRY(vm.argument(0).to_utf16_string(vm))
: Utf16String::from_utf16(fallback);
}
// 1.2.1. count(label), https://console.spec.whatwg.org/#count
@ -474,7 +474,7 @@ ThrowCompletionOr<Value> Console::count()
}
// 4. Let concat be the concatenation of label, U+003A (:), U+0020 SPACE, and ToString(map[label]).
auto concat = TRY_OR_THROW_OOM(vm, String::formatted("{}: {}", label, map.get(label).value()));
auto concat = Utf16String::formatted("{}: {}", label, map.get(label).value());
// 5. Perform Logger("count", « concat »).
GC::RootVector<Value> concat_as_vector;
@ -503,7 +503,7 @@ ThrowCompletionOr<Value> Console::count_reset()
else {
// 1. Let message be a string without any formatting specifiers indicating generically
// that the given label does not have an associated count.
auto message = TRY_OR_THROW_OOM(vm, String::formatted("\"{}\" doesn't have a count", label));
auto message = Utf16String::formatted("\"{}\" doesn't have a count", label);
// 2. Perform Logger("countReset", « message »);
GC::RootVector<Value> message_as_vector;
message_as_vector.append(PrimitiveString::create(vm, move(message)));
@ -521,7 +521,7 @@ ThrowCompletionOr<Value> Console::group()
Group group;
// 2. If data is not empty, let groupLabel be the result of Formatter(data).
String group_label {};
Utf16String group_label {};
auto data = vm_arguments();
if (!data.is_empty()) {
if (m_client) {
@ -533,7 +533,7 @@ ThrowCompletionOr<Value> Console::group()
}
// ... Otherwise, let groupLabel be an implementation-chosen label representing a group.
else {
group_label = "Group"_string;
group_label = "Group"_utf16;
}
// 3. Incorporate groupLabel as a label for group.
@ -559,7 +559,7 @@ ThrowCompletionOr<Value> Console::group_collapsed()
Group group;
// 2. If data is not empty, let groupLabel be the result of Formatter(data).
String group_label {};
Utf16String group_label {};
auto data = vm_arguments();
if (!data.is_empty()) {
if (m_client) {
@ -571,7 +571,7 @@ ThrowCompletionOr<Value> Console::group_collapsed()
}
// ... Otherwise, let groupLabel be an implementation-chosen label representing a group.
else {
group_label = "Group"_string;
group_label = "Group"_utf16;
}
// 3. Incorporate groupLabel as a label for group.
@ -618,7 +618,7 @@ ThrowCompletionOr<Value> Console::time()
if (m_client) {
GC::RootVector<Value> timer_already_exists_warning_message_as_vector;
auto message = TRY_OR_THROW_OOM(vm, String::formatted("Timer '{}' already exists.", label));
auto message = Utf16String::formatted("Timer '{}' already exists.", label);
timer_already_exists_warning_message_as_vector.append(PrimitiveString::create(vm, move(message)));
TRY(m_client->printer(LogLevel::Warn, move(timer_already_exists_warning_message_as_vector)));
@ -649,7 +649,7 @@ ThrowCompletionOr<Value> Console::time_log()
if (m_client) {
GC::RootVector<Value> timer_does_not_exist_warning_message_as_vector;
auto message = TRY_OR_THROW_OOM(vm, String::formatted("Timer '{}' does not exist.", label));
auto message = Utf16String::formatted("Timer '{}' does not exist.", label);
timer_does_not_exist_warning_message_as_vector.append(PrimitiveString::create(vm, move(message)));
TRY(m_client->printer(LogLevel::Warn, move(timer_does_not_exist_warning_message_as_vector)));
@ -662,7 +662,7 @@ ThrowCompletionOr<Value> Console::time_log()
auto duration = AK::human_readable_time(start_time.elapsed_time());
// 4. Let concat be the concatenation of label, U+003A (:), U+0020 SPACE, and duration.
auto concat = TRY_OR_THROW_OOM(vm, String::formatted("{}: {}", label, duration));
auto concat = Utf16String::formatted("{}: {}", label, duration);
// 5. Prepend concat to data.
GC::RootVector<Value> data;
@ -695,7 +695,7 @@ ThrowCompletionOr<Value> Console::time_end()
if (m_client) {
GC::RootVector<Value> timer_does_not_exist_warning_message_as_vector;
auto message = TRY_OR_THROW_OOM(vm, String::formatted("Timer '{}' does not exist.", label));
auto message = Utf16String::formatted("Timer '{}' does not exist.", label);
timer_does_not_exist_warning_message_as_vector.append(PrimitiveString::create(vm, move(message)));
TRY(m_client->printer(LogLevel::Warn, move(timer_does_not_exist_warning_message_as_vector)));
@ -711,7 +711,7 @@ ThrowCompletionOr<Value> Console::time_end()
auto duration = AK::human_readable_time(start_time.elapsed_time());
// 5. Let concat be the concatenation of label, U+003A (:), U+0020 SPACE, and duration.
auto concat = TRY_OR_THROW_OOM(vm, String::formatted("{}: {}", label, duration));
auto concat = Utf16String::formatted("{}: {}", label, duration);
// 6. Perform Printer("timeEnd", « concat »).
if (m_client) {
@ -758,25 +758,49 @@ void Console::output_debug_message(LogLevel log_level, StringView output) const
}
}
void Console::output_debug_message(LogLevel log_level, Utf16View output) const
{
switch (log_level) {
case Console::LogLevel::Debug:
dbgln("\033[32;1m(js debug)\033[0m {}", output);
break;
case Console::LogLevel::Error:
dbgln("\033[32;1m(js error)\033[0m {}", output);
break;
case Console::LogLevel::Info:
dbgln("\033[32;1m(js info)\033[0m {}", output);
break;
case Console::LogLevel::Log:
dbgln("\033[32;1m(js log)\033[0m {}", output);
break;
case Console::LogLevel::Warn:
dbgln("\033[32;1m(js warn)\033[0m {}", output);
break;
default:
dbgln("\033[32;1m(js)\033[0m {}", output);
break;
}
}
void Console::report_exception(String const& name, String const& message, JS::ErrorData const& error_data, bool in_promise) const
{
if (m_client)
m_client->report_exception(name, message, error_data, in_promise);
}
ThrowCompletionOr<String> Console::value_vector_to_string(GC::RootVector<Value> const& values)
ThrowCompletionOr<Utf16String> Console::value_vector_to_string(GC::RootVector<Value> const& values)
{
auto& vm = realm().vm();
StringBuilder builder;
Utf16StringBuilder builder;
for (auto const& item : values) {
if (!builder.is_empty())
builder.append(' ');
builder.append_ascii(' ');
builder.append(TRY(item.to_utf16_string(vm)));
}
return MUST(builder.to_string());
return builder.to_string();
}
ConsoleClient::ConsoleClient(Console& console)
@ -832,24 +856,24 @@ ThrowCompletionOr<GC::RootVector<Value>> ConsoleClient::formatter(GC::RootVector
return args;
// 2. Let target be the first element of args.
auto target = (!args.is_empty()) ? TRY(args.first().to_utf16_string(vm)).to_utf8_but_should_be_ported_to_utf16() : String {};
auto target = (!args.is_empty()) ? TRY(args.first().to_utf16_string(vm)) : Utf16String {};
// 3. Let current be the second element of args.
auto current = (args.size() > 1) ? args[1] : js_undefined();
// 4. Find the first possible format specifier specifier, from the left to the right in target.
auto find_specifier = [](StringView target) -> Optional<StringView> {
auto find_specifier = [](Utf16View target) -> Optional<Utf16View> {
size_t start_index = 0;
while (start_index < target.length()) {
auto maybe_index = target.find('%', start_index);
while (start_index < target.length_in_code_units()) {
auto maybe_index = target.find_code_unit_offset('%', start_index);
if (!maybe_index.has_value())
return {};
auto index = maybe_index.value();
if (index + 1 >= target.length())
if (index + 1 >= target.length_in_code_units())
return {};
switch (target[index + 1]) {
switch (target.code_unit_at(index + 1)) {
case 'c':
case 'd':
case 'f':
@ -864,7 +888,7 @@ ThrowCompletionOr<GC::RootVector<Value>> ConsoleClient::formatter(GC::RootVector
}
return {};
};
auto maybe_specifier = find_specifier(target);
auto maybe_specifier = find_specifier(target.utf16_view());
// 5. If no format specifier was found, return args.
if (!maybe_specifier.has_value()) {
@ -914,13 +938,16 @@ ThrowCompletionOr<GC::RootVector<Value>> ConsoleClient::formatter(GC::RootVector
// 6. TODO: process %c
else if (specifier == "%c"sv) {
// NOTE: This has no spec yet. `%c` specifiers treat the argument as CSS styling for the log message.
add_css_style_to_current_message(TRY(current.to_utf16_string(vm)).to_utf8_but_should_be_ported_to_utf16());
converted = PrimitiveString::create(vm, String {});
auto css_style = TRY(current.to_utf16_string(vm));
add_css_style_to_current_message(css_style.utf16_view());
converted = PrimitiveString::create(vm, Utf16String {});
}
// 7. If any of the previous steps set converted, replace specifier in target with converted.
if (converted.has_value())
target = TRY_OR_THROW_OOM(vm, target.replace(specifier, TRY(converted->to_utf16_string(vm)).to_utf8_but_should_be_ported_to_utf16(), ReplaceMode::FirstOnly));
if (converted.has_value()) {
auto converted_string = TRY(converted->to_utf16_string(vm));
target = target.replace(specifier, converted_string.utf16_view(), ReplaceMode::FirstOnly);
}
}
// 7. Let result be a list containing target together with the elements of args starting from the third onward.
@ -934,7 +961,7 @@ ThrowCompletionOr<GC::RootVector<Value>> ConsoleClient::formatter(GC::RootVector
return formatter(result);
}
ThrowCompletionOr<String> ConsoleClient::generically_format_values(GC::RootVector<Value> const& values)
ThrowCompletionOr<Utf16String> ConsoleClient::generically_format_values(GC::RootVector<Value> const& values)
{
AllocatingMemoryStream stream;
auto& vm = m_console->realm().vm();
@ -947,7 +974,8 @@ ThrowCompletionOr<String> ConsoleClient::generically_format_values(GC::RootVecto
first = false;
}
// FIXME: Is it possible we could end up serializing objects to invalid UTF-8?
return TRY_OR_THROW_OOM(vm, String::from_stream(stream, stream.used_buffer_size()));
auto output = TRY_OR_THROW_OOM(vm, String::from_stream(stream, stream.used_buffer_size()));
return Utf16String::from_utf8(output);
}
}

View file

@ -11,6 +11,8 @@
#include <AK/HashMap.h>
#include <AK/Noncopyable.h>
#include <AK/String.h>
#include <AK/Utf16String.h>
#include <AK/Utf16View.h>
#include <AK/Vector.h>
#include <LibCore/ElapsedTimer.h>
#include <LibGC/CellAllocator.h>
@ -52,7 +54,7 @@ public:
};
struct Group {
String label;
Utf16String label;
};
struct TraceFrame {
@ -63,7 +65,7 @@ public:
};
struct Trace {
String label;
Utf16String label;
Vector<TraceFrame> stack;
};
@ -73,8 +75,8 @@ public:
GC::RootVector<Value> vm_arguments();
HashMap<String, unsigned>& counters() { return m_counters; }
HashMap<String, unsigned> const& counters() const { return m_counters; }
HashMap<Utf16String, unsigned>& counters() { return m_counters; }
HashMap<Utf16String, unsigned> const& counters() const { return m_counters; }
ThrowCompletionOr<Value> assert_();
Value clear();
@ -97,6 +99,7 @@ public:
ThrowCompletionOr<Value> time_end();
void output_debug_message(LogLevel log_level, StringView output) const;
void output_debug_message(LogLevel log_level, Utf16View output) const;
void report_exception(String const& name, String const& message, JS::ErrorData const&, bool) const;
private:
@ -104,13 +107,13 @@ private:
virtual void visit_edges(Visitor&) override;
ThrowCompletionOr<String> value_vector_to_string(GC::RootVector<Value> const&);
ThrowCompletionOr<Utf16String> value_vector_to_string(GC::RootVector<Value> const&);
GC::Ref<Realm> m_realm;
GC::Ptr<ConsoleClient> m_client;
HashMap<String, unsigned> m_counters;
HashMap<String, Core::ElapsedTimer> m_timer_table;
HashMap<Utf16String, unsigned> m_counters;
HashMap<Utf16String, Core::ElapsedTimer> m_timer_table;
Vector<Group> m_group_stack;
};
@ -125,13 +128,13 @@ public:
ThrowCompletionOr<GC::RootVector<Value>> formatter(GC::RootVector<Value> const& args);
virtual ThrowCompletionOr<Value> printer(Console::LogLevel log_level, PrinterArguments) = 0;
virtual void add_css_style_to_current_message(StringView) { }
virtual void add_css_style_to_current_message(Utf16View) { }
virtual void report_exception(String const&, String const&, JS::ErrorData const&, bool) { }
virtual void clear() = 0;
virtual void end_group() = 0;
ThrowCompletionOr<String> generically_format_values(GC::RootVector<Value> const&);
ThrowCompletionOr<Utf16String> generically_format_values(GC::RootVector<Value> const&);
protected:
explicit ConsoleClient(Console&);

View file

@ -100,7 +100,7 @@ JS_DEFINE_NATIVE_FUNCTION($262Object::eval_script)
auto& realm = *vm.current_realm();
// 3. Let s be ParseScript(sourceText, realm, hostDefined).
auto script_or_error = Script::parse(source_text.to_utf8_but_should_be_ported_to_utf16(), realm);
auto script_or_error = Script::parse(source_text.utf16_view(), realm);
// 4. If s is a List of errors, then
if (script_or_error.is_error()) {

View file

@ -8,6 +8,7 @@
#include <AK/Concepts.h>
#include <AK/Stream.h>
#include <AK/Utf16StringBuilder.h>
#include <LibJS/Print.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/ArrayBuffer.h>
@ -62,31 +63,32 @@
namespace {
static ErrorOr<String> escape_for_string_literal(StringView string)
static ErrorOr<Utf16String> escape_for_string_literal(Utf16View string)
{
StringBuilder builder;
for (auto byte : string.bytes()) {
switch (byte) {
Utf16StringBuilder builder;
for (size_t i = 0; i < string.length_in_code_units(); ++i) {
auto code_unit = string.code_unit_at(i);
switch (code_unit) {
case '\r':
TRY(builder.try_append("\\r"sv));
builder.append_ascii("\\r"sv);
continue;
case '\v':
TRY(builder.try_append("\\v"sv));
builder.append_ascii("\\v"sv);
continue;
case '\f':
TRY(builder.try_append("\\f"sv));
builder.append_ascii("\\f"sv);
continue;
case '\b':
TRY(builder.try_append("\\b"sv));
builder.append_ascii("\\b"sv);
continue;
case '\n':
TRY(builder.try_append("\\n"sv));
builder.append_ascii("\\n"sv);
continue;
case '\\':
TRY(builder.try_append("\\\\"sv));
builder.append_ascii("\\\\"sv);
continue;
default:
TRY(builder.try_append(byte));
builder.append_code_unit(code_unit);
continue;
}
}
@ -151,6 +153,11 @@ ErrorOr<void> print_type(JS::PrintContext& print_context, StringView name)
return js_out(print_context, "[\033[36;1m{}\033[0m]", name);
}
ErrorOr<void> print_type(JS::PrintContext& print_context, Utf16View name)
{
return js_out(print_context, "[\033[36;1m{}\033[0m]", name);
}
ErrorOr<void> print_separator(JS::PrintContext& print_context, bool& first)
{
TRY(js_out(print_context, "{}", first ? " "sv : ", "sv));
@ -278,9 +285,9 @@ ErrorOr<void> print_error(JS::PrintContext& print_context, JS::Object const& obj
if (name.is_accessor() || message.is_accessor()) {
TRY(print_value(print_context, &object, seen_objects));
} else {
auto name_string = name.to_string_without_side_effects();
auto message_string = message.to_string_without_side_effects();
TRY(print_type(print_context, name_string));
auto name_string = name.to_utf16_string_without_side_effects();
auto message_string = message.to_utf16_string_without_side_effects();
TRY(print_type(print_context, name_string.utf16_view()));
if (!message_string.is_empty())
TRY(js_out(print_context, " \033[31;1m{}\033[0m", message_string));
}
@ -1033,9 +1040,9 @@ ErrorOr<void> print_value(JS::PrintContext& print_context, JS::Value value, GC::
else if (value.is_negative_zero())
TRY(js_out(print_context, "-"));
auto contents = value.to_string_without_side_effects();
auto contents = value.to_utf16_string_without_side_effects();
if (value.is_string() && !print_context.raw_strings)
TRY(js_out(print_context, "{}", TRY(escape_for_string_literal(contents))));
TRY(js_out(print_context, "{}", TRY(escape_for_string_literal(contents.utf16_view()))));
else
TRY(js_out(print_context, "{}", contents));

View file

@ -646,8 +646,8 @@ ThrowCompletionOr<Value> perform_eval(VM& vm, Value x, CallerMode strict_caller,
// 6. NOTE: In the case of a direct eval, evalRealm is the realm of both the caller of eval and of the eval function itself.
// 7. Perform ? HostEnsureCanCompileStrings(evalRealm, « », xStr, xStr, direct, « », x).
auto code_string_utf8 = code_string->utf16_string_view().to_utf8_but_should_be_ported_to_utf16();
TRY(vm.host_ensure_can_compile_strings(eval_realm, {}, code_string_utf8, code_string_utf8, direct == EvalMode::Direct ? CompilationType::DirectEval : CompilationType::IndirectEval, {}, x));
auto code_string_view = code_string->utf16_string_view();
TRY(vm.host_ensure_can_compile_strings(eval_realm, {}, code_string_view, code_string_view, direct == EvalMode::Direct ? CompilationType::DirectEval : CompilationType::IndirectEval, {}, x));
// 8. Let inFunction be false.
bool in_function = false;
@ -1959,7 +1959,7 @@ ThrowCompletionOr<Value> get_option(VM& vm, Object const& options, PropertyKey c
auto value_string = value.as_string().utf16_string_view();
auto it = find_if(values.begin(), values.end(), [&](auto allowed_value) { return value_string == allowed_value; });
if (it == values.end())
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, value_string.to_utf8_but_should_be_ported_to_utf16(), property.as_string());
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, value_string, property.as_string());
value = PrimitiveString::create(vm, *it);
}

View file

@ -457,7 +457,8 @@ String system_time_zone_identifier()
// time zone identifier or an offset time zone identifier.
auto system_time_zone_string = Unicode::current_time_zone();
if (!is_offset_time_zone_identifier(system_time_zone_string)) {
auto utf16_system_time_zone_string = Utf16String::from_utf8(system_time_zone_string);
if (!is_offset_time_zone_identifier(utf16_system_time_zone_string)) {
auto time_zone_identifier = Intl::get_available_named_time_zone_identifier(system_time_zone_string);
if (!time_zone_identifier.has_value())
return "UTC"_string;
@ -662,11 +663,10 @@ double time_clip(double time)
// 21.4.1.33.1 IsTimeZoneOffsetString ( offsetString ), https://tc39.es/ecma262/#sec-istimezoneoffsetstring
// 14.5.10 IsOffsetTimeZoneIdentifier ( offsetString ), https://tc39.es/proposal-temporal/#sec-isoffsettimezoneidentifier
bool is_offset_time_zone_identifier(StringView offset_string)
bool is_offset_time_zone_identifier(Utf16View offset_string)
{
// 1. Let parseResult be ParseText(StringToCodePoints(offsetString), UTCOffset[~SubMinutePrecision]).
auto utf16_offset_string = Utf16String::from_utf8(offset_string);
auto parse_result = Temporal::parse_utc_offset(utf16_offset_string, Temporal::SubMinutePrecision::No);
auto parse_result = Temporal::parse_utc_offset(offset_string, Temporal::SubMinutePrecision::No);
// 2. If parseResult is a List of errors, return false.
// 3. Return true.
@ -675,11 +675,10 @@ bool is_offset_time_zone_identifier(StringView offset_string)
// 21.4.1.33.2 ParseTimeZoneOffsetString ( offsetString ), https://tc39.es/ecma262/#sec-parsetimezoneoffsetstring
// 14.5.11 ParseDateTimeUTCOffset ( offsetString ), https://tc39.es/proposal-temporal/#sec-parsedatetimeutcoffset
ThrowCompletionOr<double> parse_date_time_utc_offset(VM& vm, StringView offset_string)
ThrowCompletionOr<double> parse_date_time_utc_offset(VM& vm, Utf16View offset_string)
{
// 1. Let parseResult be ParseText(offsetString, UTCOffset[+SubMinutePrecision]).
auto utf16_offset_string = Utf16String::from_utf8(offset_string);
auto parse_result = Temporal::parse_utc_offset(utf16_offset_string, Temporal::SubMinutePrecision::Yes);
auto parse_result = Temporal::parse_utc_offset(offset_string, Temporal::SubMinutePrecision::Yes);
// 2. If parseResult is a List of errors, throw a RangeError exception.
if (!parse_result.has_value())
@ -690,13 +689,12 @@ ThrowCompletionOr<double> parse_date_time_utc_offset(VM& vm, StringView offset_s
// 21.4.1.33.2 ParseTimeZoneOffsetString ( offsetString ), https://tc39.es/ecma262/#sec-parsetimezoneoffsetstring
// 14.5.11 ParseDateTimeUTCOffset ( offsetString ), https://tc39.es/proposal-temporal/#sec-parsedatetimeutcoffset
double parse_date_time_utc_offset(StringView offset_string)
double parse_date_time_utc_offset(Utf16View offset_string)
{
// OPTIMIZATION: Some callers can assume that parsing will succeed.
// 1. Let parseResult be ParseText(offsetString, UTCOffset[+SubMinutePrecision]).
auto utf16_offset_string = Utf16String::from_utf8(offset_string);
auto parse_result = Temporal::parse_utc_offset(utf16_offset_string, Temporal::SubMinutePrecision::Yes);
auto parse_result = Temporal::parse_utc_offset(offset_string, Temporal::SubMinutePrecision::Yes);
VERIFY(parse_result.has_value());
return parse_date_time_utc_offset(*parse_result);

View file

@ -7,6 +7,7 @@
#pragma once
#include <AK/Utf16View.h>
#include <LibCrypto/BigInt/SignedBigInteger.h>
#include <LibJS/Export.h>
#include <LibJS/Runtime/Object.h>
@ -97,9 +98,9 @@ JS_API double make_time(double hour, double min, double sec, double ms);
JS_API double make_day(double year, double month, double date);
JS_API double make_date(double day, double time);
double time_clip(double time);
bool is_offset_time_zone_identifier(StringView offset_string);
ThrowCompletionOr<double> parse_date_time_utc_offset(VM&, StringView offset_string);
double parse_date_time_utc_offset(StringView offset_string);
bool is_offset_time_zone_identifier(Utf16View offset_string);
ThrowCompletionOr<double> parse_date_time_utc_offset(VM&, Utf16View offset_string);
double parse_date_time_utc_offset(Utf16View offset_string);
double parse_date_time_utc_offset(Temporal::TimeZoneOffset const&);
}

View file

@ -24,7 +24,7 @@ namespace JS {
GC_DEFINE_ALLOCATOR(DateConstructor);
static double parse_date_string(VM& vm, StringView date_string)
static double parse_date_string(VM& vm, Utf16View date_string)
{
double result = DateParser::parse(date_string);
if (result == NAN)
@ -33,12 +33,6 @@ static double parse_date_string(VM& vm, StringView date_string)
return result;
}
static double parse_date_string(VM& vm, Utf16View date_string)
{
auto utf8_date_string = date_string.to_utf8_but_should_be_ported_to_utf16();
return parse_date_string(vm, utf8_date_string.bytes_as_string_view());
}
DateConstructor::DateConstructor(Realm& realm)
: NativeFunction(realm.vm().names.Date.as_string(), realm.intrinsics().function_prototype())
{

View file

@ -6,6 +6,7 @@
#pragma once
#include <AK/AllOf.h>
#include <AK/CharacterTypes.h>
#include <AK/Error.h>
#include <AK/GenericLexer.h>
@ -45,18 +46,18 @@
// - We always parse as "Month 01, Year".
// - Support Firefox less permissive punctuation but more permissive punctuation
// syntax.
class DateParser : public GenericLexer {
class DateParser : public Utf16GenericLexer {
public:
ALWAYS_INLINE static double parse(StringView string)
ALWAYS_INLINE static double parse(Utf16View string)
{
if (!string.is_ascii())
if (!all_of(string, is_ascii))
return NAN;
return DateParser(string).parse().value_or(NAN);
}
private:
explicit DateParser(StringView string)
: GenericLexer(string)
explicit DateParser(Utf16View string)
: Utf16GenericLexer(string)
{
}
@ -589,7 +590,7 @@ private:
m_timezone_utc = true;
bool space = consume_while(is_ascii_space).length() > 0;
bool space = !consume_while(is_ascii_space).is_empty();
switch (peek()) {
case '+':
case '-':
@ -953,10 +954,10 @@ private:
// Convert the input string to uppercase only ~after~ parsing ISO8601 failed.
// This saves some time (two string copies) if parsing a ISO8601 date succeeds.
// The index stays exactly where it was before converting to uppercase.
auto str_uppercase = m_input.to_ascii_uppercase_string();
m_input = str_uppercase;
auto str_uppercase = m_input.to_ascii_uppercase();
m_input = str_uppercase.utf16_view();
// FIXME: Two full string copies could be avoided, if to_uppercase can be done in place.
// The underlying StringView m_input protects itself from modifying its contents. Bummer.
// The underlying Utf16View m_input protects itself from modifying its contents. Bummer.
while (!is_eof())
if (!loop())

View file

@ -1105,7 +1105,7 @@ ByteString date_string(double time)
// 21.4.4.41.3 TimeZoneString ( tv ), https://tc39.es/ecma262/#sec-timezoneestring
// 14.5.9 TimeZoneString ( tv ), https://tc39.es/proposal-temporal/#sec-timezoneestring
ByteString time_zone_string(double time)
Utf16String time_zone_string(double time)
{
// 1. Let systemTimeZoneIdentifier be SystemTimeZoneIdentifier().
auto system_time_zone_identifier = JS::system_time_zone_identifier();
@ -1130,28 +1130,29 @@ ByteString time_zone_string(double time)
// 5. Let tzName be an implementation-defined string that is either the empty String or the string-concatenation of
// the code unit 0x0020 (SPACE), the code unit 0x0028 (LEFT PARENTHESIS), an implementation-defined timezone name,
// and the code unit 0x0029 (RIGHT PARENTHESIS).
auto tz_name = Unicode::current_time_zone();
auto time_zone_identifier = Unicode::current_time_zone();
auto tz_name = Utf16String::from_utf8(time_zone_identifier);
// Most implementations seem to prefer the long-form display name of the time zone. Not super important, but we may as well match that behavior.
if (auto name = Unicode::time_zone_display_name(Unicode::default_locale(), tz_name, in_dst, time); name.has_value())
tz_name = name->to_utf8_but_should_be_ported_to_utf16();
if (auto name = Unicode::time_zone_display_name(Unicode::default_locale(), time_zone_identifier, in_dst, time); name.has_value())
tz_name = name.release_value();
// 10. Return the string-concatenation of offsetString and tzName.
return ByteString::formatted("{} ({})", offset_string, tz_name);
return Utf16String::formatted("{} ({})", offset_string, tz_name);
}
// 21.4.4.41.4 ToDateString ( tv ), https://tc39.es/ecma262/#sec-todatestring
ByteString to_date_string(double time)
Utf16String to_date_string(double time)
{
// 1. If tv is NaN, return "Invalid Date".
if (Value(time).is_nan())
return "Invalid Date"sv;
return "Invalid Date"_utf16;
// 2. Let t be LocalTime(tv).
time = local_time(time);
// 3. Return the string-concatenation of DateString(t), the code unit 0x0020 (SPACE), TimeString(t), and TimeZoneString(tv).
return ByteString::formatted("{} {}{}", date_string(time), time_string(time), time_zone_string(time));
return Utf16String::formatted("{} {}{}", date_string(time), time_string(time), time_zone_string(time));
}
// 21.4.4.42 Date.prototype.toTimeString ( ), https://tc39.es/ecma262/#sec-date.prototype.totimestring
@ -1167,7 +1168,7 @@ JS_DEFINE_NATIVE_FUNCTION(DatePrototype::to_time_string)
// 4. Let t be LocalTime(tv).
// 5. Return the string-concatenation of TimeString(t) and TimeZoneString(tv).
auto string = ByteString::formatted("{}{}", time_string(local_time(time)), time_zone_string(time));
auto string = Utf16String::formatted("{}{}", time_string(local_time(time)), time_zone_string(time));
return PrimitiveString::create(vm, move(string));
}

View file

@ -6,6 +6,7 @@
#pragma once
#include <AK/Utf16String.h>
#include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/PrototypeObject.h>
@ -77,7 +78,7 @@ private:
ThrowCompletionOr<double> this_time_value(VM&, Value value);
ByteString time_string(double time);
ByteString date_string(double time);
ByteString time_zone_string(double time);
ByteString to_date_string(double time);
Utf16String time_zone_string(double time);
Utf16String to_date_string(double time);
}

View file

@ -109,23 +109,23 @@ ThrowCompletionOr<GC::Ref<ECMAScriptFunctionObject>> FunctionConstructor::create
auto arg_count = parameter_args.size();
// 7. Let parameterStrings be a new empty List.
Vector<String> parameter_strings;
Vector<Utf16String> parameter_strings;
parameter_strings.ensure_capacity(arg_count);
// 8. For each element arg of parameterArgs, do
for (auto const& parameter_value : parameter_args) {
// a. Append ? ToString(arg) to parameterStrings.
parameter_strings.unchecked_append(TRY(parameter_value.to_utf16_string(vm)).to_utf8_but_should_be_ported_to_utf16());
parameter_strings.unchecked_append(TRY(parameter_value.to_utf16_string(vm)));
}
// 9. Let bodyString be ? ToString(bodyArg).
auto body_string = TRY(body_arg.to_utf16_string(vm)).to_utf8_but_should_be_ported_to_utf16();
auto body_string = TRY(body_arg.to_utf16_string(vm));
// 10. Let currentRealm be the current Realm Record.
auto& realm = *vm.current_realm();
// 11. Let P be the empty String.
String parameters_string;
Utf16String parameters_string;
// 12. If argCount > 0, then
if (arg_count > 0) {
@ -135,15 +135,15 @@ ThrowCompletionOr<GC::Ref<ECMAScriptFunctionObject>> FunctionConstructor::create
// i. Let nextArgString be parameterStrings[k].
// ii. Set P to the string-concatenation of P, "," (a comma), and nextArgString.
// iii. Set k to k + 1.
parameters_string = MUST(String::join(',', parameter_strings));
parameters_string = Utf16String::join(',', parameter_strings);
}
// 13. Let bodyParseString be the string-concatenation of 0x000A (LINE FEED), bodyString, and 0x000A (LINE FEED).
auto body_parse_string = ByteString::formatted("\n{}\n", body_string);
auto body_parse_string = Utf16String::formatted("\n{}\n", body_string);
// 14. Let sourceString be the string-concatenation of prefix, " anonymous(", P, 0x000A (LINE FEED), ") {", bodyParseString, and "}".
// 15. Let sourceText be StringToCodePoints(sourceString).
auto source_text = ByteString::formatted("{} anonymous({}\n) {{{}}}", prefix, parameters_string, body_parse_string);
auto source_text = Utf16String::formatted("{} anonymous({}\n) {{{}}}", prefix, parameters_string, body_parse_string);
// 16. Perform ? HostEnsureCanCompileStrings(currentRealm, parameterStrings, bodyString, sourceString, FUNCTION, parameterArgs, bodyArg).
TRY(vm.host_ensure_can_compile_strings(realm, parameter_strings, body_string, source_text, CompilationType::Function, parameter_args, body_arg));

View file

@ -24,8 +24,8 @@
namespace JS::Intl {
// 6.2.1 IsWellFormedLanguageTag ( locale ), https://tc39.es/ecma402/#sec-iswellformedlanguagetag
bool is_well_formed_language_tag(StringView locale)
template<typename ViewType>
static bool is_well_formed_language_tag_impl(ViewType locale)
{
auto contains_duplicate_variant = [&](auto& variants) {
if (variants.is_empty())
@ -84,7 +84,7 @@ bool is_well_formed_language_tag(StringView locale)
// b. Let transformExtension be the longest substring of extensions matched by the transformed_extensions Unicode
// locale nonterminal. If there is no such substring, return true.
if (auto* transformed = extension.get_pointer<Unicode::TransformedExtension>()) {
if (auto* transformed = extension.template get_pointer<Unicode::TransformedExtension>()) {
// c. Assert: The substring of transformExtension from 0 to 3 is "-t-".
// d. Let tPrefix be the substring of transformExtension from 3.
@ -109,12 +109,28 @@ bool is_well_formed_language_tag(StringView locale)
return true;
}
// 6.2.1 IsWellFormedLanguageTag ( locale ), https://tc39.es/ecma402/#sec-iswellformedlanguagetag
bool is_well_formed_language_tag(StringView locale)
{
return is_well_formed_language_tag_impl(locale);
}
bool is_well_formed_language_tag(Utf16View locale)
{
return is_well_formed_language_tag_impl(locale);
}
// 6.2.2 CanonicalizeUnicodeLocaleId ( locale ), https://tc39.es/ecma402/#sec-canonicalizeunicodelocaleid
String canonicalize_unicode_locale_id(StringView locale)
{
return Unicode::canonicalize_unicode_locale_id(locale);
}
String canonicalize_unicode_locale_id(Utf16View locale)
{
return Unicode::canonicalize_unicode_locale_id(locale);
}
// 6.3.1 IsWellFormedCurrencyCode ( currency ), https://tc39.es/ecma402/#sec-iswellformedcurrencycode
bool is_well_formed_currency_code(StringView currency)
{
@ -131,6 +147,23 @@ bool is_well_formed_currency_code(StringView currency)
return true;
}
bool is_well_formed_currency_code(Utf16View currency)
{
// 1. If the length of currency is not 3, return false.
if (currency.length_in_code_units() != 3)
return false;
// 2. Let normalized be the ASCII-uppercase of currency.
// 3. If normalized contains any code unit outside of 0x0041 through 0x005A (corresponding to Unicode characters LATIN CAPITAL LETTER A through LATIN CAPITAL LETTER Z), return false.
for (size_t i = 0; i < currency.length_in_code_units(); ++i) {
if (!is_ascii_alpha(currency.code_unit_at(i)))
return false;
}
// 4. Return true.
return true;
}
// 6.5.1 AvailableNamedTimeZoneIdentifiers ( ), https://tc39.es/ecma402/#sup-availablenamedtimezoneidentifiers
Vector<TimeZoneIdentifier> const& available_named_time_zone_identifiers()
{
@ -205,14 +238,18 @@ Optional<TimeZoneIdentifier const&> get_available_named_time_zone_identifier(Str
}
// 6.6.1 IsWellFormedUnitIdentifier ( unitIdentifier ), https://tc39.es/ecma402/#sec-iswellformedunitidentifier
bool is_well_formed_unit_identifier(StringView unit_identifier)
bool is_well_formed_unit_identifier(Utf16View unit_identifier)
{
// 6.6.2 IsSanctionedSingleUnitIdentifier ( unitIdentifier ), https://tc39.es/ecma402/#sec-issanctionedsingleunitidentifier
constexpr auto is_sanctioned_single_unit_identifier = [](StringView unit_identifier) {
constexpr auto is_sanctioned_single_unit_identifier = [](Utf16View unit_identifier) {
// 1. If unitIdentifier is listed in Table 2 below, return true.
// 2. Else, return false.
static constexpr auto sanctioned_units = sanctioned_single_unit_identifiers();
return find(sanctioned_units.begin(), sanctioned_units.end(), unit_identifier) != sanctioned_units.end();
for (auto sanctioned_unit : sanctioned_units) {
if (unit_identifier == sanctioned_unit)
return true;
}
return false;
};
// 1. If ! IsSanctionedSingleUnitIdentifier(unitIdentifier) is true, then
@ -222,22 +259,22 @@ bool is_well_formed_unit_identifier(StringView unit_identifier)
}
// 2. Let i be StringIndexOf(unitIdentifier, "-per-", 0).
auto indices = unit_identifier.find_all("-per-"sv);
auto index = unit_identifier.find_code_unit_offset("-per-"sv);
// 3. If i is -1 or StringIndexOf(unitIdentifier, "-per-", i + 1) is not -1, then
if (indices.size() != 1) {
if (!index.has_value() || unit_identifier.find_code_unit_offset("-per-"sv, *index + 1).has_value()) {
// a. Return false.
return false;
}
// 4. Assert: The five-character substring "-per-" occurs exactly once in unitIdentifier, at index i.
// NOTE: We skip this because the indices vector being of size 1 already verifies this invariant.
// NOTE: We skip this because the checks above already verify this invariant.
// 5. Let numerator be the substring of unitIdentifier from 0 to i.
auto numerator = unit_identifier.substring_view(0, indices[0]);
auto numerator = unit_identifier.substring_view(0, *index);
// 6. Let denominator be the substring of unitIdentifier from i + 5.
auto denominator = unit_identifier.substring_view(indices[0] + 5);
auto denominator = unit_identifier.substring_view(*index + 5);
// 7. If ! IsSanctionedSingleUnitIdentifier(numerator) and ! IsSanctionedSingleUnitIdentifier(denominator) are both true, then
if (is_sanctioned_single_unit_identifier(numerator) && is_sanctioned_single_unit_identifier(denominator)) {
@ -297,26 +334,33 @@ ThrowCompletionOr<Vector<String>> canonicalize_locale_list(VM& vm, Value locales
if (!key_value.is_string() && !key_value.is_object())
return vm.throw_completion<TypeError>(ErrorType::NotAnObjectOrString, key_value);
String tag;
String canonicalized_tag;
// iii. If Type(kValue) is Object and kValue has an [[InitializedLocale]] internal slot, then
if (auto locale = key_value.as_if<Locale>()) {
// 1. Let tag be kValue.[[Locale]].
tag = locale->locale();
auto tag = locale->locale();
// v. If IsWellFormedLanguageTag(tag) is false, throw a RangeError exception.
if (!is_well_formed_language_tag(tag))
return vm.throw_completion<RangeError>(ErrorType::IntlInvalidLanguageTag, tag);
// vi. Let canonicalizedTag be ! CanonicalizeUnicodeLocaleId(tag).
canonicalized_tag = canonicalize_unicode_locale_id(tag);
}
// iv. Else,
else {
// 1. Let tag be ? ToString(kValue).
tag = TRY(key_value.to_utf16_string(vm)).to_utf8_but_should_be_ported_to_utf16();
auto tag = TRY(key_value.to_utf16_string(vm));
// v. If IsWellFormedLanguageTag(tag) is false, throw a RangeError exception.
if (!is_well_formed_language_tag(tag.utf16_view()))
return vm.throw_completion<RangeError>(ErrorType::IntlInvalidLanguageTag, tag);
// vi. Let canonicalizedTag be ! CanonicalizeUnicodeLocaleId(tag).
canonicalized_tag = canonicalize_unicode_locale_id(tag.utf16_view());
}
// v. If IsWellFormedLanguageTag(tag) is false, throw a RangeError exception.
if (!is_well_formed_language_tag(tag))
return vm.throw_completion<RangeError>(ErrorType::IntlInvalidLanguageTag, tag);
// vi. Let canonicalizedTag be ! CanonicalizeUnicodeLocaleId(tag).
auto canonicalized_tag = canonicalize_unicode_locale_id(tag);
// vii. If canonicalizedTag is not an element of seen, append canonicalizedTag as the last element of seen.
if (!seen.contains_slow(canonicalized_tag))
seen.append(move(canonicalized_tag));
@ -639,13 +683,14 @@ ThrowCompletionOr<ResolvedOptions> resolve_options(VM& vm, IntlObject& object, V
// d. If value is not undefined, then
if (!value.is_undefined()) {
// i. Set value to ! ToString(value).
auto value_string = MUST(value.to_utf16_string(vm)).to_utf8_but_should_be_ported_to_utf16();
auto value_string = MUST(value.to_utf16_string(vm));
auto value_string_view = value_string.utf16_view();
// ii. If value cannot be matched by the type Unicode locale nonterminal, throw a RangeError exception.
if (!Unicode::is_type_identifier(value_string))
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, value_string, descriptor.property);
if (!value_string_view.is_ascii() || !Unicode::is_type_identifier(value_string_view))
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, value_string_view, descriptor.property);
locale_key = move(value_string);
locale_key = MUST(value_string_view.to_utf8());
}
// e. Let key be desc.[[Key]].
@ -749,7 +794,7 @@ ThrowCompletionOr<StringOrBoolean> get_boolean_or_string_number_format_option(VM
auto value_string_view = value_string.utf16_view();
auto it = find_if(string_values.begin(), string_values.end(), [&](auto allowed_value) { return value_string_view == allowed_value; });
if (it == string_values.end())
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, value_string_view.to_utf8_but_should_be_ported_to_utf16(), property.as_string());
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, value_string_view, property.as_string());
// 7. Return value.
return StringOrBoolean { *it };

View file

@ -9,6 +9,7 @@
#include <AK/EnumBits.h>
#include <AK/Span.h>
#include <AK/String.h>
#include <AK/Utf16View.h>
#include <AK/Variant.h>
#include <AK/Vector.h>
#include <LibJS/Forward.h>
@ -64,11 +65,14 @@ AK_ENUM_BITWISE_OPERATORS(SpecialBehaviors);
using StringOrBoolean = Variant<StringView, bool>;
bool is_well_formed_language_tag(StringView locale);
bool is_well_formed_language_tag(Utf16View locale);
String canonicalize_unicode_locale_id(StringView locale);
String canonicalize_unicode_locale_id(Utf16View locale);
bool is_well_formed_currency_code(StringView currency);
bool is_well_formed_currency_code(Utf16View currency);
Vector<TimeZoneIdentifier> const& available_named_time_zone_identifiers();
Optional<TimeZoneIdentifier const&> get_available_named_time_zone_identifier(StringView time_zone_identifier);
bool is_well_formed_unit_identifier(StringView unit_identifier);
bool is_well_formed_unit_identifier(Utf16View unit_identifier);
ThrowCompletionOr<Vector<String>> canonicalize_locale_list(VM&, Value locales);
Optional<MatchedLocale> lookup_matching_locale_by_prefix(ReadonlySpan<String> requested_locales);
Optional<MatchedLocale> lookup_matching_locale_by_best_fit(ReadonlySpan<String> requested_locales);

View file

@ -8,6 +8,7 @@
#include <AK/String.h>
#include <AK/StringView.h>
#include <AK/Utf16View.h>
#include <LibJS/Runtime/Intl/CollatorCompareFunction.h>
#include <LibJS/Runtime/Intl/IntlObject.h>
#include <LibUnicode/Collator.h>
@ -28,7 +29,7 @@ public:
void set_locale(String locale) { m_locale = move(locale); }
Unicode::Usage usage() const { return m_usage; }
void set_usage(StringView usage) { m_usage = Unicode::usage_from_string(usage); }
void set_usage(Utf16View usage) { m_usage = Unicode::usage_from_string(usage); }
StringView usage_string() const LIFETIME_BOUND { return Unicode::usage_to_string(m_usage); }
Unicode::Sensitivity sensitivity() const { return m_sensitivity; }

View file

@ -4,7 +4,6 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Utf8View.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/Intl/Collator.h>
#include <LibJS/Runtime/Intl/CollatorCompareFunction.h>

View file

@ -69,7 +69,7 @@ ThrowCompletionOr<GC::Ref<Object>> CollatorConstructor::construct(FunctionObject
auto usage = TRY(get_option(vm, options, vm.names.usage, OptionType::String, { "sort"sv, "search"sv }, "sort"sv));
// 8. Set collator.[[Usage]] to usage.
collator->set_usage(usage.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
collator->set_usage(usage.as_string().utf16_string_view());
// 9. If usage is "sort", then
// a. Let localeData be %Intl.Collator%.[[SortLocaleData]].
@ -113,7 +113,7 @@ ThrowCompletionOr<GC::Ref<Object>> CollatorConstructor::construct(FunctionObject
Optional<Unicode::Sensitivity> sensitivity;
if (!sensitivity_value.is_undefined())
sensitivity = Unicode::sensitivity_from_string(sensitivity_value.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
sensitivity = Unicode::sensitivity_from_string(sensitivity_value.as_string().utf16_string_view());
// 21. Let defaultIgnorePunctuation be resolvedLocaleData.[[ignorePunctuation]].
// NOTE: We do not acquire resolvedLocaleData.[[ignorePunctuation]] here. Instead, we let LibUnicode fill in the

View file

@ -10,6 +10,7 @@
#include <AK/String.h>
#include <AK/StringView.h>
#include <AK/Types.h>
#include <AK/Utf16View.h>
#include <AK/Vector.h>
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/Intl/DateTimeFormatConstructor.h>
@ -49,12 +50,12 @@ public:
bool has_date_style() const { return m_date_style.has_value(); }
Optional<Unicode::DateTimeStyle> const& date_style() const { return m_date_style; }
StringView date_style_string() const { return Unicode::date_time_style_to_string(*m_date_style); }
void set_date_style(StringView style) { m_date_style = Unicode::date_time_style_from_string(style); }
void set_date_style(Utf16View style) { m_date_style = Unicode::date_time_style_from_string(style); }
bool has_time_style() const { return m_time_style.has_value(); }
Optional<Unicode::DateTimeStyle> const& time_style() const { return m_time_style; }
StringView time_style_string() const { return Unicode::date_time_style_to_string(*m_time_style); }
void set_time_style(StringView style) { m_time_style = Unicode::date_time_style_from_string(style); }
void set_time_style(Utf16View style) { m_time_style = Unicode::date_time_style_from_string(style); }
Unicode::CalendarPattern& date_time_format() { return m_date_time_format; }
void set_date_time_format(Unicode::CalendarPattern date_time_format) { m_date_time_format = move(date_time_format); }

View file

@ -171,6 +171,7 @@ ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, Funct
// 17. Let timeZone be ? Get(options, "timeZone").
auto time_zone_value = TRY(options->get(vm.names.timeZone));
String time_zone;
Utf16String time_zone_string;
// 18. If timeZone is undefined, then
if (time_zone_value.is_undefined()) {
@ -178,11 +179,13 @@ ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, Funct
if (to_locale_string_time_zone.has_value()) {
// i. Set timeZone to toLocaleStringTimeZone.
time_zone = *to_locale_string_time_zone;
time_zone_string = Utf16String::from_utf8(time_zone);
}
// b. Else,
else {
// i. Set timeZone to SystemTimeZoneIdentifier().
time_zone = system_time_zone_identifier();
time_zone_string = Utf16String::from_utf8(time_zone);
}
}
// 19. Else,
@ -192,22 +195,23 @@ ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, Funct
return vm.throw_completion<TypeError>(ErrorType::IntlInvalidDateTimeFormatOption, vm.names.timeZone, "a toLocaleString time zone"sv);
// b. Set timeZone to ? ToString(timeZone).
time_zone = TRY(time_zone_value.to_utf16_string(vm)).to_utf8_but_should_be_ported_to_utf16();
time_zone_string = TRY(time_zone_value.to_utf16_string(vm));
}
auto time_zone_view = time_zone_string.utf16_view();
// 20. If IsTimeZoneOffsetString(timeZone) is true, then
bool is_time_zone_offset_string = JS::is_offset_time_zone_identifier(time_zone);
auto parse_result = Temporal::parse_utc_offset(time_zone_view, Temporal::SubMinutePrecision::No);
bool is_time_zone_offset_string = parse_result.has_value();
if (is_time_zone_offset_string) {
// a. Let parseResult be ParseText(StringToCodePoints(timeZone), UTCOffset[~SubMinutePrecision]).
auto utf16_time_zone = Utf16String::from_utf8(time_zone);
auto parse_result = Temporal::parse_utc_offset(utf16_time_zone, Temporal::SubMinutePrecision::No);
// b. Assert: parseResult is a Parse Node.
VERIFY(parse_result.has_value());
// c. Let offsetNanoseconds be ? ParseDateTimeUTCOffset(timeZone).
auto offset_nanoseconds = TRY(parse_date_time_utc_offset(vm, time_zone));
auto offset_nanoseconds = parse_date_time_utc_offset(*parse_result);
// d. Let offsetMinutes be offsetNanoseconds / (6 × 10**10).
auto offset_minutes = offset_nanoseconds / 60'000'000'000;
@ -218,6 +222,10 @@ ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, Funct
// 21. Else,
else {
// a. Let timeZoneIdentifierRecord be GetAvailableNamedTimeZoneIdentifier(timeZone).
if (!time_zone_view.is_ascii())
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, time_zone_view, vm.names.timeZone);
time_zone = MUST(time_zone_view.to_utf8());
auto time_zone_identifier_record = get_available_named_time_zone_identifier(time_zone);
// b. If timeZoneIdentifierRecord is EMPTY, throw a RangeError exception.
@ -277,7 +285,7 @@ ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, Funct
// d. Set formatOptions.[[<prop>]] to value.
if (!value.is_undefined()) {
option = Unicode::calendar_pattern_style_from_string(value.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
option = Unicode::calendar_pattern_style_from_string(value.as_string().utf16_string_view());
// e. If value is not undefined, then
// i. Set hasExplicitFormatComponents to true.
@ -296,14 +304,14 @@ ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, Funct
// 29. Set dateTimeFormat.[[DateStyle]] to dateStyle.
if (!date_style.is_undefined())
date_time_format->set_date_style(date_style.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
date_time_format->set_date_style(date_style.as_string().utf16_string_view());
// 30. Let timeStyle be ? GetOption(options, "timeStyle", string, « "full", "long", "medium", "short" », undefined).
auto time_style = TRY(get_option(vm, *options, vm.names.timeStyle, OptionType::String, AK::Array { "full"sv, "long"sv, "medium"sv, "short"sv }, Empty {}));
// 31. Set dateTimeFormat.[[TimeStyle]] to timeStyle.
if (!time_style.is_undefined())
date_time_format->set_time_style(time_style.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
date_time_format->set_time_style(time_style.as_string().utf16_string_view());
// 32. Let formats be resolvedLocaleData.[[formats]].[[<resolvedCalendar>]].

View file

@ -32,7 +32,7 @@ ReadonlySpan<ResolutionOptionDescriptor> DisplayNames::resolution_option_descrip
return {};
}
void DisplayNames::set_type(StringView type)
void DisplayNames::set_type(Utf16View type)
{
if (type == "language"sv)
m_type = Type::Language;
@ -70,7 +70,7 @@ StringView DisplayNames::type_string() const
}
}
void DisplayNames::set_fallback(StringView fallback)
void DisplayNames::set_fallback(Utf16View fallback)
{
if (fallback == "none"sv)
m_fallback = Fallback::None;
@ -93,20 +93,25 @@ StringView DisplayNames::fallback_string() const
}
// 12.5.1 CanonicalCodeForDisplayNames ( type, code ), https://tc39.es/ecma402/#sec-canonicalcodefordisplaynames
ThrowCompletionOr<Value> canonical_code_for_display_names(VM& vm, DisplayNames::Type type, StringView code)
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) {
if (!code.is_ascii())
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "language"sv);
auto code_string = MUST(code.to_utf8());
// a. If code does not match the unicode_language_id production, throw a RangeError exception.
if (!Unicode::parse_unicode_language_id(code).has_value())
if (!Unicode::parse_unicode_language_id(code_string).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))
if (!is_well_formed_language_tag(code_string))
return vm.throw_completion<RangeError>(ErrorType::IntlInvalidLanguageTag, code);
// c. Return ! CanonicalizeUnicodeLocaleId(code).
auto canonicalized_tag = canonicalize_unicode_locale_id(code);
auto canonicalized_tag = canonicalize_unicode_locale_id(code_string);
return PrimitiveString::create(vm, move(canonicalized_tag));
}
@ -117,7 +122,7 @@ ThrowCompletionOr<Value> canonical_code_for_display_names(VM& vm, DisplayNames::
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_string());
return PrimitiveString::create(vm, code.to_ascii_uppercase());
}
// 3. If type is "script", then
@ -127,13 +132,13 @@ ThrowCompletionOr<Value> canonical_code_for_display_names(VM& vm, DisplayNames::
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() == 4);
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_string());
return PrimitiveString::create(vm, code.to_ascii_titlecase());
}
// 4. If type is "calendar", then
@ -143,11 +148,11 @@ ThrowCompletionOr<Value> canonical_code_for_display_names(VM& vm, DisplayNames::
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('_'))
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_string());
return PrimitiveString::create(vm, code.to_ascii_lowercase());
}
// 5. If type is "dateTimeField", then
@ -157,7 +162,7 @@ ThrowCompletionOr<Value> canonical_code_for_display_names(VM& vm, DisplayNames::
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, code, "dateTimeField"sv);
// b. Return code.
return PrimitiveString::create(vm, code);
return PrimitiveString::create(vm, Utf16String::from_utf16(code));
}
// 6. Assert: type is "currency".
@ -168,11 +173,11 @@ ThrowCompletionOr<Value> canonical_code_for_display_names(VM& vm, DisplayNames::
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_string());
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(StringView field)
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.

View file

@ -9,6 +9,7 @@
#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/StringView.h>
#include <AK/Utf16View.h>
#include <LibJS/Runtime/Intl/IntlObject.h>
#include <LibUnicode/DisplayNames.h>
#include <LibUnicode/Locale.h>
@ -45,20 +46,20 @@ public:
void set_locale(String locale) { m_locale = move(locale); }
Unicode::Style style() const { return m_style; }
void set_style(StringView style) { m_style = Unicode::style_from_string(style); }
void set_style(Utf16View style) { m_style = Unicode::style_from_string(style); }
StringView style_string() const { return Unicode::style_to_string(m_style); }
Type type() const { return m_type; }
void set_type(StringView type);
void set_type(Utf16View type);
StringView type_string() const;
Fallback fallback() const { return m_fallback; }
void set_fallback(StringView fallback);
void set_fallback(Utf16View fallback);
StringView fallback_string() const;
bool has_language_display() const { return m_language_display.has_value(); }
Unicode::LanguageDisplay language_display() const { return *m_language_display; }
void set_language_display(StringView language_display) { m_language_display = Unicode::language_display_from_string(language_display); }
void set_language_display(Utf16View language_display) { m_language_display = Unicode::language_display_from_string(language_display); }
StringView language_display_string() const { return Unicode::language_display_to_string(*m_language_display); }
private:
@ -71,7 +72,7 @@ private:
Optional<Unicode::LanguageDisplay> m_language_display; // [[LanguageDisplay]]
};
ThrowCompletionOr<Value> canonical_code_for_display_names(VM&, DisplayNames::Type, StringView code);
bool is_valid_date_time_field_code(StringView field);
ThrowCompletionOr<Value> canonical_code_for_display_names(VM&, DisplayNames::Type, Utf16View code);
bool is_valid_date_time_field_code(Utf16View field);
}

View file

@ -64,7 +64,7 @@ ThrowCompletionOr<GC::Ref<Object>> DisplayNamesConstructor::construct(FunctionOb
auto style = TRY(get_option(vm, *options, vm.names.style, OptionType::String, { "narrow"sv, "short"sv, "long"sv }, "long"sv));
// 7. Set displayNames.[[Style]] to style.
display_names->set_style(style.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
display_names->set_style(style.as_string().utf16_string_view());
// 8. Let type be ? GetOption(options, "type", string, « "language", "region", "script", "currency", "calendar", "dateTimeField" », undefined).
auto type = TRY(get_option(vm, *options, vm.names.type, OptionType::String, { "language"sv, "region"sv, "script"sv, "currency"sv, "calendar"sv, "dateTimeField"sv }, Empty {}));
@ -74,13 +74,13 @@ ThrowCompletionOr<GC::Ref<Object>> DisplayNamesConstructor::construct(FunctionOb
return vm.throw_completion<TypeError>(ErrorType::IsUndefined, "options.type"sv);
// 10. Set displayNames.[[Type]] to type.
display_names->set_type(type.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
display_names->set_type(type.as_string().utf16_string_view());
// 11. Let fallback be ? GetOption(options, "fallback", string, « "code", "none" », "code").
auto fallback = TRY(get_option(vm, *options, vm.names.fallback, OptionType::String, { "code"sv, "none"sv }, "code"sv));
// 12. Set displayNames.[[Fallback]] to fallback.
display_names->set_fallback(fallback.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
display_names->set_fallback(fallback.as_string().utf16_string_view());
// 13. Set displayNames.[[Locale]] to r.[[Locale]].
display_names->set_locale(move(result.locale));
@ -98,7 +98,7 @@ ThrowCompletionOr<GC::Ref<Object>> DisplayNamesConstructor::construct(FunctionOb
// 20. If type is "language", then
if (display_names->type() == DisplayNames::Type::Language) {
// a. Set displayNames.[[LanguageDisplay]] to languageDisplay.
display_names->set_language_display(language_display.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
display_names->set_language_display(language_display.as_string().utf16_string_view());
// b. Set typeFields to typeFields.[[<languageDisplay>]].
// c. Assert: typeFields is a Record (see 12.2.3).

View file

@ -77,8 +77,8 @@ JS_DEFINE_NATIVE_FUNCTION(DisplayNamesPrototype::of)
code = PrimitiveString::create(vm, TRY(code.to_utf16_string(vm)));
// 4. Let code be ? CanonicalCodeForDisplayNames(displayNames.[[Type]], code).
code = TRY(canonical_code_for_display_names(vm, display_names->type(), code.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16()));
auto code_string = code.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16();
code = TRY(canonical_code_for_display_names(vm, display_names->type(), code.as_string().utf16_string_view()));
auto code_string = MUST(code.as_string().utf16_string_view().to_utf8());
// 5. Let fields be displayNames.[[Fields]].
// 6. If fields has a field [[<code>]], return fields.[[<code>]].

View file

@ -50,7 +50,7 @@ ReadonlySpan<ResolutionOptionDescriptor> DurationFormat::resolution_option_descr
return *descriptors;
}
DurationFormat::Style DurationFormat::style_from_string(StringView style)
DurationFormat::Style DurationFormat::style_from_string(Utf16View style)
{
if (style == "long"sv)
return Style::Long;
@ -79,7 +79,7 @@ StringView DurationFormat::style_to_string(Style style)
}
}
DurationFormat::Display DurationFormat::display_from_string(StringView display)
DurationFormat::Display DurationFormat::display_from_string(Utf16View display)
{
if (display == "auto"sv)
return Display::Auto;
@ -88,7 +88,7 @@ DurationFormat::Display DurationFormat::display_from_string(StringView display)
VERIFY_NOT_REACHED();
}
DurationFormat::ValueStyle DurationFormat::value_style_from_string(StringView value_style)
DurationFormat::ValueStyle DurationFormat::value_style_from_string(Utf16View value_style)
{
if (value_style == "long"sv)
return ValueStyle::Long;
@ -269,7 +269,7 @@ ThrowCompletionOr<DurationFormat::DurationUnitOptions> get_duration_unit_options
display_default = "auto"sv;
}
} else {
style = DurationFormat::value_style_from_string(style_value.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
style = DurationFormat::value_style_from_string(style_value.as_string().utf16_string_view());
}
// 4. If style is "numeric" and IsFractionalSecondUnitName(unit) is true, then
@ -286,7 +286,7 @@ ThrowCompletionOr<DurationFormat::DurationUnitOptions> get_duration_unit_options
// 6. Let display be ? GetOption(options, displayField, STRING, « "auto", "always" », displayDefault).
auto display_value = TRY(get_option(vm, options, display_field, OptionType::String, { "auto"sv, "always"sv }, display_default));
auto display = DurationFormat::display_from_string(display_value.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
auto display = DurationFormat::display_from_string(display_value.as_string().utf16_string_view());
// 7. Perform ? ValidateDurationUnitStyle(unit, style, display, prevStyle).
TRY(validate_duration_unit_style(vm, unit_property_key, style, display, previous_style, display_field));
@ -690,7 +690,8 @@ Vector<DurationFormatPart> format_numeric_units(VM& vm, DurationFormat const& du
// 18. If secondsFormatted is true, then
if (seconds_formatted) {
// a. Let secondsParts be FormatNumericSeconds(durationFormat, secondsValue, minutesFormatted, signDisplayed).
auto seconds_parts = format_numeric_seconds(vm, duration_format, MathematicalValue { seconds_value.to_string(9) }, minutes_formatted, sign_displayed);
auto seconds_value_mv = MathematicalValue { Utf16String::from_utf8(seconds_value.to_string(9)) };
auto seconds_parts = format_numeric_seconds(vm, duration_format, seconds_value_mv, minutes_formatted, sign_displayed);
// b. Set numericPartsList to the list-concatenation of numericPartsList and secondsParts.
numeric_parts_list.extend(move(seconds_parts));
@ -883,7 +884,7 @@ Vector<DurationFormatPart> partition_duration_format_pattern(VM& vm, DurationFor
// iii. If display is "always" or value is not 0, then
if (display == DurationFormat::Display::Always || !value.is_zero()) {
MathematicalValue value_mv { value.to_string(9) };
auto value_mv = MathematicalValue { Utf16String::from_utf8(value.to_string(9)) };
// 1. Perform ! CreateDataPropertyOrThrow(nfOpts, "numberingSystem", durationFormat.[[NumberingSystem]]).
MUST(number_format_options->create_data_property_or_throw(vm.names.numberingSystem, PrimitiveString::create(vm, duration_format.numbering_system())));

View file

@ -9,6 +9,7 @@
#include <AK/Array.h>
#include <AK/String.h>
#include <AK/Utf16View.h>
#include <LibCrypto/BigFraction/BigFraction.h>
#include <LibJS/Runtime/Intl/IntlObject.h>
#include <LibJS/Runtime/Temporal/Duration.h>
@ -27,7 +28,7 @@ public:
Narrow,
Digital,
};
static Style style_from_string(StringView style);
static Style style_from_string(Utf16View style);
static StringView style_to_string(Style);
enum class ValueStyle {
@ -38,7 +39,7 @@ public:
TwoDigit,
Fractional,
};
static ValueStyle value_style_from_string(StringView);
static ValueStyle value_style_from_string(Utf16View);
static StringView value_style_to_string(ValueStyle);
static_assert(to_underlying(ValueStyle::Long) == to_underlying(Unicode::Style::Long));
@ -49,7 +50,7 @@ public:
Auto,
Always,
};
static Display display_from_string(StringView display);
static Display display_from_string(Utf16View display);
static StringView display_to_string(Display);
enum class Unit {
@ -88,7 +89,7 @@ public:
void set_minute_second_separator(Utf16String minute_second_separator) { m_minute_second_separator = move(minute_second_separator); }
Utf16String const& minute_second_separator() const { return m_minute_second_separator; }
void set_style(StringView style) { m_style = style_from_string(style); }
void set_style(Utf16View style) { m_style = style_from_string(style); }
Style style() const { return m_style; }
StringView style_string() const { return style_to_string(m_style); }

View file

@ -83,7 +83,7 @@ ThrowCompletionOr<GC::Ref<Object>> DurationFormatConstructor::construct(Function
auto style = TRY(get_option(vm, *options, vm.names.style, OptionType::String, { "long"sv, "short"sv, "narrow"sv, "digital"sv }, "short"sv));
// 13. Set durationFormat.[[Style]] to style.
duration_format->set_style(style.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
duration_format->set_style(style.as_string().utf16_string_view());
// 14. Let prevStyle be the empty String.
Optional<DurationFormat::ValueStyle> previous_style;

View file

@ -8,6 +8,7 @@
#include <AK/String.h>
#include <AK/StringView.h>
#include <AK/Utf16View.h>
#include <AK/Vector.h>
#include <LibJS/Runtime/Intl/IntlObject.h>
#include <LibUnicode/ListFormat.h>
@ -37,10 +38,12 @@ public:
Unicode::ListFormatType type() const { return m_type; }
void set_type(StringView type) { m_type = Unicode::list_format_type_from_string(type); }
void set_type(Utf16View type) { m_type = Unicode::list_format_type_from_string(type); }
StringView type_string() const { return Unicode::list_format_type_to_string(m_type); }
Unicode::Style style() const { return m_style; }
void set_style(StringView style) { m_style = Unicode::style_from_string(style); }
void set_style(Utf16View style) { m_style = Unicode::style_from_string(style); }
StringView style_string() const { return Unicode::style_to_string(m_style); }
Unicode::ListFormat const& formatter() const { return *m_formatter; }

View file

@ -66,13 +66,13 @@ ThrowCompletionOr<GC::Ref<Object>> ListFormatConstructor::construct(FunctionObje
auto type = TRY(get_option(vm, *options, vm.names.type, OptionType::String, { "conjunction"sv, "disjunction"sv, "unit"sv }, "conjunction"sv));
// 8. Set listFormat.[[Type]] to type.
list_format->set_type(type.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
list_format->set_type(type.as_string().utf16_string_view());
// 9. Let style be ? GetOption(options, "style", string, « "long", "short", "narrow" », "long").
auto style = TRY(get_option(vm, *options, vm.names.style, OptionType::String, { "long"sv, "short"sv, "narrow"sv }, "long"sv));
// 10. Set listFormat.[[Style]] to style.
list_format->set_style(style.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
list_format->set_style(style.as_string().utf16_string_view());
// 11. Let resolvedLocaleData be r.[[LocaleData]].
// 12. Let dataLocaleTypes be resolvedLocaleData.[[<type>]].

View file

@ -28,8 +28,28 @@ struct LocaleAndKeys {
Optional<String> nu;
};
static bool is_unicode_language_subtag(Utf16View subtag)
{
return Unicode::is_unicode_language_subtag(subtag);
}
static bool is_unicode_script_subtag(Utf16View subtag)
{
return Unicode::is_unicode_script_subtag(subtag);
}
static bool is_unicode_region_subtag(Utf16View subtag)
{
return Unicode::is_unicode_region_subtag(subtag);
}
static bool is_type_identifier(Utf16View identifier)
{
return Unicode::is_type_identifier(identifier);
}
// NOTE: This is not an AO in the spec. This just serves to abstract very similar steps in UpdateLanguageId and the Intl.Locale constructor.
static ThrowCompletionOr<Optional<String>> get_string_option(VM& vm, Object const& options, PropertyKey const& property, Function<bool(StringView)> validator, ReadonlySpan<StringView> values = {}, Optional<String> const& fallback = {})
static ThrowCompletionOr<Optional<String>> get_string_option(VM& vm, Object const& options, PropertyKey const& property, Function<bool(Utf16View)> validator, ReadonlySpan<StringView> values = {}, Optional<String> const& fallback = {})
{
auto option_default = fallback.has_value() ? OptionDefault { *fallback } : Empty {};
@ -37,10 +57,14 @@ static ThrowCompletionOr<Optional<String>> get_string_option(VM& vm, Object cons
if (option.is_undefined())
return OptionalNone {};
if (validator && !validator(option.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16()))
auto option_string = option.as_string().utf16_string_view();
if (!option_string.is_ascii())
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, option, property);
return option.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16();
if (validator && !validator(option_string))
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, option, property);
return MUST(option_string.to_utf8());
}
// 15.1.2 UpdateLanguageId ( tag, options ), https://tc39.es/ecma402/#sec-updatelanguageid
@ -54,17 +78,17 @@ static ThrowCompletionOr<String> update_language_id(VM& vm, StringView tag, Obje
// 2. Let language be ? GetOption(options, "language", STRING, EMPTY, GetLocaleLanguage(baseName)).
// 3. If language cannot be matched by the unicode_language_subtag Unicode locale nonterminal, throw a RangeError exception.
auto language = TRY(get_string_option(vm, options, vm.names.language, Unicode::is_unicode_language_subtag, {}, *base_name.language));
auto language = TRY(get_string_option(vm, options, vm.names.language, is_unicode_language_subtag, {}, *base_name.language));
// 4. Let script be ? GetOption(options, "script", STRING, EMPTY, GetLocaleScript(baseName)).
// 5. If script is not undefined, then
// a. If script cannot be matched by the unicode_script_subtag Unicode locale nonterminal, throw a RangeError exception.
auto script = TRY(get_string_option(vm, options, vm.names.script, Unicode::is_unicode_script_subtag, {}, base_name.script));
auto script = TRY(get_string_option(vm, options, vm.names.script, is_unicode_script_subtag, {}, base_name.script));
// 6. Let region be ? GetOption(options, "region", STRING, EMPTY, GetLocaleRegion(baseName)).
// 7. If region is not undefined, then
// a. If region cannot be matched by the unicode_region_subtag Unicode locale nonterminal, throw a RangeError exception.
auto region = TRY(get_string_option(vm, options, vm.names.region, Unicode::is_unicode_region_subtag, {}, base_name.region));
auto region = TRY(get_string_option(vm, options, vm.names.region, is_unicode_region_subtag, {}, base_name.region));
// 8. Let variants be ? GetOption(options, "variants", STRING, EMPTY, GetLocaleVariants(baseName)).
auto variants = TRY(get_string_option(vm, options, vm.names.variants, nullptr, {}, get_locale_variants(*locale_id)));
@ -279,21 +303,33 @@ ThrowCompletionOr<GC::Ref<Object>> LocaleConstructor::construct(FunctionObject&
if (!tag_value.is_string() && !tag_value.is_object())
return vm.throw_completion<TypeError>(ErrorType::NotAnObjectOrString, "tag"sv);
auto tag = TRY([&]() -> ThrowCompletionOr<String> {
// 8. If tag is an Object and tag has an [[InitializedLocale]] internal slot, then
// a. Let tag be tag.[[Locale]].
if (auto locale_tag = tag_value.as_if<Locale>())
return locale_tag->locale();
// 9. Else,
// a. Let tag be ? ToString(tag).
return TRY(tag_value.to_utf16_string(vm)).to_utf8_but_should_be_ported_to_utf16();
}());
String tag;
bool tag_is_canonicalized = false;
// 8. If tag is an Object and tag has an [[InitializedLocale]] internal slot, then
// a. Let tag be tag.[[Locale]].
if (auto locale_tag = tag_value.as_if<Locale>()) {
tag = locale_tag->locale();
}
// 9. Else,
else {
// a. Let tag be ? ToString(tag).
auto tag_string = TRY(tag_value.to_utf16_string(vm));
// 11. If IsWellFormedLanguageTag(tag) is false, throw a RangeError exception.
if (!is_well_formed_language_tag(tag_string.utf16_view()))
return vm.throw_completion<RangeError>(ErrorType::IntlInvalidLanguageTag, tag_string);
// 13. Set tag to CanonicalizeUnicodeLocaleId(tag).
tag = canonicalize_unicode_locale_id(tag_string.utf16_view());
tag_is_canonicalized = true;
}
// 10. Set options to ? CoerceOptionsToObject(options).
auto options = TRY(coerce_options_to_object(vm, options_value));
// 11. If IsWellFormedLanguageTag(tag) is false, throw a RangeError exception.
if (!is_well_formed_language_tag(tag))
if (!tag_is_canonicalized && !is_well_formed_language_tag(tag))
return vm.throw_completion<RangeError>(ErrorType::IntlInvalidLanguageTag, tag);
// 12. NOTE: Because LanguageId canonicalization can alter tag in arbitrary ways according to Alias Rules from
@ -301,7 +337,8 @@ ThrowCompletionOr<GC::Ref<Object>> LocaleConstructor::construct(FunctionObject&
// options.
// 13. Set tag to CanonicalizeUnicodeLocaleId(tag).
tag = canonicalize_unicode_locale_id(tag);
if (!tag_is_canonicalized)
tag = canonicalize_unicode_locale_id(tag);
// 14. Set tag to ? UpdateLanguageId(tag, options).
tag = TRY(update_language_id(vm, tag, options));
@ -313,13 +350,13 @@ ThrowCompletionOr<GC::Ref<Object>> LocaleConstructor::construct(FunctionObject&
// 17. If calendar is not undefined, then
// a. If calendar cannot be matched by the type Unicode locale nonterminal, throw a RangeError exception.
// 18. Set opt.[[ca]] to calendar.
opt.ca = TRY(get_string_option(vm, options, vm.names.calendar, Unicode::is_type_identifier));
opt.ca = TRY(get_string_option(vm, options, vm.names.calendar, is_type_identifier));
// 19. Let collation be ? GetOption(options, "collation", STRING, EMPTY, undefined).
// 20. If collation is not undefined, then
// a. If collation cannot be matched by the type Unicode locale nonterminal, throw a RangeError exception.
// 21. Set opt.[[co]] to collation.
opt.co = TRY(get_string_option(vm, options, vm.names.collation, Unicode::is_type_identifier));
opt.co = TRY(get_string_option(vm, options, vm.names.collation, is_type_identifier));
// 22. Let fw be ? GetOption(options, "firstDayOfWeek", STRING, EMPTY, undefined).
auto first_day_of_week = TRY(get_string_option(vm, options, vm.names.firstDayOfWeek, nullptr));
@ -351,13 +388,13 @@ ThrowCompletionOr<GC::Ref<Object>> LocaleConstructor::construct(FunctionObject&
// 30. If kn is not undefined, set kn to ! ToString(kn).
// 31. Set opt.[[kn]] to kn.
if (!kn.is_undefined())
opt.kn = TRY(kn.to_utf16_string(vm)).to_utf8_but_should_be_ported_to_utf16();
opt.kn = kn.as_bool() ? "true"_string : "false"_string;
// 32. Let numberingSystem be ? GetOption(options, "numberingSystem", STRING, EMPTY, undefined).
// 33. If numberingSystem is not undefined, then
// a. If numberingSystem cannot be matched by the type Unicode locale nonterminal, throw a RangeError exception.
// 34. Set opt.[[nu]] to numberingSystem.
opt.nu = TRY(get_string_option(vm, options, vm.names.numberingSystem, Unicode::is_type_identifier));
opt.nu = TRY(get_string_option(vm, options, vm.names.numberingSystem, is_type_identifier));
// 35. Let r be MakeLocaleRecord(tag, opt, localeExtensionKeys).
auto result = make_locale_record(tag, move(opt), locale_extension_keys);

View file

@ -21,13 +21,13 @@ double MathematicalValue::as_number() const
bool MathematicalValue::is_string() const
{
return m_value.has<String>();
return m_value.has<Utf16String>();
}
String const& MathematicalValue::as_string() const
Utf16String const& MathematicalValue::as_string() const
{
VERIFY(is_string());
return m_value.get<String>();
return m_value.get<Utf16String>();
}
bool MathematicalValue::is_mathematical_value() const
@ -69,7 +69,7 @@ Unicode::NumberFormat::Value MathematicalValue::to_value() const
[](double value) -> Unicode::NumberFormat::Value {
return value;
},
[](String const& value) -> Unicode::NumberFormat::Value {
[](Utf16String const& value) -> Unicode::NumberFormat::Value {
return value;
},
[](auto symbol) -> Unicode::NumberFormat::Value {

View file

@ -7,6 +7,7 @@
#pragma once
#include <AK/String.h>
#include <AK/Utf16String.h>
#include <AK/Variant.h>
#include <LibCrypto/BigInt/SignedBigInteger.h>
#include <LibJS/Export.h>
@ -33,7 +34,7 @@ public:
{
}
explicit MathematicalValue(String value)
explicit MathematicalValue(Utf16String value)
: m_value(move(value))
{
}
@ -46,7 +47,7 @@ public:
MathematicalValue(Value value)
: m_value(value.is_number()
? value_from_number(value.as_double())
: ValueType(MUST(value.as_bigint().big_integer().to_base(10))))
: ValueType(Utf16String::from_utf8(MUST(value.as_bigint().big_integer().to_base(10)))))
{
}
@ -54,7 +55,7 @@ public:
double as_number() const;
bool is_string() const;
String const& as_string() const;
Utf16String const& as_string() const;
bool is_mathematical_value() const;
bool is_positive_infinity() const;
@ -65,7 +66,7 @@ public:
Unicode::NumberFormat::Value to_value() const;
private:
using ValueType = Variant<double, String, Symbol>;
using ValueType = Variant<double, Utf16String, Symbol>;
static ValueType value_from_number(double number);

View file

@ -210,7 +210,7 @@ ThrowCompletionOr<MathematicalValue> to_intl_mathematical_value(VM& vm, Value va
// 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));
return Utf16String::from_utf8(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.
@ -222,7 +222,7 @@ ThrowCompletionOr<MathematicalValue> to_intl_mathematical_value(VM& vm, Value va
// 3. If Type(primValue) is String,
// a. Let str be primValue.
auto string = primitive_value.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16();
auto string = primitive_value.as_string().utf16_string_view();
// Step 4 handled separately by the FIXME above.
@ -234,7 +234,7 @@ ThrowCompletionOr<MathematicalValue> to_intl_mathematical_value(VM& vm, Value va
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('-'))
if (mathematical_value == 0.0 && string.trim_ascii_whitespace(TrimMode::Left).starts_with('-'))
return MathematicalValue::Symbol::NegativeZero;
// 8. If mv is 10^10000 and str contains Infinity, return positive-infinity.
@ -246,7 +246,7 @@ ThrowCompletionOr<MathematicalValue> to_intl_mathematical_value(VM& vm, Value va
return MathematicalValue::Symbol::NegativeInfinity;
// 10. Return mv.
return string;
return Utf16String::from_utf16(string);
}
// 16.5.19 PartitionNumberRangePattern ( numberFormat, x, y ), https://tc39.es/ecma402/#sec-partitionnumberrangepattern

View file

@ -8,6 +8,7 @@
#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/Utf16View.h>
#include <LibJS/Export.h>
#include <LibJS/Runtime/Intl/AbstractOperations.h>
#include <LibJS/Runtime/Intl/IntlObject.h>
@ -56,11 +57,13 @@ public:
Unicode::Notation notation() const { return m_notation; }
StringView notation_string() const { return Unicode::notation_to_string(m_notation); }
void set_notation(StringView notation) { m_notation = Unicode::notation_from_string(notation); }
void set_notation(Utf16View notation) { m_notation = Unicode::notation_from_string(notation); }
bool has_compact_display() const { return m_compact_display.has_value(); }
Unicode::CompactDisplay compact_display() const { return *m_compact_display; }
StringView compact_display_string() const { return Unicode::compact_display_to_string(*m_compact_display); }
void set_compact_display(StringView compact_display) { m_compact_display = Unicode::compact_display_from_string(compact_display); }
void set_compact_display(Utf16View compact_display) { m_compact_display = Unicode::compact_display_from_string(compact_display); }
Unicode::RoundingType rounding_type() const { return m_rounding_type; }
StringView rounding_type_string() const { return Unicode::rounding_type_to_string(m_rounding_type); }
@ -73,6 +76,7 @@ public:
Unicode::RoundingMode rounding_mode() const { return m_rounding_mode; }
StringView rounding_mode_string() const { return Unicode::rounding_mode_to_string(m_rounding_mode); }
void set_rounding_mode(StringView rounding_mode) { m_rounding_mode = Unicode::rounding_mode_from_string(rounding_mode); }
void set_rounding_mode(Utf16View rounding_mode) { m_rounding_mode = Unicode::rounding_mode_from_string(rounding_mode); }
int rounding_increment() const { return m_rounding_increment; }
void set_rounding_increment(int rounding_increment) { m_rounding_increment = rounding_increment; }
@ -80,6 +84,7 @@ public:
Unicode::TrailingZeroDisplay trailing_zero_display() const { return m_trailing_zero_display; }
StringView trailing_zero_display_string() const { return Unicode::trailing_zero_display_to_string(m_trailing_zero_display); }
void set_trailing_zero_display(StringView trailing_zero_display) { m_trailing_zero_display = Unicode::trailing_zero_display_from_string(trailing_zero_display); }
void set_trailing_zero_display(Utf16View trailing_zero_display) { m_trailing_zero_display = Unicode::trailing_zero_display_from_string(trailing_zero_display); }
virtual Unicode::DisplayOptions display_options() const;
Unicode::RoundingOptions rounding_options() const;
@ -125,6 +130,7 @@ public:
Unicode::NumberFormatStyle style() const { return m_style; }
StringView style_string() const { return Unicode::number_format_style_to_string(m_style); }
void set_style(StringView style) { m_style = Unicode::number_format_style_from_string(style); }
void set_style(Utf16View style) { m_style = Unicode::number_format_style_from_string(style); }
bool has_currency() const { return m_currency.has_value(); }
String const& currency() const { return m_currency.value(); }
@ -134,11 +140,13 @@ public:
Unicode::CurrencyDisplay currency_display() const { return *m_currency_display; }
StringView currency_display_string() const { return Unicode::currency_display_to_string(*m_currency_display); }
void set_currency_display(StringView currency_display) { m_currency_display = Unicode::currency_display_from_string(currency_display); }
void set_currency_display(Utf16View currency_display) { m_currency_display = Unicode::currency_display_from_string(currency_display); }
bool has_currency_sign() const { return m_currency_sign.has_value(); }
Unicode::CurrencySign currency_sign() const { return *m_currency_sign; }
StringView currency_sign_string() const { return Unicode::currency_sign_to_string(*m_currency_sign); }
void set_currency_sign(StringView currency_sign) { m_currency_sign = Unicode::currency_sign_from_string(currency_sign); }
void set_currency_sign(Utf16View currency_sign) { m_currency_sign = Unicode::currency_sign_from_string(currency_sign); }
bool has_unit() const { return m_unit.has_value(); }
String const& unit() const { return m_unit.value(); }
@ -148,6 +156,7 @@ public:
Unicode::Style unit_display() const { return *m_unit_display; }
StringView unit_display_string() const { return Unicode::style_to_string(*m_unit_display); }
void set_unit_display(StringView unit_display) { m_unit_display = Unicode::style_from_string(unit_display); }
void set_unit_display(Utf16View unit_display) { m_unit_display = Unicode::style_from_string(unit_display); }
Unicode::Grouping use_grouping() const { return m_use_grouping; }
Value use_grouping_to_value(VM&) const;
@ -156,6 +165,7 @@ public:
Unicode::SignDisplay sign_display() const { return m_sign_display; }
StringView sign_display_string() const { return Unicode::sign_display_to_string(m_sign_display); }
void set_sign_display(StringView sign_display) { m_sign_display = Unicode::sign_display_from_string(sign_display); }
void set_sign_display(Utf16View sign_display) { m_sign_display = Unicode::sign_display_from_string(sign_display); }
NativeFunction* bound_format() const { return m_bound_format; }
void set_bound_format(NativeFunction* bound_format) { m_bound_format = bound_format; }

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/CharacterTypes.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/GlobalObject.h>
@ -15,6 +16,19 @@ namespace JS::Intl {
GC_DEFINE_ALLOCATOR(NumberFormatConstructor);
static String ascii_uppercase_currency_code(Utf16View currency)
{
VERIFY(currency.length_in_code_units() == 3);
char code[3];
for (size_t i = 0; i < currency.length_in_code_units(); ++i) {
VERIFY(is_ascii_alpha(currency.code_unit_at(i)));
code[i] = static_cast<char>(to_ascii_uppercase(currency.code_unit_at(i)));
}
return String::from_ascii_short_string_without_validation(code, 3);
}
// 16.1 The Intl.NumberFormat Constructor, https://tc39.es/ecma402/#sec-intl-numberformat-constructor
NumberFormatConstructor::NumberFormatConstructor(Realm& realm)
: NativeFunction(realm.vm().names.NumberFormat.as_string(), realm.intrinsics().function_prototype())
@ -78,7 +92,7 @@ ThrowCompletionOr<GC::Ref<Object>> NumberFormatConstructor::construct(FunctionOb
auto notation = TRY(get_option(vm, *options, vm.names.notation, OptionType::String, { "standard"sv, "scientific"sv, "engineering"sv, "compact"sv }, "standard"sv));
// 12. Set numberFormat.[[Notation]] to notation.
number_format->set_notation(notation.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
number_format->set_notation(notation.as_string().utf16_string_view());
int default_min_fraction_digits = 0;
int default_max_fraction_digits = 0;
@ -121,7 +135,7 @@ ThrowCompletionOr<GC::Ref<Object>> NumberFormatConstructor::construct(FunctionOb
// 18. If notation is "compact", then
if (number_format->notation() == Unicode::Notation::Compact) {
// a. Set numberFormat.[[CompactDisplay]] to compactDisplay.
number_format->set_compact_display(compact_display.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
number_format->set_compact_display(compact_display.as_string().utf16_string_view());
// b. Set defaultUseGrouping to "min2".
default_use_grouping = "min2"sv;
@ -150,7 +164,7 @@ ThrowCompletionOr<GC::Ref<Object>> NumberFormatConstructor::construct(FunctionOb
auto sign_display = TRY(get_option(vm, *options, vm.names.signDisplay, OptionType::String, { "auto"sv, "never"sv, "always"sv, "exceptZero"sv, "negative"sv }, "auto"sv));
// 25. Set numberFormat.[[SignDisplay]] to signDisplay.
number_format->set_sign_display(sign_display.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
number_format->set_sign_display(sign_display.as_string().utf16_string_view());
// 26. If the implementation supports the normative optional constructor mode of 4.3 Note 1, then
// a. Let this be the this value.
@ -202,7 +216,7 @@ ThrowCompletionOr<void> set_number_format_digit_options(VM& vm, NumberFormatBase
// 10. Let roundingPriority be ? GetOption(options, "roundingPriority", STRING, « "auto", "morePrecision", "lessPrecision" », "auto").
auto rounding_priority_option = TRY(get_option(vm, options, vm.names.roundingPriority, OptionType::String, { "auto"sv, "morePrecision"sv, "lessPrecision"sv }, "auto"sv));
auto rounding_priority = rounding_priority_option.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16();
auto rounding_priority = rounding_priority_option.as_string().utf16_string_view();
// 11. Let trailingZeroDisplay be ? GetOption(options, "trailingZeroDisplay", STRING, « "auto", "stripIfInteger" », "auto").
auto trailing_zero_display = TRY(get_option(vm, options, vm.names.trailingZeroDisplay, OptionType::String, { "auto"sv, "stripIfInteger"sv }, "auto"sv));
@ -217,10 +231,10 @@ ThrowCompletionOr<void> set_number_format_digit_options(VM& vm, NumberFormatBase
intl_object.set_rounding_increment(*rounding_increment);
// 15. Set intlObj.[[RoundingMode]] to roundingMode.
intl_object.set_rounding_mode(rounding_mode.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
intl_object.set_rounding_mode(rounding_mode.as_string().utf16_string_view());
// 16. Set intlObj.[[TrailingZeroDisplay]] to trailingZeroDisplay.
intl_object.set_trailing_zero_display(trailing_zero_display.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
intl_object.set_trailing_zero_display(trailing_zero_display.as_string().utf16_string_view());
// 17. If mnsd is undefined and mxsd is undefined, let hasSd be false. Otherwise, let hasSd be true.
bool has_significant_digits = !min_significant_digits.is_undefined() || !max_significant_digits.is_undefined();
@ -379,7 +393,7 @@ ThrowCompletionOr<void> set_number_format_unit_options(VM& vm, NumberFormat& int
auto style = TRY(get_option(vm, options, vm.names.style, OptionType::String, { "decimal"sv, "percent"sv, "currency"sv, "unit"sv }, "decimal"sv));
// 2. Set intlObj.[[Style]] to style.
intl_object.set_style(style.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
intl_object.set_style(style.as_string().utf16_string_view());
// 3. Let currency be ? GetOption(options, "currency", STRING, EMPTY, undefined).
auto currency = TRY(get_option(vm, options, vm.names.currency, OptionType::String, {}, Empty {}));
@ -392,7 +406,7 @@ ThrowCompletionOr<void> set_number_format_unit_options(VM& vm, NumberFormat& int
}
// 5. Else,
// a. If IsWellFormedCurrencyCode(currency) is false, throw a RangeError exception.
else if (!is_well_formed_currency_code(currency.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16())) {
else if (!is_well_formed_currency_code(currency.as_string().utf16_string_view())) {
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, currency, "currency"sv);
}
@ -413,7 +427,7 @@ ThrowCompletionOr<void> set_number_format_unit_options(VM& vm, NumberFormat& int
}
// 10. Else,
// a. If IsWellFormedUnitIdentifier(unit) is false, throw a RangeError exception.
else if (!is_well_formed_unit_identifier(unit.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16())) {
else if (!is_well_formed_unit_identifier(unit.as_string().utf16_string_view())) {
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, unit, "unit"sv);
}
@ -423,22 +437,22 @@ ThrowCompletionOr<void> set_number_format_unit_options(VM& vm, NumberFormat& int
// 12. If style is "currency", then
if (intl_object.style() == Unicode::NumberFormatStyle::Currency) {
// a. Set intlObj.[[Currency]] to the ASCII-uppercase of currency.
intl_object.set_currency(MUST(currency.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16().to_uppercase()));
intl_object.set_currency(ascii_uppercase_currency_code(currency.as_string().utf16_string_view()));
// c. Set intlObj.[[CurrencyDisplay]] to currencyDisplay.
intl_object.set_currency_display(currency_display.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
intl_object.set_currency_display(currency_display.as_string().utf16_string_view());
// d. Set intlObj.[[CurrencySign]] to currencySign.
intl_object.set_currency_sign(currency_sign.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
intl_object.set_currency_sign(currency_sign.as_string().utf16_string_view());
}
// 13. If style is "unit", then
if (intl_object.style() == Unicode::NumberFormatStyle::Unit) {
// a. Set intlObj.[[Unit]] to unit.
intl_object.set_unit(unit.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
intl_object.set_unit(MUST(unit.as_string().utf16_string_view().to_utf8()));
// b. Set intlObj.[[UnitDisplay]] to unitDisplay.
intl_object.set_unit_display(unit_display.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
intl_object.set_unit_display(unit_display.as_string().utf16_string_view());
}
// 14. Return UNUSED.

View file

@ -7,6 +7,7 @@
#pragma once
#include <AK/StringView.h>
#include <AK/Utf16View.h>
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/Intl/NumberFormat.h>
#include <LibJS/Runtime/Object.h>
@ -27,6 +28,7 @@ public:
Unicode::PluralForm type() const { return m_type; }
StringView type_string() const { return Unicode::plural_form_to_string(m_type); }
void set_type(StringView type) { m_type = Unicode::plural_form_from_string(type); }
void set_type(Utf16View type) { m_type = Unicode::plural_form_from_string(type); }
private:
explicit PluralRules(Object& prototype);

View file

@ -67,13 +67,13 @@ ThrowCompletionOr<GC::Ref<Object>> PluralRulesConstructor::construct(FunctionObj
auto type = TRY(get_option(vm, *options, vm.names.type, OptionType::String, AK::Array { "cardinal"sv, "ordinal"sv }, "cardinal"sv));
// 8. Set pluralRules.[[Type]] to t.
plural_rules->set_type(type.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
plural_rules->set_type(type.as_string().utf16_string_view());
// 9. Let notation be ? GetOption(options, "notation", string, « "standard", "scientific", "engineering", "compact" », "standard").
auto notation = TRY(get_option(vm, *options, vm.names.notation, OptionType::String, { "standard"sv, "scientific"sv, "engineering"sv, "compact"sv }, "standard"sv));
// 10. Set pluralRules.[[Notation]] to notation.
plural_rules->set_notation(notation.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
plural_rules->set_notation(notation.as_string().utf16_string_view());
// 11. Let compactDisplay be ? GetOption(options, "compactDisplay", string, « "short", "long" », "short").
auto compact_display = TRY(get_option(vm, *options, vm.names.compactDisplay, OptionType::String, { "short"sv, "long"sv }, "short"sv));
@ -81,7 +81,7 @@ ThrowCompletionOr<GC::Ref<Object>> PluralRulesConstructor::construct(FunctionObj
// 12. If notation is "compact", then
if (plural_rules->notation() == Unicode::Notation::Compact) {
// a. Set pluralRules.[[CompactDisplay]] to compactDisplay.
plural_rules->set_compact_display(compact_display.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
plural_rules->set_compact_display(compact_display.as_string().utf16_string_view());
}
// 13. Perform ? SetNumberFormatDigitOptions(pluralRules, options, 0, 3, notation).

View file

@ -43,7 +43,7 @@ ReadonlySpan<ResolutionOptionDescriptor> RelativeTimeFormat::resolution_option_d
}
// 18.5.1 SingularRelativeTimeUnit ( unit ), https://tc39.es/ecma402/#sec-singularrelativetimeunit
ThrowCompletionOr<Unicode::TimeUnit> singular_relative_time_unit(VM& vm, StringView unit)
ThrowCompletionOr<Unicode::TimeUnit> singular_relative_time_unit(VM& vm, Utf16View unit)
{
// 1. If unit is "seconds", return "second".
if (unit == "seconds"sv)
@ -78,7 +78,7 @@ ThrowCompletionOr<Unicode::TimeUnit> singular_relative_time_unit(VM& vm, StringV
}
// 18.5.2 PartitionRelativeTimePattern ( relativeTimeFormat, value, unit ), https://tc39.es/ecma402/#sec-PartitionRelativeTimePattern
ThrowCompletionOr<Vector<Unicode::RelativeTimeFormat::Partition>> partition_relative_time_pattern(VM& vm, RelativeTimeFormat& relative_time_format, double value, StringView unit)
ThrowCompletionOr<Vector<Unicode::RelativeTimeFormat::Partition>> partition_relative_time_pattern(VM& vm, RelativeTimeFormat& relative_time_format, double value, Utf16View unit)
{
// 1. If value is NaN, +∞𝔽, or -∞𝔽, throw a RangeError exception.
if (!Value(value).is_finite_number())
@ -91,7 +91,7 @@ ThrowCompletionOr<Vector<Unicode::RelativeTimeFormat::Partition>> partition_rela
}
// 18.5.4 FormatRelativeTime ( relativeTimeFormat, value, unit ), https://tc39.es/ecma402/#sec-FormatRelativeTime
ThrowCompletionOr<Utf16String> format_relative_time(VM& vm, RelativeTimeFormat& relative_time_format, double value, StringView unit)
ThrowCompletionOr<Utf16String> format_relative_time(VM& vm, RelativeTimeFormat& relative_time_format, double value, Utf16View unit)
{
// 1. Let parts be ? PartitionRelativeTimePattern(relativeTimeFormat, value, unit).
auto time_unit = TRY([&]() -> ThrowCompletionOr<Unicode::TimeUnit> {
@ -114,7 +114,7 @@ ThrowCompletionOr<Utf16String> format_relative_time(VM& vm, RelativeTimeFormat&
}
// 18.5.5 FormatRelativeTimeToParts ( relativeTimeFormat, value, unit ), https://tc39.es/ecma402/#sec-FormatRelativeTimeToParts
ThrowCompletionOr<GC::Ref<Array>> format_relative_time_to_parts(VM& vm, RelativeTimeFormat& relative_time_format, double value, StringView unit)
ThrowCompletionOr<GC::Ref<Array>> format_relative_time_to_parts(VM& vm, RelativeTimeFormat& relative_time_format, double value, Utf16View unit)
{
auto& realm = *vm.current_realm();

View file

@ -8,6 +8,7 @@
#include <AK/String.h>
#include <AK/StringView.h>
#include <AK/Utf16View.h>
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/Intl/IntlObject.h>
#include <LibUnicode/Locale.h>
@ -33,10 +34,12 @@ public:
Unicode::Style style() const { return m_style; }
void set_style(StringView style) { m_style = Unicode::style_from_string(style); }
void set_style(Utf16View style) { m_style = Unicode::style_from_string(style); }
StringView style_string() const { return Unicode::style_to_string(m_style); }
Unicode::NumericDisplay numeric() const { return m_numeric; }
void set_numeric(StringView numeric) { m_numeric = Unicode::numeric_display_from_string(numeric); }
void set_numeric(Utf16View numeric) { m_numeric = Unicode::numeric_display_from_string(numeric); }
StringView numeric_string() const { return Unicode::numeric_display_to_string(m_numeric); }
Unicode::RelativeTimeFormat const& formatter() const { return *m_formatter; }
@ -54,9 +57,9 @@ private:
OwnPtr<Unicode::RelativeTimeFormat> m_formatter;
};
ThrowCompletionOr<Unicode::TimeUnit> singular_relative_time_unit(VM&, StringView unit);
ThrowCompletionOr<Vector<Unicode::RelativeTimeFormat::Partition>> partition_relative_time_pattern(VM&, RelativeTimeFormat&, double value, StringView unit);
ThrowCompletionOr<Utf16String> format_relative_time(VM&, RelativeTimeFormat&, double value, StringView unit);
ThrowCompletionOr<GC::Ref<Array>> format_relative_time_to_parts(VM&, RelativeTimeFormat&, double value, StringView unit);
ThrowCompletionOr<Unicode::TimeUnit> singular_relative_time_unit(VM&, Utf16View unit);
ThrowCompletionOr<Vector<Unicode::RelativeTimeFormat::Partition>> partition_relative_time_pattern(VM&, RelativeTimeFormat&, double value, Utf16View unit);
ThrowCompletionOr<Utf16String> format_relative_time(VM&, RelativeTimeFormat&, double value, Utf16View unit);
ThrowCompletionOr<GC::Ref<Array>> format_relative_time_to_parts(VM&, RelativeTimeFormat&, double value, Utf16View unit);
}

View file

@ -75,13 +75,13 @@ ThrowCompletionOr<GC::Ref<Object>> RelativeTimeFormatConstructor::construct(Func
auto style = TRY(get_option(vm, *options, vm.names.style, OptionType::String, { "long"sv, "short"sv, "narrow"sv }, "long"sv));
// 11. Set relativeTimeFormat.[[Style]] to style.
relative_time_format->set_style(style.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
relative_time_format->set_style(style.as_string().utf16_string_view());
// 12. Let numeric be ? GetOption(options, "numeric", STRING, « "always", "auto" », "always").
auto numeric = TRY(get_option(vm, *options, vm.names.numeric, OptionType::String, { "always"sv, "auto"sv }, "always"sv));
// 13. Set relativeTimeFormat.[[Numeric]] to numeric.
relative_time_format->set_numeric(numeric.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
relative_time_format->set_numeric(numeric.as_string().utf16_string_view());
// 14. Let nfOptions be OrdinaryObjectCreate(null).
// 15. Perform ! CreateDataPropertyOrThrow(nfOptions, "numberingSystem", relativeTimeFormat.[[NumberingSystem]]).

View file

@ -71,10 +71,10 @@ JS_DEFINE_NATIVE_FUNCTION(RelativeTimeFormatPrototype::format)
auto value = TRY(vm.argument(0).to_number(vm));
// 4. Let unit be ? ToString(unit).
auto unit = TRY(vm.argument(1).to_utf16_string(vm)).to_utf8_but_should_be_ported_to_utf16();
auto unit = TRY(vm.argument(1).to_utf16_string(vm));
// 5. Return ? FormatRelativeTime(relativeTimeFormat, value, unit).
auto formatted = TRY(format_relative_time(vm, relative_time_format, value.as_double(), unit));
auto formatted = TRY(format_relative_time(vm, relative_time_format, value.as_double(), unit.utf16_view()));
return PrimitiveString::create(vm, move(formatted));
}
@ -89,10 +89,10 @@ JS_DEFINE_NATIVE_FUNCTION(RelativeTimeFormatPrototype::format_to_parts)
auto value = TRY(vm.argument(0).to_number(vm));
// 4. Let unit be ? ToString(unit).
auto unit = TRY(vm.argument(1).to_utf16_string(vm)).to_utf8_but_should_be_ported_to_utf16();
auto unit = TRY(vm.argument(1).to_utf16_string(vm));
// 5. Return ? FormatRelativeTimeToParts(relativeTimeFormat, value, unit).
return TRY(format_relative_time_to_parts(vm, relative_time_format, value.as_double(), unit));
return TRY(format_relative_time_to_parts(vm, relative_time_format, value.as_double(), unit.utf16_view()));
}
}

View file

@ -8,6 +8,7 @@
#pragma once
#include <AK/String.h>
#include <AK/Utf16View.h>
#include <LibJS/Runtime/Intl/IntlObject.h>
#include <LibUnicode/Segmenter.h>
@ -28,6 +29,7 @@ public:
Unicode::SegmenterGranularity segmenter_granularity() const { return m_segmenter_granularity; }
void set_segmenter_granularity(StringView segmenter_granularity) { m_segmenter_granularity = Unicode::segmenter_granularity_from_string(segmenter_granularity); }
void set_segmenter_granularity(Utf16View segmenter_granularity) { m_segmenter_granularity = Unicode::segmenter_granularity_from_string(segmenter_granularity); }
StringView segmenter_granularity_string() const { return Unicode::segmenter_granularity_to_string(m_segmenter_granularity); }
Unicode::Segmenter const& segmenter() const { return *m_segmenter; }

View file

@ -67,7 +67,7 @@ ThrowCompletionOr<GC::Ref<Object>> SegmenterConstructor::construct(FunctionObjec
auto granularity = TRY(get_option(vm, *options, vm.names.granularity, OptionType::String, { "grapheme"sv, "word"sv, "sentence"sv }, "grapheme"sv));
// 9. Set segmenter.[[SegmenterGranularity]] to granularity.
segmenter->set_segmenter_granularity(granularity.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16());
segmenter->set_segmenter_granularity(granularity.as_string().utf16_string_view());
auto locale_segmenter = Unicode::Segmenter::create(segmenter->locale(), segmenter->segmenter_granularity());
segmenter->set_segmenter(move(locale_segmenter));

View file

@ -10,7 +10,6 @@
#include <AK/TypeCasts.h>
#include <AK/Utf16StringBuilder.h>
#include <AK/Utf16View.h>
#include <AK/Utf8View.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/BigIntObject.h>
@ -445,7 +444,7 @@ JS_DEFINE_NATIVE_FUNCTION(JSONObject::parse)
auto reviver = vm.argument(1);
// 1. Let jsonString be ? ToString(text).
auto json_string = TRY(text.to_utf16_string(vm)).to_utf8_but_should_be_ported_to_utf16();
auto json_string = TRY(text.to_utf16_string(vm));
// 2. Let parseResult be ? ParseJSON(jsonString).
// 3. Let unfiltered be parseResult.[[Value]].
@ -868,20 +867,28 @@ static ThrowCompletionOr<Value> parse_simdjson_document(VM& vm, simdjson::ondema
VERIFY_NOT_REACHED();
}
static StringView utf8_json_text_bytes(Utf16View text, Optional<String>& utf8_text)
{
if (text.has_ascii_storage())
return { text.bytes() };
utf8_text = MUST(text.to_utf8());
return utf8_text->bytes_as_string_view();
}
// 25.5.1.1 ParseJSON ( text ), https://tc39.es/ecma262/#sec-ParseJSON
ThrowCompletionOr<Value> JSONObject::parse_json(VM& vm, StringView text, JSONParseRecord* root_record)
ThrowCompletionOr<Value> JSONObject::parse_json(VM& vm, Utf16View text, JSONParseRecord* root_record)
{
// 1. If StringToCodePoints(text) is not a valid JSON text as specified in ECMA-404, throw a SyntaxError exception.
// NB: Per ECMA-404, the BOM is not valid JSON whitespace. simdjson silently skips it, so we must reject it explicitly.
if (text.length() >= 3
&& static_cast<u8>(text[0]) == 0xEF
&& static_cast<u8>(text[1]) == 0xBB
&& static_cast<u8>(text[2]) == 0xBF) {
if (text.length_in_code_units() >= 1 && text.code_unit_at(0) == 0xFEFF)
return vm.throw_completion<SyntaxError>(ErrorType::JsonMalformed);
}
Optional<String> utf8_text;
auto text_bytes = utf8_json_text_bytes(text, utf8_text);
simdjson::ondemand::parser parser;
simdjson::padded_string padded(text.characters_without_null_termination(), text.length());
simdjson::padded_string padded(text_bytes.characters_without_null_termination(), text_bytes.length());
simdjson::ondemand::document document;
if (parser.iterate(padded).get(document))
@ -996,18 +1003,18 @@ JS_DEFINE_NATIVE_FUNCTION(JSONObject::raw_json)
auto& realm = *vm.current_realm();
// 1. Let jsonString be ? ToString(text).
auto json_string = TRY(vm.argument(0).to_utf16_string(vm)).to_utf8_but_should_be_ported_to_utf16();
auto json_string = TRY(vm.argument(0).to_utf16_string(vm));
// 2. Throw a SyntaxError exception if jsonString is the empty String, or if either the first or last code unit of
// jsonString is any of 0x0009 (CHARACTER TABULATION), 0x000A (LINE FEED), 0x000D (CARRIAGE RETURN), or
// 0x0020 (SPACE).
auto bytes = json_string.bytes_as_string_view();
if (bytes.is_empty())
auto json_string_view = json_string.utf16_view();
if (json_string_view.is_empty())
return vm.throw_completion<SyntaxError>(ErrorType::JsonMalformed);
static constexpr AK::Array invalid_code_points { 0x09, 0x0A, 0x0D, 0x20 };
auto first_char = bytes[0];
auto last_char = bytes[bytes.length() - 1];
auto first_char = json_string_view.code_unit_at(0);
auto last_char = json_string_view.code_unit_at(json_string_view.length_in_code_units() - 1);
if (invalid_code_points.contains_slow(first_char) || invalid_code_points.contains_slow(last_char))
return vm.throw_completion<SyntaxError>(ErrorType::JsonMalformed);
@ -1015,8 +1022,11 @@ JS_DEFINE_NATIVE_FUNCTION(JSONObject::raw_json)
// 3. Parse StringToCodePoints(jsonString) as a JSON text as specified in ECMA-404. Throw a SyntaxError exception
// if it is not a valid JSON text as defined in that specification, or if its outermost value is an object or
// array as defined in that specification.
Optional<String> utf8_text;
auto text_bytes = utf8_json_text_bytes(json_string_view, utf8_text);
simdjson::ondemand::parser parser;
simdjson::padded_string padded(json_string.bytes_as_string_view().characters_without_null_termination(), json_string.bytes_as_string_view().length());
simdjson::padded_string padded(text_bytes.characters_without_null_termination(), text_bytes.length());
simdjson::ondemand::document doc;
if (parser.iterate(padded).get(doc))

View file

@ -41,7 +41,7 @@ public:
// test-js to communicate between the JS tests and the C++ test runner.
static ThrowCompletionOr<Optional<Utf16String>> stringify_impl(VM&, Value value, Value replacer, Value space);
static ThrowCompletionOr<Value> parse_json(VM&, StringView text, JSONParseRecord* root_record = nullptr);
static ThrowCompletionOr<Value> parse_json(VM&, Utf16View text, JSONParseRecord* root_record = nullptr);
private:
explicit JSONObject(Realm&);

View file

@ -148,61 +148,61 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::to_string)
// 4. Let isArray be ? IsArray(O).
auto is_array = TRY(Value(object).is_array(vm));
StringView builtin_tag;
Utf16View builtin_tag;
// 5. If isArray is true, let builtinTag be "Array".
if (is_array)
builtin_tag = "Array"sv;
builtin_tag = u"Array"sv;
// 6. Else if O has a [[ParameterMap]] internal slot, let builtinTag be "Arguments".
else if (object->has_parameter_map())
builtin_tag = "Arguments"sv;
builtin_tag = u"Arguments"sv;
// 7. Else if O has a [[Call]] internal method, let builtinTag be "Function".
else if (object->is_function())
builtin_tag = "Function"sv;
builtin_tag = u"Function"sv;
// 8. Else if O has an [[ErrorData]] internal slot, let builtinTag be "Error".
else if (object->has_error_data())
builtin_tag = "Error"sv;
builtin_tag = u"Error"sv;
// 9. Else if O has a [[BooleanData]] internal slot, let builtinTag be "Boolean".
else if (is<BooleanObject>(*object))
builtin_tag = "Boolean"sv;
builtin_tag = u"Boolean"sv;
// 10. Else if O has a [[NumberData]] internal slot, let builtinTag be "Number".
else if (is<NumberObject>(*object))
builtin_tag = "Number"sv;
builtin_tag = u"Number"sv;
// 11. Else if O has a [[StringData]] internal slot, let builtinTag be "String".
else if (is<StringObject>(*object))
builtin_tag = "String"sv;
builtin_tag = u"String"sv;
// 12. Else if O has a [[DateValue]] internal slot, let builtinTag be "Date".
else if (is<Date>(*object))
builtin_tag = "Date"sv;
builtin_tag = u"Date"sv;
// 13. Else if O has a [[RegExpMatcher]] internal slot, let builtinTag be "RegExp".
else if (is<RegExpObject>(*object))
builtin_tag = "RegExp"sv;
builtin_tag = u"RegExp"sv;
// 14. Else, let builtinTag be "Object".
else
builtin_tag = "Object"sv;
builtin_tag = u"Object"sv;
// 15. Let tag be ? Get(O, @@toStringTag).
static auto& cache = *new Bytecode::StaticPropertyLookupCache;
auto to_string_tag = TRY(object->get(vm.well_known_symbol_to_string_tag(), cache));
// Optimization: Instead of creating another PrimitiveString from builtin_tag, we separate tag and to_string_tag and add an additional branch to step 16.
StringView tag;
String custom_tag;
Utf16View tag;
Utf16String custom_tag;
// 16. If Type(tag) is not String, set tag to builtinTag.
if (!to_string_tag.is_string())
tag = builtin_tag;
else {
custom_tag = to_string_tag.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16();
tag = custom_tag;
custom_tag = to_string_tag.as_string().utf16_string();
tag = custom_tag.utf16_view();
}
// 17. Return the string-concatenation of "[object ", tag, and "]".
// OPTIMIZATION: The VM has a cache for the extremely common "[object Object]" string.
if (tag == "Object"sv)
if (tag == u"Object"sv)
return vm.cached_strings.object_Object;
return PrimitiveString::create(vm, MUST(String::formatted("[object {}]", tag)));
return PrimitiveString::create(vm, Utf16String::formatted("[object {}]", tag));
}
// 20.1.3.7 Object.prototype.valueOf ( ), https://tc39.es/ecma262/#sec-object.prototype.valueof
@ -262,7 +262,7 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::define_getter)
// 2. If IsCallable(getter) is false, throw a TypeError exception.
if (!getter.is_function())
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, getter.to_string_without_side_effects());
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, getter.to_utf16_string_without_side_effects());
// 3. Let desc be PropertyDescriptor { [[Get]]: getter, [[Enumerable]]: true, [[Configurable]]: true }.
auto descriptor = PropertyDescriptor { .get = &getter.as_function(), .enumerable = true, .configurable = true };
@ -288,7 +288,7 @@ JS_DEFINE_NATIVE_FUNCTION(ObjectPrototype::define_setter)
// 2. If IsCallable(setter) is false, throw a TypeError exception.
if (!setter.is_function())
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, setter.to_string_without_side_effects());
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, setter.to_utf16_string_without_side_effects());
// 3. Let desc be PropertyDescriptor { [[Set]]: setter, [[Enumerable]]: true, [[Configurable]]: true }.
auto descriptor = PropertyDescriptor { .set = &setter.as_function(), .enumerable = true, .configurable = true };

View file

@ -54,25 +54,25 @@ public:
namespace AK {
template<>
struct Formatter<JS::PropertyDescriptor> : Formatter<StringView> {
struct Formatter<JS::PropertyDescriptor> : Formatter<FormatString> {
ErrorOr<void> format(FormatBuilder& builder, JS::PropertyDescriptor const& property_descriptor)
{
Vector<String> parts;
Vector<Utf16String> parts;
if (property_descriptor.value.has_value())
TRY(parts.try_append(TRY(String::formatted("[[Value]]: {}", property_descriptor.value->to_string_without_side_effects()))));
TRY(parts.try_append(Utf16String::formatted("[[Value]]: {}", property_descriptor.value->to_utf16_string_without_side_effects())));
if (property_descriptor.get.has_value())
TRY(parts.try_append(TRY(String::formatted("[[Get]]: JS::Function* @ {:p}", property_descriptor.get->ptr()))));
TRY(parts.try_append(Utf16String::formatted("[[Get]]: JS::Function* @ {:p}", property_descriptor.get->ptr())));
if (property_descriptor.set.has_value())
TRY(parts.try_append(TRY(String::formatted("[[Set]]: JS::Function* @ {:p}", property_descriptor.set->ptr()))));
TRY(parts.try_append(Utf16String::formatted("[[Set]]: JS::Function* @ {:p}", property_descriptor.set->ptr())));
if (property_descriptor.writable.has_value())
TRY(parts.try_append(TRY(String::formatted("[[Writable]]: {}", *property_descriptor.writable))));
TRY(parts.try_append(Utf16String::formatted("[[Writable]]: {}", *property_descriptor.writable)));
if (property_descriptor.enumerable.has_value())
TRY(parts.try_append(TRY(String::formatted("[[Enumerable]]: {}", *property_descriptor.enumerable))));
TRY(parts.try_append(Utf16String::formatted("[[Enumerable]]: {}", *property_descriptor.enumerable)));
if (property_descriptor.configurable.has_value())
TRY(parts.try_append(TRY(String::formatted("[[Configurable]]: {}", *property_descriptor.configurable))));
TRY(parts.try_append(Utf16String::formatted("[[Configurable]]: {}", *property_descriptor.configurable)));
if (property_descriptor.unimplemented.has_value())
TRY(parts.try_append(TRY(String::formatted("[[Unimplemented]]: {}", *property_descriptor.unimplemented))));
return Formatter<StringView>::format(builder, TRY(String::formatted("PropertyDescriptor {{ {} }}", TRY(String::join(", "sv, parts)))));
TRY(parts.try_append(Utf16String::formatted("[[Unimplemented]]: {}", *property_descriptor.unimplemented)));
return Formatter<Utf16String> {}.format(builder, Utf16String::formatted("PropertyDescriptor {{ {} }}", Utf16String::join(", "sv, parts)));
}
};

View file

@ -239,7 +239,7 @@ struct Formatter<JS::PropertyKey> : Formatter<Utf16String> {
{
if (property_key.is_number())
return builder.put_u64(property_key.as_number());
return Formatter<Utf16String>::format(builder, property_key.to_string());
return Formatter<Utf16String> {}.format(builder, property_key.to_string());
}
};

View file

@ -221,11 +221,11 @@ static Utf16String encode_for_regexp_escape(u32 code_point)
// 3. Let otherPunctuators be the string-concatenation of ",-=<>#&!%:;@~'`" and the code unit 0x0022 (QUOTATION MARK).
// 4. Let toEscape be StringToCodePoints(otherPunctuators).
static constexpr Utf8View to_escape { ",-=<>#&!%:;@~'`\""sv };
static constexpr auto to_escape = ",-=<>#&!%:;@~'`\""sv;
// 5. If toEscape contains c, c is matched by either WhiteSpace or LineTerminator, or c has the same numeric value
// as a leading surrogate or trailing surrogate, then
if (to_escape.contains(code_point) || is_whitespace(code_point) || is_line_terminator(code_point) || is_unicode_surrogate(code_point)) {
if ((is_ascii(code_point) && to_escape.contains(static_cast<char>(code_point))) || is_whitespace(code_point) || is_line_terminator(code_point) || is_unicode_surrogate(code_point)) {
// a. Let cNum be the numeric value of c.
// b. If cNum ≤ 0xFF, then
if (code_point <= 0xFF) {

View file

@ -4,7 +4,6 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Utf8View.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/StringIterator.h>
@ -13,15 +12,15 @@ namespace JS {
GC_DEFINE_ALLOCATOR(StringIterator);
GC::Ref<StringIterator> StringIterator::create(Realm& realm, String string)
GC::Ref<StringIterator> StringIterator::create(Realm& realm, Utf16String string)
{
return realm.create<StringIterator>(move(string), realm.intrinsics().string_iterator_prototype());
}
StringIterator::StringIterator(String string, Object& prototype)
StringIterator::StringIterator(Utf16String string, Object& prototype)
: Object(ConstructWithPrototypeTag::Tag, prototype)
, m_string(move(string))
, m_iterator(Utf8View(m_string).begin())
, m_iterator(m_string.begin())
{
}
@ -42,14 +41,14 @@ ThrowCompletionOr<void> StringIterator::next(VM& vm, bool& done, Value& value)
return {};
}
if (m_iterator.done()) {
if (m_iterator == m_string.end()) {
m_done = true;
done = true;
value = js_undefined();
return {};
}
auto code_point = String::from_code_point(*m_iterator);
auto code_point = Utf16String::from_code_point(*m_iterator);
++m_iterator;
value = PrimitiveString::create(vm, move(code_point));

View file

@ -6,8 +6,7 @@
#pragma once
#include <AK/String.h>
#include <AK/Utf8View.h>
#include <AK/Utf16String.h>
#include <LibJS/Runtime/Iterator.h>
#include <LibJS/Runtime/Object.h>
@ -19,7 +18,7 @@ class StringIterator final : public Object
GC_DECLARE_ALLOCATOR(StringIterator);
public:
static GC::Ref<StringIterator> create(Realm&, String string);
static GC::Ref<StringIterator> create(Realm&, Utf16String string);
virtual ~StringIterator() override = default;
@ -27,12 +26,12 @@ public:
ThrowCompletionOr<void> next(VM&, bool& done, Value& value) override;
private:
explicit StringIterator(String string, Object& prototype);
explicit StringIterator(Utf16String string, Object& prototype);
friend class StringIteratorPrototype;
String m_string;
Utf8CodePointIterator m_iterator;
Utf16String m_string;
AK::Utf16CodePointIterator m_iterator;
bool m_done { false };
};

View file

@ -37,12 +37,6 @@ namespace JS {
GC_DEFINE_ALLOCATOR(StringPrototype);
static ThrowCompletionOr<String> utf8_string_from(VM& vm)
{
auto this_value = TRY(require_object_coercible(vm, vm.this_value()));
return TRY(this_value.to_utf16_string(vm)).to_utf8_but_should_be_ported_to_utf16();
}
static ThrowCompletionOr<GC::Ref<PrimitiveString>> primitive_string_from(VM& vm)
{
auto this_value = TRY(require_object_coercible(vm, vm.this_value()));
@ -704,26 +698,26 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::normalize)
{
// 1. Let O be ? RequireObjectCoercible(this value).
// 2. Let S be ? ToString(O).
auto string = TRY(utf8_string_from(vm));
auto string = TRY(primitive_string_from(vm));
String form;
Utf16String form;
// 3. If form is undefined, let f be "NFC".
if (auto form_value = vm.argument(0); form_value.is_undefined()) {
form = "NFC"_string;
form = "NFC"_utf16;
}
// 4. Else, let f be ? ToString(form).
else {
form = TRY(form_value.to_utf16_string(vm)).to_utf8_but_should_be_ported_to_utf16();
form = TRY(form_value.to_utf16_string(vm));
}
// 5. If f is not one of "NFC", "NFD", "NFKC", or "NFKD", throw a RangeError exception.
if (!form.is_one_of("NFC"sv, "NFD"sv, "NFKC"sv, "NFKD"sv))
if (!form.utf16_view().is_one_of(u"NFC"sv, u"NFD"sv, u"NFKC"sv, u"NFKD"sv))
return vm.throw_completion<RangeError>(ErrorType::InvalidNormalizationForm, form);
// 6. Let ns be the String value that is the result of normalizing S into the normalization form named by f as specified in https://unicode.org/reports/tr15/.
auto unicode_form = Unicode::normalization_form_from_string(form);
auto ns = Unicode::normalize(string, unicode_form);
auto unicode_form = Unicode::normalization_form_from_string(form.utf16_view());
auto ns = Unicode::normalize(string->utf16_string_view(), unicode_form);
// 7. Return ns.
return PrimitiveString::create(vm, move(ns));
@ -824,11 +818,11 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::repeat)
// 5. If n = 0, return the empty String.
if (n == 0)
return PrimitiveString::create(vm, String {});
return PrimitiveString::create(vm, Utf16String {});
// OPTIMIZATION: If the string is empty, the result will be empty as well.
if (string->is_empty())
return PrimitiveString::create(vm, String {});
return PrimitiveString::create(vm, Utf16String {});
if (n > static_cast<double>(NumericLimits<size_t>::max()))
return vm.throw_completion<RangeError>(ErrorType::StringRepeatCountMustNotOverflow);
@ -1348,7 +1342,7 @@ enum class TargetCase {
};
// 20.1.2.1 TransformCase ( S, locales, targetCase ), https://tc39.es/ecma402/#sec-transform-case
static ThrowCompletionOr<String> transform_case(VM& vm, String const& string, Value locales, TargetCase target_case)
static ThrowCompletionOr<Utf16String> transform_case(VM& vm, Utf16String const& string, Value locales, TargetCase target_case)
{
// 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
auto requested_locales = TRY(Intl::canonicalize_locale_list(vm, locales));
@ -1375,19 +1369,19 @@ static ThrowCompletionOr<String> transform_case(VM& vm, String const& string, Va
// 7. Let codePoints be StringToCodePoints(S).
String new_code_points;
Utf16String new_code_points;
switch (target_case) {
// 8. If targetCase is lower, then
case TargetCase::Lower:
// a. Let newCodePoints be a List whose elements are the result of a lowercase transformation of codePoints according to an implementation-derived algorithm using locale or the Unicode Default Case Conversion algorithm.
new_code_points = MUST(string.to_lowercase(locale));
new_code_points = string.to_lowercase(locale);
break;
// 9. Else,
case TargetCase::Upper:
// a. Assert: targetCase is upper.
// b. Let newCodePoints be a List whose elements are the result of an uppercase transformation of codePoints according to an implementation-derived algorithm using locale or the Unicode Default Case Conversion algorithm.
new_code_points = MUST(string.to_uppercase(locale));
new_code_points = string.to_uppercase(locale);
break;
default:
VERIFY_NOT_REACHED();
@ -1405,10 +1399,10 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_locale_lowercase)
// 1. Let O be ? RequireObjectCoercible(this value).
// 2. Let S be ? ToString(O).
auto string = TRY(utf8_string_from(vm));
auto string = TRY(primitive_string_from(vm));
// 3. Return ? TransformCase(S, locales, lower).
return PrimitiveString::create(vm, TRY(transform_case(vm, string, locales, TargetCase::Lower)));
return PrimitiveString::create(vm, TRY(transform_case(vm, string->utf16_string(), locales, TargetCase::Lower)));
}
// 22.1.3.27 String.prototype.toLocaleUpperCase ( [ reserved1 [ , reserved2 ] ] ), https://tc39.es/ecma262/#sec-string.prototype.tolocaleuppercase
@ -1419,10 +1413,10 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_locale_uppercase)
// 1. Let O be ? RequireObjectCoercible(this value).
// 2. Let S be ? ToString(O).
auto string = TRY(utf8_string_from(vm));
auto string = TRY(primitive_string_from(vm));
// 3. Return ? TransformCase(S, locales, upper).
return PrimitiveString::create(vm, TRY(transform_case(vm, string, locales, TargetCase::Upper)));
return PrimitiveString::create(vm, TRY(transform_case(vm, string->utf16_string(), locales, TargetCase::Upper)));
}
// 22.1.3.28 String.prototype.toLowerCase ( ), https://tc39.es/ecma262/#sec-string.prototype.tolowercase
@ -1431,10 +1425,10 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_lowercase)
// 1. Let O be ? RequireObjectCoercible(this value).
// 2. Let S be ? ToString(O).
// 3. Let sText be StringToCodePoints(S).
auto string = TRY(utf8_string_from(vm));
auto string = TRY(primitive_string_from(vm));
// 4. Let lowerText be the result of toLowercase(sText), according to the Unicode Default Case Conversion algorithm.
auto lowercase = MUST(string.to_lowercase());
auto lowercase = string->utf16_string().to_lowercase();
// 5. Let L be CodePointsToString(lowerText).
// 6. Return L.
@ -1453,8 +1447,8 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::to_uppercase)
{
// This method interprets a String value as a sequence of UTF-16 encoded code points, as described in 6.1.4.
// It behaves in exactly the same way as String.prototype.toLowerCase, except that the String is mapped using the toUppercase algorithm of the Unicode Default Case Conversion.
auto string = TRY(utf8_string_from(vm));
auto uppercase = MUST(string.to_uppercase());
auto string = TRY(primitive_string_from(vm));
auto uppercase = string->utf16_string().to_uppercase();
return PrimitiveString::create(vm, move(uppercase));
}
@ -1537,7 +1531,7 @@ JS_DEFINE_NATIVE_FUNCTION(StringPrototype::symbol_iterator)
auto this_object = TRY(require_object_coercible(vm, vm.this_value()));
// 2. Let s be ? ToString(O).
auto string = TRY(this_object.to_utf16_string(vm)).to_utf8_but_should_be_ported_to_utf16();
auto string = TRY(this_object.to_utf16_string(vm));
// 3. Let closure be a new Abstract Closure with no parameters that captures s and performs the following steps when called:
// ...

View file

@ -480,7 +480,7 @@ ThrowCompletionOr<RelativeTo> get_temporal_relative_to_option(VM& vm, Object con
String calendar;
Optional<String> time_zone;
Optional<String> offset_string;
Optional<Utf16String> offset_string;
ISODate iso_date;
Variant<ParsedISODateTime::StartOfDay, Time> time { Time {} };
@ -560,8 +560,7 @@ ThrowCompletionOr<RelativeTo> get_temporal_relative_to_option(VM& vm, Object con
// f. Else,
else {
// i. Let timeZone be ? ToTemporalTimeZoneIdentifier(annotation).
auto utf16_annotation = Utf16String::from_utf8(*annotation);
time_zone = TRY(to_temporal_time_zone_identifier(vm, utf16_annotation));
time_zone = TRY(to_temporal_time_zone_identifier(vm, *annotation));
// ii. If result.[[TimeZone]].[[Z]] is true, then
if (result.time_zone.z_designator) {
@ -580,8 +579,7 @@ ThrowCompletionOr<RelativeTo> get_temporal_relative_to_option(VM& vm, Object con
// v. If offsetString is not EMPTY, then
if (offset_string.has_value()) {
// 1. Let offsetParseResult be ParseText(StringToCodePoints(offsetString), UTCOffset[+SubMinutePrecision]).
auto utf16_offset_string = Utf16String::from_utf8(*offset_string);
auto offset_parse_result = parse_utc_offset(utf16_offset_string, SubMinutePrecision::Yes);
auto offset_parse_result = parse_utc_offset(*offset_string, SubMinutePrecision::Yes);
// 2. Assert: offsetParseResult is a Parse Node.
VERIFY(offset_parse_result.has_value());
@ -594,10 +592,10 @@ ThrowCompletionOr<RelativeTo> get_temporal_relative_to_option(VM& vm, Object con
// g. Let calendar be result.[[Calendar]].
// h. If calendar is EMPTY, set calendar to "iso8601".
calendar = result.calendar.value_or("iso8601"_string);
// i. Set calendar to ? CanonicalizeCalendar(calendar).
calendar = TRY(canonicalize_calendar(vm, calendar));
if (result.calendar.has_value())
calendar = TRY(canonicalize_calendar(vm, *result.calendar));
else
calendar = TRY(canonicalize_calendar(vm, "iso8601"sv));
// j. Let isoDate be CreateISODateRecord(result.[[Year]], result.[[Month]], result.[[Day]]).
iso_date = create_iso_date_record(*result.year, result.month, result.day);
@ -620,7 +618,7 @@ ThrowCompletionOr<RelativeTo> get_temporal_relative_to_option(VM& vm, Object con
// 8. If offsetBehaviour is OPTION, then
if (offset_behavior == OffsetBehavior::Option) {
// a. Let offsetNs be ! ParseDateTimeUTCOffset(offsetString).
offset_nanoseconds = parse_date_time_utc_offset(offset_string->bytes_as_string_view());
offset_nanoseconds = parse_date_time_utc_offset(*offset_string);
}
// 9. Else,
else {
@ -1103,7 +1101,7 @@ ThrowCompletionOr<ParsedISODateTime> parse_iso_date_time(VM& vm, Utf16View iso_s
Optional<ParseResult> parse_result;
// 2. Let calendar be EMPTY.
Optional<String> calendar;
Optional<Utf16String> calendar;
// 3. Let yearAbsent be false.
auto year_absent = false;
@ -1135,7 +1133,7 @@ ThrowCompletionOr<ParsedISODateTime> parse_iso_date_time(VM& vm, Utf16View iso_s
// i. If calendar is EMPTY, then
if (!calendar.has_value()) {
// i. Set calendar to CodePointsToString(value).
calendar = value.to_utf8_but_should_be_ported_to_utf16();
calendar = Utf16String::from_utf16(value);
// ii. If annotation contains an AnnotationCriticalFlag Parse Node, set calendarWasCritical to true.
if (annotation.critical)
@ -1160,14 +1158,14 @@ ThrowCompletionOr<ParsedISODateTime> parse_iso_date_time(VM& vm, Utf16View iso_s
// 3. If goal is TemporalYearMonthString and parseResult does not contain a DateDay Parse Node, then
if (goal == Production::TemporalYearMonthString && !parse_result->date_day.has_value()) {
// a. If calendar is not EMPTY and the ASCII-lowercase of calendar is not "iso8601", throw a RangeError exception.
if (calendar.has_value() && !calendar->equals_ignoring_ascii_case(ISO8601_CALENDAR))
if (calendar.has_value() && !calendar->equals_ignoring_ascii_case("iso8601"sv))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidCalendarIdentifier, *calendar);
}
// 4. If goal is TemporalMonthDayString and parseResult does not contain a DateYear Parse Node, then
if (goal == Production::TemporalMonthDayString && !parse_result->date_year.has_value()) {
// a. If calendar is not EMPTY and the ASCII-lowercase of calendar is not "iso8601", throw a RangeError exception.
if (calendar.has_value() && !calendar->equals_ignoring_ascii_case(ISO8601_CALENDAR))
if (calendar.has_value() && !calendar->equals_ignoring_ascii_case("iso8601"sv))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidCalendarIdentifier, *calendar);
// b. Set yearAbsent to true.
@ -1291,7 +1289,7 @@ ThrowCompletionOr<ParsedISODateTime> parse_iso_date_time(VM& vm, Utf16View iso_s
if (parse_result->time_zone_identifier.has_value()) {
// a. Let identifier be the source text matched by the TimeZoneIdentifier Parse Node contained within parseResult.
// b. Set timeZoneResult.[[TimeZoneAnnotation]] to CodePointsToString(identifier).
time_zone_result.time_zone_annotation = parse_result->time_zone_identifier->to_utf8_but_should_be_ported_to_utf16();
time_zone_result.time_zone_annotation = Utf16String::from_utf16(*parse_result->time_zone_identifier);
}
// 26. If parseResult contains a UTCDesignator Parse Node, then
@ -1303,7 +1301,7 @@ ThrowCompletionOr<ParsedISODateTime> parse_iso_date_time(VM& vm, Utf16View iso_s
else if (parse_result->date_time_offset.has_value()) {
// a. Let offset be the source text matched by the UTCOffset[+SubMinutePrecision] Parse Node contained within parseResult.
// b. Set timeZoneResult.[[OffsetString]] to CodePointsToString(offset).
time_zone_result.offset_string = parse_result->date_time_offset->source_text.to_utf8_but_should_be_ported_to_utf16();
time_zone_result.offset_string = Utf16String::from_utf16(parse_result->date_time_offset->source_text);
}
// 28. If yearAbsent is true, let yearReturn be EMPTY; else let yearReturn be yearMV.
@ -1316,7 +1314,7 @@ ThrowCompletionOr<ParsedISODateTime> parse_iso_date_time(VM& vm, Utf16View iso_s
}
// 13.36 ParseTemporalCalendarString ( string ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalcalendarstring
ThrowCompletionOr<String> parse_temporal_calendar_string(VM& vm, Utf16View string)
ThrowCompletionOr<Utf16String> parse_temporal_calendar_string(VM& vm, Utf16View string)
{
// 1. Let parseResult be Completion(ParseISODateTime(string, « TemporalDateTimeString[+Zoned], TemporalDateTimeString[~Zoned],
// TemporalInstantString, TemporalTimeString, TemporalMonthDayString, TemporalYearMonthString »)).
@ -1338,7 +1336,7 @@ ThrowCompletionOr<String> parse_temporal_calendar_string(VM& vm, Utf16View strin
// b. If calendar is EMPTY, return "iso8601".
// c. Else, return calendar.
return calendar.value_or("iso8601"_string);
return calendar.value_or(Utf16String::from_utf8_without_validation("iso8601"sv));
}
// 3. Set parseResult to ParseText(StringToCodePoints(string), AnnotationValue).
@ -1346,10 +1344,10 @@ ThrowCompletionOr<String> parse_temporal_calendar_string(VM& vm, Utf16View strin
// 4. If parseResult is a List of errors, throw a RangeError exception.
if (!annotation_parse_result.has_value())
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidCalendarString, string.to_utf8_but_should_be_ported_to_utf16());
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidCalendarString, string);
// 5. Return string.
return string.to_utf8_but_should_be_ported_to_utf16();
return Utf16String::from_utf16(string);
}
// 13.37 ParseTemporalDurationString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldurationstring
@ -1600,7 +1598,7 @@ ThrowCompletionOr<ParsedTimeZoneIdentifier> parse_temporal_time_zone_string(VM&
// 5. If timeZoneResult.[[TimeZoneAnnotation]] is not EMPTY, return ! ParseTimeZoneIdentifier(timeZoneResult.[[TimeZoneAnnotation]]).
if (time_zone_result.time_zone_annotation.has_value())
return parse_time_zone_identifier(*time_zone_result.time_zone_annotation);
return TRY(parse_time_zone_identifier(vm, *time_zone_result.time_zone_annotation));
// 6. If timeZoneResult.[[Z]] is true, return ! ParseTimeZoneIdentifier("UTC").
if (time_zone_result.z_designator)
@ -1615,7 +1613,7 @@ ThrowCompletionOr<ParsedTimeZoneIdentifier> parse_temporal_time_zone_string(VM&
}
// 13.41 ToOffsetString ( argument ), https://tc39.es/proposal-temporal/#sec-temporal-tooffsetstring
ThrowCompletionOr<String> to_offset_string(VM& vm, Value argument)
ThrowCompletionOr<Utf16String> to_offset_string(VM& vm, Value argument)
{
// 1. Let offset be ? ToPrimitive(argument, STRING).
auto offset = TRY(argument.to_primitive(vm, Value::PreferredType::String));
@ -1625,11 +1623,11 @@ ThrowCompletionOr<String> to_offset_string(VM& vm, Value argument)
return vm.throw_completion<TypeError>(ErrorType::TemporalInvalidTimeZoneString, offset);
// 3. Perform ? ParseDateTimeUTCOffset(offset).
auto offset_string = offset.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16();
TRY(parse_date_time_utc_offset(vm, offset_string.bytes_as_string_view()));
auto offset_string = offset.as_string().utf16_string_view();
TRY(parse_date_time_utc_offset(vm, offset_string));
// 4. Return offset.
return offset_string;
return Utf16String::from_utf16(offset_string);
}
// 13.42 ISODateToFields ( calendar, isoDate, type ), https://tc39.es/proposal-temporal/#sec-temporal-isodatetofields

View file

@ -192,10 +192,10 @@ double round_number_to_increment(double, u64 increment, RoundingMode);
Crypto::SignedBigInteger round_number_to_increment(Crypto::SignedBigInteger const&, Crypto::UnsignedBigInteger const& increment, RoundingMode);
Crypto::SignedBigInteger round_number_to_increment_as_if_positive(Crypto::SignedBigInteger const&, Crypto::UnsignedBigInteger const& increment, RoundingMode);
ThrowCompletionOr<ParsedISODateTime> parse_iso_date_time(VM&, Utf16View iso_string, ReadonlySpan<Production> allowed_formats);
ThrowCompletionOr<String> parse_temporal_calendar_string(VM&, Utf16View);
ThrowCompletionOr<Utf16String> parse_temporal_calendar_string(VM&, Utf16View);
ThrowCompletionOr<GC::Ref<Duration>> parse_temporal_duration_string(VM&, Utf16View iso_string);
ThrowCompletionOr<ParsedTimeZoneIdentifier> parse_temporal_time_zone_string(VM&, Utf16View time_zone_string);
ThrowCompletionOr<String> to_offset_string(VM&, Value argument);
ThrowCompletionOr<Utf16String> to_offset_string(VM&, Value argument);
CalendarFields iso_date_to_fields(String const& calendar, ISODate, DateType);
ThrowCompletionOr<DifferenceSettings> get_difference_settings(VM&, DurationOperation, Object const& options, UnitGroup, ReadonlySpan<Unit> disallowed_units, Unit fallback_smallest_unit, Unit smallest_largest_default_unit);

View file

@ -286,8 +286,11 @@ ThrowCompletionOr<String> canonicalize_calendar(VM& vm, StringView id)
ThrowCompletionOr<String> canonicalize_calendar(VM& vm, Utf16View id)
{
auto utf8_id = id.to_utf8_but_should_be_ported_to_utf16();
return canonicalize_calendar(vm, utf8_id.bytes_as_string_view());
if (!id.is_ascii())
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidCalendarIdentifier, id);
auto id_string = MUST(id.to_utf8());
return canonicalize_calendar(vm, id_string.bytes_as_string_view());
}
// 12.1.2 AvailableCalendars ( ), https://tc39.es/proposal-temporal/#sec-availablecalendars
@ -324,8 +327,7 @@ ThrowCompletionOr<MonthCode> parse_month_code(VM& vm, Value argument)
if (!month_code.is_string())
return vm.throw_completion<TypeError>(ErrorType::NotAString, month_code);
auto month_code_string = month_code.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16();
return parse_month_code(vm, month_code_string.bytes_as_string_view());
return parse_month_code(vm, month_code.as_string().utf16_string_view());
}
// 12.2.1 ParseMonthCode ( argument ), https://tc39.es/proposal-temporal/#sec-temporal-parsemonthcode
@ -337,6 +339,15 @@ ThrowCompletionOr<MonthCode> parse_month_code(VM& vm, StringView month_code)
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidMonthCode);
}
// 12.2.1 ParseMonthCode ( argument ), https://tc39.es/proposal-temporal/#sec-temporal-parsemonthcode
ThrowCompletionOr<MonthCode> parse_month_code(VM& vm, Utf16View month_code)
{
// 3. If ParseText(StringToCodePoints(monthCode), MonthCode) is a List of errors, throw a RangeError exception.
if (auto result = Unicode::parse_month_code(month_code); result.has_value())
return result.release_value();
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidMonthCode);
}
// 12.3.3 PrepareCalendarFields ( calendar, fields, calendarFieldNames, nonCalendarFieldNames, requiredFieldNames ), https://tc39.es/proposal-temporal/#sec-temporal-preparecalendarfields
ThrowCompletionOr<CalendarFields> prepare_calendar_fields(VM& vm, String const& calendar, Object const& fields, CalendarFieldList calendar_field_names, CalendarFieldList non_calendar_field_names, CalendarFieldListOrPartial required_field_names)
{
@ -395,7 +406,7 @@ ThrowCompletionOr<CalendarFields> prepare_calendar_fields(VM& vm, String const&
// v. Else if Conversion is TO-STRING, then
case CalendarFieldConversion::ToString:
// 1. Set value to ? ToString(value).
set_field_value(key, result, TRY(value.to_utf16_string(vm)).to_utf8_but_should_be_ported_to_utf16());
set_field_value(key, result, TRY(value.to_utf16_string(vm)));
break;
// vi. Else if Conversion is TO-TEMPORAL-TIME-ZONE-IDENTIFIER, then
case CalendarFieldConversion::ToTemporalTimeZoneIdentifier:
@ -1510,7 +1521,7 @@ ThrowCompletionOr<void> non_iso_resolve_fields(VM& vm, String const& calendar, C
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidCalendarFieldName, "era"sv);
// c. Let arithmeticYear be CalendarDateArithmeticYearForEraYear(calendar, canonicalEra, fields.[[EraYear]]).
auto arithmetic_year = calendar_date_arithmetic_year_for_era_year(calendar, *canonical_era, *fields.era_year);
auto arithmetic_year = calendar_date_arithmetic_year_for_era_year(calendar, *fields.era, *fields.era_year);
// d. If fields.[[Year]] is not UNSET, and fields.[[Year]] ≠ arithmeticYear, throw a RangeError exception.
if (fields.year.has_value() && *fields.year != arithmetic_year)
@ -1650,7 +1661,7 @@ bool calendar_supports_era(String const& calendar)
}
// 4.1.2 CanonicalizeEraInCalendar ( calendar, era ), https://tc39.es/proposal-intl-era-monthcode/#sec-temporal-canonicalizeeraincalendar
Optional<StringView> canonicalize_era_in_calendar(String const& calendar, StringView era)
Optional<StringView> canonicalize_era_in_calendar(String const& calendar, Utf16View era)
{
// 1. For each row of Table 2, except the header row, do
for (auto const& row : CALENDAR_ERA_DATA) {
@ -1661,12 +1672,12 @@ Optional<StringView> canonicalize_era_in_calendar(String const& calendar, String
auto canonical_name = row.era;
// ii. If canonicalName is equal to era, return canonicalName.
if (canonical_name == era)
if (era == canonical_name)
return canonical_name;
// iii. Let aliases be a List whose elements are the strings given in the "Aliases" column of the row.
// iv. If aliases contains era, return canonicalName.
if (row.alias == era)
if (era == row.alias)
return canonical_name;
}
}
@ -1824,16 +1835,16 @@ u8 calendar_days_in_month(String const& calendar, i32 arithmetic_year, u8 ordina
}
// 4.1.12 CalendarDateArithmeticYearForEraYear ( calendar, era, eraYear ), https://tc39.es/proposal-intl-era-monthcode/#sec-temporal-calendardatearithmeticyearforerayear
i32 calendar_date_arithmetic_year_for_era_year(String const& calendar, StringView era, i32 era_year)
i32 calendar_date_arithmetic_year_for_era_year(String const& calendar, Utf16View era, i32 era_year)
{
// 1. Let era be CanonicalizeEraInCalendar(calendar, era).
// 2. Assert: era is not undefined.
era = canonicalize_era_in_calendar(calendar, era).release_value();
auto canonical_era = canonicalize_era_in_calendar(calendar, era).release_value();
// 3. If calendar is not listed in the "Calendar Type" column of Table 1, return an implementation-defined value.
// 4. Let r be the row in Table 2 with a value in the Calendar column matching calendar and a value in the Era
// column matching era.
auto row = find_value(CALENDAR_ERA_DATA, [&](auto const& row) { return row.calendar == calendar && row.era == era; });
auto row = find_value(CALENDAR_ERA_DATA, [&](auto const& row) { return row.calendar == calendar && row.era == canonical_era; });
if (!row.has_value())
return era_year;

View file

@ -11,6 +11,7 @@
#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/Utf16String.h>
#include <AK/Utf16View.h>
#include <AK/Vector.h>
#include <LibJS/Forward.h>
@ -71,7 +72,7 @@ struct CalendarFields {
};
}
Optional<String> era;
Optional<Utf16String> era;
Optional<i32> era_year;
Optional<i32> year;
Optional<u32> month;
@ -83,7 +84,7 @@ struct CalendarFields {
Optional<u16> millisecond { 0 };
Optional<u16> microsecond { 0 };
Optional<u16> nanosecond { 0 };
Optional<String> offset_string;
Optional<Utf16String> offset_string;
Optional<String> time_zone;
};
@ -103,6 +104,7 @@ Vector<String> const& available_calendars();
ThrowCompletionOr<MonthCode> parse_month_code(VM&, Value argument);
ThrowCompletionOr<MonthCode> parse_month_code(VM&, StringView month_code);
ThrowCompletionOr<MonthCode> parse_month_code(VM&, Utf16View month_code);
ThrowCompletionOr<CalendarFields> prepare_calendar_fields(VM&, String const& calendar, Object const& fields, CalendarFieldList calendar_field_names, CalendarFieldList non_calendar_field_names, CalendarFieldListOrPartial required_field_names);
ThrowCompletionOr<ISODate> calendar_date_from_fields(VM&, String const& calendar, CalendarFields&, Overflow);
@ -135,14 +137,14 @@ ThrowCompletionOr<void> non_iso_resolve_fields(VM&, String const& calendar, Cale
ThrowCompletionOr<void> calendar_resolve_fields(VM&, String const& calendar, CalendarFields&, DateType);
bool calendar_supports_era(String const& calendar);
Optional<StringView> canonicalize_era_in_calendar(String const& calendar, StringView era);
Optional<StringView> canonicalize_era_in_calendar(String const& calendar, Utf16View era);
bool calendar_has_mid_year_eras(String const& calendar);
bool is_valid_month_code_for_calendar(String const& calendar, StringView month_code);
bool year_contains_month_code(String const& calendar, i32 arithmetic_year, StringView month_code);
ThrowCompletionOr<String> constrain_month_code(VM&, String const& calendar, i32 arithmetic_year, String const& month_code, Overflow overflow);
u8 month_code_to_ordinal(String const& calendar, i32 arithmetic_year, StringView month_code);
u8 calendar_days_in_month(String const& calendar, i32 arithmetic_year, u8 ordinal_month);
i32 calendar_date_arithmetic_year_for_era_year(String const& calendar, StringView era, i32 era_year);
i32 calendar_date_arithmetic_year_for_era_year(String const& calendar, Utf16View era, i32 era_year);
ThrowCompletionOr<ISODate> calendar_integers_to_iso(VM&, String const& calendar, i32 arithmetic_year, u8 ordinal_month, u8 day);
u8 calendar_months_in_year(String const& calendar, i32 arithmetic_year);
BalancedDate balance_non_iso_date(String const& calendar, i32 arithmetic_year, i32 ordinal_month, i32 day);

View file

@ -9,6 +9,7 @@
#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/Types.h>
#include <AK/Utf16String.h>
#include <AK/Variant.h>
#include <LibCrypto/BigInt/SignedBigInteger.h>
#include <LibUnicode/Calendar.h>
@ -50,8 +51,8 @@ struct ISOYearMonth {
// 13.32 ISO String Time Zone Parse Records, https://tc39.es/proposal-temporal/#sec-temporal-iso-string-time-zone-parse-records
struct ParsedISOTimeZone {
bool z_designator { false };
Optional<String> offset_string;
Optional<String> time_zone_annotation;
Optional<Utf16String> offset_string;
Optional<Utf16String> time_zone_annotation;
};
// 13.33 Time Zone Identifier Parse Records, https://tc39.es/proposal-temporal/#sec-temporal-time-zone-identifier-parse-records
@ -69,7 +70,7 @@ struct ParsedISODateTime {
u8 day { 0 };
Variant<StartOfDay, Time> time;
ParsedISOTimeZone time_zone;
Optional<String> calendar;
Optional<Utf16String> calendar;
};
}

View file

@ -139,10 +139,9 @@ ThrowCompletionOr<GC::Ref<PlainDate>> to_temporal_date(VM& vm, Value item, Value
// 5. Let calendar be result.[[Calendar]].
// 6. If calendar is empty, set calendar to "iso8601".
auto calendar = result.calendar.value_or("iso8601"_string);
// 7. Set calendar to ? CanonicalizeCalendar(calendar).
calendar = TRY(canonicalize_calendar(vm, calendar));
auto calendar = result.calendar.has_value()
? TRY(canonicalize_calendar(vm, *result.calendar))
: TRY(canonicalize_calendar(vm, "iso8601"sv));
// 8. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY(get_options_object(vm, options));

View file

@ -182,10 +182,9 @@ ThrowCompletionOr<GC::Ref<PlainDateTime>> to_temporal_date_time(VM& vm, Value it
// 6. Let calendar be result.[[Calendar]].
// 7. If calendar is empty, set calendar to "iso8601".
auto calendar = result.calendar.value_or("iso8601"_string);
// 8. Set calendar to ? CanonicalizeCalendar(calendar).
calendar = TRY(canonicalize_calendar(vm, calendar));
auto calendar = result.calendar.has_value()
? TRY(canonicalize_calendar(vm, *result.calendar))
: TRY(canonicalize_calendar(vm, "iso8601"sv));
// 9. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY(get_options_object(vm, options));

View file

@ -73,10 +73,9 @@ ThrowCompletionOr<GC::Ref<PlainMonthDay>> to_temporal_month_day(VM& vm, Value it
// 5. Let calendar be result.[[Calendar]].
// 6. If calendar is empty, set calendar to "iso8601".
auto calendar = parse_result.calendar.value_or("iso8601"_string);
// 7. Set calendar to ? CanonicalizeCalendar(calendar).
calendar = TRY(canonicalize_calendar(vm, calendar));
auto calendar = parse_result.calendar.has_value()
? TRY(canonicalize_calendar(vm, *parse_result.calendar))
: TRY(canonicalize_calendar(vm, "iso8601"sv));
// 8. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY(get_options_object(vm, options));

View file

@ -76,10 +76,9 @@ ThrowCompletionOr<GC::Ref<PlainYearMonth>> to_temporal_year_month(VM& vm, Value
// 5. Let calendar be result.[[Calendar]].
// 6. If calendar is empty, set calendar to "iso8601".
auto calendar = parse_result.calendar.value_or("iso8601"_string);
// 7. Set calendar to ? CanonicalizeCalendar(calendar).
calendar = TRY(canonicalize_calendar(vm, calendar));
auto calendar = parse_result.calendar.has_value()
? TRY(canonicalize_calendar(vm, *parse_result.calendar))
: TRY(canonicalize_calendar(vm, "iso8601"sv));
// 8. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY(get_options_object(vm, options));

View file

@ -453,7 +453,8 @@ ThrowCompletionOr<Crypto::SignedBigInteger> get_start_of_day(VM& vm, String cons
return move(possible_epoch_nanoseconds[0]);
// 4. Assert: IsOffsetTimeZoneIdentifier(timeZone) is false.
VERIFY(!is_offset_time_zone_identifier(time_zone));
auto utf16_time_zone = Utf16String::from_utf8(time_zone);
VERIFY(!is_offset_time_zone_identifier(utf16_time_zone));
// 5. Let possibleEpochNsAfter be GetNamedTimeZoneEpochNanoseconds(timeZone, isoDateTimeAfter), where isoDateTimeAfter
// is the ISO Date-Time Record for which DifferenceISODateTime(isoDateTime, isoDateTimeAfter, "iso8601", hour).[[Time]]
@ -542,7 +543,25 @@ ThrowCompletionOr<ParsedTimeZoneIdentifier> parse_time_zone_identifier(VM& vm, S
ThrowCompletionOr<ParsedTimeZoneIdentifier> parse_time_zone_identifier(VM& vm, Utf16View identifier)
{
return parse_time_zone_identifier(vm, identifier.to_utf8_but_should_be_ported_to_utf16());
Optional<String> cache_key;
if (identifier.is_ascii()) {
cache_key = MUST(identifier.to_utf8());
if (auto result = time_zone_id_cache().get(*cache_key); result.has_value())
return *result;
}
// 1. Let parseResult be ParseText(StringToCodePoints(identifier), TimeZoneIdentifier).
auto parse_result = parse_iso8601(Production::TimeZoneIdentifier, identifier);
// 2. If parseResult is a List of errors, throw a RangeError exception.
if (!parse_result.has_value())
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidTimeZoneString, identifier);
auto result = parse_time_zone_identifier(*parse_result);
if (cache_key.has_value())
time_zone_id_cache().set(*cache_key, result);
return result;
}
// 11.1.16 ParseTimeZoneIdentifier ( identifier ), https://tc39.es/proposal-temporal/#sec-parsetimezoneidentifier
@ -570,7 +589,7 @@ ParsedTimeZoneIdentifier parse_time_zone_identifier(ParseResult const& parse_res
// b. NOTE: name is syntactically valid, but does not necessarily conform to IANA Time Zone Database naming
// guidelines or correspond with an available named time zone identifier.
// c. Return Time Zone Identifier Parse Record { [[Name]]: CodePointsToString(name), [[OffsetMinutes]]: EMPTY }.
return ParsedTimeZoneIdentifier { .name = parse_result.time_zone_iana_name->to_utf8_but_should_be_ported_to_utf16(), .offset_minutes = {} };
return ParsedTimeZoneIdentifier { .name = MUST(parse_result.time_zone_iana_name->to_utf8()), .offset_minutes = {} };
}
// 4. Assert: parseResult contains a UTCOffset[~SubMinutePrecision] Parse Node.

View file

@ -139,7 +139,7 @@ ThrowCompletionOr<GC::Ref<ZonedDateTime>> to_temporal_zoned_date_time(VM& vm, Va
String calendar;
String time_zone;
Optional<String> offset_string;
Optional<Utf16String> offset_string;
Disambiguation disambiguation;
OffsetOption offset_option;
@ -223,8 +223,7 @@ ThrowCompletionOr<GC::Ref<ZonedDateTime>> to_temporal_zoned_date_time(VM& vm, Va
VERIFY(annotation.has_value());
// e. Let timeZone be ? ToTemporalTimeZoneIdentifier(annotation).
auto utf16_annotation = Utf16String::from_utf8(*annotation);
time_zone = TRY(to_temporal_time_zone_identifier(vm, utf16_annotation));
time_zone = TRY(to_temporal_time_zone_identifier(vm, *annotation));
// f. Let offsetString be result.[[TimeZone]].[[OffsetString]].
offset_string = move(result.time_zone.offset_string);
@ -237,10 +236,9 @@ ThrowCompletionOr<GC::Ref<ZonedDateTime>> to_temporal_zoned_date_time(VM& vm, Va
// h. Let calendar be result.[[Calendar]].
// i. If calendar is EMPTY, set calendar to "iso8601".
calendar = result.calendar.value_or("iso8601"_string);
// j. Set calendar to ? CanonicalizeCalendar(calendar).
calendar = TRY(canonicalize_calendar(vm, calendar));
calendar = result.calendar.has_value()
? TRY(canonicalize_calendar(vm, *result.calendar))
: TRY(canonicalize_calendar(vm, "iso8601"sv));
// k. Set matchBehaviour to MATCH-MINUTES.
match_behavior = MatchBehavior::MatchMinutes;
@ -248,8 +246,7 @@ ThrowCompletionOr<GC::Ref<ZonedDateTime>> to_temporal_zoned_date_time(VM& vm, Va
// l. If offsetString is not EMPTY, then
if (offset_string.has_value()) {
// i. Let offsetParseResult be ParseText(StringToCodePoints(offsetString), UTCOffset[+SubMinutePrecision]).
auto utf16_offset_string = Utf16String::from_utf8(*offset_string);
auto offset_parse_result = parse_utc_offset(utf16_offset_string, SubMinutePrecision::Yes);
auto offset_parse_result = parse_utc_offset(*offset_string, SubMinutePrecision::Yes);
// ii. Assert: offsetParseResult is a Parse Node.
VERIFY(offset_parse_result.has_value());

View file

@ -6,6 +6,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Utf16String.h>
#include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/Intl/DateTimeFormat.h>
#include <LibJS/Runtime/Intl/DateTimeFormatConstructor.h>
@ -415,7 +416,7 @@ JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::with)
fields.nanosecond = iso_date_time.time.nanosecond;
// 16. Set fields.[[OffsetString]] to FormatUTCOffsetNanoseconds(offsetNanoseconds).
fields.offset_string = format_utc_offset_nanoseconds(offset_nanoseconds);
fields.offset_string = Utf16String::from_utf8(format_utc_offset_nanoseconds(offset_nanoseconds));
// 17. Let partialZonedDateTime be ? PrepareCalendarFields(calendar, temporalZonedDateTimeLike, « YEAR, MONTH, MONTH-CODE, DAY », « HOUR, MINUTE, SECOND, MILLISECOND, MICROSECOND, NANOSECOND, OFFSET », PARTIAL).
static constexpr auto calendar_field_names = to_array({ CalendarField::Year, CalendarField::Month, CalendarField::MonthCode, CalendarField::Day });
@ -900,7 +901,8 @@ JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::get_time_zone_transition)
auto direction = TRY(get_direction_option(vm, *direction_param));
// 8. If IsOffsetTimeZoneIdentifier(timeZone) is true, return null.
if (is_offset_time_zone_identifier(time_zone))
auto utf16_time_zone = Utf16String::from_utf8(time_zone);
if (is_offset_time_zone_identifier(utf16_time_zone))
return js_null();
Optional<Crypto::SignedBigInteger> transition;

View file

@ -116,7 +116,7 @@ JS_DEFINE_NATIVE_FUNCTION(Uint8ArrayConstructorHelpers::from_base64)
}
// 9. Let result be FromBase64(string, alphabet, lastChunkHandling).
auto result = JS::from_base64(vm, string_value.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16(), alphabet, last_chunk_handling);
auto result = JS::from_base64(vm, string_value.as_string().utf16_string_view(), alphabet, last_chunk_handling);
// 10. If result.[[Error]] is not NONE, then
if (result.error.has_value()) {
@ -220,7 +220,7 @@ JS_DEFINE_NATIVE_FUNCTION(Uint8ArrayPrototypeHelpers::set_from_base64)
auto byte_length = typed_array_length(typed_array_record);
// 14. Let result be FromBase64(string, alphabet, lastChunkHandling, byteLength).
auto result = JS::from_base64(vm, string_value.as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16(), alphabet, last_chunk_handling, byte_length);
auto result = JS::from_base64(vm, string_value.as_string().utf16_string_view(), alphabet, last_chunk_handling, byte_length);
// 15. Let bytes be result.[[Bytes]].
auto bytes = move(result.bytes);
@ -505,7 +505,7 @@ void set_uint8_array_bytes(TypedArrayBase& into, ReadonlyBytes bytes)
}
// 23.3.3.7 FromBase64 ( string, alphabet, lastChunkHandling [ , maxLength ] ), https://tc39.es/ecma262/#sec-frombase64
DecodeResult from_base64(VM& vm, StringView string, Alphabet alphabet, AK::LastChunkHandling last_chunk_handling, Optional<size_t> max_length)
DecodeResult from_base64(VM& vm, Utf16View string, Alphabet alphabet, AK::LastChunkHandling last_chunk_handling, Optional<size_t> max_length)
{
auto output = MUST(ByteBuffer::create_uninitialized(max_length.value_or_lazy_evaluated([&]() {
return AK::size_required_to_decode_base64(string);

View file

@ -53,7 +53,7 @@ ThrowCompletionOr<GC::Ref<TypedArrayBase>> validate_uint8_array(VM&);
ThrowCompletionOr<ByteBuffer> get_uint8_array_bytes(VM&, TypedArrayBase const&);
ThrowCompletionOr<ReadonlyBytes> get_uint8_array_bytes_view(VM&, TypedArrayBase const&);
void set_uint8_array_bytes(TypedArrayBase&, ReadonlyBytes);
DecodeResult from_base64(VM&, StringView string, Alphabet alphabet, AK::LastChunkHandling last_chunk_handling, Optional<size_t> max_length = {});
DecodeResult from_base64(VM&, Utf16View string, Alphabet alphabet, AK::LastChunkHandling last_chunk_handling, Optional<size_t> max_length = {});
DecodeResult from_hex(VM&, Utf16View string, Optional<size_t> max_length = {});
}

View file

@ -154,7 +154,7 @@ VM::VM(ErrorMessages error_messages)
};
// 2 HostEnsureCanCompileStrings ( calleeRealm, parameterStrings, bodyString, codeString, compilationType, parameterArgs, bodyArg ), https://tc39.es/proposal-dynamic-code-brand-checks/#sec-hostensurecancompilestrings
host_ensure_can_compile_strings = [](Realm&, ReadonlySpan<String>, StringView, StringView, CompilationType, ReadonlySpan<Value>, Value) -> ThrowCompletionOr<void> {
host_ensure_can_compile_strings = [](Realm&, ReadonlySpan<Utf16String>, Utf16View, Utf16View, CompilationType, ReadonlySpan<Value>, Value) -> ThrowCompletionOr<void> {
// The host-defined abstract operation HostEnsureCanCompileStrings takes arguments calleeRealm (a Realm Record),
// parameterStrings (a List of Strings), bodyString (a String), and direct (a Boolean) and returns either a normal
// completion containing unused or a throw completion.
@ -230,7 +230,7 @@ VM::VM(ErrorMessages error_messages)
};
// AD-HOC: Inform the host that we received a date string we were unable to parse.
host_unrecognized_date_string = [](StringView) {
host_unrecognized_date_string = [](Utf16View) {
};
}

View file

@ -435,11 +435,11 @@ public:
Function<void(GC::Ref<GC::Function<ThrowCompletionOr<Value>()>>, Realm*)> host_enqueue_promise_job;
Function<GC::Ref<JobCallback>(FunctionObject&)> host_make_job_callback;
Function<GC::Ptr<PrimitiveString>(Object const&)> host_get_code_for_eval;
Function<ThrowCompletionOr<void>(Realm&, ReadonlySpan<String>, StringView, StringView, CompilationType, ReadonlySpan<Value>, Value)> host_ensure_can_compile_strings;
Function<ThrowCompletionOr<void>(Realm&, ReadonlySpan<Utf16String>, Utf16View, Utf16View, CompilationType, ReadonlySpan<Value>, Value)> host_ensure_can_compile_strings;
Function<ThrowCompletionOr<void>(Object&)> host_ensure_can_add_private_element;
Function<ThrowCompletionOr<HandledByHost>(ArrayBuffer&, size_t)> host_resize_array_buffer;
Function<ThrowCompletionOr<HandledByHost>(ArrayBuffer&, size_t)> host_grow_shared_array_buffer;
Function<void(StringView)> host_unrecognized_date_string;
Function<void(Utf16View)> host_unrecognized_date_string;
Function<Crypto::SignedBigInteger(Object const& global)> host_system_utc_epoch_nanoseconds;
Function<bool()> host_promise_job_queue_is_empty;

View file

@ -412,39 +412,6 @@ GC::Ref<PrimitiveString> Value::typeof_(VM& vm) const
}
}
String Value::to_string_without_side_effects() const
{
if (is_double())
return number_to_string(m_value.as_double);
switch (m_value.tag) {
case UNDEFINED_TAG:
return "undefined"_string;
case NULL_TAG:
return "null"_string;
case BOOLEAN_TAG:
return as_bool() ? "true"_string : "false"_string;
case INT32_TAG:
return String::number(as_i32());
case STRING_TAG:
return as_string().utf16_string_view().to_utf8_but_should_be_ported_to_utf16();
case SYMBOL_TAG:
return as_symbol().descriptive_string().to_utf8_but_should_be_ported_to_utf16();
case BIGINT_TAG:
return as_bigint().to_string().release_value();
case OBJECT_TAG:
return String::formatted("[object {}]", as_object().class_name()).release_value();
case ACCESSOR_TAG:
return "<accessor>"_string;
case EMPTY_TAG:
return "<empty>"_string;
default:
if (is_cell())
return String::formatted("[internal object {}]", as_cell().class_name()).release_value();
VERIFY_NOT_REACHED();
}
}
Utf16String Value::to_utf16_string_without_side_effects() const
{
if (is_double())
@ -613,7 +580,7 @@ ThrowCompletionOr<Value> Value::to_primitive_slow_case(VM& vm, PreferredType pre
return result;
// vi. Throw a TypeError exception.
return vm.throw_completion<TypeError>(ErrorType::ToPrimitiveReturnedObject, to_string_without_side_effects(), hint);
return vm.throw_completion<TypeError>(ErrorType::ToPrimitiveReturnedObject, to_utf16_string_without_side_effects(), hint);
}
// c. If preferredType is not present, let preferredType be number.
@ -801,7 +768,7 @@ double string_to_number(Utf16View string)
// 4. Return StringNumericValue of literal.
if (result->base != 10) {
auto bigint = MUST(Crypto::UnsignedBigInteger::from_base(result->base, result->literal.to_utf8_but_should_be_ported_to_utf16()));
auto bigint = MUST(Crypto::UnsignedBigInteger::from_base(result->base, result->literal));
return bigint.to_double();
}
@ -970,7 +937,7 @@ static Optional<BigInt*> string_to_bigint(VM& vm, Utf16View string)
// 4. Let mv be the MV of literal.
// 5. Assert: mv is an integer.
auto bigint = MUST(Crypto::SignedBigInteger::from_base(result->base, result->literal.to_utf8_but_should_be_ported_to_utf16()));
auto bigint = MUST(Crypto::SignedBigInteger::from_base(result->base, result->literal));
if (result->is_negative && (bigint != bigint_zero()))
bigint.negate();

View file

@ -18,6 +18,7 @@
#include <AK/SourceLocation.h>
#include <AK/String.h>
#include <AK/Types.h>
#include <AK/Utf16String.h>
#include <AK/Utf16View.h>
#include <LibGC/NanBoxedValue.h>
#include <LibGC/Ptr.h>
@ -433,7 +434,6 @@ public:
ThrowCompletionOr<GC::Ptr<FunctionObject>> get_method(VM&, PropertyKey const&) const;
ThrowCompletionOr<GC::Ptr<FunctionObject>> get_method(VM&, PropertyKey const&, Bytecode::PropertyLookupCache&) const;
[[nodiscard]] String to_string_without_side_effects() const;
[[nodiscard]] Utf16String to_utf16_string_without_side_effects() const;
[[nodiscard]] GC::Ref<PrimitiveString> typeof_(VM&) const;
@ -669,12 +669,12 @@ inline Root<JS::Value> make_root(JS::Value value, SourceLocation location = Sour
namespace AK {
template<>
struct Formatter<JS::Value> : Formatter<StringView> {
struct Formatter<JS::Value> : Formatter<FormatString> {
ErrorOr<void> format(FormatBuilder& builder, JS::Value value)
{
if (value.is_special_empty_value())
return Formatter<StringView>::format(builder, "<empty>"sv);
return Formatter<StringView>::format(builder, value.to_string_without_side_effects());
return Formatter<Utf16String> {}.format(builder, value.to_utf16_string_without_side_effects());
}
};

View file

@ -230,7 +230,7 @@ fn utf16_to_string(s: &[u16]) -> String {
/// Once the C++ pipeline is removed, this can be replaced with
/// a native implementation.
fn format_f64(value: f64) -> String {
// C++ AST dump formats JS::Value which uses to_string_without_side_effects(),
// C++ AST dump formats JS::Value which uses to_utf16_string_without_side_effects(),
// producing "Infinity"/"-Infinity"/"NaN". The rust_format_double FFI uses
// AK's double formatter which produces "inf"/"-inf"/"nan" instead.
if value.is_nan() {

View file

@ -704,11 +704,11 @@ Optional<Result<ScriptResult, Vector<ParserError>>> materialize_bytecode_cache_s
return builder.result;
}
Optional<Result<ScriptResult, Vector<ParserError>>> compile_script(StringView source_text, Realm& realm, StringView filename, size_t line_number_offset)
Optional<Result<ScriptResult, Vector<ParserError>>> compile_script(Utf16View source_text, Realm& realm, StringView filename, size_t line_number_offset)
{
auto source_code = SourceCode::create(
String::from_utf8(filename).release_value_but_fixme_should_propagate_errors(),
Utf16String::from_utf8(source_text));
Utf16String::from_utf16(source_text));
auto const* source_ptr = source_code->utf16_data();
auto length = source_code->length_in_code_units();
@ -1001,16 +1001,13 @@ Optional<Result<ModuleResult, Vector<ParserError>>> compile_module(StringView so
}
Optional<Result<GC::Ref<SharedFunctionInstanceData>, String>> compile_dynamic_function(
VM& vm, StringView source_text, StringView parameters_string, StringView body_parse_string,
VM& vm, Utf16View source_text, Utf16View parameters_string, Utf16View body_parse_string,
FunctionKind kind)
{
auto source_code = SourceCode::create({}, Utf16String::from_utf8(source_text));
auto source_code = SourceCode::create({}, Utf16String::from_utf16(source_text));
auto const& code_view = source_code->code_view();
auto full_length = code_view.length_in_code_units();
auto params_utf16 = Utf16String::from_utf8(parameters_string);
auto body_utf16 = Utf16String::from_utf8(body_parse_string);
auto prepare_utf16 = [](Utf16View const& view, Vector<u16>& buf) -> u16 const* {
if (view.has_ascii_storage()) {
auto ascii = view.ascii_span();
@ -1024,16 +1021,16 @@ Optional<Result<GC::Ref<SharedFunctionInstanceData>, String>> compile_dynamic_fu
Vector<u16> full_buf, params_buf, body_buf;
auto const* full_data = prepare_utf16(code_view, full_buf);
auto const* params_data = prepare_utf16(params_utf16.utf16_view(), params_buf);
auto const* body_data = prepare_utf16(body_utf16.utf16_view(), body_buf);
auto const* params_data = prepare_utf16(parameters_string, params_buf);
auto const* body_data = prepare_utf16(body_parse_string, body_buf);
GC::DeferGC defer_gc(vm.heap());
String parse_error;
void* sfd_ptr = rust_compile_dynamic_function(
full_data, full_length,
params_data, params_utf16.utf16_view().length_in_code_units(),
body_data, body_utf16.utf16_view().length_in_code_units(),
params_data, parameters_string.length_in_code_units(),
body_data, body_parse_string.length_in_code_units(),
&vm, source_code.ptr(),
static_cast<u8>(kind),
&parse_error, collect_single_parse_error,
@ -1043,7 +1040,7 @@ Optional<Result<GC::Ref<SharedFunctionInstanceData>, String>> compile_dynamic_fu
return parse_error;
auto& function_data = *static_cast<SharedFunctionInstanceData*>(sfd_ptr);
function_data.m_source_text_owner = Utf16String::from_utf8(source_text);
function_data.m_source_text_owner = Utf16String::from_utf16(source_text);
return GC::Ref<SharedFunctionInstanceData> { function_data };
}

View file

@ -14,6 +14,7 @@
#include <AK/Span.h>
#include <AK/StringBuilder.h>
#include <AK/Utf16FlyString.h>
#include <AK/Utf16View.h>
#include <LibCore/Forward.h>
#include <LibCore/ImmutableBytes.h>
#include <LibGC/Ptr.h>
@ -157,7 +158,7 @@ Optional<Result<ScriptResult, Vector<ParserError>>> compile_parsed_script(FFI::P
Optional<Result<ScriptResult, Vector<ParserError>>> materialize_compiled_script(FFI::CompiledProgram* compiled, NonnullRefPtr<SourceCode const> source_code, Realm& realm);
// Compile a script. Returns nullopt if Rust is not available.
Optional<Result<ScriptResult, Vector<ParserError>>> compile_script(StringView source_text, Realm& realm, StringView filename, size_t line_number_offset);
Optional<Result<ScriptResult, Vector<ParserError>>> compile_script(Utf16View source_text, Realm& realm, StringView filename, size_t line_number_offset);
// Compile eval code. Returns nullopt if Rust is not available.
// On success, the executable's name is set to "eval".
@ -181,7 +182,7 @@ Optional<Result<ModuleResult, Vector<ParserError>>> compile_module(StringView so
// Compile a dynamic function (new Function()).
// On success, returns a SharedFunctionInstanceData with source_text set.
JS_API Optional<Result<GC::Ref<SharedFunctionInstanceData>, String>> compile_dynamic_function(
VM& vm, StringView source_text, StringView parameters_string, StringView body_parse_string,
VM& vm, Utf16View source_text, Utf16View parameters_string, Utf16View body_parse_string,
FunctionKind kind);
// Compile a builtin JS file. Returns nullopt if Rust is not available.

View file

@ -22,7 +22,7 @@ bool g_dump_ast_use_color = false;
GC_DEFINE_ALLOCATOR(Script);
// 16.1.5 ParseScript ( sourceText, realm, hostDefined ), https://tc39.es/ecma262/#sec-parse-script
Result<GC::Ref<Script>, Vector<ParserError>> Script::parse(StringView source_text, Realm& realm, StringView filename, HostDefined* host_defined, size_t line_number_offset)
Result<GC::Ref<Script>, Vector<ParserError>> Script::parse(Utf16View source_text, Realm& realm, StringView filename, HostDefined* host_defined, size_t line_number_offset)
{
auto rust_compilation = RustIntegration::compile_script(source_text, realm, filename, line_number_offset);
if (!rust_compilation.has_value())

View file

@ -8,6 +8,7 @@
#include <AK/HashTable.h>
#include <AK/Utf16FlyString.h>
#include <AK/Utf16View.h>
#include <LibGC/Ptr.h>
#include <LibGC/Root.h>
#include <LibJS/ExecutableBacking.h>
@ -57,7 +58,7 @@ public:
};
virtual ~Script() override;
static Result<GC::Ref<Script>, Vector<ParserError>> parse(StringView source_text, Realm&, StringView filename = {}, HostDefined* = nullptr, size_t line_number_offset = 1);
static Result<GC::Ref<Script>, Vector<ParserError>> parse(Utf16View source_text, Realm&, StringView filename = {}, HostDefined* = nullptr, size_t line_number_offset = 1);
static Result<GC::Ref<Script>, Vector<ParserError>> create_from_parsed(FFI::ParsedProgram* parsed, NonnullRefPtr<SourceCode const> source_code, Realm&, HostDefined* = nullptr);
static Result<GC::Ref<Script>, Vector<ParserError>> create_from_compiled(FFI::CompiledProgram* compiled, NonnullRefPtr<SourceCode const> source_code, Realm&, HostDefined* = nullptr);
static Result<GC::Ref<Script>, Vector<ParserError>> create_from_bytecode_cache(NonnullRefPtr<RustIntegration::DecodedBytecodeCache>, NonnullRefPtr<SourceCode const> source_code, Realm&, HostDefined* = nullptr);

View file

@ -63,7 +63,10 @@ ThrowCompletionOr<GC::Ref<SyntheticModule>> parse_json_module(Realm& realm, Stri
auto& vm = realm.vm();
// 1. Let json be ? ParseJSON(source).
auto json = TRY(JSONObject::parse_json(vm, source_text));
auto json_text = Utf16String::try_from_utf8(source_text);
if (json_text.is_error())
return vm.throw_completion<SyntaxError>(ErrorType::JsonMalformed);
auto json = TRY(JSONObject::parse_json(vm, json_text.release_value()));
// 3. Return CreateDefaultExportSyntheticModule(json).
return SyntheticModule::create_default_export_synthetic_module(realm, json, move(filename));

View file

@ -235,11 +235,12 @@ inline ByteBuffer load_entire_file(StringView path)
inline AK::Result<GC::Ref<JS::Script>, ParserError> parse_script(StringView path, JS::Realm& realm)
{
auto contents = load_entire_file(path);
auto script_or_errors = JS::Script::parse(contents, realm, path);
auto source_text = Utf16String::from_utf8(StringView { contents.bytes() });
auto script_or_errors = JS::Script::parse(source_text.utf16_view(), realm, path);
if (script_or_errors.is_error()) {
auto errors = script_or_errors.release_error();
return ParserError { errors[0], errors[0].source_location_hint(Utf16String::from_utf8(contents)) };
return ParserError { errors[0], errors[0].source_location_hint(source_text) };
}
return script_or_errors.release_value();
@ -419,7 +420,7 @@ inline JSFileResult TestRunner::run_file_test(ByteString const& test_path)
auto& arr = user_output.as_array_exotic_object();
for (u32 i = 0; i < arr.indexed_array_like_size(); ++i) {
auto message = MUST(arr.get(i));
file_result.logged_messages.append(message.to_string_without_side_effects().to_byte_string());
file_result.logged_messages.append(message.to_utf16_string_without_side_effects().to_utf8().to_byte_string());
}
test_json.value().as_object().for_each_member([&](String const& suite_name, JsonValue const& suite_value) {
@ -492,11 +493,14 @@ inline JSFileResult TestRunner::run_file_test(ByteString const& test_path)
auto message = error_object.get_without_side_effects(g_vm->names.message);
if (name.is_accessor() || message.is_accessor()) {
detail_builder.append(error.to_string_without_side_effects());
auto error_string = error.to_utf16_string_without_side_effects();
detail_builder.append(error_string.utf16_view());
} else {
detail_builder.append(name.to_string_without_side_effects());
auto name_string = name.to_utf16_string_without_side_effects();
detail_builder.append(name_string.utf16_view());
detail_builder.append(": "sv);
detail_builder.append(message.to_string_without_side_effects());
auto message_string = message.to_utf16_string_without_side_effects();
detail_builder.append(message_string.utf16_view());
}
if (is<JS::Error>(error_object)) {
@ -507,7 +511,7 @@ inline JSFileResult TestRunner::run_file_test(ByteString const& test_path)
test_case.details = MUST(detail_builder.to_string());
} else {
test_case.details = error.to_string_without_side_effects();
test_case.details = error.to_utf16_string_without_side_effects().to_utf8();
}
suite.tests.append(move(test_case));

View file

@ -35,6 +35,31 @@ static constexpr bool is_valid_month_code_string(StringView month_code)
return true;
}
static constexpr bool is_valid_month_code_string(Utf16View month_code)
{
// MonthCode :::
// M00L
// M0 NonZeroDigit L[opt]
// M NonZeroDigit DecimalDigit L[opt]
auto length = month_code.length_in_code_units();
if (length != 3 && length != 4)
return false;
if (month_code.code_unit_at(0) != 'M')
return false;
if (!is_ascii_digit(month_code.code_unit_at(1)) || !is_ascii_digit(month_code.code_unit_at(2)))
return false;
if (length == 3 && month_code.code_unit_at(1) == '0' && month_code.code_unit_at(2) == '0')
return false;
if (length == 4 && month_code.code_unit_at(3) != 'L')
return false;
return true;
}
// 12.2.1 ParseMonthCode ( argument ), https://tc39.es/proposal-temporal/#sec-temporal-parsemonthcode
Optional<MonthCode> parse_month_code(StringView month_code)
{
@ -64,6 +89,34 @@ Optional<MonthCode> parse_month_code(StringView month_code)
return MonthCode { month_number, is_leap_month };
}
// 12.2.1 ParseMonthCode ( argument ), https://tc39.es/proposal-temporal/#sec-temporal-parsemonthcode
Optional<MonthCode> parse_month_code(Utf16View month_code)
{
// 3. If ParseText(StringToCodePoints(monthCode), MonthCode) is a List of errors, throw a RangeError exception.
if (!is_valid_month_code_string(month_code))
return {};
// 4. Let isLeapMonth be false.
auto is_leap_month = false;
// 5. If the length of monthCode = 4, then
if (month_code.length_in_code_units() == 4) {
// a. Assert: The fourth code unit of monthCode is 0x004C (LATIN CAPITAL LETTER L).
VERIFY(month_code.code_unit_at(3) == 'L');
// b. Set isLeapMonth to true.
is_leap_month = true;
}
// 6. Let monthCodeDigits be the substring of monthCode from 1 to 3.
// 7. Let monthNumber be (StringToNumber(monthCodeDigits)).
auto month_number = static_cast<u8>((month_code.code_unit_at(1) - '0') * 10 + (month_code.code_unit_at(2) - '0'));
// 8. Return the Record { [[MonthNumber]]: monthNumber, [[IsLeapMonth]]: isLeapMonth }.
return MonthCode { month_number, is_leap_month };
}
// 12.2.2 CreateMonthCode ( monthNumber, isLeapMonth ), https://tc39.es/proposal-temporal/#sec-temporal-createmonthcode
String create_month_code(u8 month_number, bool is_leap_month)
{

View file

@ -9,6 +9,7 @@
#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/Types.h>
#include <AK/Utf16View.h>
namespace Unicode {
@ -50,6 +51,7 @@ struct CalendarDate {
};
Optional<MonthCode> parse_month_code(StringView month_code);
Optional<MonthCode> parse_month_code(Utf16View month_code);
String create_month_code(u8 month_number, bool is_leap_month);
CalendarDate iso_date_to_calendar_date(String const& calendar, ISODate);

View file

@ -11,7 +11,7 @@
namespace Unicode {
Usage usage_from_string(StringView usage)
Usage usage_from_string(Utf16View usage)
{
if (usage == "sort"sv)
return Usage::Sort;
@ -49,7 +49,7 @@ static NonnullOwnPtr<icu::Locale> apply_usage_to_locale(icu::Locale const& local
return result;
}
Sensitivity sensitivity_from_string(StringView sensitivity)
Sensitivity sensitivity_from_string(Utf16View sensitivity)
{
if (sensitivity == "base"sv)
return Sensitivity::Base;

View file

@ -8,6 +8,7 @@
#include <AK/NonnullOwnPtr.h>
#include <AK/StringView.h>
#include <AK/Utf16View.h>
namespace Unicode {
@ -15,7 +16,7 @@ enum class Usage {
Sort,
Search,
};
Usage usage_from_string(StringView);
Usage usage_from_string(Utf16View);
StringView usage_to_string(Usage);
enum class Sensitivity {
@ -24,7 +25,7 @@ enum class Sensitivity {
Case,
Variant,
};
Sensitivity sensitivity_from_string(StringView);
Sensitivity sensitivity_from_string(Utf16View);
StringView sensitivity_to_string(Sensitivity);
enum class CaseFirst {

View file

@ -30,7 +30,7 @@
namespace Unicode {
DateTimeStyle date_time_style_from_string(StringView style)
DateTimeStyle date_time_style_from_string(Utf16View style)
{
if (style == "full"sv)
return DateTimeStyle::Full;
@ -146,7 +146,7 @@ static constexpr char icu_hour_cycle(Optional<HourCycle> const& hour_cycle, Opti
VERIFY_NOT_REACHED();
}
CalendarPatternStyle calendar_pattern_style_from_string(StringView style)
CalendarPatternStyle calendar_pattern_style_from_string(Utf16View style)
{
if (style == "narrow"sv)
return CalendarPatternStyle::Narrow;

View file

@ -13,6 +13,7 @@
#include <AK/Time.h>
#include <AK/Types.h>
#include <AK/Utf16String.h>
#include <AK/Utf16View.h>
#include <AK/Vector.h>
#include <LibUnicode/Forward.h>
@ -24,7 +25,7 @@ enum class DateTimeStyle {
Medium,
Short,
};
DateTimeStyle date_time_style_from_string(StringView);
DateTimeStyle date_time_style_from_string(Utf16View);
StringView date_time_style_to_string(DateTimeStyle);
enum class Weekday {
@ -58,7 +59,7 @@ enum class CalendarPatternStyle {
ShortGeneric,
LongGeneric,
};
CalendarPatternStyle calendar_pattern_style_from_string(StringView style);
CalendarPatternStyle calendar_pattern_style_from_string(Utf16View style);
StringView calendar_pattern_style_to_string(CalendarPatternStyle style);
struct CalendarPattern {

View file

@ -16,7 +16,7 @@
namespace Unicode {
LanguageDisplay language_display_from_string(StringView language_display)
LanguageDisplay language_display_from_string(Utf16View language_display)
{
if (language_display == "standard"sv)
return LanguageDisplay::Standard;

View file

@ -9,6 +9,7 @@
#include <AK/Optional.h>
#include <AK/StringView.h>
#include <AK/Utf16String.h>
#include <AK/Utf16View.h>
#include <LibUnicode/Locale.h>
#include <LibUnicode/TimeZone.h>
@ -19,7 +20,7 @@ enum class LanguageDisplay {
Dialect,
};
LanguageDisplay language_display_from_string(StringView language_display);
LanguageDisplay language_display_from_string(Utf16View language_display);
StringView language_display_to_string(LanguageDisplay language_display);
Optional<Utf16String> language_display_name(StringView locale, StringView language, LanguageDisplay);

View file

@ -23,6 +23,17 @@ ListFormatType list_format_type_from_string(StringView list_format_type)
VERIFY_NOT_REACHED();
}
ListFormatType list_format_type_from_string(Utf16View list_format_type)
{
if (list_format_type == "conjunction"sv)
return ListFormatType::Conjunction;
if (list_format_type == "disjunction"sv)
return ListFormatType::Disjunction;
if (list_format_type == "unit"sv)
return ListFormatType::Unit;
VERIFY_NOT_REACHED();
}
StringView list_format_type_to_string(ListFormatType list_format_type)
{
switch (list_format_type) {

View file

@ -7,6 +7,7 @@
#pragma once
#include <AK/Utf16String.h>
#include <AK/Utf16View.h>
#include <AK/Vector.h>
#include <LibUnicode/Locale.h>
@ -18,6 +19,7 @@ enum class ListFormatType {
Unit,
};
ListFormatType list_format_type_from_string(StringView);
ListFormatType list_format_type_from_string(Utf16View);
StringView list_format_type_to_string(ListFormatType);
class ListFormat {

View file

@ -18,12 +18,41 @@
namespace Unicode {
static bool is_key(StringView key)
template<typename ViewType>
static constexpr size_t view_length(ViewType const& view)
{
if constexpr (IsSame<ViewType, Utf16View>)
return view.length_in_code_units();
else
return view.length();
}
template<typename ViewType>
static constexpr u32 view_code_unit_at(ViewType const& view, size_t index)
{
if constexpr (IsSame<ViewType, Utf16View>)
return view.code_unit_at(index);
else
return static_cast<u8>(view[index]);
}
static String string_from_ascii_view(StringView string)
{
return MUST(String::from_utf8(string));
}
static String string_from_ascii_view(Utf16View string)
{
return MUST(string.to_utf8());
}
template<typename ViewType>
static bool is_key(ViewType key)
{
// key = alphanum alpha
if (key.length() != 2)
if (view_length(key) != 2)
return false;
return is_ascii_alphanumeric(key[0]) && is_ascii_alpha(key[1]);
return is_ascii_alphanumeric(view_code_unit_at(key, 0)) && is_ascii_alpha(view_code_unit_at(key, 1));
}
static bool is_single_type(StringView type)
@ -35,34 +64,47 @@ static bool is_single_type(StringView type)
return all_of(type, is_ascii_alphanumeric);
}
static bool is_attribute(StringView type)
static bool is_single_type(Utf16View type)
{
// attribute = alphanum{3,8}
if ((type.length() < 3) || (type.length() > 8))
// type = alphanum{3,8} (sep alphanum{3,8})*
// Note: Consecutive types are not handled here, that is left to the caller.
if ((type.length_in_code_units() < 3) || (type.length_in_code_units() > 8))
return false;
return all_of(type, is_ascii_alphanumeric);
}
static bool is_transformed_key(StringView key)
template<typename ViewType>
static bool is_attribute(ViewType type)
{
// tkey = alpha digit
if (key.length() != 2)
// attribute = alphanum{3,8}
if ((view_length(type) < 3) || (view_length(type) > 8))
return false;
return is_ascii_alpha(key[0]) && is_ascii_digit(key[1]);
return all_of(type, is_ascii_alphanumeric);
}
static bool is_single_transformed_value(StringView value)
template<typename ViewType>
static bool is_transformed_key(ViewType key)
{
// tkey = alpha digit
if (view_length(key) != 2)
return false;
return is_ascii_alpha(view_code_unit_at(key, 0)) && is_ascii_digit(view_code_unit_at(key, 1));
}
template<typename ViewType>
static bool is_single_transformed_value(ViewType value)
{
// tvalue = (sep alphanum{3,8})+
// Note: Consecutive values are not handled here, that is left to the caller.
if ((value.length() < 3) || (value.length() > 8))
if ((view_length(value) < 3) || (view_length(value) > 8))
return false;
return all_of(value, is_ascii_alphanumeric);
}
static Optional<StringView> consume_next_segment(GenericLexer& lexer, bool with_separator = true)
template<typename Lexer>
static Optional<typename Lexer::ViewType> consume_next_segment(Lexer& lexer, bool with_separator = true)
{
constexpr auto is_separator = is_any_of("-_"sv);
constexpr auto is_separator = [](auto code_unit) { return code_unit == '-' || code_unit == '_'; };
if (with_separator) {
if (!lexer.next_is(is_separator))
@ -95,7 +137,40 @@ bool is_type_identifier(StringView identifier)
return lexer.is_eof() && (lexer.tell() > 0);
}
static Optional<LanguageID> parse_unicode_language_id(GenericLexer& lexer)
bool is_type_identifier(Utf16View identifier)
{
// type = alphanum{3,8} (sep alphanum{3,8})*
bool saw_type = false;
bool is_valid = true;
size_t start = 0;
auto validate_type = [&](Utf16View type) {
saw_type = true;
if (type.is_empty() || !is_single_type(type)) {
is_valid = false;
return IterationDecision::Break;
}
return IterationDecision::Continue;
};
for (size_t i = 0; i < identifier.length_in_code_units(); ++i) {
auto code_unit = identifier.code_unit_at(i);
if (code_unit != '-' && code_unit != '_')
continue;
if (validate_type(identifier.substring_view(start, i - start)) == IterationDecision::Break)
return false;
start = i + 1;
}
if (validate_type(identifier.substring_view(start)) == IterationDecision::Break)
return false;
return saw_type && is_valid;
}
template<typename Lexer>
static Optional<LanguageID> parse_unicode_language_id_from_lexer(Lexer& lexer)
{
// https://unicode.org/reports/tr35/#Unicode_language_identifier
//
@ -130,10 +205,10 @@ static Optional<LanguageID> parse_unicode_language_id(GenericLexer& lexer)
case ParseState::ParsingLanguageOrScript:
if (is_unicode_language_subtag(*segment)) {
state = ParseState::ParsingScript;
language_id.language = MUST(String::from_utf8(*segment));
language_id.language = string_from_ascii_view(*segment);
} else if (is_unicode_script_subtag(*segment)) {
state = ParseState::ParsingRegion;
language_id.script = MUST(String::from_utf8(*segment));
language_id.script = string_from_ascii_view(*segment);
} else {
return {};
}
@ -142,7 +217,7 @@ static Optional<LanguageID> parse_unicode_language_id(GenericLexer& lexer)
case ParseState::ParsingScript:
if (is_unicode_script_subtag(*segment)) {
state = ParseState::ParsingRegion;
language_id.script = MUST(String::from_utf8(*segment));
language_id.script = string_from_ascii_view(*segment);
break;
}
@ -152,7 +227,7 @@ static Optional<LanguageID> parse_unicode_language_id(GenericLexer& lexer)
case ParseState::ParsingRegion:
if (is_unicode_region_subtag(*segment)) {
state = ParseState::ParsingVariant;
language_id.region = MUST(String::from_utf8(*segment));
language_id.region = string_from_ascii_view(*segment);
break;
}
@ -161,9 +236,9 @@ static Optional<LanguageID> parse_unicode_language_id(GenericLexer& lexer)
case ParseState::ParsingVariant:
if (is_unicode_variant_subtag(*segment)) {
language_id.variants.append(MUST(String::from_utf8(*segment)));
language_id.variants.append(string_from_ascii_view(*segment));
} else {
lexer.retreat(segment->length() + 1);
lexer.retreat(view_length(*segment) + 1);
state = ParseState::Done;
}
break;
@ -176,7 +251,8 @@ static Optional<LanguageID> parse_unicode_language_id(GenericLexer& lexer)
return language_id;
}
static Optional<LocaleExtension> parse_unicode_locale_extension(GenericLexer& lexer)
template<typename Lexer>
static Optional<LocaleExtension> parse_unicode_locale_extension(Lexer& lexer)
{
// https://unicode.org/reports/tr35/#unicode_locale_extensions
//
@ -203,7 +279,7 @@ static Optional<LocaleExtension> parse_unicode_locale_extension(GenericLexer& le
switch (state) {
case ParseState::ParsingAttribute:
if (is_attribute(*segment)) {
locale_extension.attributes.append(MUST(String::from_utf8(*segment)));
locale_extension.attributes.append(string_from_ascii_view(*segment));
break;
}
@ -212,11 +288,11 @@ static Optional<LocaleExtension> parse_unicode_locale_extension(GenericLexer& le
case ParseState::ParsingKeyword: {
// keyword = key (sep type)?
Keyword keyword { .key = MUST(String::from_utf8(*segment)) };
Vector<StringView> keyword_values;
Keyword keyword { .key = string_from_ascii_view(*segment) };
Vector<String> keyword_values;
if (!is_key(*segment)) {
lexer.retreat(segment->length() + 1);
lexer.retreat(view_length(*segment) + 1);
state = ParseState::Done;
break;
}
@ -226,11 +302,11 @@ static Optional<LocaleExtension> parse_unicode_locale_extension(GenericLexer& le
if (!type.has_value() || !is_single_type(*type)) {
if (type.has_value())
lexer.retreat(type->length() + 1);
lexer.retreat(view_length(*type) + 1);
break;
}
keyword_values.append(*type);
keyword_values.append(string_from_ascii_view(*type));
}
StringBuilder builder;
@ -251,7 +327,8 @@ static Optional<LocaleExtension> parse_unicode_locale_extension(GenericLexer& le
return locale_extension;
}
static Optional<TransformedExtension> parse_transformed_extension(GenericLexer& lexer)
template<typename Lexer>
static Optional<TransformedExtension> parse_transformed_extension(Lexer& lexer)
{
// https://unicode.org/reports/tr35/#transformed_extensions
//
@ -277,9 +354,9 @@ static Optional<TransformedExtension> parse_transformed_extension(GenericLexer&
switch (state) {
case ParseState::ParsingLanguage:
lexer.retreat(segment->length());
lexer.retreat(view_length(*segment));
if (auto language_id = parse_unicode_language_id(lexer); language_id.has_value()) {
if (auto language_id = parse_unicode_language_id_from_lexer(lexer); language_id.has_value()) {
transformed_extension.language = language_id.release_value();
state = ParseState::ParsingField;
break;
@ -289,11 +366,11 @@ static Optional<TransformedExtension> parse_transformed_extension(GenericLexer&
case ParseState::ParsingField: {
// tfield = tkey tvalue;
TransformedField field { .key = MUST(String::from_utf8(*segment)) };
Vector<StringView> field_values;
TransformedField field { .key = string_from_ascii_view(*segment) };
Vector<String> field_values;
if (!is_transformed_key(*segment)) {
lexer.retreat(segment->length() + 1);
lexer.retreat(view_length(*segment) + 1);
state = ParseState::Done;
break;
}
@ -303,11 +380,11 @@ static Optional<TransformedExtension> parse_transformed_extension(GenericLexer&
if (!value.has_value() || !is_single_transformed_value(*value)) {
if (value.has_value())
lexer.retreat(value->length() + 1);
lexer.retreat(view_length(*value) + 1);
break;
}
field_values.append(*value);
field_values.append(string_from_ascii_view(*value));
}
if (field_values.is_empty())
@ -331,28 +408,30 @@ static Optional<TransformedExtension> parse_transformed_extension(GenericLexer&
return transformed_extension;
}
static Optional<OtherExtension> parse_other_extension(char key, GenericLexer& lexer)
template<typename Lexer>
static Optional<OtherExtension> parse_other_extension(u32 key, Lexer& lexer)
{
// https://unicode.org/reports/tr35/#other_extensions
//
// other_extensions = sep [alphanum-[tTuUxX]] (sep alphanum{2,8})+ ;
OtherExtension other_extension { .key = key };
Vector<StringView> other_values;
Vector<String> other_values;
if (!is_ascii_alphanumeric(key) || (key == 'x') || (key == 'X'))
return {};
OtherExtension other_extension { .key = static_cast<char>(key) };
while (true) {
auto segment = consume_next_segment(lexer);
if (!segment.has_value())
break;
if ((segment->length() < 2) || (segment->length() > 8) || !all_of(*segment, is_ascii_alphanumeric)) {
lexer.retreat(segment->length() + 1);
if ((view_length(*segment) < 2) || (view_length(*segment) > 8) || !all_of(*segment, is_ascii_alphanumeric)) {
lexer.retreat(view_length(*segment) + 1);
break;
}
other_values.append(*segment);
other_values.append(string_from_ascii_view(*segment));
}
if (other_values.is_empty())
@ -365,15 +444,16 @@ static Optional<OtherExtension> parse_other_extension(char key, GenericLexer& le
return other_extension;
}
static Optional<Extension> parse_extension(GenericLexer& lexer)
template<typename Lexer>
static Optional<Extension> parse_extension(Lexer& lexer)
{
// https://unicode.org/reports/tr35/#extensions
//
// extensions = unicode_locale_extensions | transformed_extensions | other_extensions
size_t starting_position = lexer.tell();
if (auto header = consume_next_segment(lexer); header.has_value() && (header->length() == 1)) {
switch (char key = (*header)[0]) {
if (auto header = consume_next_segment(lexer); header.has_value() && (view_length(*header) == 1)) {
switch (auto key = view_code_unit_at(*header, 0)) {
case 'u':
case 'U':
if (auto extension = parse_unicode_locale_extension(lexer); extension.has_value())
@ -397,7 +477,8 @@ static Optional<Extension> parse_extension(GenericLexer& lexer)
return {};
}
static Vector<String> parse_private_use_extensions(GenericLexer& lexer)
template<typename Lexer>
static Vector<String> parse_private_use_extensions(Lexer& lexer)
{
// https://unicode.org/reports/tr35/#pu_extensions
//
@ -416,18 +497,18 @@ static Vector<String> parse_private_use_extensions(GenericLexer& lexer)
if (!segment.has_value())
break;
if ((segment->length() < 1) || (segment->length() > 8) || !all_of(*segment, is_ascii_alphanumeric)) {
lexer.retreat(segment->length() + 1);
if ((view_length(*segment) < 1) || (view_length(*segment) > 8) || !all_of(*segment, is_ascii_alphanumeric)) {
lexer.retreat(view_length(*segment) + 1);
break;
}
extensions.append(MUST(String::from_utf8(*segment)));
extensions.append(string_from_ascii_view(*segment));
}
return extensions;
};
if ((header->length() == 1) && (((*header)[0] == 'x') || ((*header)[0] == 'X'))) {
if ((view_length(*header) == 1) && ((view_code_unit_at(*header, 0) == 'x') || (view_code_unit_at(*header, 0) == 'X'))) {
if (auto extensions = parse_values(); !extensions.is_empty())
return extensions;
}
@ -436,27 +517,15 @@ static Vector<String> parse_private_use_extensions(GenericLexer& lexer)
return {};
}
Optional<LanguageID> parse_unicode_language_id(StringView language)
template<typename Lexer>
static Optional<LocaleID> parse_unicode_locale_id_from_lexer(Lexer& lexer)
{
GenericLexer lexer { language };
auto language_id = parse_unicode_language_id(lexer);
if (!lexer.is_eof())
return {};
return language_id;
}
Optional<LocaleID> parse_unicode_locale_id(StringView locale)
{
GenericLexer lexer { locale };
// https://unicode.org/reports/tr35/#Unicode_locale_identifier
//
// unicode_locale_id = unicode_language_id
// extensions*
// pu_extensions?
auto language_id = parse_unicode_language_id(lexer);
auto language_id = parse_unicode_language_id_from_lexer(lexer);
if (!language_id.has_value())
return {};
@ -477,11 +546,52 @@ Optional<LocaleID> parse_unicode_locale_id(StringView locale)
return locale_id;
}
Optional<LanguageID> parse_unicode_language_id(StringView language)
{
GenericLexer lexer { language };
auto language_id = parse_unicode_language_id_from_lexer(lexer);
if (!lexer.is_eof())
return {};
return language_id;
}
Optional<LanguageID> parse_unicode_language_id(Utf16View language)
{
Utf16GenericLexer lexer { language };
auto language_id = parse_unicode_language_id_from_lexer(lexer);
if (!lexer.is_eof())
return {};
return language_id;
}
Optional<LocaleID> parse_unicode_locale_id(StringView locale)
{
GenericLexer lexer { locale };
return parse_unicode_locale_id_from_lexer(lexer);
}
Optional<LocaleID> parse_unicode_locale_id(Utf16View locale)
{
Utf16GenericLexer lexer { locale };
return parse_unicode_locale_id_from_lexer(lexer);
}
String canonicalize_unicode_locale_id(StringView locale)
{
return LocaleData::canonicalize(locale);
}
String canonicalize_unicode_locale_id(Utf16View locale)
{
return LocaleData::canonicalize(string_from_ascii_view(locale));
}
String canonicalize_unicode_extension_values(StringView key, StringView value)
{
UErrorCode status = U_ZERO_ERROR;
@ -566,6 +676,17 @@ Style style_from_string(StringView style)
VERIFY_NOT_REACHED();
}
Style style_from_string(Utf16View style)
{
if (style == "narrow"sv)
return Style::Narrow;
if (style == "short"sv)
return Style::Short;
if (style == "long"sv)
return Style::Long;
VERIFY_NOT_REACHED();
}
StringView style_to_string(Style style)
{
switch (style) {

View file

@ -11,6 +11,7 @@
#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/StringView.h>
#include <AK/Utf16View.h>
#include <AK/Variant.h>
#include <AK/Vector.h>
#include <LibUnicode/Forward.h>
@ -106,6 +107,14 @@ constexpr bool is_unicode_language_subtag(StringView subtag)
return all_of(subtag, is_ascii_alpha);
}
constexpr bool is_unicode_language_subtag(Utf16View subtag)
{
// unicode_language_subtag = alpha{2,3} | alpha{5,8}
if ((subtag.length_in_code_units() < 2) || (subtag.length_in_code_units() == 4) || (subtag.length_in_code_units() > 8))
return false;
return all_of(subtag, is_ascii_alpha);
}
constexpr bool is_unicode_script_subtag(StringView subtag)
{
// unicode_script_subtag = alpha{4}
@ -114,6 +123,14 @@ constexpr bool is_unicode_script_subtag(StringView subtag)
return all_of(subtag, is_ascii_alpha);
}
constexpr bool is_unicode_script_subtag(Utf16View subtag)
{
// unicode_script_subtag = alpha{4}
if (subtag.length_in_code_units() != 4)
return false;
return all_of(subtag, is_ascii_alpha);
}
constexpr bool is_unicode_region_subtag(StringView subtag)
{
// unicode_region_subtag = (alpha{2} | digit{3})
@ -124,6 +141,16 @@ constexpr bool is_unicode_region_subtag(StringView subtag)
return false;
}
constexpr bool is_unicode_region_subtag(Utf16View subtag)
{
// unicode_region_subtag = (alpha{2} | digit{3})
if (subtag.length_in_code_units() == 2)
return all_of(subtag, is_ascii_alpha);
if (subtag.length_in_code_units() == 3)
return all_of(subtag, is_ascii_digit);
return false;
}
constexpr bool is_unicode_variant_subtag(StringView subtag)
{
// unicode_variant_subtag = (alphanum{5,8} | digit alphanum{3})
@ -134,18 +161,33 @@ constexpr bool is_unicode_variant_subtag(StringView subtag)
return false;
}
constexpr bool is_unicode_variant_subtag(Utf16View subtag)
{
// unicode_variant_subtag = (alphanum{5,8} | digit alphanum{3})
if ((subtag.length_in_code_units() >= 5) && (subtag.length_in_code_units() <= 8))
return all_of(subtag, is_ascii_alphanumeric);
if (subtag.length_in_code_units() == 4)
return is_ascii_digit(subtag.code_unit_at(0)) && all_of(subtag.substring_view(1), is_ascii_alphanumeric);
return false;
}
bool is_type_identifier(StringView);
bool is_type_identifier(Utf16View);
Optional<LanguageID> parse_unicode_language_id(StringView);
Optional<LanguageID> parse_unicode_language_id(Utf16View);
Optional<LocaleID> parse_unicode_locale_id(StringView);
Optional<LocaleID> parse_unicode_locale_id(Utf16View);
String canonicalize_unicode_locale_id(StringView);
String canonicalize_unicode_locale_id(Utf16View);
String canonicalize_unicode_extension_values(StringView key, StringView value);
StringView default_locale();
bool is_locale_available(StringView locale);
Style style_from_string(StringView style);
Style style_from_string(Utf16View style);
StringView style_to_string(Style style);
Optional<String> add_likely_subtags(StringView);

View file

@ -26,6 +26,19 @@ NormalizationForm normalization_form_from_string(StringView form)
VERIFY_NOT_REACHED();
}
NormalizationForm normalization_form_from_string(Utf16View form)
{
if (form == u"NFD"sv)
return NormalizationForm::NFD;
if (form == u"NFC"sv)
return NormalizationForm::NFC;
if (form == u"NFKD"sv)
return NormalizationForm::NFKD;
if (form == u"NFKC"sv)
return NormalizationForm::NFKC;
VERIFY_NOT_REACHED();
}
StringView normalization_form_to_string(NormalizationForm form)
{
switch (form) {
@ -41,25 +54,25 @@ StringView normalization_form_to_string(NormalizationForm form)
VERIFY_NOT_REACHED();
}
static icu::Normalizer2 const* normalizer_for_form(NormalizationForm form, UErrorCode& status)
{
switch (form) {
case NormalizationForm::NFD:
return icu::Normalizer2::getNFDInstance(status);
case NormalizationForm::NFC:
return icu::Normalizer2::getNFCInstance(status);
case NormalizationForm::NFKD:
return icu::Normalizer2::getNFKDInstance(status);
case NormalizationForm::NFKC:
return icu::Normalizer2::getNFKCInstance(status);
}
VERIFY_NOT_REACHED();
}
String normalize(StringView string, NormalizationForm form)
{
UErrorCode status = U_ZERO_ERROR;
icu::Normalizer2 const* normalizer = nullptr;
switch (form) {
case NormalizationForm::NFD:
normalizer = icu::Normalizer2::getNFDInstance(status);
break;
case NormalizationForm::NFC:
normalizer = icu::Normalizer2::getNFCInstance(status);
break;
case NormalizationForm::NFKD:
normalizer = icu::Normalizer2::getNFKDInstance(status);
break;
case NormalizationForm::NFKC:
normalizer = icu::Normalizer2::getNFKCInstance(status);
break;
}
auto const* normalizer = normalizer_for_form(form, status);
if (icu_failure(status))
return MUST(String::from_utf8(string));
@ -76,4 +89,23 @@ String normalize(StringView string, NormalizationForm form)
return MUST(builder.to_string());
}
Utf16String normalize(Utf16View string, NormalizationForm form)
{
UErrorCode status = U_ZERO_ERROR;
auto const* normalizer = normalizer_for_form(form, status);
if (icu_failure(status))
return Utf16String::from_utf16(string);
VERIFY(normalizer);
auto icu_input = icu_string(string);
UErrorCode normalize_status = U_ZERO_ERROR;
auto icu_output = normalizer->normalize(icu_input, normalize_status);
if (icu_failure(normalize_status))
return Utf16String::from_utf16(string);
return icu_string_to_utf16_string(icu_output);
}
}

View file

@ -9,6 +9,8 @@
#include <AK/String.h>
#include <AK/StringView.h>
#include <AK/Utf16String.h>
#include <AK/Utf16View.h>
namespace Unicode {
@ -19,8 +21,10 @@ enum class NormalizationForm {
NFKC
};
NormalizationForm normalization_form_from_string(StringView);
NormalizationForm normalization_form_from_string(Utf16View);
StringView normalization_form_to_string(NormalizationForm);
String normalize(StringView string, NormalizationForm form);
Utf16String normalize(Utf16View string, NormalizationForm form);
}

View file

@ -6,7 +6,6 @@
#include <AK/CharacterTypes.h>
#include <AK/QuickSort.h>
#include <AK/Utf8View.h>
#include <LibUnicode/ICU.h>
#include <LibUnicode/Locale.h>
#include <LibUnicode/NumberFormat.h>
@ -32,6 +31,19 @@ NumberFormatStyle number_format_style_from_string(StringView number_format_style
VERIFY_NOT_REACHED();
}
NumberFormatStyle number_format_style_from_string(Utf16View number_format_style)
{
if (number_format_style == "decimal"sv)
return NumberFormatStyle::Decimal;
if (number_format_style == "percent"sv)
return NumberFormatStyle::Percent;
if (number_format_style == "currency"sv)
return NumberFormatStyle::Currency;
if (number_format_style == "unit"sv)
return NumberFormatStyle::Unit;
VERIFY_NOT_REACHED();
}
StringView number_format_style_to_string(NumberFormatStyle number_format_style)
{
switch (number_format_style) {
@ -62,6 +74,21 @@ SignDisplay sign_display_from_string(StringView sign_display)
VERIFY_NOT_REACHED();
}
SignDisplay sign_display_from_string(Utf16View sign_display)
{
if (sign_display == "auto"sv)
return SignDisplay::Auto;
if (sign_display == "never"sv)
return SignDisplay::Never;
if (sign_display == "always"sv)
return SignDisplay::Always;
if (sign_display == "exceptZero"sv)
return SignDisplay::ExceptZero;
if (sign_display == "negative"sv)
return SignDisplay::Negative;
VERIFY_NOT_REACHED();
}
StringView sign_display_to_string(SignDisplay sign_display)
{
switch (sign_display) {
@ -109,6 +136,19 @@ Notation notation_from_string(StringView notation)
VERIFY_NOT_REACHED();
}
Notation notation_from_string(Utf16View notation)
{
if (notation == "standard"sv)
return Notation::Standard;
if (notation == "scientific"sv)
return Notation::Scientific;
if (notation == "engineering"sv)
return Notation::Engineering;
if (notation == "compact"sv)
return Notation::Compact;
VERIFY_NOT_REACHED();
}
StringView notation_to_string(Notation notation)
{
switch (notation) {
@ -153,6 +193,15 @@ CompactDisplay compact_display_from_string(StringView compact_display)
VERIFY_NOT_REACHED();
}
CompactDisplay compact_display_from_string(Utf16View compact_display)
{
if (compact_display == "short"sv)
return CompactDisplay::Short;
if (compact_display == "long"sv)
return CompactDisplay::Long;
VERIFY_NOT_REACHED();
}
StringView compact_display_to_string(CompactDisplay compact_display)
{
switch (compact_display) {
@ -220,6 +269,19 @@ CurrencyDisplay currency_display_from_string(StringView currency_display)
VERIFY_NOT_REACHED();
}
CurrencyDisplay currency_display_from_string(Utf16View currency_display)
{
if (currency_display == "code"sv)
return CurrencyDisplay::Code;
if (currency_display == "symbol"sv)
return CurrencyDisplay::Symbol;
if (currency_display == "narrowSymbol"sv)
return CurrencyDisplay::NarrowSymbol;
if (currency_display == "name"sv)
return CurrencyDisplay::Name;
VERIFY_NOT_REACHED();
}
StringView currency_display_to_string(CurrencyDisplay currency_display)
{
switch (currency_display) {
@ -259,6 +321,15 @@ CurrencySign currency_sign_from_string(StringView currency_sign)
VERIFY_NOT_REACHED();
}
CurrencySign currency_sign_from_string(Utf16View currency_sign)
{
if (currency_sign == "standard"sv)
return CurrencySign::Standard;
if (currency_sign == "accounting"sv)
return CurrencySign::Accounting;
VERIFY_NOT_REACHED();
}
StringView currency_sign_to_string(CurrencySign currency_sign)
{
switch (currency_sign) {
@ -321,6 +392,29 @@ RoundingMode rounding_mode_from_string(StringView rounding_mode)
VERIFY_NOT_REACHED();
}
RoundingMode rounding_mode_from_string(Utf16View rounding_mode)
{
if (rounding_mode == "ceil"sv)
return RoundingMode::Ceil;
if (rounding_mode == "expand"sv)
return RoundingMode::Expand;
if (rounding_mode == "floor"sv)
return RoundingMode::Floor;
if (rounding_mode == "halfCeil"sv)
return RoundingMode::HalfCeil;
if (rounding_mode == "halfEven"sv)
return RoundingMode::HalfEven;
if (rounding_mode == "halfExpand"sv)
return RoundingMode::HalfExpand;
if (rounding_mode == "halfFloor"sv)
return RoundingMode::HalfFloor;
if (rounding_mode == "halfTrunc"sv)
return RoundingMode::HalfTrunc;
if (rounding_mode == "trunc"sv)
return RoundingMode::Trunc;
VERIFY_NOT_REACHED();
}
StringView rounding_mode_to_string(RoundingMode rounding_mode)
{
switch (rounding_mode) {
@ -380,6 +474,15 @@ TrailingZeroDisplay trailing_zero_display_from_string(StringView trailing_zero_d
VERIFY_NOT_REACHED();
}
TrailingZeroDisplay trailing_zero_display_from_string(Utf16View trailing_zero_display)
{
if (trailing_zero_display == "auto"sv)
return TrailingZeroDisplay::Auto;
if (trailing_zero_display == "stripIfInteger"sv)
return TrailingZeroDisplay::StripIfInteger;
VERIFY_NOT_REACHED();
}
StringView trailing_zero_display_to_string(TrailingZeroDisplay trailing_zero_display)
{
switch (trailing_zero_display) {
@ -522,7 +625,7 @@ static constexpr StringView icu_number_format_field_to_string(i32 field, NumberF
case UNUM_SIGN_FIELD: {
auto is_negative = value.visit(
[&](double number) { return signbit(number); },
[&](String const& number) { return number.starts_with('-'); });
[&](Utf16String const& number) { return number.starts_with('-'); });
return is_negative ? "minusSign"sv : "plusSign"sv;
}
case UNUM_MEASURE_UNIT_FIELD:
@ -723,13 +826,36 @@ public:
}
private:
struct DecimalStringPiece {
String utf8_storage;
icu::StringPiece string_piece;
};
static DecimalStringPiece decimal_string_piece(Utf16String const& number)
{
auto number_view = number.utf16_view();
if (number_view.has_ascii_storage()) {
auto bytes = number_view.bytes();
return { {}, { reinterpret_cast<char const*>(bytes.data()), static_cast<i32>(bytes.size()) } };
}
DecimalStringPiece result;
result.utf8_storage = MUST(number_view.to_utf8());
result.string_piece = icu_string_piece(result.utf8_storage);
return result;
}
static icu::Formattable value_to_formattable(Value const& value)
{
UErrorCode status = U_ZERO_ERROR;
auto formattable = value.visit(
[&](double number) { return icu::Formattable { number }; },
[&](String const& number) { return icu::Formattable(icu_string_piece(number), status); });
[&](Utf16String const& number) {
auto decimal_number = decimal_string_piece(number);
return icu::Formattable(decimal_number.string_piece, status);
});
verify_icu_success(status);
return formattable;
@ -743,8 +869,9 @@ private:
[&](double number) {
return m_formatter.formatDouble(number, status);
},
[&](String const& number) {
return m_formatter.formatDecimal(icu_string_piece(number), status);
[&](Utf16String const& number) {
auto decimal_number = decimal_string_piece(number);
return m_formatter.formatDecimal(decimal_number.string_piece, status);
});
if (icu_failure(status))

View file

@ -10,6 +10,7 @@
#include <AK/String.h>
#include <AK/StringView.h>
#include <AK/Utf16String.h>
#include <AK/Utf16View.h>
#include <AK/Variant.h>
#include <AK/Vector.h>
#include <LibUnicode/Forward.h>
@ -24,6 +25,7 @@ enum class NumberFormatStyle {
Unit,
};
NumberFormatStyle number_format_style_from_string(StringView);
NumberFormatStyle number_format_style_from_string(Utf16View);
StringView number_format_style_to_string(NumberFormatStyle);
enum class SignDisplay {
@ -34,6 +36,7 @@ enum class SignDisplay {
Negative,
};
SignDisplay sign_display_from_string(StringView);
SignDisplay sign_display_from_string(Utf16View);
StringView sign_display_to_string(SignDisplay);
enum class Notation {
@ -43,6 +46,7 @@ enum class Notation {
Compact,
};
Notation notation_from_string(StringView);
Notation notation_from_string(Utf16View);
StringView notation_to_string(Notation);
enum class CompactDisplay {
@ -50,6 +54,7 @@ enum class CompactDisplay {
Long,
};
CompactDisplay compact_display_from_string(StringView);
CompactDisplay compact_display_from_string(Utf16View);
StringView compact_display_to_string(CompactDisplay);
enum class Grouping {
@ -68,6 +73,7 @@ enum class CurrencyDisplay {
Name,
};
CurrencyDisplay currency_display_from_string(StringView);
CurrencyDisplay currency_display_from_string(Utf16View);
StringView currency_display_to_string(CurrencyDisplay);
enum class CurrencySign {
@ -75,6 +81,7 @@ enum class CurrencySign {
Accounting,
};
CurrencySign currency_sign_from_string(StringView);
CurrencySign currency_sign_from_string(Utf16View);
StringView currency_sign_to_string(CurrencySign);
struct DisplayOptions {
@ -115,6 +122,7 @@ enum class RoundingMode {
Trunc,
};
RoundingMode rounding_mode_from_string(StringView);
RoundingMode rounding_mode_from_string(Utf16View);
StringView rounding_mode_to_string(RoundingMode);
enum class TrailingZeroDisplay {
@ -122,6 +130,7 @@ enum class TrailingZeroDisplay {
StripIfInteger,
};
TrailingZeroDisplay trailing_zero_display_from_string(StringView);
TrailingZeroDisplay trailing_zero_display_from_string(Utf16View);
StringView trailing_zero_display_to_string(TrailingZeroDisplay);
struct RoundingOptions {
@ -154,7 +163,7 @@ public:
StringView source;
};
using Value = Variant<double, String>;
using Value = Variant<double, Utf16String>;
virtual Utf16String format(Value const&) const = 0;
virtual Vector<Partition> format_to_parts(Value const&) const = 0;

Some files were not shown because too many files have changed in this diff Show more