blob: b03f21d25adff67dca0faea59244960345f077b8 [file] [log] [blame]
use crate::ext::LitIntExtension;
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{
parse::{Parse, ParseStream},
LitFloat, LitInt, Result, Token,
};
#[derive(PartialEq)]
enum AmPm {
Am,
Pm,
}
impl Parse for AmPm {
fn parse(input: ParseStream<'_>) -> Result<Self> {
use crate::kw::{am, pm, AM, PM};
if input.peek(am) {
input.parse::<am>()?;
Ok(AmPm::Am)
} else if input.peek(AM) {
input.parse::<AM>()?;
Ok(AmPm::Am)
} else if input.peek(pm) {
input.parse::<pm>()?;
Ok(AmPm::Pm)
} else if input.peek(PM) {
input.parse::<PM>()?;
Ok(AmPm::Pm)
} else {
error!("expected am or pm")
}
}
}
pub(crate) struct Time {
pub(crate) hour: LitInt,
pub(crate) minute: LitInt,
pub(crate) second: LitInt,
pub(crate) nanosecond: LitInt,
}
impl Parse for Time {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let mut hour = input.parse::<LitInt>()?;
input.parse::<Token![:]>()?;
let minute = input.parse::<LitInt>()?;
// Seconds and nanoseconds are optional, defaulting to zero.
let (second, nanosecond) = if input.peek(Token![:]) {
input.parse::<Token![:]>()?;
if input.peek(LitFloat) {
let float = input.parse::<LitFloat>()?;
// Temporary value to satisfy the compiler.
let float_str = float.to_string();
let parts: Vec<_> = float_str.splitn(2, '.').collect();
let seconds = LitInt::new(parts[0], float.span());
let nanoseconds = {
// Prepend a zero to avoid having an empty string.
// Strip the suffix to avoid syn thinking it's a float.
let raw_padded = format!("0{}", parts[1].trim_end_matches(float.suffix()));
// Take an extra digit due to the padding.
let digits: String = raw_padded
.chars()
.filter(char::is_ascii_digit)
.take(10)
.collect();
// Scale the value based on how many digits were provided.
#[allow(clippy::cast_possible_truncation)]
let value = LitInt::new(&digits, float.span()).base10_parse::<usize>()?
* 10_usize.pow(10 - digits.len() as u32);
LitInt::create(value).with_span(float.span())
};
(seconds, nanoseconds)
} else {
(input.parse::<LitInt>()?, LitInt::create(0))
}
} else {
(LitInt::create(0), LitInt::create(0))
};
let am_pm = input.parse::<AmPm>().ok();
// Ensure none of the components are out of range.
match am_pm {
Some(am_pm) => {
hour.ensure_in_range(1..=12)?;
// Adjust the hour if necessary.
hour = match (hour.value()?, am_pm) {
(12, AmPm::Am) => LitInt::create(0).with_span(hour.span()),
(value, AmPm::Pm) if value != 12 => {
LitInt::create(value + 12).with_span(hour.span())
}
_ => hour,
}
}
None => hour.ensure_in_range(0..24)?,
}
minute.ensure_in_range(0..60)?;
second.ensure_in_range(0..60)?;
// This likely isn't necessary, but it can't hurt.
nanosecond.ensure_in_range(0..1_000_000_000)?;
Ok(Self {
hour,
minute,
second,
nanosecond,
})
}
}
impl ToTokens for Time {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
hour,
minute,
second,
nanosecond,
} = self;
tokens.extend(quote! {
::time::internals::Time::from_hms_nanos_unchecked(#hour, #minute, #second, #nanosecond)
});
}
}