use core::str;
use joto_constants::temperature::u32::{
KELVIN, MILLIKELVIN, RANKINE, SMIDGE, THOUSANDTH_RANKINE, ZERO_CELSIUS, ZERO_FAHRENHEIT,
};
#[derive(Copy, Clone, PartialEq, Eq, Debug, PartialOrd, Ord, Hash)]
pub enum Unit {
Smidge,
Millikelvin,
Kelvin,
ThousandthRankine,
Rankine,
Celsius,
Fahrenheit,
}
impl Unit {
pub const fn abbr(self) -> &'static str {
use Unit::*;
match self {
Smidge => "sd",
Millikelvin => "mK",
Kelvin => "K",
ThousandthRankine => "m°R",
Rankine => "°R",
Celsius => "°C",
Fahrenheit => "°F",
}
}
pub const fn ascii_abbr(self) -> &'static [u8] {
use Unit::*;
match self {
Smidge => b"sd",
Millikelvin => b"mK",
Kelvin => b"K",
ThousandthRankine => b"mR",
Rankine => b"R",
Celsius => b"C",
Fahrenheit => b"F",
}
}
#[inline]
pub const fn is_si(self) -> bool {
use Unit::*;
matches!(self, Smidge | Millikelvin | Kelvin | Celsius)
}
#[inline]
pub const fn max_decimal_digits(self) -> u8 {
use Unit::*;
match self {
Smidge => 0,
Millikelvin => 1,
Kelvin => 4,
ThousandthRankine => 1,
Rankine => 4,
Celsius => 4,
Fahrenheit => 4,
}
}
#[inline]
pub const fn scale(self) -> u32 {
use Unit::*;
match self {
Smidge => SMIDGE,
Millikelvin => MILLIKELVIN,
Kelvin | Celsius => KELVIN,
ThousandthRankine => THOUSANDTH_RANKINE,
Rankine | Fahrenheit => RANKINE,
}
}
#[inline]
pub const fn origin_offset(self) -> u32 {
use Unit::*;
match self {
Celsius => ZERO_CELSIUS,
Fahrenheit => ZERO_FAHRENHEIT,
_ => 0,
}
}
#[inline]
pub const fn least_significant_digit_value(self) -> u32 {
use Unit::*;
match self {
Smidge => SMIDGE,
Millikelvin | Kelvin | Celsius => 9,
ThousandthRankine | Rankine | Fahrenheit => 5,
}
}
#[inline]
const fn take_decimal_frac(self, rest: &str) -> Result<(&str, u32, bool), ParseError> {
let unit = self;
let at = rest.len();
if let [r @ .., b'.'] = rest.as_bytes() {
return if let [.., b'0'..=b'9'] = r {
Ok((unsafe { str::from_utf8_unchecked(r) }, 0u32, false))
} else {
Err(ParseError::EmptyQuantity { unit, at })
};
}
let (d_rest, digits) = strip_digits(rest);
if d_rest.is_empty() {
return Ok((rest, 0, false));
}
let had_frac_digits = !digits.is_empty();
let nonzero_digits = trim_trailing_zeroes(digits);
let len = nonzero_digits.len();
let [r @ .., b'.'] = d_rest.as_bytes() else {
return Ok((rest, 0, false));
};
let rest = unsafe { str::from_utf8_unchecked(r) };
if len == 0 {
return if !rest.is_empty() || !digits.is_empty() {
Ok((rest, 0, had_frac_digits))
} else {
Err(ParseError::EmptyQuantity { unit, at })
};
}
let scale = unit.max_decimal_digits() as usize;
if len > scale {
return Err(ParseError::TooPrecise { unit, at });
}
let b = nonzero_digits.as_bytes();
let len = b.len();
let mut pv = unit.least_significant_digit_value();
let mut acc = 0u32;
let mut i = scale;
while i > len {
i -= 1;
pv = unsafe { pv.unchecked_mul(10) };
}
while i > 0 {
i -= 1;
unsafe {
let d = b[i] & 0xF;
let dv = (d as u32).unchecked_mul(pv);
acc = acc.unchecked_add(dv);
pv = pv.unchecked_mul(10);
}
}
Ok((rest, acc, true))
}
}
pub const fn strip_unit(s: &str) -> Option<(&str, Unit)> {
let b = s.as_bytes();
const fn bs(r: &[u8]) -> &str {
unsafe { str::from_utf8_unchecked(r) }
}
Some(match b {
[r @ .., b's', b'd'] => (bs(r), Unit::Smidge),
[r @ .., 0xC2, 0xB0, b'C'] | [r @ .., b'C'] => (bs(r), Unit::Celsius),
[r @ .., 0xC2, 0xB0, b'F'] | [r @ .., b'F'] => (bs(r), Unit::Fahrenheit),
[r @ .., b'm', b'K'] => (bs(r), Unit::Millikelvin),
[r @ .., b'K'] => (bs(r), Unit::Kelvin),
[r @ .., b'm', 0xC2, 0xB0, b'R'] | [r @ .., b'm', b'R'] => (bs(r), Unit::ThousandthRankine),
[r @ .., 0xC2, 0xB0, b'R'] | [r @ .., b'R'] => (bs(r), Unit::Rankine),
_ => {
return None;
}
})
}
#[inline]
const fn trim_end(s: &str) -> &str {
let mut rest = s.as_bytes();
while let [r @ .., b' ']
| [r @ .., 0xE2, 0x80, 0xAF | 0x80..=0x8B]
| [r @ .., 0xEF, 0xBB, 0xBF]
| [r @ .., 0xC2, 0xA0] = rest
{
rest = r;
}
unsafe { str::from_utf8_unchecked(rest) }
}
#[inline]
const fn trim_trailing_zeroes(s: &str) -> &str {
let mut rest = s.as_bytes();
while let [r @ .., b'0'] = rest {
rest = r;
}
unsafe { str::from_utf8_unchecked(rest) }
}
#[inline]
const fn strip_digits(s: &str) -> (&str, &str) {
let mut rest = s.as_bytes();
while let [r @ .., b'0'..=b'9'] = rest {
rest = r;
}
let (_, stripped) = unsafe { s.as_bytes().split_at_unchecked(rest.len()) };
unsafe {
(
str::from_utf8_unchecked(rest),
str::from_utf8_unchecked(stripped),
)
}
}
#[inline]
const fn strip_sign(s: &str) -> (&str, i8, bool) {
let b = s.as_bytes();
const fn bs(r: &[u8]) -> &str {
unsafe { str::from_utf8_unchecked(r) }
}
match b {
[r @ .., b'+'] => (bs(r), 1, true),
[r @ .., b'-'] => (bs(r), -1, true),
[r @ .., 0xE2, 0x88, 0x92] => (bs(r), -1, true),
_ => (s, 1, false),
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ParseError {
Empty,
NoUnit {
at: usize,
},
EmptyQuantity {
unit: Unit,
at: usize,
},
TooBig {
unit: Unit,
at: usize,
},
TooSmall {
unit: Unit,
at: usize,
},
TooPrecise {
unit: Unit,
at: usize,
},
InvalidSign {
unit: Unit,
at: usize,
},
}
macro_rules! typed_mod {
( $T:ident ) => {
pub mod $T {
use super::*;
type Target = $T;
#[inline]
const fn parse_relative_restart_i32(
unit: Unit,
s: &str,
frac_u32: u32,
had_frac: bool,
) -> Option<Result<Target, ParseError>> {
if Target::BITS <= 32 {
return None;
}
if unit.origin_offset() == 0 {
return None;
}
let b = s.as_bytes();
let has_whole = matches!(b, [.., b'0'..=b'9']);
if !has_whole && !had_frac {
return Some(Err(ParseError::EmptyQuantity { unit, at: s.len() }));
}
let origin_u32 = unit.origin_offset();
let origin_i32 = origin_u32 as i32;
let frac_i32 = frac_u32 as i32;
let (rest, mag_opt) = parse_whole_magnitude_i32(unit, frac_i32, s);
let rest = trim_end(rest);
let (rest, sign, has_sign) = strip_sign(rest);
let at = rest.len();
if has_sign && sign < 0 {
let Some(mag_i32) = mag_opt else {
return Some(Err(ParseError::TooSmall { unit, at }));
};
let abs_i32 = origin_i32 - mag_i32;
if abs_i32 >= 0 {
Some(Ok(abs_i32 as Target))
} else {
Some(Err(ParseError::TooSmall { unit, at }))
}
} else {
let Some(mag_i32) = mag_opt else {
return None;
};
let abs_u32 = origin_u32 + mag_i32 as u32;
Some(Ok(abs_u32 as Target))
}
}
#[inline]
const fn parse_whole_magnitude_i32(
unit: Unit,
acc: i32,
s: &str,
) -> (&str, Option<i32>) {
let mut pv = unit.scale();
let mut acc = acc;
let mut acc_ok = true;
let mut only_zeroes = false;
let mut rest = s.as_bytes();
'parse: loop {
while let [r @ .., c @ b'0'..=b'9'] = rest {
rest = r;
if acc_ok {
let d = ((*c) & 0xF) as i32;
if !only_zeroes {
if pv <= (i32::MAX as u32) / 9 {
let dv = unsafe { d.unchecked_mul(pv as i32) };
if let Some(na) = acc.checked_add(dv) {
acc = na;
} else {
acc_ok = false;
}
} else if d != 0 {
acc_ok = false;
} else {
only_zeroes = true;
}
if !only_zeroes {
if let Some(npv) = pv.checked_mul(10) {
pv = npv;
} else {
only_zeroes = true;
}
}
} else if d != 0 {
acc_ok = false;
}
}
}
if let [r @ .., b','] | [r @ .., 0xE2, 0x80, 0x88] = rest {
rest = r;
} else {
break 'parse;
}
}
let rest = unsafe { str::from_utf8_unchecked(rest) };
if acc_ok {
(rest, Some(acc))
} else {
(rest, None)
}
}
#[inline(always)]
const fn max_whole_place(unit: Unit) -> u8 {
use Unit::*;
match unit {
Smidge => (Target::MAX / SMIDGE as Target).ilog10() as u8,
Millikelvin => (Target::MAX / MILLIKELVIN as Target).ilog10() as u8,
Kelvin | Celsius => (Target::MAX / KELVIN as Target).ilog10() as u8,
ThousandthRankine => {
(Target::MAX / THOUSANDTH_RANKINE as Target).ilog10() as u8
}
Rankine | Fahrenheit => (Target::MAX / RANKINE as Target).ilog10() as u8,
}
}
const fn accum_max_digit(
unit: Unit,
acc: Target,
pv: Target,
rest: &str,
) -> Result<(&str, Target), ParseError> {
let mut acc = acc;
let mut rest = rest.as_bytes();
if let [r @ .., c @ b'0'..=b'9'] = rest {
let at = rest.len();
rest = r;
let v = unsafe { (*c as u32).unchecked_sub(b'0' as u32) };
let Some(mv) = pv.checked_mul(v as Target) else {
return Err(ParseError::TooBig { unit, at });
};
let Some(na) = acc.checked_add(mv) else {
return Err(ParseError::TooBig { unit, at });
};
acc = na;
}
loop {
while let [r @ .., b'0'] = rest {
rest = r;
}
if let [r @ .., b','] | [r @ .., 0xE2, 0x80, 0x88] = rest {
rest = r;
} else {
break;
}
}
if let [.., b'1'..=b'9'] = rest {
return Err(ParseError::TooBig {
unit,
at: rest.len(),
});
}
Ok((unsafe { str::from_utf8_unchecked(rest) }, acc))
}
#[inline]
const fn parse_whole_diagnostic(
unit: Unit,
acc: Target,
s: &str,
) -> Result<(&str, Target), ParseError> {
let mut pv = unit.scale() as Target;
let mut acc = acc;
let max_place = max_whole_place(unit);
let mut p = 0;
let mut rest = s.as_bytes();
let [.., b'0'..=b'9'] = rest else {
return Err(ParseError::EmptyQuantity {
unit,
at: rest.len(),
});
};
'parse: loop {
while let [r @ .., c @ b'0'..=b'9'] = rest {
if p < max_place {
let at = rest.len();
rest = r;
let v = unsafe { (*c as u32).unchecked_sub(b'0' as u32) };
let mv = unsafe { pv.unchecked_mul(v as Target) };
let Some(na) = acc.checked_add(mv) else {
return Err(ParseError::TooBig { unit, at });
};
acc = na;
pv = unsafe { pv.unchecked_mul(10) };
p += 1;
} else {
break 'parse;
}
}
if let [r @ .., b','] | [r @ .., 0xE2, 0x80, 0x88] = rest {
rest = r;
} else {
break;
}
}
let rest = unsafe { str::from_utf8_unchecked(rest) };
if p != max_place {
Ok((rest, acc))
} else {
accum_max_digit(unit, acc, pv, rest)
}
}
#[inline]
const fn finalize(unit: Unit, acc: Target, rest: &str) -> Result<Target, ParseError> {
let rest = trim_end(rest);
let (rest, sign, has_sign) = strip_sign(rest);
if has_sign && unit.origin_offset() == 0 {
return Err(ParseError::InvalidSign {
unit,
at: rest.len(),
});
}
let origin = unit.origin_offset() as Target;
if has_sign && sign < 0 {
if acc > origin {
Err(ParseError::TooSmall {
unit,
at: rest.len(),
})
} else {
Ok(origin - acc)
}
} else {
match origin.checked_add(acc) {
Some(v) => Ok(v),
None => Err(ParseError::TooBig {
unit,
at: rest.len(),
}),
}
}
}
#[inline]
pub const fn parse_dim_diagnostic(s: &str) -> Result<Target, ParseError> {
let rest = trim_end(s);
if rest.is_empty() {
return Err(ParseError::Empty);
}
let at = rest.len();
if let Some((rest, unit)) = strip_unit(rest) {
let at = rest.len();
let rest = trim_end(rest);
if !rest.is_empty() {
let (rest, frac_u32, had_frac) = match unit.take_decimal_frac(rest) {
Ok((rest, v, had_frac)) => (rest, v, had_frac),
Err(e) => {
return Err(e);
}
};
if let Some(r) = parse_relative_restart_i32(unit, rest, frac_u32, had_frac)
{
return r;
}
let frac = frac_u32 as Target;
if rest.is_empty() {
return finalize(unit, frac, rest);
}
match parse_whole_diagnostic(unit, frac, rest) {
Ok((rest, acc)) => finalize(unit, acc, rest),
Err(ParseError::EmptyQuantity { .. }) if had_frac => {
finalize(unit, frac, rest)
}
Err(e) => Err(e),
}
} else {
Err(ParseError::EmptyQuantity { unit, at })
}
} else {
Err(ParseError::NoUnit { at })
}
}
#[inline]
pub const fn parse_dim(s: &str) -> Option<Target> {
match parse_dim_diagnostic(s) {
Ok(v) => Some(v),
_ => None,
}
}
#[inline]
pub const fn parse_as_diagnostic(s: &str, unit: Unit) -> Result<Target, ParseError> {
let rest = trim_end(s);
if rest.is_empty() {
return Err(ParseError::EmptyQuantity { unit, at: 0 });
}
let (rest, frac_u32, had_frac) = match unit.take_decimal_frac(rest) {
Ok((rest, v, had_frac)) => (rest, v, had_frac),
Err(e) => {
return Err(e);
}
};
if let Some(r) = parse_relative_restart_i32(unit, rest, frac_u32, had_frac) {
return r;
}
let frac = frac_u32 as Target;
if rest.is_empty() {
return finalize(unit, frac, rest);
}
match parse_whole_diagnostic(unit, frac, rest) {
Ok((rest, acc)) => finalize(unit, acc, rest),
Err(ParseError::EmptyQuantity { .. }) if had_frac => finalize(unit, frac, rest),
Err(e) => Err(e),
}
}
#[inline]
pub const fn parse_as(s: &str, unit: Unit) -> Option<Target> {
match parse_as_diagnostic(s, unit) {
Ok(v) => Some(v),
_ => None,
}
}
}
};
}
typed_mod!(u32);
typed_mod!(i32);
typed_mod!(u64);
typed_mod!(i64);
typed_mod!(u128);
typed_mod!(i128);
pub mod f64 {
use super::i64::parse_dim as parse_dim_i64;
use super::i64::parse_dim_diagnostic as parse_dim_diagnostic_i64;
use super::{strip_unit, trim_end, ParseError};
const MAX_SAFE: i64 = (1i64) << f64::MANTISSA_DIGITS;
#[inline]
pub const fn parse_dim_diagnostic(s: &str) -> Result<f64, ParseError> {
match parse_dim_diagnostic_i64(s) {
Ok(si) => {
if si <= MAX_SAFE && si >= -MAX_SAFE {
Ok(si as f64)
} else {
let Some((r, unit)) = strip_unit(trim_end(s)) else {
unreachable!();
};
Err(ParseError::TooBig { at: r.len(), unit })
}
}
Err(e) => Err(e),
}
}
pub const fn parse_dim(s: &str) -> Option<f64> {
match parse_dim_i64(s) {
Some(si) if (si <= MAX_SAFE && si >= -MAX_SAFE) => Some(si as f64),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
macro_rules! test_submod {
( $T:ident ) => {
#[cfg(test)]
mod $T {
use super::super::$T::*;
use super::super::{ParseError, Unit};
type Target = $T;
use joto_constants::temperature::$T::{
KELVIN, MILLIKELVIN, RANKINE, SMIDGE, THOUSANDTH_RANKINE, ZERO_CELSIUS,
ZERO_FAHRENHEIT,
};
const fn p(s: &str) -> Target {
parse_dim(s).unwrap()
}
#[test]
fn invertibility_sanity_check() {
const V1: Target = p("100\u{00B0}C") - p("373.15K");
assert_eq!(0, V1);
const V2: Target = p("32\u{00B0}F") - p("0\u{00B0}C");
assert_eq!(0, V2);
}
#[test]
fn basic_parse_dim() {
assert_eq!(p("0K"), 0);
assert_eq!(p("0\u{00B0}C"), ZERO_CELSIUS);
assert_eq!(p("0\u{00B0}F"), ZERO_FAHRENHEIT);
assert_eq!(p("459.67\u{00B0}R"), ZERO_FAHRENHEIT);
}
#[test]
fn decimals_parse_dim() {
assert_eq!(const { p(".0K") }, 0);
assert_eq!(const { p(".0001K") }, 9 * SMIDGE);
assert_eq!(parse_dim(".00001K"), None);
assert_eq!(const { p(".0mK") }, 0);
assert_eq!(const { p(".1mK") }, 9 * SMIDGE);
assert_eq!(parse_dim(".01mK"), None);
assert_eq!(const { p(".0\u{00B0}R") }, 0);
assert_eq!(const { p(".0001\u{00B0}R") }, 5 * SMIDGE);
assert_eq!(parse_dim(".00001\u{00B0}R"), None);
assert_eq!(const { p(".0\u{00B0}C") }, ZERO_CELSIUS);
assert_eq!(const { p(".0001\u{00B0}C") }, ZERO_CELSIUS + 9 * SMIDGE);
assert_eq!(parse_dim(".00001\u{00B0}C"), None);
assert_eq!(const { p(".0\u{00B0}F") }, ZERO_FAHRENHEIT);
assert_eq!(const { p(".0001\u{00B0}F") }, ZERO_FAHRENHEIT + 5 * SMIDGE);
assert_eq!(parse_dim(".00001\u{00B0}F"), None);
assert_eq!(const { p(".0m\u{00B0}R") }, 0);
assert_eq!(const { p(".1m\u{00B0}R") }, 5 * SMIDGE);
assert_eq!(parse_dim(".01m\u{00B0}R"), None);
}
#[test]
fn whole_with_separators() {
assert_eq!(p("1,000K"), 1_000 * KELVIN);
assert_eq!(p("1\u{2008}000mK"), 1_000 * MILLIKELVIN);
assert_eq!(p("1\u{2008}000\u{00B0}R"), 1_000 * RANKINE);
}
#[test]
fn sign_parse_dim() {
assert_eq!(p("-10\u{00B0}C"), ZERO_CELSIUS - 10 * KELVIN);
assert_eq!(p("\u{2212}10\u{00B0}F"), ZERO_FAHRENHEIT - 10 * RANKINE);
assert_eq!(p("+10\u{00B0}C"), ZERO_CELSIUS + 10 * KELVIN);
assert_eq!(parse_dim("+10K"), None);
}
#[test]
fn parse_diagnostics() {
extern crate alloc;
use alloc::format;
const P1: Target = p("9.0000\u{202F}mK");
assert_eq!(P1, 9 * MILLIKELVIN);
const P2: Result<Target, ParseError> = parse_dim_diagnostic("9.01mK");
assert_eq!(
P2,
Err(ParseError::TooPrecise {
unit: Unit::Millikelvin,
at: 4
})
);
const P3: Result<Target, ParseError> = parse_dim_diagnostic("K");
assert_eq!(
P3,
Err(ParseError::EmptyQuantity {
unit: Unit::Kelvin,
at: 0
})
);
const P4: Result<Target, ParseError> = parse_dim_diagnostic("39");
assert_eq!(P4, Err(ParseError::NoUnit { at: 2 }));
const P5: Result<Target, ParseError> = parse_dim_diagnostic("");
assert_eq!(P5, Err(ParseError::Empty));
const P6: Result<Target, ParseError> = parse_dim_diagnostic(" ");
assert_eq!(P6, Err(ParseError::Empty));
const P7: Result<Target, ParseError> = parse_dim_diagnostic("1foo");
assert_eq!(P7, Err(ParseError::NoUnit { at: 4 }));
const P8: Result<Target, ParseError> = parse_dim_diagnostic(" mK ");
assert_eq!(
P8,
Err(ParseError::EmptyQuantity {
unit: Unit::Millikelvin,
at: 1
})
);
const P9: Result<Target, ParseError> = parse_dim_diagnostic("1.00001K");
assert_eq!(
P9,
Err(ParseError::TooPrecise {
unit: Unit::Kelvin,
at: 7
})
);
let digits = (Target::MAX.ilog10() as usize) + 1;
let p10_str = format!("1{}sd", "0".repeat(digits));
let p10: Result<Target, ParseError> = parse_dim_diagnostic(&p10_str);
assert_eq!(
p10,
Err(ParseError::TooBig {
unit: Unit::Smidge,
at: 1
})
);
let p11_str = format!("1{}sd", "9".repeat(digits));
let p11: Result<Target, ParseError> = parse_dim_diagnostic(&p11_str);
assert_eq!(
p11,
Err(ParseError::TooBig {
unit: Unit::Smidge,
at: 2
})
);
let p12_str = format!("9{}sd", "0".repeat(digits));
let p12: Result<Target, ParseError> = parse_dim_diagnostic(&p12_str);
assert_eq!(
p12,
Err(ParseError::TooBig {
unit: Unit::Smidge,
at: 1
})
);
let p13: Target = p(format!("{}sd", Target::MAX).as_ref());
assert_eq!(p13, Target::MAX);
const P14: Result<Target, ParseError> = parse_dim_diagnostic("-1K");
assert_eq!(
P14,
Err(ParseError::InvalidSign {
unit: Unit::Kelvin,
at: 0
})
);
const P15: Target = p("1mR");
assert_eq!(P15, THOUSANDTH_RANKINE);
const P16: Result<Target, ParseError> = parse_dim_diagnostic("-274\u{00B0}C");
assert_eq!(
P16,
Err(ParseError::TooSmall {
unit: Unit::Celsius,
at: 0
})
);
}
#[test]
fn parse_as_sanity() {
use super::super::$T::parse_as;
use super::super::Unit::*;
use joto_constants::temperature::$T as t;
assert_eq!(parse_as("(unrelated) 1 ", Smidge), Some(t::SMIDGE));
assert_eq!(
parse_as("(unrelated) 1 ", Millikelvin),
Some(t::MILLIKELVIN)
);
assert_eq!(parse_as("(unrelated) 1 ", Kelvin), Some(t::KELVIN));
assert_eq!(
parse_as("(unrelated) 1 ", ThousandthRankine),
Some(t::THOUSANDTH_RANKINE)
);
assert_eq!(parse_as("(unrelated) 1 ", Rankine), Some(t::RANKINE));
assert_eq!(parse_as("(unrelated) 0 ", Celsius), Some(t::ZERO_CELSIUS));
assert_eq!(
parse_as("(unrelated) 0 ", Fahrenheit),
Some(t::ZERO_FAHRENHEIT)
);
assert_eq!(parse_as(".0001", Kelvin), Some(9 * t::SMIDGE));
assert_eq!(parse_as(" ", Kelvin), None);
assert_eq!(parse_as(".01", Millikelvin), None);
assert_eq!(parse_as("foo37", Kelvin), Some(37 * t::KELVIN));
}
#[test]
fn parse_as_diagnostic_sanity() {
use super::super::$T::parse_as_diagnostic;
use super::super::Unit;
assert_eq!(
parse_as_diagnostic(" ", Unit::Kelvin),
Err(ParseError::EmptyQuantity {
unit: Unit::Kelvin,
at: 0
})
);
}
}
};
}
test_submod!(u32);
test_submod!(i32);
test_submod!(u64);
test_submod!(i64);
test_submod!(u128);
test_submod!(i128);
mod f64 {
use super::super::f64::parse_dim;
use joto_constants::temperature::f64::{KELVIN, ZERO_CELSIUS};
#[test]
fn parse_sanity() {
assert_eq!(
parse_dim("-10\u{00B0}C").unwrap(),
ZERO_CELSIUS - 10. * KELVIN
);
}
}
#[test]
fn basic_trim_end() {
assert_eq!(super::trim_end("foo \u{202F} "), "foo");
}
}