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