use alloc::borrow::Cow;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[must_use]
pub(crate) struct Remaining<'s, T> {
pub(crate) r: &'s str,
pub(crate) d: T,
}
impl<'s, T> Remaining<'s, T> {
pub(crate) const fn new(remaining: &'s str, data: T) -> Self {
Self {
r: remaining,
d: data,
}
}
}
impl<'s, 'd> Remaining<'s, &'d str> {
pub(crate) fn d_as_cow(self) -> Remaining<'s, Cow<'d, str>> {
let Self { r, d } = self;
Remaining::new(r, Cow::from(d))
}
}
impl<'s> Remaining<'s, &'s str> {
pub(crate) fn split_at(s: &'s str, mid: usize) -> Self {
let (data, remaining) = s.split_at(mid);
Self::new(remaining, data)
}
}
pub(crate) trait StrParsing<'s> {
#[must_use]
fn p_number(&'s self) -> Option<Remaining<'s, usize>>;
#[must_use]
fn p_hex_number(&'s self) -> Option<Remaining<'s, usize>>;
#[must_use]
fn p_digit(&'s self) -> Option<Remaining<'s, usize>>;
#[must_use]
fn p_number_maybe_multi_digit(&'s self) -> Option<Remaining<'s, usize>>;
#[must_use]
fn p_first(&'s self) -> Option<Remaining<'s, char>>;
}
impl<'s> StrParsing<'s> for str {
fn p_number(&'s self) -> Option<Remaining<'s, usize>> {
let (remaining, data) = if let Some(index) = self.find(|c: char| !c.is_ascii_digit()) {
(&self[index..], self[..index].parse().ok()?)
} else {
("", self.parse().ok()?)
};
Some(Remaining::new(remaining, data))
}
fn p_hex_number(&'s self) -> Option<Remaining<'s, usize>> {
let (remaining, data) = if let Some(index) = self.find(|c: char| !c.is_ascii_hexdigit()) {
(
&self[index..],
usize::from_str_radix(&self[..index], 16).ok()?,
)
} else {
("", usize::from_str_radix(self, 16).ok()?)
};
Some(Remaining::new(remaining, data))
}
fn p_digit(&'s self) -> Option<Remaining<'s, usize>> {
let c = self.chars().next()?;
if c.is_ascii_digit() {
let digit = (c as usize).wrapping_sub('0' as usize);
Some(Remaining::new(&self[1..], digit))
} else {
None
}
}
fn p_number_maybe_multi_digit(&'s self) -> Option<Remaining<'s, usize>> {
if self.is_empty() {
None
} else if self.len() == 1 {
Some(Remaining::new("", self.parse().ok()?))
} else if let Some(index) = self.find(|c: char| !c.is_ascii_digit()) {
if index == 0 {
None
} else if self[index..].starts_with('_') {
let new_start = if index > 1 { index + 1 } else { index };
Some(Remaining::new(
&self[new_start..],
self[..index].parse().ok()?,
))
} else {
Some(Remaining::new(&self[1..], self[..1].parse().ok()?))
}
} else {
Some(Remaining::new(&self[1..], self[..1].parse().ok()?))
}
}
fn p_first(&'s self) -> Option<Remaining<'s, char>> {
let c = self.chars().next()?;
Some(Remaining::new(&self[1..], c))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_number_maybe_multi_digit() {
assert_eq!(
"1junk".p_number_maybe_multi_digit(),
Some(Remaining::new("junk", 1)),
);
assert_eq!(
"12_junk".p_number_maybe_multi_digit(),
Some(Remaining::new("junk", 12)),
);
assert_eq!(
"54junk".p_number_maybe_multi_digit(),
Some(Remaining::new("4junk", 5)),
);
assert_eq!(
"2".p_number_maybe_multi_digit(),
Some(Remaining::new("", 2)),
);
assert_eq!(
"32".p_number_maybe_multi_digit(),
Some(Remaining::new("2", 3)),
);
assert_eq!(
"1_junk".p_number_maybe_multi_digit(),
Some(Remaining::new("_junk", 1)),
);
}
}