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