/* * Copyright (c) 2022-2023, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include namespace JS { NonnullRefPtr SourceCode::create(String filename, Utf16String code) { return adopt_ref(*new SourceCode(move(filename), move(code))); } NonnullRefPtr SourceCode::create(String filename, size_t length_in_code_units, String source_encoding, Core::ImmutableBytes source_bytes) { return adopt_ref(*new SourceCode(move(filename), length_in_code_units, move(source_encoding), move(source_bytes))); } SourceCode::SourceCode(String filename, Utf16String code) : m_filename(move(filename)) , m_code(move(code)) , m_code_view(m_code->utf16_view()) , m_length_in_code_units(m_code_view.length_in_code_units()) { } SourceCode::SourceCode(String filename, size_t length_in_code_units, String source_encoding, Core::ImmutableBytes source_bytes) : m_filename(move(filename)) , m_source_encoding(move(source_encoding)) , m_source_bytes(move(source_bytes)) , m_length_in_code_units(length_in_code_units) { } void SourceCode::ensure_code() const { if (m_code.has_value()) return; m_code = decode_source_range(0, m_length_in_code_units); m_source_bytes = {}; m_code_view = m_code->utf16_view(); } Utf16String const& SourceCode::code() const { ensure_code(); return *m_code; } Utf16View const& SourceCode::code_view() const { ensure_code(); return m_code_view; } u16 const* SourceCode::utf16_data() const { ensure_code(); if (!m_code_view.has_ascii_storage()) return reinterpret_cast(m_code_view.utf16_span().data()); if (m_utf16_data_cache.is_empty() && m_length_in_code_units > 0) { auto ascii = m_code_view.ascii_span(); m_utf16_data_cache.ensure_capacity(m_length_in_code_units); for (size_t i = 0; i < m_length_in_code_units; ++i) m_utf16_data_cache.unchecked_append(static_cast(ascii[i])); } return m_utf16_data_cache.data(); } Utf16String SourceCode::source_text_from_offsets(size_t start_offset, size_t length) const { if (length == 0) return {}; if (m_code.has_value()) return Utf16String::from_utf16(m_code->utf16_view().substring_view(start_offset, length)); if (m_source_bytes.is_valid()) return decode_source_range(start_offset, length); ensure_code(); return Utf16String::from_utf16(m_code->utf16_view().substring_view(start_offset, length)); } Utf16String SourceCode::decode_source_range(size_t start_offset, size_t length) const { if (length == 0) return {}; VERIFY(start_offset <= NumericLimits::max() - length); auto end_offset = start_offset + length; StringView input { m_source_bytes.bytes() }; auto decoder = TextCodec::decoder_for(m_source_encoding); VERIFY(decoder.has_value()); TextCodec::Decoder* actual_decoder = &decoder.value(); auto unicode_decoder = TextCodec::bom_sniff_to_decoder(input); if (unicode_decoder.has_value()) { auto input_bytes = input.bytes(); auto byte_order_mark_size = input_bytes.size() >= 3 && input_bytes[0] == 0xEF && input_bytes[1] == 0xBB && input_bytes[2] == 0xBF ? 3 : 2; actual_decoder = &unicode_decoder.value(); input = input.substring_view(byte_order_mark_size); } StringBuilder builder(StringBuilder::Mode::UTF16, length); size_t current_offset = 0; auto result = actual_decoder->process_code_points(input, [&](auto code_point) -> ErrorOr { char16_t code_units[2]; size_t code_point_length_in_code_units = 0; (void)AK::UnicodeUtils::code_point_to_utf16(code_point, [&](auto code_unit) { code_units[code_point_length_in_code_units++] = code_unit; }); for (size_t i = 0; i < code_point_length_in_code_units; ++i) { auto code_unit_offset = current_offset + i; if (code_unit_offset >= start_offset && code_unit_offset < end_offset) TRY(builder.try_append_code_unit(code_units[i])); } current_offset += code_point_length_in_code_units; return {}; }); result.release_value_but_fixme_should_propagate_errors(); return builder.to_utf16_string(); } void SourceCode::fill_position_cache() const { constexpr size_t predicted_minimum_cached_positions = 8; constexpr size_t minimum_distance_between_cached_positions = 32; constexpr size_t maximum_distance_between_cached_positions = 8192; auto const& code = this->code(); if (code.is_empty()) return; u32 previous_code_point = 0; u32 line = 1; u32 column = 1; u32 offset_of_last_starting_point = 0; m_cached_positions.ensure_capacity(predicted_minimum_cached_positions + (code.length_in_code_units() / maximum_distance_between_cached_positions)); m_cached_positions.append({ .position = { .line = 1, .column = 1 }, .offset = 0 }); auto view = code.utf16_view(); for (auto it = view.begin(); it != view.end(); ++it) { u32 code_point = *it; bool is_line_terminator = code_point == '\r' || (code_point == '\n' && previous_code_point != '\r') || code_point == LINE_SEPARATOR || code_point == PARAGRAPH_SEPARATOR; auto offset = view.iterator_offset(it); VERIFY(offset <= NumericLimits::max()); bool is_nonempty_line = is_line_terminator && previous_code_point != '\n' && previous_code_point != LINE_SEPARATOR && previous_code_point != PARAGRAPH_SEPARATOR && (code_point == '\n' || previous_code_point != '\r'); auto distance_between_cached_position = offset - offset_of_last_starting_point; if ((distance_between_cached_position >= minimum_distance_between_cached_positions && is_nonempty_line) || distance_between_cached_position >= maximum_distance_between_cached_positions) { m_cached_positions.append({ .position = { .line = line, .column = column }, .offset = static_cast(offset) }); offset_of_last_starting_point = offset; } if (is_line_terminator) { line += 1; column = 1; } else { column += 1; } previous_code_point = code_point; } } SourceRange SourceCode::range_from_offsets(u32 start_offset, [[maybe_unused]] u32 end_offset) const { // If the underlying code is an empty string, the range is 1,1 no matter what. auto const& code = this->code(); if (code.is_empty()) return { *this, { .line = 1, .column = 1 } }; if (m_cached_positions.is_empty()) fill_position_cache(); CachedPosition current { .position = { .line = 1, .column = 1 }, .offset = 0 }; if (!m_cached_positions.is_empty()) { CachedPosition const dummy; size_t nearest_index = 0; binary_search(m_cached_positions, dummy, &nearest_index, [&](auto&, auto& starting_point) { return start_offset - starting_point.offset; }); current = m_cached_positions[nearest_index]; } Optional start; u32 previous_code_point = 0; auto view = code.utf16_view(); for (auto it = view.iterator_at_code_unit_offset(current.offset); it != view.end(); ++it) { // If we're on or after the start offset, this is the start position. if (!start.has_value() && view.iterator_offset(it) >= start_offset) { start = Position { .line = current.position.line, .column = current.position.column, }; } u32 code_point = *it; bool const is_line_terminator = code_point == '\r' || (code_point == '\n' && previous_code_point != '\r') || code_point == LINE_SEPARATOR || code_point == PARAGRAPH_SEPARATOR; previous_code_point = code_point; if (is_line_terminator) { current.position.line += 1; current.position.column = 1; continue; } current.position.column += 1; } // If we didn't find a start position, just return 1,1. // FIXME: This is a hack. Find a way to return the nicest possible values here. if (!start.has_value()) return SourceRange { *this, { .line = 1, .column = 1 } }; return SourceRange { *this, *start }; } }