LibJS: Use Utf16StringBuilder for more strings
Port more UTF-16 string construction sites to Utf16StringBuilder. This covers JSON serialization, URI escaping, RegExp replacement, date and Temporal formatting, Uint8Array hex conversion, and stack string formatting. Keep byte-oriented debug, bytecode, parser, and print plumbing on StringBuilder. Add a checked JS string length sum helper and use it for accumulation paths that append JS-observable string pieces.
This commit is contained in:
parent
595ae25e74
commit
7c86352f34
11 changed files with 146 additions and 60 deletions
|
|
@ -52,6 +52,15 @@ size_t max_js_string_length()
|
|||
return NumericLimits<u32>::max();
|
||||
}
|
||||
|
||||
ThrowCompletionOr<size_t> checked_js_string_length_sum(VM& vm, size_t addend_a, size_t addend_b, ErrorType const& error_type)
|
||||
{
|
||||
Checked<size_t> sum = addend_a;
|
||||
sum += addend_b;
|
||||
if (sum.has_overflow() || sum.value() > max_js_string_length())
|
||||
return vm.throw_completion<RangeError>(error_type);
|
||||
return sum.value();
|
||||
}
|
||||
|
||||
ThrowCompletionOr<size_t> checked_js_string_length_product(VM& vm, size_t factor_a, size_t factor_b, ErrorType const& error_type)
|
||||
{
|
||||
Checked<size_t> product = factor_a;
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ bool all_import_attributes_supported(VM& vm, Vector<ImportAttribute> const& attr
|
|||
ThrowCompletionOr<Value> perform_import_call(VM&, Value specifier, Value options_value);
|
||||
|
||||
size_t max_js_string_length();
|
||||
ThrowCompletionOr<size_t> checked_js_string_length_sum(VM&, size_t, size_t, ErrorType const&);
|
||||
ThrowCompletionOr<size_t> checked_js_string_length_product(VM&, size_t, size_t, ErrorType const&);
|
||||
|
||||
enum class CanonicalIndexMode {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
#include <AK/NeverDestroyed.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/Utf16StringBuilder.h>
|
||||
#include <LibJS/Runtime/ErrorData.h>
|
||||
#include <LibJS/Runtime/ExecutionContext.h>
|
||||
#include <LibJS/Runtime/ExternalMemory.h>
|
||||
|
|
@ -65,7 +65,7 @@ Utf16String ErrorData::stack_string(CompactTraceback compact) const
|
|||
if (m_traceback.is_empty())
|
||||
return {};
|
||||
|
||||
StringBuilder stack_string_builder(StringBuilder::Mode::UTF16);
|
||||
Utf16StringBuilder stack_string_builder;
|
||||
|
||||
// Note: We roughly follow V8's formatting
|
||||
auto append_frame = [&](TracebackFrame const& frame) {
|
||||
|
|
@ -75,11 +75,11 @@ Utf16String ErrorData::stack_string(CompactTraceback compact) const
|
|||
if (!source_range.filename().is_empty() || source_range.start.line != 0 || source_range.start.column != 0) {
|
||||
|
||||
if (function_name.is_empty())
|
||||
stack_string_builder.appendff(" at {}:{}:{}\n", source_range.filename(), source_range.start.line, source_range.start.column);
|
||||
stack_string_builder.append(Utf16String::formatted(" at {}:{}:{}\n", source_range.filename(), source_range.start.line, source_range.start.column));
|
||||
else
|
||||
stack_string_builder.appendff(" at {} ({}:{}:{})\n", function_name, source_range.filename(), source_range.start.line, source_range.start.column);
|
||||
stack_string_builder.append(Utf16String::formatted(" at {} ({}:{}:{})\n", function_name, source_range.filename(), source_range.start.line, source_range.start.column));
|
||||
} else {
|
||||
stack_string_builder.appendff(" at {}\n", function_name.is_empty() ? "<unknown>"_utf16 : function_name);
|
||||
stack_string_builder.append(Utf16String::formatted(" at {}\n", function_name.is_empty() ? "<unknown>"_utf16 : function_name));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -110,7 +110,7 @@ Utf16String ErrorData::stack_string(CompactTraceback compact) const
|
|||
// the name only once and show the number of repetitions instead. This prevents
|
||||
// printing ridiculously large call stacks of recursive functions.
|
||||
append_frame(frame);
|
||||
stack_string_builder.appendff(" {} more calls\n", repetitions);
|
||||
stack_string_builder.append(Utf16String::formatted(" {} more calls\n", repetitions));
|
||||
} else {
|
||||
for (size_t j = 0; j < repetitions + 1; j++)
|
||||
append_frame(frame);
|
||||
|
|
@ -120,7 +120,7 @@ Utf16String ErrorData::stack_string(CompactTraceback compact) const
|
|||
for (size_t j = 0; j < repetitions; j++)
|
||||
append_frame(m_traceback[used_frames - 1]);
|
||||
|
||||
return stack_string_builder.to_utf16_string();
|
||||
return stack_string_builder.to_string();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -244,6 +244,7 @@
|
|||
M(StringNonGlobalRegExp, "RegExp argument is non-global") \
|
||||
M(StringRepeatCountMustBe, "repeat count must be a {} number") \
|
||||
M(StringRepeatCountMustNotOverflow, "repeat count must not overflow") \
|
||||
M(StringSizeMustNotOverflow, "string size must not overflow") \
|
||||
M(TemporalDifferentCalendars, "Cannot compare dates from two different calendars") \
|
||||
M(TemporalDifferentTimeZones, "Cannot compare dates from two different time zones") \
|
||||
M(TemporalDisambiguatePossibleEpochNSRejectMoreThanOne, "Cannot disambiguate two or more possible epoch nanoseconds") \
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
*/
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <LibGC/RootVector.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
#include <AK/GenericShorthands.h>
|
||||
#include <AK/NeverDestroyed.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/Utf16String.h>
|
||||
#include <AK/Utf16StringBuilder.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include <AK/StringBuilder.h>
|
||||
#include <AK/StringConversions.h>
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <AK/Utf16StringBuilder.h>
|
||||
#include <AK/Utf16View.h>
|
||||
#include <AK/Utf8View.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
|
|
@ -487,7 +488,7 @@ JS_DEFINE_NATIVE_FUNCTION(JSONObject::parse)
|
|||
// Returns {} on malformed escape sequences.
|
||||
static Optional<Utf16String> unescape_json_string(StringView raw)
|
||||
{
|
||||
StringBuilder builder(StringBuilder::Mode::UTF16, raw.length());
|
||||
Utf16StringBuilder builder(raw.length());
|
||||
|
||||
GenericLexer lexer { raw };
|
||||
|
||||
|
|
@ -586,7 +587,7 @@ static Optional<Utf16String> unescape_json_string(StringView raw)
|
|||
}
|
||||
}
|
||||
|
||||
return builder.to_utf16_string();
|
||||
return builder.to_string();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
|
|
|
|||
|
|
@ -755,6 +755,7 @@ ThrowCompletionOr<Value> RegExpPrototype::symbol_replace_impl(VM& vm, Object& re
|
|||
&& &realm == &typed_regexp->realm();
|
||||
|
||||
Utf16StringBuilder accumulated_result;
|
||||
size_t accumulated_result_length = 0;
|
||||
size_t next_source_position = 0;
|
||||
bool had_match = false;
|
||||
size_t last_match_start = 0;
|
||||
|
|
@ -775,7 +776,10 @@ ThrowCompletionOr<Value> RegExpPrototype::symbol_replace_impl(VM& vm, Object& re
|
|||
for (int i = 0; i < num_matches; ++i) {
|
||||
auto [match_start, match_end] = compiled_regex->find_all_match(i);
|
||||
if (static_cast<size_t>(match_start) >= next_source_position) {
|
||||
accumulated_result.append(utf16_view.substring_view(next_source_position, match_start - next_source_position));
|
||||
auto substring = utf16_view.substring_view(next_source_position, match_start - next_source_position);
|
||||
accumulated_result_length = TRY(checked_js_string_length_sum(vm, accumulated_result_length, substring.length_in_code_units(), ErrorType::StringSizeMustNotOverflow));
|
||||
accumulated_result.append(substring);
|
||||
accumulated_result_length = TRY(checked_js_string_length_sum(vm, accumulated_result_length, replace_string.length_in_code_units(), ErrorType::StringSizeMustNotOverflow));
|
||||
accumulated_result.append(replace_string);
|
||||
next_source_position = match_end;
|
||||
}
|
||||
|
|
@ -826,7 +830,10 @@ ThrowCompletionOr<Value> RegExpPrototype::symbol_replace_impl(VM& vm, Object& re
|
|||
|
||||
// Append the part of the string before this match + the replacement.
|
||||
if (match_start >= next_source_position) {
|
||||
accumulated_result.append(utf16_view.substring_view(next_source_position, match_start - next_source_position));
|
||||
auto substring = utf16_view.substring_view(next_source_position, match_start - next_source_position);
|
||||
accumulated_result_length = TRY(checked_js_string_length_sum(vm, accumulated_result_length, substring.length_in_code_units(), ErrorType::StringSizeMustNotOverflow));
|
||||
accumulated_result.append(substring);
|
||||
accumulated_result_length = TRY(checked_js_string_length_sum(vm, accumulated_result_length, replace_string.length_in_code_units(), ErrorType::StringSizeMustNotOverflow));
|
||||
accumulated_result.append(replace_string);
|
||||
next_source_position = match_start + match_length;
|
||||
}
|
||||
|
|
@ -874,8 +881,11 @@ ThrowCompletionOr<Value> RegExpPrototype::symbol_replace_impl(VM& vm, Object& re
|
|||
return string;
|
||||
|
||||
// Append the trailing portion of the string.
|
||||
if (next_source_position < length_s)
|
||||
accumulated_result.append(utf16_view.substring_view(next_source_position));
|
||||
if (next_source_position < length_s) {
|
||||
auto substring = utf16_view.substring_view(next_source_position);
|
||||
accumulated_result_length = TRY(checked_js_string_length_sum(vm, accumulated_result_length, substring.length_in_code_units(), ErrorType::StringSizeMustNotOverflow));
|
||||
accumulated_result.append(substring);
|
||||
}
|
||||
|
||||
return PrimitiveString::create(vm, accumulated_result.to_string());
|
||||
}
|
||||
|
|
@ -948,6 +958,7 @@ ThrowCompletionOr<Value> RegExpPrototype::symbol_replace_impl(VM& vm, Object& re
|
|||
|
||||
// 13. Let accumulatedResult be the empty String.
|
||||
Utf16StringBuilder accumulated_result;
|
||||
size_t accumulated_result_length = 0;
|
||||
|
||||
// 14. Let nextSourcePosition be 0.
|
||||
size_t next_source_position = 0;
|
||||
|
|
@ -1042,7 +1053,9 @@ ThrowCompletionOr<Value> RegExpPrototype::symbol_replace_impl(VM& vm, Object& re
|
|||
|
||||
// ii. Set accumulatedResult to the string-concatenation of accumulatedResult, the substring of S from nextSourcePosition to position, and replacement.
|
||||
auto substring = string->utf16_string_view().substring_view(next_source_position, position - next_source_position);
|
||||
accumulated_result_length = TRY(checked_js_string_length_sum(vm, accumulated_result_length, substring.length_in_code_units(), ErrorType::StringSizeMustNotOverflow));
|
||||
accumulated_result.append(substring);
|
||||
accumulated_result_length = TRY(checked_js_string_length_sum(vm, accumulated_result_length, replacement.length_in_code_units(), ErrorType::StringSizeMustNotOverflow));
|
||||
accumulated_result.append(replacement);
|
||||
|
||||
// iii. Set nextSourcePosition to position + matchLength.
|
||||
|
|
@ -1056,6 +1069,7 @@ ThrowCompletionOr<Value> RegExpPrototype::symbol_replace_impl(VM& vm, Object& re
|
|||
|
||||
// 17. Return the string-concatenation of accumulatedResult and the substring of S from nextSourcePosition.
|
||||
auto substring = string->utf16_string_view().substring_view(next_source_position);
|
||||
accumulated_result_length = TRY(checked_js_string_length_sum(vm, accumulated_result_length, substring.length_in_code_units(), ErrorType::StringSizeMustNotOverflow));
|
||||
accumulated_result.append(substring);
|
||||
|
||||
return PrimitiveString::create(vm, accumulated_result.to_string());
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <AK/Utf16StringBuilder.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Array.h>
|
||||
#include <LibJS/Runtime/ArrayIterator.h>
|
||||
|
|
@ -1043,42 +1044,46 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::join)
|
|||
// 3. Let len be TypedArrayLength(taRecord).
|
||||
auto length = typed_array_length(typed_array_record);
|
||||
|
||||
String sep {};
|
||||
Utf16String sep {};
|
||||
|
||||
// 4. If separator is undefined, let sep be ",".
|
||||
if (separator.is_undefined())
|
||||
sep = String::from_code_point(',');
|
||||
sep = ","_utf16;
|
||||
// 5. Else, let sep be ? ToString(separator).
|
||||
else
|
||||
sep = TRY(separator.to_string(vm));
|
||||
sep = TRY(separator.to_utf16_string(vm));
|
||||
|
||||
// 6. Let R be the empty String.
|
||||
StringBuilder builder;
|
||||
Utf16StringBuilder builder;
|
||||
size_t result_length = 0;
|
||||
|
||||
// 7. Let k be 0.
|
||||
// 8. Repeat, while k < len,
|
||||
for (size_t k = 0; k < length; ++k) {
|
||||
// a. If k > 0, set R to the string-concatenation of R and sep.
|
||||
if (k > 0)
|
||||
if (k > 0) {
|
||||
result_length = TRY(checked_js_string_length_sum(vm, result_length, sep.length_in_code_units(), ErrorType::StringSizeMustNotOverflow));
|
||||
builder.append(sep);
|
||||
}
|
||||
|
||||
// b. Let element be ! Get(O, ! ToString(𝔽(k))).
|
||||
auto element = MUST(typed_array->get(k));
|
||||
|
||||
String next {};
|
||||
Utf16String next {};
|
||||
|
||||
// c. If element is undefined, let next be the empty String; otherwise, let next be ! ToString(element).
|
||||
if (!element.is_undefined())
|
||||
next = MUST(element.to_string(vm));
|
||||
next = MUST(element.to_utf16_string(vm));
|
||||
|
||||
// d. Set R to the string-concatenation of R and next.
|
||||
result_length = TRY(checked_js_string_length_sum(vm, result_length, next.length_in_code_units(), ErrorType::StringSizeMustNotOverflow));
|
||||
builder.append(next);
|
||||
|
||||
// e. Set k to k + 1.
|
||||
}
|
||||
|
||||
// 9. Return R.
|
||||
return PrimitiveString::create(vm, MUST(builder.to_string()));
|
||||
return PrimitiveString::create(vm, builder.to_string());
|
||||
}
|
||||
|
||||
// 23.2.3.19 %TypedArray%.prototype.keys ( ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.keys
|
||||
|
|
@ -2014,7 +2019,8 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::to_locale_string)
|
|||
constexpr auto separator = ',';
|
||||
|
||||
// 4. Let R be the empty String.
|
||||
StringBuilder builder;
|
||||
Utf16StringBuilder builder;
|
||||
size_t result_length = 0;
|
||||
|
||||
// 5. Let k be 0.
|
||||
// 6. Repeat, while k < len,
|
||||
|
|
@ -2022,7 +2028,8 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::to_locale_string)
|
|||
// a. If k > 0, then
|
||||
if (k > 0) {
|
||||
// i. Set R to the string-concatenation of R and separator.
|
||||
builder.append(separator);
|
||||
result_length = TRY(checked_js_string_length_sum(vm, result_length, 1, ErrorType::StringSizeMustNotOverflow));
|
||||
builder.append_ascii(separator);
|
||||
}
|
||||
|
||||
// b. Let nextElement be ? Get(array, ! ToString(k)).
|
||||
|
|
@ -2032,9 +2039,10 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::to_locale_string)
|
|||
if (!next_element.is_nullish()) {
|
||||
// i. Let S be ? ToString(? Invoke(nextElement, "toLocaleString", « locales, options »)).
|
||||
auto locale_string_value = TRY(next_element.invoke(vm, vm.names.toLocaleString, locales, options));
|
||||
auto locale_string = TRY(locale_string_value.to_string(vm));
|
||||
auto locale_string = TRY(locale_string_value.to_utf16_string(vm));
|
||||
|
||||
// ii. Set R to the string-concatenation of R and S.
|
||||
result_length = TRY(checked_js_string_length_sum(vm, result_length, locale_string.length_in_code_units(), ErrorType::StringSizeMustNotOverflow));
|
||||
builder.append(locale_string);
|
||||
}
|
||||
|
||||
|
|
@ -2042,7 +2050,7 @@ JS_DEFINE_NATIVE_FUNCTION(TypedArrayPrototype::to_locale_string)
|
|||
}
|
||||
|
||||
// 7. Return R.
|
||||
return PrimitiveString::create(vm, builder.to_string_without_validation());
|
||||
return PrimitiveString::create(vm, builder.to_string());
|
||||
}
|
||||
|
||||
// 23.2.3.32 %TypedArray%.prototype.toReversed ( ), https://tc39.es/ecma262/#sec-%typedarray%.prototype.toreversed
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/StringConversions.h>
|
||||
#include <AK/StringUtils.h>
|
||||
#include <AK/Utf16String.h>
|
||||
#include <AK/Utf16StringBuilder.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/TypedArray.h>
|
||||
#include <LibJS/Runtime/Uint8Array.h>
|
||||
|
|
@ -15,6 +16,13 @@
|
|||
|
||||
namespace JS {
|
||||
|
||||
static void append_lowercase_hex_byte(Utf16StringBuilder& builder, u8 byte)
|
||||
{
|
||||
constexpr auto hex_digits = "0123456789abcdef"sv;
|
||||
builder.append_ascii(hex_digits[byte >> 4]);
|
||||
builder.append_ascii(hex_digits[byte & 0xf]);
|
||||
}
|
||||
|
||||
void Uint8ArrayConstructorHelpers::initialize(Realm& realm, Object& constructor)
|
||||
{
|
||||
auto& vm = constructor.vm();
|
||||
|
|
@ -377,18 +385,18 @@ JS_DEFINE_NATIVE_FUNCTION(Uint8ArrayPrototypeHelpers::to_hex)
|
|||
auto to_encode = TRY(get_uint8_array_bytes(vm, typed_array));
|
||||
|
||||
// 4. Let out be the empty String.
|
||||
StringBuilder out;
|
||||
Utf16StringBuilder out(to_encode.bytes().size() * 2);
|
||||
|
||||
// 5. For each byte byte of toEncode, do
|
||||
for (auto byte : to_encode.bytes()) {
|
||||
// a. Let hex be Number::toString(𝔽(byte), 16).
|
||||
// b. Set hex to StringPad(hex, 2, "0", START).
|
||||
// c. Set out to the string-concatenation of out and hex.
|
||||
out.appendff("{:02x}", byte);
|
||||
append_lowercase_hex_byte(out, byte);
|
||||
}
|
||||
|
||||
// 6. Return out.
|
||||
return PrimitiveString::create(vm, MUST(out.to_string()));
|
||||
return PrimitiveString::create(vm, out.to_string());
|
||||
}
|
||||
|
||||
// 23.3.3.1 ValidateUint8Array ( ta ), https://tc39.es/ecma262/#sec-validateuint8array
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
#include <AK/StringBuilder.h>
|
||||
#include <AK/StringConversions.h>
|
||||
#include <AK/Utf16String.h>
|
||||
#include <AK/Utf8View.h>
|
||||
#include <AK/Utf16StringBuilder.h>
|
||||
#include <LibCrypto/BigInt/SignedBigInteger.h>
|
||||
#include <LibJS/Bytecode/PropertyAccess.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
|
|
@ -75,9 +75,50 @@ static ALWAYS_INLINE bool both_bigint(Value const& lhs, Value const& rhs)
|
|||
return lhs.is_bigint() && rhs.is_bigint();
|
||||
}
|
||||
|
||||
static void append_ascii_for_number(StringBuilder& builder, char code_unit)
|
||||
{
|
||||
builder.append(code_unit);
|
||||
}
|
||||
|
||||
static void append_ascii_for_number(Utf16StringBuilder& builder, char code_unit)
|
||||
{
|
||||
builder.append_ascii(code_unit);
|
||||
}
|
||||
|
||||
static void append_ascii_for_number(StringBuilder& builder, StringView string)
|
||||
{
|
||||
builder.append(string);
|
||||
}
|
||||
|
||||
static void append_ascii_for_number(Utf16StringBuilder& builder, StringView string)
|
||||
{
|
||||
builder.append_ascii(string);
|
||||
}
|
||||
|
||||
static void append_ascii_for_number(StringBuilder& builder, char const* string, size_t length)
|
||||
{
|
||||
builder.append(string, length);
|
||||
}
|
||||
|
||||
static void append_ascii_for_number(Utf16StringBuilder& builder, char const* string, size_t length)
|
||||
{
|
||||
builder.append_ascii(StringView { string, length });
|
||||
}
|
||||
|
||||
static void append_repeated_ascii_for_number(StringBuilder& builder, char code_unit, size_t count)
|
||||
{
|
||||
builder.append_repeated(code_unit, count);
|
||||
}
|
||||
|
||||
static void append_repeated_ascii_for_number(Utf16StringBuilder& builder, char code_unit, size_t count)
|
||||
{
|
||||
builder.append_repeated_ascii(code_unit, count);
|
||||
}
|
||||
|
||||
// 6.1.6.1.20 Number::toString ( x ), https://tc39.es/ecma262/#sec-numeric-types-number-tostring
|
||||
// Implementation for radix = 10
|
||||
void number_to_string(StringBuilder& builder, double d, NumberToStringMode mode)
|
||||
template<typename Builder>
|
||||
static void number_to_string_impl(Builder& builder, double d, NumberToStringMode mode)
|
||||
{
|
||||
auto convert_to_decimal_digits_array = [](auto x, auto& digits, auto& length) {
|
||||
for (; x; x /= 10)
|
||||
|
|
@ -88,24 +129,24 @@ void number_to_string(StringBuilder& builder, double d, NumberToStringMode mode)
|
|||
|
||||
// 1. If x is NaN, return "NaN".
|
||||
if (isnan(d)) {
|
||||
builder.append("NaN"sv);
|
||||
append_ascii_for_number(builder, "NaN"sv);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. If x is +0𝔽 or -0𝔽, return "0".
|
||||
if (d == +0.0 || d == -0.0) {
|
||||
builder.append("0"sv);
|
||||
append_ascii_for_number(builder, "0"sv);
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. If x is +∞𝔽, return "Infinity".
|
||||
if (isinf(d)) {
|
||||
if (d > 0) {
|
||||
builder.append("Infinity"sv);
|
||||
append_ascii_for_number(builder, "Infinity"sv);
|
||||
return;
|
||||
}
|
||||
|
||||
builder.append("-Infinity"sv);
|
||||
append_ascii_for_number(builder, "-Infinity"sv);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -124,7 +165,7 @@ void number_to_string(StringBuilder& builder, double d, NumberToStringMode mode)
|
|||
|
||||
// 3. If x < -0𝔽, return the string-concatenation of "-" and Number::toString(-x, radix).
|
||||
if (sign)
|
||||
builder.append('-');
|
||||
append_ascii_for_number(builder, '-');
|
||||
|
||||
// Non-standard: Intl needs number-to-string conversions for extremely large numbers without any
|
||||
// exponential formatting, as it will handle such formatting itself in a locale-aware way.
|
||||
|
|
@ -136,31 +177,31 @@ void number_to_string(StringBuilder& builder, double d, NumberToStringMode mode)
|
|||
if (n >= k) {
|
||||
// i. Return the string-concatenation of:
|
||||
// the code units of the k digits of the representation of s using radix radix
|
||||
builder.append(mantissa_digits.data(), k);
|
||||
append_ascii_for_number(builder, mantissa_digits.data(), k);
|
||||
// n - k occurrences of the code unit 0x0030 (DIGIT ZERO)
|
||||
builder.append_repeated('0', n - k);
|
||||
append_repeated_ascii_for_number(builder, '0', n - k);
|
||||
// b. Else if n > 0, then
|
||||
} else if (n > 0) {
|
||||
// i. Return the string-concatenation of:
|
||||
// the code units of the most significant n digits of the representation of s using radix radix
|
||||
builder.append(mantissa_digits.data(), n);
|
||||
append_ascii_for_number(builder, mantissa_digits.data(), n);
|
||||
// the code unit 0x002E (FULL STOP)
|
||||
builder.append('.');
|
||||
append_ascii_for_number(builder, '.');
|
||||
// the code units of the remaining k - n digits of the representation of s using radix radix
|
||||
builder.append(mantissa_digits.data() + n, k - n);
|
||||
append_ascii_for_number(builder, mantissa_digits.data() + n, k - n);
|
||||
// c. Else,
|
||||
} else {
|
||||
// i. Assert: n ≤ 0.
|
||||
VERIFY(n <= 0);
|
||||
// ii. Return the string-concatenation of:
|
||||
// the code unit 0x0030 (DIGIT ZERO)
|
||||
builder.append('0');
|
||||
append_ascii_for_number(builder, '0');
|
||||
// the code unit 0x002E (FULL STOP)
|
||||
builder.append('.');
|
||||
append_ascii_for_number(builder, '.');
|
||||
// -n occurrences of the code unit 0x0030 (DIGIT ZERO)
|
||||
builder.append_repeated('0', -n);
|
||||
append_repeated_ascii_for_number(builder, '0', -n);
|
||||
// the code units of the k digits of the representation of s using radix radix
|
||||
builder.append(mantissa_digits.data(), k);
|
||||
append_ascii_for_number(builder, mantissa_digits.data(), k);
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
@ -182,30 +223,35 @@ void number_to_string(StringBuilder& builder, double d, NumberToStringMode mode)
|
|||
if (k == 1) {
|
||||
// a. Return the string-concatenation of:
|
||||
// the code unit of the single digit of s
|
||||
builder.append(mantissa_digits[0]);
|
||||
append_ascii_for_number(builder, mantissa_digits[0]);
|
||||
// the code unit 0x0065 (LATIN SMALL LETTER E)
|
||||
builder.append('e');
|
||||
append_ascii_for_number(builder, 'e');
|
||||
// exponentSign
|
||||
builder.append(exponent_sign);
|
||||
append_ascii_for_number(builder, exponent_sign);
|
||||
// the code units of the decimal representation of abs(n - 1)
|
||||
builder.append(exponent_digits.data(), exponent_length);
|
||||
append_ascii_for_number(builder, exponent_digits.data(), exponent_length);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 12. Return the string-concatenation of:
|
||||
// the code unit of the most significant digit of the decimal representation of s
|
||||
builder.append(mantissa_digits[0]);
|
||||
append_ascii_for_number(builder, mantissa_digits[0]);
|
||||
// the code unit 0x002E (FULL STOP)
|
||||
builder.append('.');
|
||||
append_ascii_for_number(builder, '.');
|
||||
// the code units of the remaining k - 1 digits of the decimal representation of s
|
||||
builder.append(mantissa_digits.data() + 1, k - 1);
|
||||
append_ascii_for_number(builder, mantissa_digits.data() + 1, k - 1);
|
||||
// the code unit 0x0065 (LATIN SMALL LETTER E)
|
||||
builder.append('e');
|
||||
append_ascii_for_number(builder, 'e');
|
||||
// exponentSign
|
||||
builder.append(exponent_sign);
|
||||
append_ascii_for_number(builder, exponent_sign);
|
||||
// the code units of the decimal representation of abs(n - 1)
|
||||
builder.append(exponent_digits.data(), exponent_length);
|
||||
append_ascii_for_number(builder, exponent_digits.data(), exponent_length);
|
||||
}
|
||||
|
||||
void number_to_string(StringBuilder& builder, double d, NumberToStringMode mode)
|
||||
{
|
||||
number_to_string_impl(builder, d, mode);
|
||||
}
|
||||
|
||||
String number_to_string(double d, NumberToStringMode mode)
|
||||
|
|
@ -217,9 +263,9 @@ String number_to_string(double d, NumberToStringMode mode)
|
|||
|
||||
Utf16String number_to_utf16_string(double d, NumberToStringMode mode)
|
||||
{
|
||||
StringBuilder builder(StringBuilder::Mode::UTF16);
|
||||
number_to_string(builder, d, mode);
|
||||
return builder.to_utf16_string();
|
||||
Utf16StringBuilder builder;
|
||||
number_to_string_impl(builder, d, mode);
|
||||
return builder.to_string();
|
||||
}
|
||||
|
||||
ByteString number_to_byte_string(double d, NumberToStringMode mode)
|
||||
|
|
|
|||
Loading…
Reference in a new issue