mod single_byte;
mod utf16;
mod utf8;
use crate::cjk::{big5, euc_kr, gb18030};
use crate::jis::{euc_jp, iso2022jp, shift_jis};
use crate::label::Encoding;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DecodeError {
pub offset: usize,
pub length: usize,
pub kind: DecodeErrorKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DecodeErrorKind {
MalformedSequence,
TruncatedSequence,
UnpairedSurrogate,
DisallowedByte,
}
impl std::fmt::Display for DecodeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let what = match self.kind {
DecodeErrorKind::MalformedSequence => "malformed byte sequence",
DecodeErrorKind::TruncatedSequence => "truncated byte sequence at end of input",
DecodeErrorKind::UnpairedSurrogate => "unpaired UTF-16 surrogate",
DecodeErrorKind::DisallowedByte => "byte outside the encoding's repertoire",
};
write!(
f,
"{what} at offset {} (length {})",
self.offset, self.length
)
}
}
impl std::error::Error for DecodeError {}
#[derive(Debug, Clone)]
pub struct Decoder {
kind: DecoderKind,
}
#[derive(Debug, Clone)]
enum DecoderKind {
Utf8(utf8::Utf8State),
Utf16Le(utf16::Utf16State),
Utf16Be(utf16::Utf16State),
Latin1,
Windows1252,
Ascii,
LegacySingleByte(Encoding),
ShiftJis(shift_jis::ShiftJisState),
EucJp(euc_jp::EucJpState),
Iso2022Jp(iso2022jp::Iso2022JpState),
Gbk(gb18030::Gb18030State),
Gb18030(gb18030::Gb18030State),
Big5(big5::Big5State),
EucKr(euc_kr::EucKrState),
}
impl Decoder {
#[must_use]
pub(crate) const fn new(encoding: Encoding) -> Decoder {
let kind = match encoding {
Encoding::Utf8 => DecoderKind::Utf8(utf8::Utf8State::new()),
Encoding::Utf16Le => DecoderKind::Utf16Le(utf16::Utf16State::new()),
Encoding::Utf16Be => DecoderKind::Utf16Be(utf16::Utf16State::new()),
Encoding::Latin1 => DecoderKind::Latin1,
Encoding::Windows1252 => DecoderKind::Windows1252,
Encoding::Ascii => DecoderKind::Ascii,
Encoding::ShiftJis => DecoderKind::ShiftJis(shift_jis::ShiftJisState::new()),
Encoding::EucJp => DecoderKind::EucJp(euc_jp::EucJpState::new()),
Encoding::Iso2022Jp => DecoderKind::Iso2022Jp(iso2022jp::Iso2022JpState::new()),
Encoding::Gbk => DecoderKind::Gbk(gb18030::Gb18030State::new()),
Encoding::Gb18030 => DecoderKind::Gb18030(gb18030::Gb18030State::new()),
Encoding::Big5 => DecoderKind::Big5(big5::Big5State::new()),
Encoding::EucKr => DecoderKind::EucKr(euc_kr::EucKrState::new()),
other => {
debug_assert!(
other.is_legacy_single_byte_table(),
"Decoder::new: unhandled Encoding variant"
);
DecoderKind::LegacySingleByte(other)
}
};
Decoder { kind }
}
#[must_use]
pub const fn encoding(&self) -> Encoding {
match &self.kind {
DecoderKind::Utf8(_) => Encoding::Utf8,
DecoderKind::Utf16Le(_) => Encoding::Utf16Le,
DecoderKind::Utf16Be(_) => Encoding::Utf16Be,
DecoderKind::Latin1 => Encoding::Latin1,
DecoderKind::Windows1252 => Encoding::Windows1252,
DecoderKind::Ascii => Encoding::Ascii,
DecoderKind::LegacySingleByte(encoding) => *encoding,
DecoderKind::ShiftJis(_) => Encoding::ShiftJis,
DecoderKind::EucJp(_) => Encoding::EucJp,
DecoderKind::Iso2022Jp(_) => Encoding::Iso2022Jp,
DecoderKind::Gbk(_) => Encoding::Gbk,
DecoderKind::Gb18030(_) => Encoding::Gb18030,
DecoderKind::Big5(_) => Encoding::Big5,
DecoderKind::EucKr(_) => Encoding::EucKr,
}
}
#[must_use]
pub const fn has_pending_state(&self) -> bool {
match &self.kind {
DecoderKind::Utf8(state) => state.has_pending(),
DecoderKind::Utf16Le(state) | DecoderKind::Utf16Be(state) => state.has_pending(),
DecoderKind::Latin1
| DecoderKind::Windows1252
| DecoderKind::Ascii
| DecoderKind::LegacySingleByte(_) => false,
DecoderKind::ShiftJis(state) => state.has_pending(),
DecoderKind::EucJp(state) => state.has_pending(),
DecoderKind::Iso2022Jp(state) => state.has_pending(),
DecoderKind::Gbk(state) | DecoderKind::Gb18030(state) => state.has_pending(),
DecoderKind::Big5(state) => state.has_pending(),
DecoderKind::EucKr(state) => state.has_pending(),
}
}
pub fn decode_to_string(
&mut self,
input: &[u8],
dst: &mut String,
last: bool,
) -> Result<usize, DecodeError> {
match &mut self.kind {
DecoderKind::Utf8(state) => utf8::decode(state, input, dst, last),
DecoderKind::Utf16Le(state) => utf16::decode(state, input, dst, last, false),
DecoderKind::Utf16Be(state) => utf16::decode(state, input, dst, last, true),
DecoderKind::Latin1 => single_byte::decode_latin1(input, dst),
DecoderKind::Windows1252 => single_byte::decode_windows1252(input, dst),
DecoderKind::Ascii => single_byte::decode_ascii(input, dst),
DecoderKind::LegacySingleByte(encoding) => {
single_byte::decode_legacy_single_byte(*encoding, input, dst)
}
DecoderKind::ShiftJis(state) => shift_jis::decode(state, input, dst, last),
DecoderKind::EucJp(state) => euc_jp::decode(state, input, dst, last),
DecoderKind::Iso2022Jp(state) => iso2022jp::decode(state, input, dst, last),
DecoderKind::Gbk(state) | DecoderKind::Gb18030(state) => {
gb18030::decode(state, input, dst, last)
}
DecoderKind::Big5(state) => big5::decode(state, input, dst, last),
DecoderKind::EucKr(state) => euc_kr::decode(state, input, dst, last),
}
}
pub fn decode_to_string_lossy(&mut self, input: &[u8], dst: &mut String, last: bool) -> usize {
match &mut self.kind {
DecoderKind::Utf8(state) => utf8::decode_lossy(state, input, dst, last),
DecoderKind::Utf16Le(state) => utf16::decode_lossy(state, input, dst, last, false),
DecoderKind::Utf16Be(state) => utf16::decode_lossy(state, input, dst, last, true),
DecoderKind::Latin1 => single_byte::decode_latin1_lossy(input, dst),
DecoderKind::Windows1252 => single_byte::decode_windows1252_lossy(input, dst),
DecoderKind::Ascii => single_byte::decode_ascii_lossy(input, dst),
DecoderKind::LegacySingleByte(encoding) => {
single_byte::decode_legacy_single_byte_lossy(*encoding, input, dst)
}
DecoderKind::ShiftJis(state) => shift_jis::decode_lossy(state, input, dst, last),
DecoderKind::EucJp(state) => euc_jp::decode_lossy(state, input, dst, last),
DecoderKind::Iso2022Jp(state) => iso2022jp::decode_lossy(state, input, dst, last),
DecoderKind::Gbk(state) | DecoderKind::Gb18030(state) => {
gb18030::decode_lossy(state, input, dst, last)
}
DecoderKind::Big5(state) => big5::decode_lossy(state, input, dst, last),
DecoderKind::EucKr(state) => euc_kr::decode_lossy(state, input, dst, last),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encoding_round_trips_for_every_variant() {
for &enc in Encoding::ALL {
assert_eq!(enc.new_decoder().encoding(), enc);
}
}
#[test]
fn fresh_decoder_has_no_pending_state() {
for &enc in Encoding::ALL {
assert!(!enc.new_decoder().has_pending_state(), "{enc}");
}
}
#[test]
fn shift_jis_euc_jp_iso2022jp_decoders_report_pending_state_mid_sequence()
-> Result<(), DecodeError> {
let mut decoder = Encoding::ShiftJis.new_decoder();
let mut out = String::new();
decoder.decode_to_string(&[0x82], &mut out, false)?;
assert!(decoder.has_pending_state());
decoder.decode_to_string(&[0xA0], &mut out, true)?;
assert!(!decoder.has_pending_state());
let mut decoder = Encoding::EucJp.new_decoder();
let mut out = String::new();
decoder.decode_to_string(&[0xA4], &mut out, false)?;
assert!(decoder.has_pending_state());
decoder.decode_to_string(&[0xA2], &mut out, true)?;
assert!(!decoder.has_pending_state());
let mut decoder = Encoding::Iso2022Jp.new_decoder();
let mut out = String::new();
decoder.decode_to_string(&[0x1B], &mut out, false)?;
assert!(decoder.has_pending_state());
decoder.decode_to_string(&[0x28, 0x42], &mut out, true)?;
assert!(!decoder.has_pending_state());
Ok(())
}
#[test]
fn cjk_decoders_report_pending_state_mid_sequence() -> Result<(), DecodeError> {
for (encoding, lead, trail) in [
(Encoding::Gbk, 0xD6u8, 0xD0u8),
(Encoding::Gb18030, 0xD6, 0xD0),
(Encoding::Big5, 0xA4, 0xA4),
(Encoding::EucKr, 0xC7, 0xD1),
] {
let mut decoder = encoding.new_decoder();
let mut out = String::new();
decoder.decode_to_string(&[lead], &mut out, false)?;
assert!(decoder.has_pending_state(), "{encoding}");
decoder.decode_to_string(&[trail], &mut out, true)?;
assert!(!decoder.has_pending_state(), "{encoding}");
}
Ok(())
}
#[test]
fn legacy_single_byte_decoders_never_report_pending_state() {
for &enc in Encoding::ALL {
if !enc.is_legacy_single_byte_table() {
continue;
}
let mut decoder = enc.new_decoder();
let mut out = String::new();
let _ = decoder.decode_to_string(&[0x80], &mut out, false);
assert!(!decoder.has_pending_state(), "{enc}");
}
}
#[test]
fn utf8_decoder_reports_pending_state_mid_sequence() -> Result<(), DecodeError> {
let mut decoder = Encoding::Utf8.new_decoder();
let mut out = String::new();
decoder.decode_to_string(&[0xE2], &mut out, false)?;
assert!(decoder.has_pending_state());
decoder.decode_to_string(&[0x86, 0x92], &mut out, true)?;
assert!(!decoder.has_pending_state());
assert_eq!(out, "→");
Ok(())
}
#[test]
fn decode_error_display_is_human_readable() {
let err = DecodeError {
offset: 3,
length: 1,
kind: DecodeErrorKind::DisallowedByte,
};
assert_eq!(
err.to_string(),
"byte outside the encoding's repertoire at offset 3 (length 1)"
);
}
}