You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

401 lines
9.0 KiB

//! Parsing times from definition files
use std::{
str,
num::{self, NonZeroU64},
fmt,
iter::FromIterator,
};
/// Interval for job, in miliseconds.
#[derive(Debug,Clone,PartialEq,Eq,Hash,Ord,PartialOrd)]
pub enum Unit
{
Second,
Minute,
Hour,
Day,
Week,
Year,
// Fuck months lole
Aeon,
}
impl Unit
{
/// Multiplier to get to miliseconds
#[inline]
#[cfg(nightly)]
const fn multiplier(&self) -> u64 {
use Unit::*;
match self {
Second => 1000,
Minute => Second.multiplier() * 60,
Hour => Minute.multiplier() * 60,
Day => Hour.multiplier() * 24,
Week => Day.multiplier() * 7,
Year => Week.multiplier() * 52,
Aeon => Year.multiplier() * 1000000,
}
}
#[cfg(not(nightly))]
fn multiplier(&self) -> u64 {
use Unit::*;
match self {
Second => 1000,
Minute => Second.multiplier() * 60,
Hour => Minute.multiplier() * 60,
Day => Hour.multiplier() * 24,
Week => Day.multiplier() * 7,
Year => Week.multiplier() * 52,
Aeon => Year.multiplier() * 1000000,
}
}
}
/// A time object parsed from the definition file.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord,PartialOrd)]
pub struct Time
{
unit: Unit,
value: NonZeroU64,
/// Value in miliseconds
absolute: Option<NonZeroU64>,
}
impl FromIterator<Time> for Time
{
fn from_iter<I: IntoIterator<Item=Time>>(iter: I) -> Self
{
iter.into_iter().fold(Option::<Self>::None, |acc, x| {
Some(match acc {
Some(acc) => acc.extend(x).unwrap(),
None => x,
})
}).unwrap()
}
}
impl Time
{
fn into_abs(mut self) -> Result<Self, ParseError>
{
self.absolute = Some(NonZeroU64::new(self.unit.multiplier()
.checked_mul(u64::from(self.value))
.ok_or(ParseError::nz_err())?)
.ok_or(ParseError::nz_err())?);
Ok(self)
}
/// Create a new `Time`
///
/// # Panics
/// If value in miliseconds would overflow a 64 bit unisnged integer
pub fn new(value: NonZeroU64, unit: Unit) -> Self
{
Self {
unit,
value,
absolute: None,
}.into_abs().expect("operation would result in overflow")
}
/// Create a new `Time` from miliseconds.
///
/// # Panics
/// If `value` is less than 1 second (1000ms)
pub fn from_ms(value: NonZeroU64) -> Self
{
let value_sec = NonZeroU64::new(u64::from(value) / 1000).expect("absolute `value` is too small for `Time`");
Self{
unit: Unit::Second,
value: value_sec,
absolute: Some(value),
}
}
/// Attempt to create a new `Time` from miliseconds.
pub fn try_from_ms(value: NonZeroU64) -> Result<Self, ParseError>
{
let value_sec = NonZeroU64::new(u64::from(value) / 1000).ok_or(ParseError::nz_err())?;
Ok(Self{
unit: Unit::Second,
value: value_sec,
absolute: Some(value),
})
}
}
impl Time
{
/// Get the absolute value of the interval in miliseconds
pub fn ms(&self) -> u64
{
self.absolute.unwrap().into()
}
fn extend(self, other: Self) -> Result<Self, ParseError>
{
Ok(Self::try_from_ms(unsafe{NonZeroU64::new_unchecked(u64::from(self.absolute.unwrap()) + u64::from(other.absolute.unwrap()))})?)
}
fn parse_chars<I>(from: &mut I) -> Result<Self, ParseError>
where I: Iterator<Item=char>
{
let mut token = String::new();
let mut unit = None;
let mut is_num = true;
for (i, tok) in (0..).zip(from)
{
if is_num && tok.is_whitespace() {
continue;
} else if is_num && tok.is_ascii_digit() {
token.push(tok);
} else if unit.is_none() {
is_num=false;
unit = match tok {
's' | 'S' => Some(Unit::Second),
'm' | 'M' => Some(Unit::Minute),
'h' | 'H' => Some(Unit::Hour),
'd' | 'D' => Some(Unit::Day),
'w' | 'W' => Some(Unit::Week),
'y' | 'Y' => Some(Unit::Year),
'∞' => Some(Unit::Aeon),
white if white.is_whitespace() => continue,
_ => return Err(ParseError::UnexpectedToken(tok, i)),
};
break;
} else {return Err(ParseError::UnexpectedToken(tok, i));}
}
Self {
unit: unit.unwrap_or(Unit::Second),
value: token.parse::<NonZeroU64>()?,
absolute: None,
}.into_abs()
}
}
impl str::FromStr for Time
{
type Err = ParseError;
fn from_str(from: &str) -> Result<Self,Self::Err>
{
Self::parse_chars(&mut from.chars())
}
}
/// Parse many `Time`s
pub fn parse_many(string: impl AsRef<str>, output: &mut Vec<Time>) -> Result<usize, ParseErrorMany>
{
let chars = string.as_ref();
let mut chars = chars.chars();
let mut i=0;
while chars.as_str().len() > 0 {
output.push(match Time::parse_chars(&mut chars) {
Ok(v) => v,
Err(e) => return Err(ParseErrorMany{error: e, line: i}),
});
i+=1;
}
Ok(i)
}
/// Parse many `Time`s into a single one
pub fn parse_many_into(string: impl AsRef<str>) -> Result<Time, ParseErrorMany>
{
let mut output = Vec::new();
parse_many(string, &mut output)?;
let mut line=0;
output.into_iter().fold(Option::<Result<Time, ParseErrorMany>>::None, |acc, x| {
line+=1;
match acc {
Some(Ok(acc)) => Some(acc.extend(x).map_err(|error| ParseErrorMany{error, line: line-1})),
None => Some(Ok(x)),
x=> x,
}
}).unwrap_or(Err(ParseErrorMany{error: ParseError::Empty, line:0}))
}
impl std::fmt::Display for Unit
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
use Unit::*;
write!(f, "{}", match self {
Second => 's',
Minute => 'm',
Hour => 'h',
Day => 'd',
Week => 'w',
Year => 'y',
Aeon => '∞',
})
}
}
impl std::fmt::Display for Time
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "{} {}", self.value, self.unit)
}
}
/// Shows which atom parse error originated
#[derive(Debug)]
pub struct ParseErrorMany {
error: ParseError,
line: usize,
}
impl ParseErrorMany
{
/// The atom number that error originated
pub fn atom(&self) -> usize
{
self.line
}
}
impl std::error::Error for ParseErrorMany
{
/// The `ParseError` source of this error
fn source(&self) -> Option<&(dyn std::error::Error +'static)>
{
Some(&self.error)
}
}
impl std::fmt::Display for ParseErrorMany
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
write!(f, "atom {}: {}", self.line, self.error)
}
}
#[cfg(nightly)]
type IntErrorKind = std::num::IntErrorKind;
#[cfg(not(nightly))]
type IntErrorKind = ();
/// Error in parsing `Time`
#[derive(Debug)]
pub enum ParseError {
Empty,
UnexpectedToken(char, usize),
InvalidNumber(Option<IntErrorKind>),
}
impl std::error::Error for ParseError{}
impl std::fmt::Display for ParseError
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
{
match self {
Self::Empty => write!(f, "empty string is not a time"),
Self::UnexpectedToken(tok, loc) => write!(f, "unexpected token `{}' at position {}", tok, loc),
Self::InvalidNumber(None) => write!(f, "number must be a valid non-zero 64 bit integer"),
#[cfg(nightly)]
Self::InvalidNumber(Some(kind)) => write!(f, "failed to parse integer: {:?}", kind),
#[cfg(not(nightly))]
Self::InvalidNumber(Some(_)) => write!(f, "failed to parse integer"),
}
}
}
impl From<num::TryFromIntError> for ParseError
{
fn from(_from: num::TryFromIntError) -> Self
{
Self::InvalidNumber(None)
}
}
impl ParseError
{
/// Non-zero conversion error
const fn nz_err() -> Self
{
Self::InvalidNumber(None)
}
}
impl From<num::ParseIntError> for ParseError
{
#[allow(unused_variables)]
fn from(from: num::ParseIntError) -> Self
{
#[cfg(nightly)]
return Self::InvalidNumber(Some(from.kind().clone()));
#[cfg(not(nightly))]
return Self::InvalidNumber(Some(()));
}
}
#[cfg(test)]
mod tests
{
use super::*;
#[test]
fn parse_single()
{
let time: Time = "100∞".parse().expect("parse failed");
assert_eq!(time, Time::new(NonZeroU64::try_from(100).unwrap(), Unit::Aeon));
}
#[test]
fn parse_many()
{
macro_rules! time {
($value:expr, $unit:expr) => {
Time::new(NonZeroU64::try_from($value).unwrap(), $unit)
}
};
let mut output = Vec::new();
let time = super::parse_many("1w4d 3h 2m 10s", &mut output).expect("parse failed");
assert_eq!(output.len(), time);
assert_eq!(&output[..],&[time!(1, Unit::Week),
time!(4, Unit::Day),
time!(3, Unit::Hour),
time!(2, Unit::Minute),
time!(10, Unit::Second)]);
}
#[test]
fn parse_many_into_one()
{
macro_rules! time {
($value:expr, $unit:expr) => {
Time::new(NonZeroU64::try_from($value).unwrap(), $unit)
}
};
let mut output = Vec::new();
const STR_FROM: &str = "1w4d 3h 2m 10s";
let time = super::parse_many(STR_FROM, &mut output).expect("parse failed");
assert_eq!(output.len(), time);
assert_eq!(&output[..],&[time!(1, Unit::Week),
time!(4, Unit::Day),
time!(3, Unit::Hour),
time!(2, Unit::Minute),
time!(10, Unit::Second)]);
let output: Time = output.iter().cloned().collect();
let input = super::parse_many_into(STR_FROM).expect("parse into failed");
assert_eq!(output,input);
}
}