#![no_std]
#![doc(html_root_url = "https://docs.rs/nth-check/0.1.0")]
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
struct ReadmeDoctests;
use core::fmt;
use core::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Nth {
a: i32,
b: i32,
}
impl Nth {
#[must_use]
pub const fn new(a: i32, b: i32) -> Self {
Self { a, b }
}
#[must_use]
pub const fn a(self) -> i32 {
self.a
}
#[must_use]
pub const fn b(self) -> i32 {
self.b
}
#[must_use]
pub const fn matches(self, index: i32) -> bool {
let diff = index as i64 - self.b as i64;
let a = self.a as i64;
if a == 0 {
diff == 0
} else {
diff % a == 0 && diff / a >= 0
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseError;
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("invalid An+B microsyntax")
}
}
impl core::error::Error for ParseError {}
impl FromStr for Nth {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
parse(s)
}
}
impl fmt::Display for Nth {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.a == 0 {
return write!(f, "{}", self.b);
}
match self.a {
1 => f.write_str("n")?,
-1 => f.write_str("-n")?,
a => write!(f, "{a}n")?,
}
if self.b != 0 {
write!(f, "{:+}", self.b)?;
}
Ok(())
}
}
pub fn parse(input: &str) -> Result<Nth, ParseError> {
let s = input.trim();
if s.is_empty() {
return Err(ParseError);
}
if s.eq_ignore_ascii_case("odd") {
return Ok(Nth { a: 2, b: 1 });
}
if s.eq_ignore_ascii_case("even") {
return Ok(Nth { a: 2, b: 0 });
}
match s.bytes().position(|c| c == b'n' || c == b'N') {
None => Ok(Nth {
a: 0,
b: parse_int(s)?,
}),
Some(pos) => {
let before = &s[..pos];
let after = &s[pos + 1..];
if after.bytes().any(|c| c == b'n' || c == b'N') {
return Err(ParseError);
}
let a = match before {
"" | "+" => 1,
"-" => -1,
other => parse_int(other)?,
};
Ok(Nth {
a,
b: parse_b(after)?,
})
}
}
}
fn parse_int(s: &str) -> Result<i32, ParseError> {
s.parse::<i32>().map_err(|_| ParseError)
}
fn parse_b(after: &str) -> Result<i32, ParseError> {
let t = after.trim();
if t.is_empty() {
return Ok(0);
}
let (sign, rest) = match t.as_bytes()[0] {
b'+' => (1_i64, &t[1..]),
b'-' => (-1_i64, &t[1..]),
_ => return Err(ParseError),
};
let digits = rest.trim_start();
if digits.is_empty() || !digits.bytes().all(|c| c.is_ascii_digit()) {
return Err(ParseError);
}
let magnitude = digits.parse::<i64>().map_err(|_| ParseError)?;
i32::try_from(sign * magnitude).map_err(|_| ParseError)
}