Skip to main content

qubit_codec_text/codec/
ascii_codec.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0.
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8use crate::{
9    Ascii,
10    Charset,
11    CharsetCodec,
12    CharsetDecodeError,
13    CharsetDecodeErrorKind,
14    CharsetDecodeResult,
15    CharsetEncodeError,
16    CharsetEncodeErrorKind,
17    CharsetEncodeProbe,
18    CharsetEncodeResult,
19};
20use qubit_codec::Codec;
21
22/// Single-byte ASCII codec for bytes.
23///
24/// `AsciiCodec` converts between one-byte ASCII-encoded data and Unicode scalar
25/// values.
26#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
27pub struct AsciiCodec;
28
29impl AsciiCodec {
30    /// Returns the ASCII charset descriptor.
31    ///
32    /// # Returns
33    ///
34    /// Returns [`Charset::ASCII`].
35    #[must_use]
36    #[inline(always)]
37    pub const fn charset(self) -> Charset {
38        Charset::ASCII
39    }
40}
41
42impl CharsetCodec for AsciiCodec {
43    /// Returns the charset descriptor for this codec.
44    ///
45    /// # Returns
46    ///
47    /// Returns [`Charset::ASCII`].
48    #[inline(always)]
49    fn charset(&self) -> Charset {
50        Charset::ASCII
51    }
52}
53
54impl CharsetEncodeProbe for AsciiCodec {
55    /// Encodes one `char` into one ASCII byte.
56    ///
57    /// # Parameters
58    ///
59    /// - `ch`: The character to encode.
60    /// - `index`: Input character index used for error context.
61    ///
62    /// # Returns
63    ///
64    /// `Ok(1)` when one byte is needed.
65    ///
66    /// # Errors
67    ///
68    /// * `CharsetEncodeErrorKind::UnmappableCharacter` if `ch` is not ASCII.
69    #[inline]
70    fn encode_len(&self, ch: char, index: usize) -> CharsetEncodeResult<usize> {
71        if ch > Ascii::MAX_CHAR {
72            let kind = CharsetEncodeErrorKind::UnmappableCharacter {
73                value: ch as u32,
74            };
75            return Err(CharsetEncodeError::new(Charset::ASCII, kind, index));
76        }
77        Ok(1)
78    }
79}
80
81unsafe impl Codec for AsciiCodec {
82    type Value = char;
83    type Unit = u8;
84    type DecodeError = CharsetDecodeError;
85    type EncodeError = CharsetEncodeError;
86
87    #[inline(always)]
88    fn min_units_per_value(&self) -> core::num::NonZeroUsize {
89        core::num::NonZeroUsize::MIN
90    }
91
92    #[inline(always)]
93    fn max_units_per_value(&self) -> core::num::NonZeroUsize {
94        core::num::NonZeroUsize::MIN
95    }
96
97    #[inline]
98    unsafe fn decode_unchecked(
99        &self,
100        input: &[u8],
101        index: usize,
102    ) -> CharsetDecodeResult<(char, core::num::NonZeroUsize)> {
103        if index > input.len() {
104            let kind = CharsetDecodeErrorKind::InvalidInputIndex {
105                input_len: input.len(),
106            };
107            return Err(CharsetDecodeError::new(Charset::ASCII, kind, index));
108        }
109        if index == input.len() {
110            let kind = CharsetDecodeErrorKind::IncompleteSequence {
111                required: 1,
112                available: 0,
113            };
114            return Err(CharsetDecodeError::new(Charset::ASCII, kind, index));
115        }
116
117        let value = input[index];
118        if value > Ascii::MAX_BYTE {
119            let kind = CharsetDecodeErrorKind::MalformedSequence {
120                value: Some(value as u32),
121            };
122            return Err(CharsetDecodeError::new(Charset::ASCII, kind, index));
123        }
124        debug_assert!(index < input.len());
125        Ok((value as char, core::num::NonZeroUsize::MIN))
126    }
127
128    #[inline]
129    unsafe fn encode_unchecked(
130        &self,
131        ch: &char,
132        output: &mut [u8],
133        index: usize,
134    ) -> CharsetEncodeResult<usize> {
135        if *ch > Ascii::MAX_CHAR {
136            let kind = CharsetEncodeErrorKind::UnmappableCharacter {
137                value: *ch as u32,
138            };
139            return Err(CharsetEncodeError::new(Charset::ASCII, kind, index));
140        }
141        if index >= output.len() {
142            let kind = CharsetEncodeErrorKind::BufferTooSmall {
143                required: required_index(index, 1),
144                available: 0,
145            };
146            return Err(CharsetEncodeError::new(Charset::ASCII, kind, index));
147        }
148        output[index] = *ch as u8;
149        Ok(1)
150    }
151}
152
153#[inline(always)]
154const fn required_index(index: usize, required_units: usize) -> usize {
155    match index.checked_add(required_units) {
156        Some(required) => required,
157        None => usize::MAX,
158    }
159}