use crate::{
CodecError,
CodecResult,
Decoder,
};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct CIntegerLiteralCodec;
impl CIntegerLiteralCodec {
pub fn new() -> Self {
Self
}
pub fn decode(&self, text: &str) -> CodecResult<u64> {
let (trimmed, trim_offset) = trim_with_offset(text);
if trimmed.is_empty() {
return Err(invalid_c_integer_input("expected at least one digit"));
}
let components = LiteralComponents::parse(trimmed, trim_offset)?;
validate_digits(components)?;
u64::from_str_radix(components.digits, components.radix).map_err(|error| {
invalid_c_integer_input(&format!("integer literal is out of range: {error}"))
})
}
}
impl Decoder<str> for CIntegerLiteralCodec {
type Error = CodecError;
type Output = u64;
fn decode(&self, input: &str) -> Result<Self::Output, Self::Error> {
CIntegerLiteralCodec::decode(self, input)
}
}
#[derive(Debug, Clone, Copy)]
struct LiteralComponents<'a> {
radix: u32,
digits: &'a str,
digits_offset: usize,
}
impl<'a> LiteralComponents<'a> {
fn parse(trimmed: &'a str, trim_offset: usize) -> CodecResult<Self> {
if let Some(digits) = trimmed
.strip_prefix("0x")
.or_else(|| trimmed.strip_prefix("0X"))
{
if digits.is_empty() {
return Err(invalid_c_integer_input(
"hexadecimal literal requires at least one digit",
));
}
return Ok(Self {
radix: 16,
digits,
digits_offset: trim_offset + 2,
});
}
if trimmed.len() > 1
&& let Some(digits) = trimmed.strip_prefix('0')
{
return Ok(Self {
radix: 8,
digits,
digits_offset: trim_offset + 1,
});
}
Ok(Self {
radix: 10,
digits: trimmed,
digits_offset: trim_offset,
})
}
}
fn trim_with_offset(text: &str) -> (&str, usize) {
let trimmed_start = text.trim_start();
let start = text.len() - trimmed_start.len();
(trimmed_start.trim_end(), start)
}
fn validate_digits(components: LiteralComponents<'_>) -> CodecResult<()> {
for (index, character) in components.digits.char_indices() {
if character.is_digit(components.radix) {
continue;
}
return Err(CodecError::InvalidDigit {
radix: components.radix,
index: components.digits_offset + index,
character,
});
}
Ok(())
}
fn invalid_c_integer_input(reason: &str) -> CodecError {
CodecError::InvalidInput {
codec: "c-integer-literal",
reason: reason.to_owned(),
}
}