use core::{
fmt,
marker::PhantomData,
};
use qubit_codec::{
BufferedEncodeHooks,
EncodeContext,
EncodePlan,
};
use crate::{
CharsetEncodeError,
CharsetEncodeErrorKind,
CharsetEncodeResult,
UnmappableAction,
};
use super::{
charset_encode_action::CharsetEncodeAction,
charset_encode_probe::CharsetEncodeProbe,
};
#[derive(Clone)]
pub(crate) struct CharsetEncodeHooks<Unit> {
pub(super) unmappable_action: UnmappableAction,
pub(super) replacement: char,
pub(super) replacement_units_len: usize,
unit: PhantomData<fn() -> Unit>,
}
impl<Unit> fmt::Debug for CharsetEncodeHooks<Unit> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CharsetEncodeHooks")
.field("unmappable_action", &self.unmappable_action)
.field("replacement", &self.replacement)
.field("replacement_units_len", &self.replacement_units_len)
.finish()
}
}
impl<Unit> Eq for CharsetEncodeHooks<Unit> {}
impl<Unit> PartialEq for CharsetEncodeHooks<Unit> {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
self.unmappable_action == other.unmappable_action
&& self.replacement == other.replacement
&& self.replacement_units_len == other.replacement_units_len
}
}
impl<Unit> CharsetEncodeHooks<Unit> {
#[must_use]
#[inline(always)]
pub(crate) const fn new(
unmappable_action: UnmappableAction,
replacement: char,
) -> Self {
Self {
unmappable_action,
replacement,
replacement_units_len: 0,
unit: PhantomData,
}
}
#[inline(always)]
pub(crate) const fn set_replacement_units_len(
&mut self,
replacement_units_len: usize,
) {
self.replacement_units_len = replacement_units_len;
}
}
impl<C> BufferedEncodeHooks<C> for CharsetEncodeHooks<C::Unit>
where
C: CharsetEncodeProbe,
{
type Error = CharsetEncodeError;
type PlanAction = CharsetEncodeAction;
#[inline]
fn prepare_encode(
&mut self,
codec: &C,
ch: &char,
input_index: usize,
) -> Result<EncodePlan<Self::PlanAction>, Self::Error> {
match codec.encode_len(*ch, input_index) {
Ok(max_output_units) => Ok(EncodePlan::new(
max_output_units,
CharsetEncodeAction::WriteOriginal,
)),
Err(error)
if matches!(
error.kind(),
CharsetEncodeErrorKind::UnmappableCharacter { .. }
) =>
{
match self.unmappable_action {
UnmappableAction::Report => Err(error),
UnmappableAction::Ignore => {
Ok(EncodePlan::new(0, CharsetEncodeAction::Skip))
}
UnmappableAction::Replace => Ok(EncodePlan::new(
self.replacement_units_len,
CharsetEncodeAction::WriteReplacement,
)),
}
}
Err(error) => Err(error),
}
}
#[inline]
unsafe fn write_encode(
&mut self,
codec: &C,
context: EncodeContext<'_, char, C::Unit>,
plan: EncodePlan<Self::PlanAction>,
) -> Result<usize, Self::Error> {
match plan.action {
CharsetEncodeAction::WriteOriginal => unsafe {
codec.encode_unchecked(
context.input_value,
context.output,
context.output_index,
)
},
CharsetEncodeAction::WriteReplacement => unsafe {
codec.encode_unchecked(
&self.replacement,
context.output,
context.output_index,
)
},
CharsetEncodeAction::Skip => Ok(0),
}
}
#[inline]
fn invalid_input_index(
&mut self,
codec: &C,
index: usize,
input_len: usize,
) -> Self::Error {
let kind = CharsetEncodeErrorKind::InvalidInputIndex { input_len };
CharsetEncodeError::new(codec.charset(), kind, index)
}
#[inline]
fn invalid_output_index(
&mut self,
codec: &C,
index: usize,
output_len: usize,
) -> Self::Error {
let kind = CharsetEncodeErrorKind::InvalidOutputIndex { output_len };
CharsetEncodeError::new(codec.charset(), kind, index)
}
}
#[inline(always)]
pub(super) fn replacement_len<C>(
codec: &C,
ch: char,
) -> CharsetEncodeResult<usize>
where
C: CharsetEncodeProbe,
{
codec.encode_len(ch, 0)
}