use crate::convert::implicitly_convert_to_int;
use crate::extn::core::symbol::Symbol;
use crate::extn::prelude::*;
const NANOS_IN_SECOND: i64 = 1_000_000_000;
const MILLIS_IN_NANO: i64 = 1_000_000;
const MICROS_IN_NANO: i64 = 1_000;
const NANOS_IN_NANO: i64 = 1;
#[allow(clippy::cast_precision_loss)]
const MIN_FLOAT_SECONDS: f64 = i64::MIN as f64;
#[allow(clippy::cast_precision_loss)]
const MAX_FLOAT_SECONDS: f64 = i64::MAX as f64;
const MIN_FLOAT_NANOS: f64 = 0.0;
#[allow(clippy::cast_precision_loss)]
const MAX_FLOAT_NANOS: f64 = NANOS_IN_SECOND as f64;
enum SubsecMultiplier {
Millis,
Micros,
Nanos,
}
impl SubsecMultiplier {
#[must_use]
const fn as_nanos(&self) -> i64 {
match self {
Self::Millis => MILLIS_IN_NANO,
Self::Micros => MICROS_IN_NANO,
Self::Nanos => NANOS_IN_NANO,
}
}
}
impl TryConvertMut<Option<Value>, SubsecMultiplier> for Artichoke {
type Error = Error;
fn try_convert_mut(&mut self, subsec_type: Option<Value>) -> Result<SubsecMultiplier, Self::Error> {
let mut subsec_type = match subsec_type {
Some(t) => t,
None => return Ok(SubsecMultiplier::Micros),
};
let subsec_type_symbol = if let Ruby::Symbol = subsec_type.ruby_type() {
unsafe { Symbol::unbox_from_value(&mut subsec_type, self)? }.bytes(self)
} else {
let mut message = b"unexpected unit: ".to_vec();
message.extend_from_slice(subsec_type.inspect(self).as_slice());
return Err(ArgumentError::from(message).into());
};
match subsec_type_symbol {
b"milliseconds" => Ok(SubsecMultiplier::Millis),
b"usec" => Ok(SubsecMultiplier::Micros),
b"nsec" => Ok(SubsecMultiplier::Nanos),
_ => {
let mut message = b"unexpected unit: ".to_vec();
message.extend_from_slice(subsec_type_symbol);
Err(ArgumentError::from(message).into())
}
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct Subsec {
secs: i64,
nanos: u32,
}
impl Subsec {
#[must_use]
pub fn to_tuple(self) -> (i64, u32) {
(self.secs, self.nanos)
}
}
impl TryConvertMut<(Option<Value>, Option<Value>), Subsec> for Artichoke {
type Error = Error;
fn try_convert_mut(&mut self, params: (Option<Value>, Option<Value>)) -> Result<Subsec, Self::Error> {
let (subsec, subsec_unit) = params;
let subsec = match subsec {
Some(subsec) => subsec,
None => return Ok(Subsec { secs: 0, nanos: 0 }),
};
let multiplier: SubsecMultiplier = self.try_convert_mut(subsec_unit)?;
let multiplier_nanos = multiplier.as_nanos();
let seconds_base = NANOS_IN_SECOND / multiplier_nanos;
if subsec.ruby_type() == Ruby::Float {
let subsec: f64 = self.try_convert(subsec)?;
if subsec.is_nan() {
return Err(FloatDomainError::with_message("NaN").into());
}
if subsec.is_infinite() {
if subsec.is_sign_negative() {
return Err(FloatDomainError::with_message("-Infinity").into());
}
return Err(FloatDomainError::with_message("Infinity").into());
}
#[allow(clippy::cast_precision_loss)]
let seconds_base = seconds_base as f64;
#[allow(clippy::cast_precision_loss)]
let multiplier_nanos = multiplier_nanos as f64;
let mut secs = subsec / seconds_base;
let mut nanos = (subsec % seconds_base) * multiplier_nanos;
if subsec < -0.0 {
secs -= 1.0;
#[allow(clippy::cast_precision_loss)]
if nanos != 0.0 && nanos != -0.0 {
nanos += NANOS_IN_SECOND as f64;
}
}
if !(MIN_FLOAT_SECONDS..=MAX_FLOAT_SECONDS).contains(&secs)
|| !(MIN_FLOAT_NANOS..=MAX_FLOAT_NANOS).contains(&nanos)
{
return Err(ArgumentError::with_message("subsec outside of bounds").into());
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
Ok(Subsec {
secs: secs as i64,
nanos: nanos as u32,
})
} else {
let subsec: i64 = implicitly_convert_to_int(self, subsec)?;
let mut secs = subsec / seconds_base;
let mut nanos = (subsec % seconds_base) * multiplier_nanos;
if subsec.is_negative() {
secs = secs
.checked_sub(1)
.ok_or(ArgumentError::with_message("Time too small"))?;
if nanos.signum() != 0 {
nanos += NANOS_IN_SECOND;
}
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
Ok(Subsec {
secs,
nanos: nanos as u32,
})
}
}
}
#[cfg(test)]
#[allow(clippy::unnecessary_literal_unwrap)]
mod tests {
use bstr::ByteSlice;
use super::Subsec;
use crate::test::prelude::*;
fn subsec(interp: &mut Artichoke, params: (Option<&[u8]>, Option<&[u8]>)) -> Result<Subsec, Error> {
let (subsec, subsec_type) = params;
let subsec = subsec.map(|s| interp.eval(s).unwrap());
let subsec_type = subsec_type.map(|s| interp.eval(s).unwrap());
interp.try_convert_mut((subsec, subsec_type))
}
#[test]
fn no_subsec_provided() {
let mut interp = interpreter();
let result: Subsec = interp.try_convert_mut((None, None)).unwrap();
let (secs, nanos) = result.to_tuple();
assert_eq!(secs, 0);
assert_eq!(nanos, 0);
}
#[test]
fn no_subsec_provided_but_has_unit() {
let mut interp = interpreter();
let unit = interp.eval(b":usec").unwrap();
let result: Subsec = interp.try_convert_mut((None, Some(unit))).unwrap();
let (secs, nanos) = result.to_tuple();
assert_eq!(secs, 0);
assert_eq!(nanos, 0);
}
#[test]
fn int_no_unit_implies_micros() {
let mut interp = interpreter();
let expectations = [
(b"-1000001".as_slice(), (-2, 999_999_000)),
(b"-1000000".as_slice(), (-2, 0)),
(b"-999999".as_slice(), (-1, 1_000)),
(b"-1".as_slice(), (-1, 999_999_000)),
(b"0".as_slice(), (0, 0)),
(b"1".as_slice(), (0, 1_000)),
(b"999999".as_slice(), (0, 999_999_000)),
(b"1000000".as_slice(), (1, 0)),
(b"1000001".as_slice(), (1, 1_000)),
];
let subsec_unit: Option<&[u8]> = None;
for (input, expectation) in &expectations {
let result = subsec(&mut interp, (Some(input), subsec_unit)).unwrap();
assert_eq!(
result.to_tuple(),
*expectation,
"Expected TryConvertMut<(Some({}), None), Result<Subsec>>, to return {} secs, {} nanos",
input.as_bstr(),
expectation.0,
expectation.1
);
}
}
#[test]
fn int_subsec_millis() {
let mut interp = interpreter();
let expectations = [
(b"-1001".as_slice(), (-2, 999_000_000)),
(b"-1000".as_slice(), (-2, 0)),
(b"-999".as_slice(), (-1, 1_000_000)),
(b"-1".as_slice(), (-1, 999_000_000)),
(b"0".as_slice(), (0, 0)),
(b"1".as_slice(), (0, 1_000_000)),
(b"999".as_slice(), (0, 999_000_000)),
(b"1000".as_slice(), (1, 0)),
(b"1001".as_slice(), (1, 1_000_000)),
];
let subsec_unit: Option<&[u8]> = Some(b":milliseconds");
for (input, expectation) in &expectations {
let result = subsec(&mut interp, (Some(input), subsec_unit)).unwrap();
assert_eq!(
result.to_tuple(),
*expectation,
"Expected TryConvertMut<(Some({}), Some({})), Result<Subsec>>, to return {} secs, {} nanos",
input.as_bstr(),
subsec_unit.unwrap().as_bstr(),
expectation.0,
expectation.1
);
}
}
#[test]
fn int_subsec_micros() {
let mut interp = interpreter();
let expectations = [
(b"-1000001".as_slice(), (-2, 999_999_000)),
(b"-1000000".as_slice(), (-2, 0)),
(b"-999999".as_slice(), (-1, 1_000)),
(b"-1".as_slice(), (-1, 999_999_000)),
(b"0".as_slice(), (0, 0)),
(b"1".as_slice(), (0, 1_000)),
(b"999999".as_slice(), (0, 999_999_000)),
(b"1000000".as_slice(), (1, 0)),
(b"1000001".as_slice(), (1, 1_000)),
];
let subsec_unit: Option<&[u8]> = Some(b":usec");
for (input, expectation) in &expectations {
let result = subsec(&mut interp, (Some(input), subsec_unit)).unwrap();
assert_eq!(
result.to_tuple(),
*expectation,
"Expected TryConvertMut<(Some({}), Some({})), Result<Subsec>>, to return {} secs, {} nanos",
input.as_bstr(),
subsec_unit.unwrap().as_bstr(),
expectation.0,
expectation.1
);
}
}
#[test]
fn int_subsec_nanos() {
let mut interp = interpreter();
let expectations = [
(b"-1000000001".as_slice(), (-2, 999_999_999)),
(b"-1000000000".as_slice(), (-2, 0)),
(b"-999999999".as_slice(), (-1, 1)),
(b"-1".as_slice(), (-1, 999_999_999)),
(b"0".as_slice(), (0, 0)),
(b"1".as_slice(), (0, 1)),
(b"999999999".as_slice(), (0, 999_999_999)),
(b"1000000000".as_slice(), (1, 0)),
(b"1000000001".as_slice(), (1, 1)),
];
let subsec_unit: Option<&[u8]> = Some(b":nsec");
for (input, expectation) in &expectations {
let result = subsec(&mut interp, (Some(input), subsec_unit)).unwrap();
assert_eq!(
result.to_tuple(),
*expectation,
"Expected TryConvertMut<(Some({}), Some({})), Result<Subsec>>, to return {} secs, {} nanos",
input.as_bstr(),
subsec_unit.unwrap().as_bstr(),
expectation.0,
expectation.1
);
}
}
#[test]
fn float_no_unit_implies_micros() {
let mut interp = interpreter();
let expectations = [
(b"-1000000.5".as_slice(), (-2, 999_999_500)),
(b"-1000000.0".as_slice(), (-2, 0)),
(b"-999999.5".as_slice(), (-1, 500)),
(b"-999999.0".as_slice(), (-1, 1_000)),
(b"-1000.5".as_slice(), (-1, 998_999_500)),
(b"-1.5".as_slice(), (-1, 999_998_500)),
(b"-1.0".as_slice(), (-1, 999_999_000)),
(b"-0.0".as_slice(), (0, 0)),
(b"0.0".as_slice(), (0, 0)),
(b"1.0".as_slice(), (0, 1_000)),
(b"1.5".as_slice(), (0, 1_500)),
(b"1000.5".as_slice(), (0, 1_000_500)),
(b"999999.0".as_slice(), (0, 999_999_000)),
(b"999999.5".as_slice(), (0, 999_999_500)),
(b"1000000.0".as_slice(), (1, 0)),
(b"1000000.5".as_slice(), (1, 500)),
(b"1000001.0".as_slice(), (1, 1000)),
(b"0.123".as_slice(), (0, 123)),
(b"0.001".as_slice(), (0, 1)),
(b"0.0001".as_slice(), (0, 0)),
(b"0.0009".as_slice(), (0, 0)),
];
let subsec_unit: Option<&[u8]> = None;
for (input, expectation) in &expectations {
let result = subsec(&mut interp, (Some(input), subsec_unit)).unwrap();
assert_eq!(
result.to_tuple(),
*expectation,
"Expected TryConvertMut<(Some({}), None), Result<Subsec>>, to return {} secs, {} nanos",
input.as_bstr(),
expectation.0,
expectation.1
);
}
}
#[test]
fn float_subsec_millis() {
let mut interp = interpreter();
let expectations = [
(b"-1000.5".as_slice(), (-2, 999_500_000)),
(b"-1000.0".as_slice(), (-2, 0)),
(b"-999.5".as_slice(), (-1, 500_000)),
(b"-999.0".as_slice(), (-1, 1_000_000)),
(b"-1.5".as_slice(), (-1, 998_500_000)),
(b"-1.0".as_slice(), (-1, 999_000_000)),
(b"-0.0".as_slice(), (0, 0)),
(b"0.0".as_slice(), (0, 0)),
(b"1.0".as_slice(), (0, 1_000_000)),
(b"1.5".as_slice(), (0, 1_500_000)),
(b"999.0".as_slice(), (0, 999_000_000)),
(b"999.5".as_slice(), (0, 999_500_000)),
(b"1000.0".as_slice(), (1, 0)),
(b"1000.5".as_slice(), (1, 500_000)),
(b"1001.0".as_slice(), (1, 1_000_000)),
(b"0.123456".as_slice(), (0, 123_456)),
(b"0.000001".as_slice(), (0, 1)),
(b"0.0000001".as_slice(), (0, 0)),
(b"0.0000009".as_slice(), (0, 0)),
];
let subsec_unit: Option<&[u8]> = Some(b":milliseconds");
for (input, expectation) in &expectations {
let result = subsec(&mut interp, (Some(input), subsec_unit)).unwrap();
assert_eq!(
result.to_tuple(),
*expectation,
"Expected TryConvertMut<(Some({}), None), Result<Subsec>>, to return {} secs, {} nanos",
input.as_bstr(),
expectation.0,
expectation.1
);
}
}
#[test]
fn float_subsec_micros() {
let mut interp = interpreter();
let expectations = [
(b"-1000000.5".as_slice(), (-2, 999_999_500)),
(b"-1000000.0".as_slice(), (-2, 0)),
(b"-999999.5".as_slice(), (-1, 500)),
(b"-999999.0".as_slice(), (-1, 1_000)),
(b"-1000.5".as_slice(), (-1, 998_999_500)),
(b"-1.5".as_slice(), (-1, 999_998_500)),
(b"-1.0".as_slice(), (-1, 999_999_000)),
(b"-0.0".as_slice(), (0, 0)),
(b"0.0".as_slice(), (0, 0)),
(b"1.0".as_slice(), (0, 1_000)),
(b"1.5".as_slice(), (0, 1_500)),
(b"1000.5".as_slice(), (0, 1_000_500)),
(b"999999.0".as_slice(), (0, 999_999_000)),
(b"999999.5".as_slice(), (0, 999_999_500)),
(b"1000000.0".as_slice(), (1, 0)),
(b"1000000.5".as_slice(), (1, 500)),
(b"1000001.0".as_slice(), (1, 1000)),
(b"0.123".as_slice(), (0, 123)),
(b"0.001".as_slice(), (0, 1)),
(b"0.0001".as_slice(), (0, 0)),
(b"0.0009".as_slice(), (0, 0)),
];
let subsec_unit: Option<&[u8]> = Some(b":usec");
for (input, expectation) in &expectations {
let result = subsec(&mut interp, (Some(input), subsec_unit)).unwrap();
assert_eq!(
result.to_tuple(),
*expectation,
"Expected TryConvertMut<(Some({}), None), Result<Subsec>>, to return {} secs, {} nanos",
input.as_bstr(),
expectation.0,
expectation.1
);
}
}
#[test]
fn float_subsec_nanos() {
let mut interp = interpreter();
let expectations = [
(b"-1000000000.5".as_slice(), (-2, 999_999_999)),
(b"-1000000000.0".as_slice(), (-2, 0)),
(b"-999999999.5".as_slice(), (-1, 0)),
(b"-999999999.0".as_slice(), (-1, 1)),
(b"-1000.5".as_slice(), (-1, 999_998_999)),
(b"-1.5".as_slice(), (-1, 999_999_998)),
(b"-1.0".as_slice(), (-1, 999_999_999)),
(b"-0.0".as_slice(), (0, 0)),
(b"0.0".as_slice(), (0, 0)),
(b"1.0".as_slice(), (0, 1)),
(b"1.5".as_slice(), (0, 1)),
(b"1000.5".as_slice(), (0, 1_000)),
(b"999999999.0".as_slice(), (0, 999_999_999)),
(b"999999999.5".as_slice(), (0, 999_999_999)),
(b"1000000000.0".as_slice(), (1, 0)),
(b"1000000000.5".as_slice(), (1, 0)),
(b"1000000001.0".as_slice(), (1, 1)),
(b"-0.1".as_slice(), (-1, 999_999_999)),
(b"0.1".as_slice(), (0, 0)),
];
let subsec_unit: Option<&[u8]> = Some(b":nsec");
for (input, expectation) in &expectations {
let result = subsec(&mut interp, (Some(input), subsec_unit)).unwrap();
assert_eq!(
result.to_tuple(),
*expectation,
"Expected TryConvertMut<(Some({}), None), Result<Subsec>>, to return {} secs, {} nanos",
input.as_bstr(),
expectation.0,
expectation.1
);
}
}
#[test]
fn float_nan_raises() {
let mut interp = interpreter();
let err = subsec(&mut interp, (Some(b"Float::NAN"), None)).unwrap_err();
assert_eq!(err.name(), "FloatDomainError");
assert_eq!(err.message(), b"NaN".as_slice());
}
#[test]
fn float_infinite_raises() {
let mut interp = interpreter();
let err = subsec(&mut interp, (Some(b"Float::INFINITY"), None)).unwrap_err();
assert_eq!(err.name(), "FloatDomainError");
assert_eq!(err.message().as_bstr(), b"Infinity".as_bstr());
let err = subsec(&mut interp, (Some(b"-Float::INFINITY"), None)).unwrap_err();
assert_eq!(err.name(), "FloatDomainError");
assert_eq!(err.message().as_bstr(), b"-Infinity".as_bstr());
}
#[test]
fn invalid_subsec_unit() {
let mut interp = interpreter();
let err = subsec(&mut interp, (Some(b"1"), Some(b":bad_unit"))).unwrap_err();
assert_eq!(err.name(), "ArgumentError");
assert_eq!(err.message().as_bstr(), b"unexpected unit: bad_unit".as_bstr());
}
#[test]
fn subsec_unit_non_symbol() {
let mut interp = interpreter();
let err = subsec(&mut interp, (Some(b"1"), Some(b":bad_unit"))).unwrap_err();
assert_eq!(err.name(), "ArgumentError");
assert_eq!(err.message().as_bstr(), b"unexpected unit: bad_unit".as_bstr());
let err = subsec(&mut interp, (Some(b"1"), Some(b"1"))).unwrap_err();
assert_eq!(err.name(), "ArgumentError");
assert_eq!(err.message().as_bstr(), b"unexpected unit: 1".as_bstr());
let err = subsec(&mut interp, (Some(b"1"), Some(b"Object.new"))).unwrap_err();
assert_eq!(err.name(), "ArgumentError");
assert!(err
.message()
.as_bstr()
.starts_with(b"unexpected unit: #<Object:".as_bstr()));
}
#[test]
fn subsec_unit_requires_explicit_symbol() {
let mut interp = interpreter();
let err = subsec(
&mut interp,
(Some(b"1"), Some(b"class A; def to_sym; :usec; end; end && A.new")),
)
.unwrap_err();
assert_eq!(err.name(), "ArgumentError");
assert!(err.message().as_bstr().starts_with(b"unexpected unit: #<A:".as_bstr()));
}
}