use std::cmp::min;
use oxc_ast::ast::Program;
use oxc_span::Span;
use oxc_syntax::module_record::VisitMutModuleRecord;
use crate::VisitMut;
use super::Translation;
pub struct Utf8ToUtf16Converter<'t> {
translations: &'t [Translation],
range_start_utf8: u32,
range_len_utf8: u32,
range_start_utf16: u32,
index: u32,
#[cfg(feature = "conformance")]
previous_offset_utf8: u32,
#[cfg(feature = "conformance")]
ascending_order: bool,
}
impl<'t> Utf8ToUtf16Converter<'t> {
#[cfg_attr(not(feature = "conformance"), expect(unused_variables))]
pub(super) unsafe fn new(translations: &'t [Translation], ascending_order: bool) -> Self {
debug_assert!(translations.len() >= 2);
let range_len_utf8 = unsafe { translations.get_unchecked(1) }.utf8_offset;
Self {
translations,
range_start_utf8: 0,
range_start_utf16: 0,
range_len_utf8,
index: 0,
#[cfg(feature = "conformance")]
previous_offset_utf8: 0,
#[cfg(feature = "conformance")]
ascending_order,
}
}
pub fn reset(&mut self) {
self.range_start_utf8 = 0;
self.range_start_utf16 = 0;
self.range_len_utf8 = unsafe { self.translations.get_unchecked(1) }.utf8_offset;
self.index = 0;
#[cfg(feature = "conformance")]
{
self.previous_offset_utf8 = 0;
}
}
#[expect(clippy::inline_always)]
#[inline(always)]
pub fn convert_offset(&mut self, offset: &mut u32) {
let utf8_offset = *offset;
#[cfg(feature = "conformance")]
if self.ascending_order {
assert!(utf8_offset >= self.previous_offset_utf8);
self.previous_offset_utf8 = utf8_offset;
}
if utf8_offset == 0 {
return;
}
let bytes_from_start_of_range = utf8_offset.wrapping_sub(self.range_start_utf8);
if bytes_from_start_of_range < self.range_len_utf8 {
*offset = self.range_start_utf16 + bytes_from_start_of_range;
} else {
self.convert_offset_slow(offset);
}
}
#[cold]
#[inline(never)]
#[expect(clippy::cast_possible_truncation)]
fn convert_offset_slow(&mut self, offset: &mut u32) {
let utf8_offset = *offset;
let (next_index, range_end_utf8) = if utf8_offset < self.range_start_utf8 {
#[cfg(feature = "conformance")]
{
assert!(!self.ascending_order);
}
self.find_range_before(utf8_offset)
} else {
self.find_range_after(utf8_offset)
};
let index = next_index - 1;
let translation = unsafe { *self.translations.get_unchecked(index) };
let range_start_utf8 = translation.utf8_offset;
let utf16_difference = translation.utf16_difference;
self.index = index as u32;
self.range_start_utf8 = range_start_utf8;
self.range_len_utf8 = range_end_utf8 - range_start_utf8;
self.range_start_utf16 = range_start_utf8 - utf16_difference;
*offset = utf8_offset - utf16_difference;
}
fn find_range_before(
&self,
utf8_offset: u32,
) -> (
usize, // index of next range
u32, // UTF-8 offset of start of next range
) {
let search_ranges = unsafe { self.translations.get_unchecked(..self.index as usize) };
let next_index =
search_ranges.partition_point(|translation| utf8_offset >= translation.utf8_offset);
let range_end_utf8 = unsafe { self.translations.get_unchecked(next_index) }.utf8_offset;
(next_index, range_end_utf8)
}
fn find_range_after(
&self,
utf8_offset: u32,
) -> (
usize, // index of next range
u32, // UTF-8 offset of start of next range
) {
const LINEAR_SEARCH_ITERATIONS: usize = 8;
let mut next_index = self.index as usize + 2;
let linear_search_end_index =
min(next_index + LINEAR_SEARCH_ITERATIONS, self.translations.len());
while next_index < linear_search_end_index {
let translation = unsafe { self.translations.get_unchecked(next_index) };
if utf8_offset < translation.utf8_offset {
return (next_index, translation.utf8_offset);
}
next_index += 1;
}
if next_index == self.translations.len() {
return (next_index, u32::MAX);
}
let search_ranges = unsafe { self.translations.get_unchecked(next_index..) };
next_index +=
search_ranges.partition_point(|translation| utf8_offset >= translation.utf8_offset);
let range_end_utf8 = if next_index < self.translations.len() {
self.translations[next_index].utf8_offset
} else {
u32::MAX
};
(next_index, range_end_utf8)
}
#[inline] pub fn convert_program(&mut self, program: &mut Program<'_>) {
self.visit_program(program);
}
pub fn convert_span(&mut self, span: &mut Span) {
self.convert_offset(&mut span.start);
self.convert_offset(&mut span.end);
}
pub fn convert_offset_back(&self, offset: &mut u32) {
let utf16_offset = *offset;
let next_index = self.translations.partition_point(|translation| {
utf16_offset >= translation.utf8_offset - translation.utf16_difference
});
let index = next_index - 1;
let translation = unsafe { self.translations.get_unchecked(index) };
*offset += translation.utf16_difference;
}
pub fn convert_span_back(&self, span: &mut Span) {
self.convert_offset_back(&mut span.start);
self.convert_offset_back(&mut span.end);
}
}
impl VisitMutModuleRecord for Utf8ToUtf16Converter<'_> {
fn visit_span(&mut self, span: &mut Span) {
self.convert_span(span);
}
}