mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
* common: add PyPy CJK codec engines
Assisted-by: Codex:GPT-5
* common: mark CJK mapping tables generated
Assisted-by: Codex:GPT-5
* common: add reusable byte and text engines
Assisted-by: Codex:GPT-5
* common: record opt-in engine dependencies
Assisted-by: Codex:GPT-5
* stdlib: add `_multibytecodec` and the six `_codecs_*` modules
Wire the CJK codec engines in `rustpython-common` up to the VM. `_multibytecodec`
holds `MultibyteCodec`, `MultibyteIncrementalEncoder`, `MultibyteIncrementalDecoder`,
`MultibyteStreamReader` and `MultibyteStreamWriter`, the encode/decode drivers and
the error-handler protocol; each `_codecs_*` module exposes `getcodec` for the
encodings of its region.
`rustpython-stdlib` now enables the `cjk-codecs` feature of `rustpython-common`.
Add `cjk::decode_reset`, which the decoder `reset()` methods need. It differs from
building a fresh decoder state: ISO 2022 keeps its G1..G3 designations and the
escape-throughout flag, and resets only G0 and the shifted flag.
Drop the markers from the CJK codec tests and from the `test_codecs`, `test_io`,
`test_email`, `test_zipfile` and `test_traceback` cases that needed a CJK codec.
`test_codecs.BasicUnicodeTest.{test_basics,test_decoder_state}` stay expected
failures for an unrelated reason: `codecs.charmap_encode` rejects a `None` mapping.
Assisted-by: Claude Code:claude-opus-5
* stdlib: use the byte and text engines from `rustpython-common`
`binascii`, `_json` and `_socket`'s address converters now call the engines in
`rustpython-common` instead of carrying their own copies, and `rustpython-stdlib`
enables the `binascii`, `inet` and `json` features for them. That leaves
`base64` and `crc32fast` with no caller in `rustpython-stdlib`, and
`crates/stdlib/src/json/machinery.rs`, the last of the json_in_type-derived code,
goes away with its last caller.
The address converters change behaviour. `inet_aton` reads the lenient forms
(`127.1`, `16909060`, `0x7f.1`, `0177.0.0.1`), `inet_pton` rejects an octet with a
redundant leading zero and a group of five hex digits, `inet_ntop` writes the
IPv4-compatible form as `::c000:201`, and an address family with no converter
raises `EAFNOSUPPORT` instead of a bare `OSError`.
Give the JSON engine the closing-quote scan and the allocation-free `\uXXXX`
escape it needs to carry `_json`'s workload. Against the code it replaces,
`json.dumps` runs 2.3x faster with `ensure_ascii=True` and 1.7x without it, and
`json.loads` 1.2x.
`binascii.a2b_base64` now reports the messages `binascii.c` does: no
`error decoding base64: ` prefix, and the data-character count in parentheses.
Assisted-by: Claude Code:claude-opus-5
* test: enable the Windows cp932 codec case
The shared CJK engine now satisfies the Windows code-page adapter, so keeping this test marked as an expected failure turns its success into a CI failure.
Assisted-by: Codex:GPT-5
655 lines
21 KiB
Rust
655 lines
21 KiB
Rust
use core::ops::{self, Range};
|
|
|
|
use num_traits::ToPrimitive;
|
|
|
|
use crate::str::StrKind;
|
|
use crate::wtf8::{CodePoint, Wtf8, Wtf8Buf};
|
|
|
|
#[cfg(feature = "cjk-codecs")]
|
|
pub mod cjk;
|
|
|
|
pub trait StrBuffer: AsRef<Wtf8> {
|
|
fn is_compatible_with(&self, kind: StrKind) -> bool {
|
|
let s = self.as_ref();
|
|
match kind {
|
|
StrKind::Ascii => s.is_ascii(),
|
|
StrKind::Utf8 => s.is_utf8(),
|
|
StrKind::Wtf8 => true,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub trait CodecContext: Sized {
|
|
type Error;
|
|
type StrBuf: StrBuffer;
|
|
type BytesBuf: AsRef<[u8]>;
|
|
|
|
fn string(&self, s: Wtf8Buf) -> Self::StrBuf;
|
|
fn bytes(&self, b: Vec<u8>) -> Self::BytesBuf;
|
|
}
|
|
|
|
pub trait EncodeContext: CodecContext {
|
|
fn full_data(&self) -> &Wtf8;
|
|
fn data_len(&self) -> StrSize;
|
|
|
|
fn remaining_data(&self) -> &Wtf8;
|
|
fn position(&self) -> StrSize;
|
|
|
|
fn restart_from(&mut self, pos: StrSize) -> Result<(), Self::Error>;
|
|
|
|
fn error_encoding(&self, range: Range<StrSize>, reason: Option<&str>) -> Self::Error;
|
|
|
|
fn handle_error<E>(
|
|
&mut self,
|
|
errors: &E,
|
|
range: Range<StrSize>,
|
|
reason: Option<&str>,
|
|
) -> Result<EncodeReplace<Self>, Self::Error>
|
|
where
|
|
E: EncodeErrorHandler<Self>,
|
|
{
|
|
let (replace, restart) = errors.handle_encode_error(self, range, reason)?;
|
|
self.restart_from(restart)?;
|
|
Ok(replace)
|
|
}
|
|
}
|
|
|
|
pub trait DecodeContext: CodecContext {
|
|
fn full_data(&self) -> &[u8];
|
|
|
|
fn remaining_data(&self) -> &[u8];
|
|
fn position(&self) -> usize;
|
|
|
|
fn advance(&mut self, by: usize);
|
|
|
|
fn restart_from(&mut self, pos: usize) -> Result<(), Self::Error>;
|
|
|
|
fn error_decoding(&self, byte_range: Range<usize>, reason: Option<&str>) -> Self::Error;
|
|
|
|
fn handle_error<E>(
|
|
&mut self,
|
|
errors: &E,
|
|
byte_range: Range<usize>,
|
|
reason: Option<&str>,
|
|
) -> Result<Self::StrBuf, Self::Error>
|
|
where
|
|
E: DecodeErrorHandler<Self>,
|
|
{
|
|
let (replace, restart) = errors.handle_decode_error(self, byte_range, reason)?;
|
|
self.restart_from(restart)?;
|
|
Ok(replace)
|
|
}
|
|
}
|
|
|
|
pub trait EncodeErrorHandler<Ctx: EncodeContext> {
|
|
fn handle_encode_error(
|
|
&self,
|
|
ctx: &mut Ctx,
|
|
range: Range<StrSize>,
|
|
reason: Option<&str>,
|
|
) -> Result<(EncodeReplace<Ctx>, StrSize), Ctx::Error>;
|
|
}
|
|
pub trait DecodeErrorHandler<Ctx: DecodeContext> {
|
|
fn handle_decode_error(
|
|
&self,
|
|
ctx: &mut Ctx,
|
|
byte_range: Range<usize>,
|
|
reason: Option<&str>,
|
|
) -> Result<(Ctx::StrBuf, usize), Ctx::Error>;
|
|
}
|
|
|
|
pub enum EncodeReplace<Ctx: CodecContext> {
|
|
Str(Ctx::StrBuf),
|
|
Bytes(Ctx::BytesBuf),
|
|
}
|
|
|
|
#[derive(Copy, Clone, Default, Debug)]
|
|
pub struct StrSize {
|
|
pub bytes: usize,
|
|
pub chars: usize,
|
|
}
|
|
|
|
fn iter_code_points(w: &Wtf8) -> impl Iterator<Item = (StrSize, CodePoint)> {
|
|
w.code_point_indices()
|
|
.enumerate()
|
|
.map(|(chars, (bytes, c))| (StrSize { bytes, chars }, c))
|
|
}
|
|
|
|
impl ops::Add for StrSize {
|
|
type Output = Self;
|
|
fn add(self, rhs: Self) -> Self::Output {
|
|
Self {
|
|
bytes: self.bytes + rhs.bytes,
|
|
chars: self.chars + rhs.chars,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ops::AddAssign for StrSize {
|
|
fn add_assign(&mut self, rhs: Self) {
|
|
self.bytes += rhs.bytes;
|
|
self.chars += rhs.chars;
|
|
}
|
|
}
|
|
|
|
struct DecodeError<'a> {
|
|
valid_prefix: &'a str,
|
|
rest: &'a [u8],
|
|
err_len: Option<usize>,
|
|
}
|
|
|
|
/// # Safety
|
|
/// `v[..valid_up_to]` must be valid utf8
|
|
const unsafe fn make_decode_err(
|
|
v: &[u8],
|
|
valid_up_to: usize,
|
|
err_len: Option<usize>,
|
|
) -> DecodeError<'_> {
|
|
let (valid_prefix, rest) = unsafe { v.split_at_unchecked(valid_up_to) };
|
|
let valid_prefix = unsafe { core::str::from_utf8_unchecked(valid_prefix) };
|
|
DecodeError {
|
|
valid_prefix,
|
|
rest,
|
|
err_len,
|
|
}
|
|
}
|
|
|
|
enum HandleResult<'a> {
|
|
Done,
|
|
Error {
|
|
err_len: Option<usize>,
|
|
reason: &'a str,
|
|
},
|
|
}
|
|
|
|
fn decode_utf8_compatible<Ctx, E, DecodeF, ErrF>(
|
|
mut ctx: Ctx,
|
|
errors: &E,
|
|
decode: DecodeF,
|
|
handle_error: ErrF,
|
|
) -> Result<(Wtf8Buf, usize), Ctx::Error>
|
|
where
|
|
Ctx: DecodeContext,
|
|
E: DecodeErrorHandler<Ctx>,
|
|
DecodeF: Fn(&[u8]) -> Result<&str, DecodeError<'_>>,
|
|
ErrF: Fn(&[u8], Option<usize>) -> HandleResult<'static>,
|
|
{
|
|
if ctx.remaining_data().is_empty() {
|
|
return Ok((Wtf8Buf::new(), 0));
|
|
}
|
|
let mut out = Wtf8Buf::with_capacity(ctx.remaining_data().len());
|
|
loop {
|
|
match decode(ctx.remaining_data()) {
|
|
Ok(decoded) => {
|
|
out.push_str(decoded);
|
|
ctx.advance(decoded.len());
|
|
break;
|
|
}
|
|
Err(e) => {
|
|
out.push_str(e.valid_prefix);
|
|
match handle_error(e.rest, e.err_len) {
|
|
HandleResult::Done => {
|
|
ctx.advance(e.valid_prefix.len());
|
|
break;
|
|
}
|
|
HandleResult::Error { err_len, reason } => {
|
|
let err_start = ctx.position() + e.valid_prefix.len();
|
|
let err_end = match err_len {
|
|
Some(len) => err_start + len,
|
|
None => ctx.full_data().len(),
|
|
};
|
|
let err_range = err_start..err_end;
|
|
let replace = ctx.handle_error(errors, err_range, Some(reason))?;
|
|
out.push_wtf8(replace.as_ref());
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok((out, ctx.position()))
|
|
}
|
|
|
|
#[inline]
|
|
fn encode_utf8_compatible<Ctx, E>(
|
|
mut ctx: Ctx,
|
|
errors: &E,
|
|
err_reason: &str,
|
|
target_kind: StrKind,
|
|
) -> Result<Vec<u8>, Ctx::Error>
|
|
where
|
|
Ctx: EncodeContext,
|
|
E: EncodeErrorHandler<Ctx>,
|
|
{
|
|
// let mut data = s.as_ref();
|
|
// let mut char_data_index = 0;
|
|
let mut out = Vec::<u8>::with_capacity(ctx.remaining_data().len());
|
|
loop {
|
|
let data = ctx.remaining_data();
|
|
let mut iter = iter_code_points(data);
|
|
let Some((i, _)) = iter.find(|(_, c)| !target_kind.can_encode(*c)) else {
|
|
break;
|
|
};
|
|
|
|
out.extend_from_slice(&ctx.remaining_data().as_bytes()[..i.bytes]);
|
|
|
|
let err_start = ctx.position() + i;
|
|
// number of non-compatible chars between the first non-compatible char and the next compatible char
|
|
let err_end = match { iter }.find(|(_, c)| target_kind.can_encode(*c)) {
|
|
Some((i, _)) => ctx.position() + i,
|
|
None => ctx.data_len(),
|
|
};
|
|
|
|
let range = err_start..err_end;
|
|
let replace = ctx.handle_error(errors, range.clone(), Some(err_reason))?;
|
|
match replace {
|
|
EncodeReplace::Str(s) => {
|
|
if s.is_compatible_with(target_kind) {
|
|
out.extend_from_slice(s.as_ref().as_bytes());
|
|
} else {
|
|
return Err(ctx.error_encoding(range, Some(err_reason)));
|
|
}
|
|
}
|
|
EncodeReplace::Bytes(b) => {
|
|
out.extend_from_slice(b.as_ref());
|
|
}
|
|
}
|
|
}
|
|
out.extend_from_slice(ctx.remaining_data().as_bytes());
|
|
Ok(out)
|
|
}
|
|
|
|
pub mod errors {
|
|
use crate::str::UnicodeEscapeCodepoint;
|
|
|
|
use super::*;
|
|
use core::fmt::Write;
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub struct Strict;
|
|
|
|
impl<Ctx: EncodeContext> EncodeErrorHandler<Ctx> for Strict {
|
|
fn handle_encode_error(
|
|
&self,
|
|
ctx: &mut Ctx,
|
|
range: Range<StrSize>,
|
|
reason: Option<&str>,
|
|
) -> Result<(EncodeReplace<Ctx>, StrSize), Ctx::Error> {
|
|
Err(ctx.error_encoding(range, reason))
|
|
}
|
|
}
|
|
|
|
impl<Ctx: DecodeContext> DecodeErrorHandler<Ctx> for Strict {
|
|
fn handle_decode_error(
|
|
&self,
|
|
ctx: &mut Ctx,
|
|
byte_range: Range<usize>,
|
|
reason: Option<&str>,
|
|
) -> Result<(Ctx::StrBuf, usize), Ctx::Error> {
|
|
Err(ctx.error_decoding(byte_range, reason))
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub struct Ignore;
|
|
|
|
impl<Ctx: EncodeContext> EncodeErrorHandler<Ctx> for Ignore {
|
|
fn handle_encode_error(
|
|
&self,
|
|
ctx: &mut Ctx,
|
|
range: Range<StrSize>,
|
|
_reason: Option<&str>,
|
|
) -> Result<(EncodeReplace<Ctx>, StrSize), Ctx::Error> {
|
|
Ok((EncodeReplace::Bytes(ctx.bytes(b"".into())), range.end))
|
|
}
|
|
}
|
|
|
|
impl<Ctx: DecodeContext> DecodeErrorHandler<Ctx> for Ignore {
|
|
fn handle_decode_error(
|
|
&self,
|
|
ctx: &mut Ctx,
|
|
byte_range: Range<usize>,
|
|
_reason: Option<&str>,
|
|
) -> Result<(Ctx::StrBuf, usize), Ctx::Error> {
|
|
Ok((ctx.string("".into()), byte_range.end))
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub struct Replace;
|
|
|
|
impl<Ctx: EncodeContext> EncodeErrorHandler<Ctx> for Replace {
|
|
fn handle_encode_error(
|
|
&self,
|
|
ctx: &mut Ctx,
|
|
range: Range<StrSize>,
|
|
_reason: Option<&str>,
|
|
) -> Result<(EncodeReplace<Ctx>, StrSize), Ctx::Error> {
|
|
let replace = "?".repeat(range.end.chars - range.start.chars);
|
|
Ok((EncodeReplace::Str(ctx.string(replace.into())), range.end))
|
|
}
|
|
}
|
|
|
|
impl<Ctx: DecodeContext> DecodeErrorHandler<Ctx> for Replace {
|
|
fn handle_decode_error(
|
|
&self,
|
|
ctx: &mut Ctx,
|
|
byte_range: Range<usize>,
|
|
_reason: Option<&str>,
|
|
) -> Result<(Ctx::StrBuf, usize), Ctx::Error> {
|
|
Ok((
|
|
ctx.string(char::REPLACEMENT_CHARACTER.to_string().into()),
|
|
byte_range.end,
|
|
))
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub struct XmlCharRefReplace;
|
|
|
|
impl<Ctx: EncodeContext> EncodeErrorHandler<Ctx> for XmlCharRefReplace {
|
|
fn handle_encode_error(
|
|
&self,
|
|
ctx: &mut Ctx,
|
|
range: Range<StrSize>,
|
|
_reason: Option<&str>,
|
|
) -> Result<(EncodeReplace<Ctx>, StrSize), Ctx::Error> {
|
|
let err_str = &ctx.full_data()[range.start.bytes..range.end.bytes];
|
|
let num_chars = range.end.chars - range.start.chars;
|
|
// capacity rough guess; assuming that the codepoints are 3 digits in decimal + the &#;
|
|
let mut out = String::with_capacity(num_chars * 6);
|
|
for c in err_str.code_points() {
|
|
write!(out, "&#{};", c.to_u32()).unwrap()
|
|
}
|
|
Ok((EncodeReplace::Str(ctx.string(out.into())), range.end))
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub struct BackslashReplace;
|
|
|
|
impl<Ctx: EncodeContext> EncodeErrorHandler<Ctx> for BackslashReplace {
|
|
fn handle_encode_error(
|
|
&self,
|
|
ctx: &mut Ctx,
|
|
range: Range<StrSize>,
|
|
_reason: Option<&str>,
|
|
) -> Result<(EncodeReplace<Ctx>, StrSize), Ctx::Error> {
|
|
let err_str = &ctx.full_data()[range.start.bytes..range.end.bytes];
|
|
let num_chars = range.end.chars - range.start.chars;
|
|
// minimum 4 output bytes per char: \xNN
|
|
let mut out = String::with_capacity(num_chars * 4);
|
|
for c in err_str.code_points() {
|
|
write!(out, "{}", UnicodeEscapeCodepoint(c)).unwrap();
|
|
}
|
|
Ok((EncodeReplace::Str(ctx.string(out.into())), range.end))
|
|
}
|
|
}
|
|
|
|
impl<Ctx: DecodeContext> DecodeErrorHandler<Ctx> for BackslashReplace {
|
|
fn handle_decode_error(
|
|
&self,
|
|
ctx: &mut Ctx,
|
|
byte_range: Range<usize>,
|
|
_reason: Option<&str>,
|
|
) -> Result<(Ctx::StrBuf, usize), Ctx::Error> {
|
|
let err_bytes = &ctx.full_data()[byte_range.clone()];
|
|
let mut replace = String::with_capacity(4 * err_bytes.len());
|
|
for &c in err_bytes {
|
|
write!(replace, "\\x{c:02x}").unwrap();
|
|
}
|
|
Ok((ctx.string(replace.into()), byte_range.end))
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub struct NameReplace;
|
|
|
|
impl<Ctx: EncodeContext> EncodeErrorHandler<Ctx> for NameReplace {
|
|
fn handle_encode_error(
|
|
&self,
|
|
ctx: &mut Ctx,
|
|
range: Range<StrSize>,
|
|
_reason: Option<&str>,
|
|
) -> Result<(EncodeReplace<Ctx>, StrSize), Ctx::Error> {
|
|
let err_str = &ctx.full_data()[range.start.bytes..range.end.bytes];
|
|
let num_chars = range.end.chars - range.start.chars;
|
|
let mut out = String::with_capacity(num_chars * 4);
|
|
for c in err_str.code_points() {
|
|
let c_u32 = c.to_u32();
|
|
if let Some(c_name) = c.to_char().and_then(rustpython_unicode::character_name) {
|
|
write!(out, "\\N{{{c_name}}}").unwrap();
|
|
} else if c_u32 >= 0x10000 {
|
|
write!(out, "\\U{c_u32:08x}").unwrap();
|
|
} else if c_u32 >= 0x100 {
|
|
write!(out, "\\u{c_u32:04x}").unwrap();
|
|
} else {
|
|
write!(out, "\\x{c_u32:02x}").unwrap();
|
|
}
|
|
}
|
|
Ok((EncodeReplace::Str(ctx.string(out.into())), range.end))
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub struct SurrogateEscape;
|
|
|
|
impl<Ctx: EncodeContext> EncodeErrorHandler<Ctx> for SurrogateEscape {
|
|
fn handle_encode_error(
|
|
&self,
|
|
ctx: &mut Ctx,
|
|
range: Range<StrSize>,
|
|
reason: Option<&str>,
|
|
) -> Result<(EncodeReplace<Ctx>, StrSize), Ctx::Error> {
|
|
let err_str = &ctx.full_data()[range.start.bytes..range.end.bytes];
|
|
let num_chars = range.end.chars - range.start.chars;
|
|
let mut out = Vec::with_capacity(num_chars);
|
|
let mut pos = range.start;
|
|
for ch in err_str.code_points() {
|
|
let ch_u32 = ch.to_u32();
|
|
if !(0xdc80..=0xdcff).contains(&ch_u32) {
|
|
if out.is_empty() {
|
|
// Can't handle even the first character
|
|
return Err(ctx.error_encoding(range, reason));
|
|
}
|
|
// Return partial result, restart from this character
|
|
return Ok((EncodeReplace::Bytes(ctx.bytes(out)), pos));
|
|
}
|
|
out.push((ch_u32 - 0xdc00) as u8);
|
|
pos += StrSize {
|
|
bytes: ch.len_wtf8(),
|
|
chars: 1,
|
|
};
|
|
}
|
|
Ok((EncodeReplace::Bytes(ctx.bytes(out)), range.end))
|
|
}
|
|
}
|
|
|
|
impl<Ctx: DecodeContext> DecodeErrorHandler<Ctx> for SurrogateEscape {
|
|
fn handle_decode_error(
|
|
&self,
|
|
ctx: &mut Ctx,
|
|
byte_range: Range<usize>,
|
|
reason: Option<&str>,
|
|
) -> Result<(Ctx::StrBuf, usize), Ctx::Error> {
|
|
let err_bytes = &ctx.full_data()[byte_range.clone()];
|
|
let mut consumed = 0;
|
|
let mut replace = Wtf8Buf::with_capacity(4 * byte_range.len());
|
|
while consumed < 4 && consumed < byte_range.len() {
|
|
let c = err_bytes[consumed] as u16;
|
|
// Refuse to escape ASCII bytes
|
|
if c < 128 {
|
|
break;
|
|
}
|
|
replace.push(CodePoint::from(0xdc00 + c));
|
|
consumed += 1;
|
|
}
|
|
if consumed == 0 {
|
|
return Err(ctx.error_decoding(byte_range, reason));
|
|
}
|
|
Ok((ctx.string(replace), byte_range.start + consumed))
|
|
}
|
|
}
|
|
}
|
|
|
|
pub mod utf8 {
|
|
use super::*;
|
|
|
|
pub const ENCODING_NAME: &str = "utf-8";
|
|
|
|
#[inline]
|
|
pub fn encode<Ctx, E>(ctx: Ctx, errors: &E) -> Result<Vec<u8>, Ctx::Error>
|
|
where
|
|
Ctx: EncodeContext,
|
|
E: EncodeErrorHandler<Ctx>,
|
|
{
|
|
encode_utf8_compatible(ctx, errors, "surrogates not allowed", StrKind::Utf8)
|
|
}
|
|
|
|
pub fn decode<Ctx: DecodeContext, E: DecodeErrorHandler<Ctx>>(
|
|
ctx: Ctx,
|
|
errors: &E,
|
|
final_decode: bool,
|
|
) -> Result<(Wtf8Buf, usize), Ctx::Error> {
|
|
decode_utf8_compatible(
|
|
ctx,
|
|
errors,
|
|
|v| {
|
|
core::str::from_utf8(v).map_err(|e| {
|
|
// SAFETY: as specified in valid_up_to's documentation, input[..e.valid_up_to()]
|
|
// is valid utf8
|
|
unsafe { make_decode_err(v, e.valid_up_to(), e.error_len()) }
|
|
})
|
|
},
|
|
|rest, err_len| {
|
|
let first_err = rest[0];
|
|
if matches!(first_err, 0x80..=0xc1 | 0xf5..=0xff) {
|
|
HandleResult::Error {
|
|
err_len: Some(1),
|
|
reason: "invalid start byte",
|
|
}
|
|
} else if err_len.is_none() {
|
|
// error_len() == None means unexpected eof
|
|
if final_decode {
|
|
HandleResult::Error {
|
|
err_len,
|
|
reason: "unexpected end of data",
|
|
}
|
|
} else {
|
|
HandleResult::Done
|
|
}
|
|
} else if !final_decode && matches!(rest, [0xed, 0xa0..=0xbf]) {
|
|
// truncated surrogate
|
|
HandleResult::Done
|
|
} else {
|
|
HandleResult::Error {
|
|
err_len,
|
|
reason: "invalid continuation byte",
|
|
}
|
|
}
|
|
},
|
|
)
|
|
}
|
|
}
|
|
|
|
pub mod latin_1 {
|
|
use super::*;
|
|
|
|
pub const ENCODING_NAME: &str = "latin-1";
|
|
|
|
const ERR_REASON: &str = "ordinal not in range(256)";
|
|
|
|
#[inline]
|
|
pub fn encode<Ctx, E>(mut ctx: Ctx, errors: &E) -> Result<Vec<u8>, Ctx::Error>
|
|
where
|
|
Ctx: EncodeContext,
|
|
E: EncodeErrorHandler<Ctx>,
|
|
{
|
|
let mut out = Vec::<u8>::new();
|
|
loop {
|
|
let data = ctx.remaining_data();
|
|
let mut iter = iter_code_points(ctx.remaining_data());
|
|
let Some((i, ch)) = iter.find(|(_, c)| !c.is_ascii()) else {
|
|
break;
|
|
};
|
|
out.extend_from_slice(&data.as_bytes()[..i.bytes]);
|
|
let err_start = ctx.position() + i;
|
|
if let Some(byte) = ch.to_u32().to_u8() {
|
|
drop(iter);
|
|
out.push(byte);
|
|
// if the codepoint is between 128..=255, it's utf8-length is 2
|
|
ctx.restart_from(err_start + StrSize { bytes: 2, chars: 1 })?;
|
|
} else {
|
|
// number of non-latin_1 chars between the first non-latin_1 char and the next latin_1 char
|
|
let err_end = match { iter }.find(|(_, c)| c.to_u32() <= 255) {
|
|
Some((i, _)) => ctx.position() + i,
|
|
None => ctx.data_len(),
|
|
};
|
|
let err_range = err_start..err_end;
|
|
let replace = ctx.handle_error(errors, err_range.clone(), Some(ERR_REASON))?;
|
|
match replace {
|
|
EncodeReplace::Str(s) => {
|
|
if s.as_ref().code_points().any(|c| c.to_u32() > 255) {
|
|
return Err(ctx.error_encoding(err_range, Some(ERR_REASON)));
|
|
}
|
|
out.extend(s.as_ref().code_points().map(|c| c.to_u32() as u8));
|
|
}
|
|
EncodeReplace::Bytes(b) => {
|
|
out.extend_from_slice(b.as_ref());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
out.extend_from_slice(ctx.remaining_data().as_bytes());
|
|
Ok(out)
|
|
}
|
|
|
|
pub fn decode<Ctx: DecodeContext, E: DecodeErrorHandler<Ctx>>(
|
|
ctx: Ctx,
|
|
_errors: &E,
|
|
) -> Result<(Wtf8Buf, usize), Ctx::Error> {
|
|
let out: String = ctx.remaining_data().iter().map(|c| *c as char).collect();
|
|
let out_len = out.len();
|
|
Ok((out.into(), out_len))
|
|
}
|
|
}
|
|
|
|
pub mod ascii {
|
|
use super::*;
|
|
use ::ascii::AsciiStr;
|
|
|
|
pub const ENCODING_NAME: &str = "ascii";
|
|
|
|
const ERR_REASON: &str = "ordinal not in range(128)";
|
|
|
|
#[inline]
|
|
pub fn encode<Ctx, E>(ctx: Ctx, errors: &E) -> Result<Vec<u8>, Ctx::Error>
|
|
where
|
|
Ctx: EncodeContext,
|
|
E: EncodeErrorHandler<Ctx>,
|
|
{
|
|
encode_utf8_compatible(ctx, errors, ERR_REASON, StrKind::Ascii)
|
|
}
|
|
|
|
pub fn decode<Ctx: DecodeContext, E: DecodeErrorHandler<Ctx>>(
|
|
ctx: Ctx,
|
|
errors: &E,
|
|
) -> Result<(Wtf8Buf, usize), Ctx::Error> {
|
|
decode_utf8_compatible(
|
|
ctx,
|
|
errors,
|
|
|v| {
|
|
AsciiStr::from_ascii(v).map(|s| s.as_str()).map_err(|e| {
|
|
// SAFETY: as specified in valid_up_to's documentation, input[..e.valid_up_to()]
|
|
// is valid ascii & therefore valid utf8
|
|
unsafe { make_decode_err(v, e.valid_up_to(), Some(1)) }
|
|
})
|
|
},
|
|
|_rest, err_len| HandleResult::Error {
|
|
err_len,
|
|
reason: ERR_REASON,
|
|
},
|
|
)
|
|
}
|
|
}
|