use winnow::ascii::{alphanumeric1, space0};
use winnow::combinator::{alt, not, opt, terminated};
use winnow::error::{AddContext, ParserError, StrContext};
use winnow::stream::{AsBytes, AsChar, Compare, Stream, StreamIsPartial};
use winnow::token::take_while;
use winnow::{PResult, Parser};
#[inline]
pub fn parse_value<I, Error: ParserError<I>>(input: &mut I) -> PResult<u32, Error>
where
I: Stream + StreamIsPartial + for<'a> Compare<&'a str>,
<I as Stream>::Slice: AsBytes,
<I as Stream>::Token: AsChar,
<I as Stream>::Token: Clone,
I: for<'a> Compare<&'a [u8; 2]>,
I: for<'a> Compare<&'a [u8; 1]>,
I: winnow::stream::Compare<u8>,
Error: AddContext<I, winnow::error::StrContext>
{
#[derive(Clone, PartialEq, Debug)]
#[repr(u32)]
enum EncodingKind {
Hex = 16,
Bin = 2,
Dec = 10,
AmbiguousBinHex = 200,
Unk = 255
}
let before_encoding: <I as Stream>::Checkpoint = input.checkpoint();
let encoding = opt(terminated(
alt((
alt((b"0x", b"0X", b"#", b"$", b"&")).value(EncodingKind::Hex), alt((b"0b", b"0B")).value(EncodingKind::AmbiguousBinHex),
b"%".value(EncodingKind::Bin) )),
space0
)
.context(StrContext::Label("Number prefix detection")))
.parse_next(input)?
.unwrap_or(EncodingKind::Unk);
let hex_digits_and_sep = || {
take_while(1.., (('0'..='9'), ('a'..='f'), ('A'..='F'), '_'))
.context(StrContext::Label("Read hexadecimal digits"))
};
let mut dec_digits_and_sep =
take_while(1.., (('0'..='9'), '_')).context(StrContext::Label("Read decimal digits"));
let mut bin_digits_and_sep =
take_while(1.., (('0'..='1'), '_')).context(StrContext::Label("Read binary digits"));
let (encoding, digits) = match encoding {
EncodingKind::Hex => (EncodingKind::Hex, hex_digits_and_sep().parse_next(input)?),
EncodingKind::Bin => (EncodingKind::Bin, bin_digits_and_sep.parse_next(input)?),
EncodingKind::Dec => unreachable!("No prefix exist for decimal kind"),
EncodingKind::AmbiguousBinHex => {
let digits = opt(hex_digits_and_sep()).parse_next(input)?;
let suffix = opt(alt((b'h', b'H')))
.verify(|s| {
if digits.is_none() {
s.is_some()
}
else {
true
}
})
.parse_next(input)?;
if suffix.is_some() {
input.reset(&before_encoding);
b'0'.parse_next(input)?; let digits = hex_digits_and_sep().parse_next(input)?;
let _suffix = alt((b'h', b'H')).parse_next(input)?;
(EncodingKind::Hex, digits)
}
else {
(EncodingKind::Bin, digits.unwrap())
}
},
EncodingKind::Unk => {
let backup = input.checkpoint();
let digits = hex_digits_and_sep().parse_next(input)?;
let suffix = opt(alt((b'h', b'H'))).parse_next(input)?;
if suffix.is_some() {
(EncodingKind::Hex, digits)
}
else {
input.reset(&backup);
let digits: &[u8] = digits.as_bytes();
let last_digit = digits[digits.len() - 1];
if last_digit == b'b' || last_digit == b'B' {
let digits = bin_digits_and_sep.parse_next(input)?;
alt((b'b', b'B')).parse_next(input)?;
(EncodingKind::Bin, digits)
}
else {
(EncodingKind::Dec, dec_digits_and_sep.parse_next(input)?)
}
}
}
};
if encoding == EncodingKind::Hex {
not(alphanumeric1)
.context(StrContext::Label("This is not an hexadecimal number"))
.parse_next(input)?;
}
debug_assert!(encoding != EncodingKind::Unk);
debug_assert!(encoding != EncodingKind::AmbiguousBinHex);
let digits: &[u8] = digits.as_bytes();
let base = encoding as u32;
let mut number = 0;
for digit in digits.iter().filter(|&&digit| digit != b'_') {
let digit = *digit;
let digit = if digit.is_ascii_digit() {
digit - b'0'
}
else if (b'a'..=b'f').contains(&digit) {
digit - b'a' + 10
}
else {
digit - b'A' + 10
} as u32;
number = base * number + digit;
}
Ok(number)
}