use core::str;
use joto_constants::mass::u64::{
DRAM, GRAIN, GRAM, KILOGRAM, LONG_HUNDREDWEIGHT, LONG_TON, MEGAGRAM, MICROGRAM, MILLIGRAM,
OUNCE, PENNYWEIGHT, POUND, SHORT_HUNDREDWEIGHT, SHORT_TON, STONE, TROY_OUNCE, WHIT,
};
#[derive(Copy, Clone, PartialEq, Eq, Debug, PartialOrd, Ord, Hash)]
#[repr(u64)]
pub enum Unit {
Whit = WHIT,
Microgram = MICROGRAM,
Milligram = MILLIGRAM,
Gram = GRAM,
Kilogram = KILOGRAM,
Megagram = MEGAGRAM,
Dram = DRAM,
Ounce = OUNCE,
Pound = POUND,
Stone = STONE,
LongHundredweight = LONG_HUNDREDWEIGHT,
LongTon = LONG_TON,
ShortHundredweight = SHORT_HUNDREDWEIGHT,
ShortTon = SHORT_TON,
Grain = GRAIN,
Pennyweight = PENNYWEIGHT,
TroyOunce = TROY_OUNCE,
}
#[inline(always)]
const fn unit_phf(u: Unit) -> u8 {
(((u as u64).wrapping_mul(3255389357u64) >> 32) & 31) as u8
}
macro_rules! phf_match_unit_const_map {
(
$(#[$outer:meta])*
$VIS:vis const fn $NAME:ident : $FN:ident -> $TARGET:ty
) => {
$(#[$outer])*
$VIS const fn $NAME(unit: Unit) -> $TARGET {
use Unit::*;
match unit_phf(unit) {
0 => const { $FN(Whit) },
25 => const { $FN(Microgram) },
14 => const { $FN(Milligram) },
29 => const { $FN(Gram) },
13 => const { $FN(Kilogram) },
21 => const { $FN(Megagram) },
2 => const { $FN(Dram) },
5 => const { $FN(Ounce) },
19 => const { $FN(Pound) },
10 => const { $FN(Stone) },
22 => const { $FN(LongHundredweight) },
8 => const { $FN(LongTon) },
18 => const { $FN(ShortHundredweight) },
9 => const { $FN(ShortTon) },
28 => const { $FN(Grain) },
12 => const { $FN(Pennyweight) },
16 => const { $FN(TroyOunce) },
_ => unsafe { ::core::hint::unreachable_unchecked() },
}
}
};
}
const fn unit_lsd_inner(unit: Unit) -> u64 {
let max_dgt = unit.max_decimal_digits() as u32;
(unit as u64) / 10u64.pow(max_dgt)
}
phf_match_unit_const_map! {
#[inline(always)]
const fn unit_lsd : unit_lsd_inner -> u64
}
const fn max_decimal_digits_inner(u: Unit) -> u8 {
use Unit::*;
match u {
Whit => 0,
Microgram => 2,
Milligram => 5,
Gram => 8,
Kilogram => 11,
Megagram => 14,
Dram => 0,
Ounce => 3,
Pound => 3,
Stone => 3,
LongHundredweight => 3,
LongTon => 4,
ShortHundredweight => 5,
ShortTon => 6,
Grain => 0,
Pennyweight => 0,
TroyOunce => 1,
}
}
phf_match_unit_const_map! {
#[inline(always)]
const fn max_decimal_digits : max_decimal_digits_inner -> u8
}
impl Unit {
pub const fn abbr(self) -> &'static str {
use Unit::*;
match self {
Whit => "wt",
Microgram => "µg",
Milligram => "mg",
Gram => "g",
Kilogram => "kg",
Megagram => "t",
Dram => "dr",
Ounce => "oz",
Pound => "lb",
Stone => "st",
LongHundredweight => "cwt.l",
LongTon => "tn.l",
ShortHundredweight => "cwt",
ShortTon => "tn",
Grain => "gr",
Pennyweight => "dwt",
TroyOunce => "ozt",
}
}
pub const fn ascii_abbr(self) -> &'static [u8] {
use Unit::*;
match self {
Whit => b"wt",
Microgram => b"ug",
Milligram => b"mg",
Gram => b"g",
Kilogram => b"kg",
Megagram => b"t",
Dram => b"dr",
Ounce => b"oz",
Pound => b"lb",
Stone => b"st",
LongHundredweight => b"cwt_l",
LongTon => b"tn_l",
ShortHundredweight => b"cwt",
ShortTon => b"tn",
Grain => b"gr",
Pennyweight => b"dwt",
TroyOunce => b"ozt",
}
}
#[inline]
pub const fn is_si(self) -> bool {
use Unit::*;
matches!(self, Microgram | Milligram | Gram | Kilogram | Megagram)
}
#[inline]
pub const fn superior(self) -> Option<Unit> {
if matches!(self, Unit::Ounce) {
Some(Unit::Pound)
} else {
None
}
}
#[inline]
pub const fn inferior(self) -> Option<Unit> {
if matches!(self, Unit::Pound) {
Some(Unit::Ounce)
} else {
None
}
}
#[inline]
pub const fn max_decimal_digits(self) -> u8 {
max_decimal_digits(self)
}
#[inline]
pub const fn least_significant_digit_value(self) -> u64 {
unit_lsd(self)
}
#[inline]
const fn take_decimal_frac(self, rest: &str) -> Result<(&str, i64), 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) }, 0))
} else {
Err(ParseError::EmptyQuantity { unit, at })
};
}
let (d_rest, digits) = strip_digits(rest);
if d_rest.is_empty() {
return Ok((rest, 0));
}
let nonzero_digits = trim_trailing_zeroes(digits);
let len = nonzero_digits.len();
let [r @ .., b'.'] = d_rest.as_bytes() else {
return Ok((rest, 0));
};
let rest = unsafe { str::from_utf8_unchecked(r) };
if len == 0 {
return if !rest.is_empty() || !digits.is_empty() {
Ok((rest, 0))
} 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_lsd(unit) as i64;
let mut acc = 0i64;
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 i64).unchecked_mul(pv);
acc = acc.unchecked_add(dv);
pv = pv.unchecked_mul(10);
}
}
Ok((rest, acc))
}
}
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'o', b'z', b't'] => (bs(r), Unit::TroyOunce),
[r @ .., b'd', b'w', b't'] => (bs(r), Unit::Pennyweight),
[r @ .., b'c', b'w', b't'] => (bs(r), Unit::ShortHundredweight),
[r @ .., b'c', b'w', b't', b'.' | b'_', b'l'] => {
(bs(r), Unit::LongHundredweight)
}
[r @ .., b't', b'n', b'.' | b'_', b'l'] => {
(bs(r), Unit::LongTon)
}
[r @ .., b'l', b'b'] => (bs(r), Unit::Pound),
[r @ .., b's', b't'] => (bs(r), Unit::Stone),
[r @ .., b't', b'n'] => (bs(r), Unit::ShortTon),
[r @ .., b'd', b'r'] => (bs(r), Unit::Dram),
[r @ .., b'o', b'z'] => (bs(r), Unit::Ounce),
[r @ .., b'g', b'r'] => (bs(r), Unit::Grain),
[r @ .., b'w', b't'] => (bs(r), Unit::Whit),
[r @ .., b'k', b'g'] => (bs(r), Unit::Kilogram),
[r @ .., b'm', b'g'] => (bs(r), Unit::Milligram),
[r @ .., b'm', b'c', b'g'] |
[r @ .., b'u', b'g'] |
[r @ .., 0xC2, 0xB5, b'g'] | [r @ .., 0xCE, 0xBC, b'g'] => (bs(r), Unit::Microgram),
[r @ .., b'g'] => (bs(r), Unit::Gram),
[r @ .., b't'] => (bs(r), Unit::Megagram),
_ => {
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),
)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ParseError {
Empty,
NoUnit {
at: usize,
},
EmptyQuantity {
unit: Unit,
at: usize,
},
TooBig {
unit: Unit,
at: usize,
},
TooPrecise {
unit: Unit,
at: usize,
},
InvalidCompound {
inferior: Unit,
found: Unit,
at: usize,
},
}
macro_rules! typed_mod {
( $T:ident ) => {
pub mod $T {
use super::*;
type Target = $T;
const fn max_whole_place_inner(unit: Unit) -> u8 {
(Target::MAX / unit as Target).ilog10() as u8
}
phf_match_unit_const_map! {
#[inline(always)]
const fn max_whole_place : max_whole_place_inner -> 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 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]
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, acc) = match unit.take_decimal_frac(rest) {
Ok((rest, v)) if !rest.is_empty() => (rest, v as Target),
Ok((_, v)) => {
return Ok(v as Target);
}
Err(e) => {
return Err(e);
}
};
let (rest, acc) = match parse_whole_diagnostic(unit, acc, rest) {
Ok((rest, acc)) => (rest, acc),
Err(ParseError::EmptyQuantity { .. }) if acc != 0 => {
return Ok(acc);
}
Err(e) => {
return Err(e);
}
};
if let Some(sup) = unit.superior() {
let rest = trim_end(rest);
let at = rest.len();
if let Some((rest, next_unit)) = strip_unit(rest) {
if next_unit as u64 == sup as u64 {
return match parse_whole_diagnostic(sup, acc, trim_end(rest)) {
Ok((_, acc)) => Ok(acc),
Err(e) => Err(e),
};
} else {
return Err(ParseError::InvalidCompound {
inferior: unit,
found: next_unit,
at,
});
}
}
}
Ok(acc)
} 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, acc) = match unit.take_decimal_frac(rest) {
Ok((rest, v)) if !rest.is_empty() => (rest, v as Target),
Ok((_, v)) => {
return Ok(v as Target);
}
Err(e) => {
return Err(e);
}
};
match parse_whole_diagnostic(unit, acc, rest) {
Ok((_, acc)) => Ok(acc),
Err(ParseError::EmptyQuantity { .. }) if acc != 0 => Ok(acc),
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!(u64);
typed_mod!(i64);
typed_mod!(u128);
typed_mod!(i128);
pub mod f64 {
use super::u64::parse_dim as parse_dim_u64;
use super::u64::parse_dim_diagnostic as parse_dim_diagnostic_u64;
use super::{strip_unit, trim_end, ParseError};
const MAX_SAFE: u64 = 1 << f64::MANTISSA_DIGITS;
#[inline]
pub const fn parse_dim_diagnostic(s: &str) -> Result<f64, ParseError> {
match parse_dim_diagnostic_u64(s) {
Ok(ui) => {
if ui <= MAX_SAFE {
Ok(ui 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_u64(s) {
Some(ui) if (ui <= MAX_SAFE) => Some(ui 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::mass::$T::{
GRAM, KILOGRAM, MICROGRAM, MILLIGRAM, OUNCE, POUND, TROY_OUNCE, WHIT,
};
const fn p(s: &str) -> Target {
parse_dim(s).unwrap()
}
#[test]
fn invertibility_sanity_check() {
const V: Target = p("2kg") + p("3oz") - p("2000g") - p("3.000oz");
assert_eq!(0, V)
}
#[test]
fn basic_parse_dim() {
assert_eq!(p("37g"), 37 * GRAM);
assert_eq!(p("11oz"), 11 * OUNCE);
assert_eq!(p("9wt"), 9 * WHIT);
}
#[test]
fn compound_parse_dim() {
assert_eq!(p("5lb3oz"), 5 * POUND + 3 * OUNCE);
assert_eq!(p("5lb 3oz"), 5 * POUND + 3 * OUNCE);
assert_eq!(p(" 5lb\u{202F}3oz "), 5 * POUND + 3 * OUNCE);
}
#[test]
fn si_whole_parse_dim() {
assert_eq!(p("1ug"), MICROGRAM);
assert_eq!(p("1µg"), MICROGRAM);
assert_eq!(p("1μg"), MICROGRAM);
assert_eq!(p("1mg"), MILLIGRAM);
assert_eq!(p("1g"), GRAM);
assert_eq!(p("1kg"), KILOGRAM);
assert_eq!(p("1t"), 1_000 * KILOGRAM);
assert_eq!(p(" 1 ug "), MICROGRAM);
assert_eq!(p(" 1 µg "), MICROGRAM);
assert_eq!(p(" 1 μg "), MICROGRAM);
assert_eq!(p(" 1 mg "), MILLIGRAM);
assert_eq!(p(" 1 g "), GRAM);
assert_eq!(p(" 1 kg "), KILOGRAM);
assert_eq!(p(" 1 t "), 1_000 * KILOGRAM);
}
#[test]
fn decimal_parse_dim() {
assert_eq!(const { p(".0mg") }, 0);
assert_eq!(const { p(".00001mg") }, 32 * WHIT);
assert_eq!(parse_dim(".000001mg"), None);
assert_eq!(const { p(".00000mg") }, 0);
assert_eq!(const { p(".0ug") }, 0);
assert_eq!(const { p(".01ug") }, MICROGRAM / 100);
assert_eq!(parse_dim(".001ug"), None);
assert_eq!(const { p(".00ug") }, 0);
assert_eq!(const { p(".0oz") }, 0);
assert_eq!(const { p(".001oz") }, OUNCE / 1000);
assert_eq!(parse_dim(".0001oz"), None);
assert_eq!(const { p(".0ozt") }, 0);
assert_eq!(const { p(".1ozt") }, TROY_OUNCE / 10);
assert_eq!(parse_dim(".01ozt"), None);
assert_eq!(parse_dim(".1wt"), None);
assert_eq!(parse_dim(".1dr"), None);
}
#[test]
fn whole_with_separators() {
assert_eq!(p("1,000g"), 1_000 * GRAM);
assert_eq!(p("1\u{2008}000g"), 1_000 * GRAM);
}
#[test]
fn parse_diagnostics() {
extern crate alloc;
use alloc::format;
const P1: Target = p("69g");
assert_eq!(P1, GRAM * 69);
const P2: Target = p("9.00\u{202F}ug");
assert_eq!(P2, 9 * MICROGRAM);
const P3: Result<Target, ParseError> = parse_dim_diagnostic("9.001\u{202F}ug");
assert_eq!(
P3,
Err(ParseError::TooPrecise {
unit: Unit::Microgram,
at: 5
})
);
const P4: Result<Target, ParseError> = parse_dim_diagnostic("ug");
assert_eq!(
P4,
Err(ParseError::EmptyQuantity {
unit: Unit::Microgram,
at: 0
})
);
const P5: Result<Target, ParseError> = parse_dim_diagnostic("39");
assert_eq!(P5, Err(ParseError::NoUnit { at: 2 }));
const P6: Result<Target, ParseError> = parse_dim_diagnostic("");
assert_eq!(P6, Err(ParseError::Empty));
const P7: Result<Target, ParseError> = parse_dim_diagnostic(" ");
assert_eq!(P7, Err(ParseError::Empty));
const P8: Result<Target, ParseError> = parse_dim_diagnostic("1foo");
assert_eq!(P8, Err(ParseError::NoUnit { at: 4 }));
const P9: Result<Target, ParseError> = parse_dim_diagnostic(" oz ");
assert_eq!(
P9,
Err(ParseError::EmptyQuantity {
unit: Unit::Ounce,
at: 1
})
);
const P10: Result<Target, ParseError> = parse_dim_diagnostic("1.0001oz");
assert_eq!(
P10,
Err(ParseError::TooPrecise {
unit: Unit::Ounce,
at: 6
})
);
let p11: Result<Target, ParseError> = parse_dim_diagnostic("1kg1oz");
assert_eq!(
p11,
Err(ParseError::InvalidCompound {
inferior: Unit::Ounce,
found: Unit::Kilogram,
at: 3
})
);
let digits = (Target::MAX.ilog10() as usize) + 1;
let p12_str = format!("1{}wt", "0".repeat(digits));
let p12: Result<Target, ParseError> = parse_dim_diagnostic(&p12_str);
assert_eq!(
p12,
Err(ParseError::TooBig {
unit: Unit::Whit,
at: 1
})
);
let p13_str = format!("1{}wt", "9".repeat(digits));
let p13: Result<Target, ParseError> = parse_dim_diagnostic(&p13_str);
assert_eq!(
p13,
Err(ParseError::TooBig {
unit: Unit::Whit,
at: 2
})
);
let p14_str = format!("9{}wt", "0".repeat(digits));
let p14: Result<Target, ParseError> = parse_dim_diagnostic(&p14_str);
assert_eq!(
p14,
Err(ParseError::TooBig {
unit: Unit::Whit,
at: 1
})
);
}
#[test]
fn parse_as_sanity() {
use super::super::$T::parse_as;
use super::super::Unit::*;
use joto_constants::mass::$T as m;
assert_eq!(parse_as("(unrelated) 1 ", Whit), Some(m::WHIT));
assert_eq!(parse_as("(unrelated) 1 ", Microgram), Some(m::MICROGRAM));
assert_eq!(parse_as("(unrelated) 1 ", Milligram), Some(m::MILLIGRAM));
assert_eq!(parse_as("(unrelated) 1 ", Gram), Some(m::GRAM));
assert_eq!(parse_as("(unrelated) 1 ", Kilogram), Some(m::KILOGRAM));
assert_eq!(parse_as("(unrelated) 1 ", Megagram), Some(m::MEGAGRAM));
assert_eq!(parse_as("(unrelated) 1 ", Dram), Some(m::DRAM));
assert_eq!(parse_as("(unrelated) 1 ", Ounce), Some(m::OUNCE));
assert_eq!(parse_as("(unrelated) 1 ", Pound), Some(m::POUND));
assert_eq!(parse_as("(unrelated) 1 ", Stone), Some(m::STONE));
assert_eq!(
parse_as("(unrelated) 1 ", LongHundredweight),
Some(m::LONG_HUNDREDWEIGHT)
);
assert_eq!(parse_as("(unrelated) 1 ", LongTon), Some(m::LONG_TON));
assert_eq!(
parse_as("(unrelated) 1 ", ShortHundredweight),
Some(m::SHORT_HUNDREDWEIGHT)
);
assert_eq!(parse_as("(unrelated) 1 ", ShortTon), Some(m::SHORT_TON));
assert_eq!(parse_as("(unrelated) 1 ", Grain), Some(m::GRAIN));
assert_eq!(
parse_as("(unrelated) 1 ", Pennyweight),
Some(m::PENNYWEIGHT)
);
assert_eq!(parse_as("(unrelated) 1 ", TroyOunce), Some(m::TROY_OUNCE));
assert_eq!(parse_as(".01", Microgram), Some(m::MICROGRAM / 100));
assert_eq!(parse_as(" ", Gram), None);
assert_eq!(parse_as(".001", Microgram), None);
assert_eq!(parse_as("foo37", Gram), Some(37 * m::GRAM));
}
#[test]
fn parse_as_diagnostic_sanity() {
use super::super::$T::parse_as_diagnostic;
use super::super::Unit;
assert_eq!(
parse_as_diagnostic(" ", Unit::Gram),
Err(ParseError::EmptyQuantity {
unit: Unit::Gram,
at: 0
})
);
}
}
};
}
test_submod!(u64);
test_submod!(i64);
test_submod!(u128);
test_submod!(i128);
mod f64 {
use super::super::f64::parse_dim;
use joto_constants::mass::f64::MICROGRAM;
#[test]
fn parse_sanity() {
assert_eq!(parse_dim("0.01ug").unwrap(), MICROGRAM / 100.);
}
#[test]
fn parse_check_overflow() {
extern crate alloc;
use alloc::format;
assert_eq!(
parse_dim(format!("{}wt", (1u64 << f64::MANTISSA_DIGITS) + 1).as_ref()),
None
);
}
}
#[test]
fn basic_trim_end() {
assert_eq!(super::trim_end("foo \u{202F} "), "foo");
}
}