Skip to main content

qubit_codec_misc/
c_integer_literal_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// =============================================================================
8//! C integer literal decoder.
9
10use qubit_codec::ValueDecoder;
11
12use crate::MiscCodecError;
13use crate::MiscCodecResult;
14use crate::internal::LiteralComponents;
15
16/// Decodes restricted non-negative C integer literal fragments.
17///
18/// This codec accepts decimal literals such as `123`, octal literals such as
19/// `0123`, and hexadecimal literals such as `0x123` or `0X123`. It trims
20/// surrounding whitespace and returns a `u64`. It intentionally accepts only
21/// a complete unsigned token: signs, integer suffixes, digit separators, and
22/// token-stream boundaries are outside this codec's contract.
23///
24/// # Examples
25///
26/// ```
27/// use qubit_codec_misc::CIntegerLiteralCodec;
28///
29/// let codec = CIntegerLiteralCodec::new();
30/// assert_eq!(codec.decode("0x2a").unwrap(), 42);
31/// ```
32#[must_use]
33#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
34pub struct CIntegerLiteralCodec;
35
36impl CIntegerLiteralCodec {
37    /// Creates a C integer literal codec.
38    ///
39    /// # Returns
40    /// A stateless C integer literal codec.
41    #[inline]
42    pub fn new() -> Self {
43        Self
44    }
45
46    /// Decodes a C integer literal into a `u64`.
47    ///
48    /// # Parameters
49    /// - `text`: C integer literal text.
50    ///
51    /// # Returns
52    /// Parsed integer value.
53    ///
54    /// # Errors
55    /// Returns [`MiscCodecError::InvalidInput`] when the input is empty, lacks
56    /// digits, or overflows `u64`; returns [`MiscCodecError::InvalidDigit`]
57    /// when a character is not valid for the detected radix.
58    #[inline]
59    pub fn decode(&self, text: &str) -> MiscCodecResult<u64> {
60        let (trimmed, trim_offset) = trim_with_offset(text);
61        if trimmed.is_empty() {
62            return Err(invalid_c_integer_input("expected at least one digit"));
63        }
64        let components = LiteralComponents::parse(trimmed, trim_offset)?;
65        validate_digits(components)?;
66        u64::from_str_radix(components.digits, components.radix)
67            .map_err(|error| invalid_c_integer_input(&format!("integer literal is out of range: {error}")))
68    }
69}
70
71impl ValueDecoder<str> for CIntegerLiteralCodec {
72    /// Error returned when a C integer token is malformed or unsupported.
73    type Error = MiscCodecError;
74
75    /// Unsigned integer produced by successfully decoding a C integer token.
76    type Output = u64;
77
78    /// Decodes a complete C integer token into a `u64`.
79    ///
80    /// # Parameters
81    /// - `input`: C integer literal text.
82    ///
83    /// # Returns
84    /// The decoded unsigned integer.
85    ///
86    /// # Errors
87    /// Returns [`MiscCodecError::InvalidInput`] when the input is empty, lacks
88    /// digits, or overflows `u64`; returns [`MiscCodecError::InvalidDigit`]
89    /// when a character is not valid for the detected radix.
90    #[inline]
91    fn decode(&mut self, input: &str) -> Result<Self::Output, Self::Error> {
92        CIntegerLiteralCodec::decode(self, input)
93    }
94}
95
96/// Trims surrounding whitespace while preserving the start byte offset.
97///
98/// # Parameters
99/// - `text`: Input text.
100///
101/// # Returns
102/// Trimmed text and the byte offset where it starts in `text`.
103#[inline]
104fn trim_with_offset(text: &str) -> (&str, usize) {
105    let trimmed_start = text.trim_start();
106    let start = text.len() - trimmed_start.len();
107    (trimmed_start.trim_end(), start)
108}
109
110/// Validates that every character is valid for the detected radix.
111///
112/// # Parameters
113/// - `components`: Parsed literal components.
114///
115/// # Errors
116/// Returns [`MiscCodecError::InvalidDigit`] with the original input byte index
117/// of the invalid character.
118fn validate_digits(components: LiteralComponents<'_>) -> MiscCodecResult<()> {
119    for (index, character) in components.digits.char_indices() {
120        if character.is_digit(components.radix) {
121            continue;
122        }
123        return Err(MiscCodecError::InvalidDigit {
124            radix: components.radix,
125            index: components.digits_offset + index,
126            character,
127        });
128    }
129    Ok(())
130}
131
132/// Builds an invalid C integer literal input error.
133///
134/// # Parameters
135/// - `reason`: Human-readable reason the input was rejected.
136///
137/// # Returns
138/// An invalid input error for the C integer literal codec.
139fn invalid_c_integer_input(reason: &str) -> MiscCodecError {
140    MiscCodecError::InvalidInput {
141        codec: "c-integer-literal",
142        reason: reason.to_owned(),
143    }
144}