use core::str;
use joto_constants::length::u64::{
CENTIMETER, DECIMETER, FOOT, INCH, IOTA, METER, MICROMETER, MILLIMETER, NANOMETER, PICA, POINT,
QUARTER_MILLIMETER, YARD,
};
#[derive(Copy, Clone, PartialEq, Eq, Debug, PartialOrd, Ord, Hash)]
#[repr(u64)]
pub enum Unit {
Iota = IOTA,
Inch = INCH,
Foot = FOOT,
Yard = YARD,
Point = POINT,
Pica = PICA,
Nanometer = NANOMETER,
Micrometer = MICROMETER,
Millimeter = MILLIMETER,
Centimeter = CENTIMETER,
Decimeter = DECIMETER,
Meter = METER,
Q = QUARTER_MILLIMETER,
}
#[inline(always)]
const fn unit_phf(u: Unit) -> u8 {
(((u as u64).wrapping_mul(2962150912u64) >> 32) & 15) 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(Iota) },
14 => const { $FN(Inch) },
13 => const { $FN(Foot) },
9 => const { $FN(Yard) },
4 => const { $FN(Point) },
5 => const { $FN(Pica) },
6 => const { $FN(Nanometer) },
15 => const { $FN(Micrometer) },
11 => const { $FN(Millimeter) },
3 => const { $FN(Centimeter) },
1 => const { $FN(Decimeter) },
10 => const { $FN(Meter) },
2 => const { $FN(Q) },
_ => unsafe { ::core::hint::unreachable_unchecked() },
}
}
};
}
const fn unit_lsd_inner(unit: Unit) -> u32 {
let max_dgt = unit.max_decimal_digits() as u32;
if max_dgt == 0 {
unit as u32
} else {
(unit as u64 / 10u64.pow(max_dgt)) as u32
}
}
phf_match_unit_const_map! {
#[inline(always)]
const fn unit_lsd : unit_lsd_inner -> u32
}
const fn max_decimal_digits_inner(u: Unit) -> u8 {
use Unit::*;
match u {
Inch => 5,
Meter => 9,
Decimeter => 8,
Centimeter => 7,
Millimeter => 6,
Micrometer => 3,
Point => 3,
Pica => 5,
Q => 4,
Yard => 5,
Foot => 5,
_ => 0,
}
}
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 {
Iota => "io",
Foot => "′",
Inch => "″",
Yard => "yd",
Point => "pt",
Pica => "pc",
Nanometer => "nm",
Micrometer => "µm",
Millimeter => "mm",
Centimeter => "cm",
Decimeter => "dm",
Meter => "m",
Q => "Q",
}
}
pub const fn ascii_abbr(self) -> &'static [u8] {
use Unit::*;
match self {
Iota => b"io",
Foot => b"'",
Inch => b"\"",
Yard => b"yd",
Point => b"pt",
Pica => b"pc",
Nanometer => b"nm",
Micrometer => b"um",
Millimeter => b"mm",
Centimeter => b"cm",
Decimeter => b"dm",
Meter => b"m",
Q => b"Q",
}
}
#[inline]
pub const fn is_si(self) -> bool {
use Unit::*;
matches!(
self,
Nanometer | Micrometer | Millimeter | Centimeter | Decimeter | Meter
)
}
#[inline]
pub const fn superior(self) -> Option<Unit> {
if matches!(self, Unit::Inch) {
Some(Unit::Foot)
} else {
None
}
}
#[inline]
pub const fn inferior(self) -> Option<Unit> {
if matches!(self, Unit::Foot) {
Some(Unit::Inch)
} else {
None
}
}
#[inline]
pub const fn max_decimal_digits(self) -> u8 {
max_decimal_digits(self)
}
#[inline]
pub const fn least_significant_digit_value(self) -> u32 {
unit_lsd(self)
}
}
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'"'] |
[r @ .., b'i', b'n'] |
[r @ .., 0xE2, 0x80, 0xB3] => (bs(r), Unit::Inch),
[r @ .., b'\''] |
[r @ .., b'f', b't'] |
[r @ .., 0xE2, 0x80, 0xB2] => (bs(r), Unit::Foot),
[r @ .., b'y', b'd'] => (bs(r), Unit::Yard),
[r @ .., b'n', b'm'] => (bs(r), Unit::Nanometer),
[r @ .., b'u', b'm'] |
[r @ .., 0xC2, 0xB5, b'm'] | [r @ .., 0xCE, 0xBC, b'm'] => (bs(r), Unit::Micrometer),
[r @ .., b'm', b'm'] => (bs(r), Unit::Millimeter),
[r @ .., b'c', b'm'] => (bs(r), Unit::Centimeter),
[r @ .., b'd', b'm'] => (bs(r), Unit::Decimeter),
[r @ .., b'm'] => (bs(r), Unit::Meter),
[r @ .., b'Q'] => (bs(r), Unit::Q),
[r @ .., b'p', b't'] => (bs(r), Unit::Point),
[r @ .., b'p', b'c'] => (bs(r), Unit::Pica),
[r @ .., b'i', b'o'] => (bs(r), Unit::Iota),
_ => {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,
},
BadDenominator {
unit: Unit,
at: usize,
},
BadNumerator {
unit: Unit,
at: usize,
},
InvalidCompound {
inferior: Unit,
found: Unit,
at: usize,
},
}
impl Unit {
#[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))
}
}
#[inline]
const fn parse_inch_denom_pv(s: &str) -> Option<(u32, u32)> {
const INCH: u32 = Unit::Inch as u32;
Some(match s.as_bytes() {
b"2" => (2, const { INCH / 2 }),
b"4" => (4, const { INCH / 4 }),
b"8" => (8, const { INCH / 8 }),
b"16" => (16, const { INCH / 16 }),
b"32" => (32, const { INCH / 32 }),
b"64" => (64, const { INCH / 64 }),
_ => {
return None;
}
})
}
#[inline]
const unsafe fn parse_inch_num(s: &str) -> Option<u32> {
Some(match s.as_bytes() {
[ones] => (*ones & 0xF) as u32,
[tens, ones] => {
let tens = (*tens & 0xF) as u32;
let ones = (*ones & 0xF) as u32;
unsafe { tens.unchecked_mul(10).unchecked_add(ones) }
}
_ => {
return None;
}
})
}
#[inline]
const fn take_inch_frac(rest: &str) -> Result<(&str, u32), ParseError> {
let at = rest.len();
let unit = Unit::Inch;
let (d_rest, denom_digits) = strip_digits(rest);
if denom_digits.is_empty() {
return Err(ParseError::EmptyQuantity { unit, at });
}
match d_rest.as_bytes() {
[r @ .., b'/'] |
[r @ .., 0xE2, 0x81, 0x84] => {
let Some((den, den_pv)) = parse_inch_denom_pv(denom_digits)
else {
return Err(ParseError::BadDenominator { unit, at });
};
let s = unsafe { str::from_utf8_unchecked(r) };
let at = s.len();
let (rest, numerator_digits) = strip_digits(s);
if numerator_digits.is_empty() {
return Err(ParseError::BadNumerator { unit, at });
}
let Some(num_v) = (unsafe { parse_inch_num(numerator_digits) }) else {
return Err(ParseError::BadNumerator { unit, at });
};
if num_v >= den || num_v < 1 {
return Err(ParseError::BadNumerator { unit, at });
}
let acc = unsafe { (den_pv).unchecked_mul(num_v) };
Ok((trim_end(rest), acc))
}
[r @ .., b'.'] => {
let scale = unit.max_decimal_digits() as usize;
let rest = unsafe { str::from_utf8_unchecked(r)};
let at = rest.len();
let digits = trim_trailing_zeroes(denom_digits);
let len = digits.len();
if len == 0 {
Ok((rest, 0))
} else if len > scale {
Err(ParseError::TooPrecise { unit, at })
} else {
let b = digits.as_bytes();
let mut pv = unit_lsd(unit);
let mut i = scale;
while i > len {
i -= 1;
pv = unsafe { pv.unchecked_mul(10) };
}
let mut acc = 0u32;
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))
}
}
_ => Ok((rest, 0))
}
}
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 {
Unit::Inch => match take_inch_frac(rest) {
Ok((rest, v)) => (rest, v as Target),
Err(e) => return Err(e),
},
_ => 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 {
Unit::Inch => match take_inch_frac(rest) {
Ok((rest, v)) => (rest, v as Target),
Err(e) => return Err(e),
},
_ => 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::length::$T::{
CENTIMETER, DECIMETER, FOOT, HUNDRED_THOUSANDTH, INCH, METER, MICROMETER,
MILLIMETER, NANOMETER, PICA, POINT, QUARTER, SIXTY_FOURTH,
};
const fn p(s: &str) -> Target {
parse_dim(s).unwrap()
}
#[test]
fn invertibility_sanity_check() {
const V: Target =
p("2.5cm") + p("1⁄64in") + p("1⁄2 in") - p("37,700μm") - p("1/64\"");
assert_eq!(0, V)
}
#[test]
fn basic_parse_dim() {
assert_eq!(p("37'"), 37 * FOOT);
assert_eq!(p("11\""), 11 * INCH);
}
#[test]
fn compound_parse_dim() {
assert_eq!(p("37'11\""), 37 * FOOT + 11 * INCH);
assert_eq!(p("37'11 1⁄4\""), 37 * FOOT + 11 * INCH + 1 * QUARTER);
}
#[test]
fn with_decimal_parse_dim() {
assert_eq!(p(".00001\""), HUNDRED_THOUSANDTH);
assert_eq!(
p("37'11.00039\""),
37 * FOOT + 11 * INCH + 39 * HUNDRED_THOUSANDTH
);
assert_eq!(p("37'11.1\""), 37 * FOOT + 11 * INCH + INCH / 10);
}
#[test]
fn si_whole_parse_dim() {
assert_eq!(p("1nm"), NANOMETER);
assert_eq!(p("1um"), MICROMETER);
assert_eq!(p("1µm"), MICROMETER);
assert_eq!(p("1Q"), MILLIMETER / 4);
assert_eq!(p("1mm"), MILLIMETER);
assert_eq!(p("1cm"), CENTIMETER);
assert_eq!(p("1dm"), DECIMETER);
assert_eq!(p("1m"), METER);
assert_eq!(p(" 1 nm "), NANOMETER);
assert_eq!(p(" 1 um "), MICROMETER);
assert_eq!(p(" 1 µm "), MICROMETER);
assert_eq!(p(" 1 Q "), MILLIMETER / 4);
assert_eq!(p(" 1 mm "), MILLIMETER);
assert_eq!(p(" 1 cm "), CENTIMETER);
assert_eq!(p(" 1 dm "), DECIMETER);
assert_eq!(p(" 1 m "), METER);
}
#[test]
fn si_decimal_parse_dim() {
assert_eq!(const { p(".0nm") }, 0);
assert_eq!(parse_dim(".1nm"), None);
assert_eq!(const { p(".000nm") }, 0);
assert_eq!(parse_dim(".0001nm"), None);
assert_eq!(const { p(".0um") }, 0);
assert_eq!(const { p(".001um") }, NANOMETER);
assert_eq!(parse_dim(".0001um"), None);
assert_eq!(const { p(".0000um") }, 0);
assert_eq!(const { p(".0µm") }, 0);
assert_eq!(const { p(".001µm") }, NANOMETER);
assert_eq!(parse_dim(".0001µm"), None);
assert_eq!(const { p(".0000µm") }, 0);
assert_eq!(const { p(".0μm") }, 0);
assert_eq!(const { p(".001μm") }, NANOMETER);
assert_eq!(parse_dim(".0001μm"), None);
assert_eq!(const { p(".0000μm") }, 0);
assert_eq!(const { p(".0Q") }, 0);
assert_eq!(const { p(".0001Q") }, 25 * NANOMETER);
assert_eq!(parse_dim(".000004Q"), None);
assert_eq!(const { p(".0000000Q") }, 0);
assert_eq!(const { p(".0mm") }, 0);
assert_eq!(const { p(".000001mm") }, NANOMETER);
assert_eq!(parse_dim(".0000001mm"), None);
assert_eq!(const { p(".0000000mm") }, 0);
assert_eq!(const { p(".0cm") }, 0);
assert_eq!(const { p(".0000001cm") }, NANOMETER);
assert_eq!(parse_dim(".00000001cm"), None);
assert_eq!(const { p(".0000000cm") }, 0);
assert_eq!(const { p(".0dm") }, 0);
assert_eq!(const { p(".00000001dm") }, NANOMETER);
assert_eq!(parse_dim(".000000001dm"), None);
assert_eq!(const { p(".00000000dm") }, 0);
assert_eq!(const { p(".0m") }, 0);
assert_eq!(const { p(".000000001m") }, NANOMETER);
assert_eq!(parse_dim(".0000000001m"), None);
assert_eq!(const { p(".000000000m") }, 0);
}
#[test]
fn si_with_decimal_parse_dim() {
assert_eq!(p("1.0nm"), NANOMETER);
assert_eq!(p("1.000nm"), NANOMETER);
assert_eq!(p("2.001um"), 2 * MICROMETER + NANOMETER);
assert_eq!(p("2.000um"), 2 * MICROMETER);
assert_eq!(p("3.000001mm"), 3 * MILLIMETER + NANOMETER);
assert_eq!(p("3.000000mm"), 3 * MILLIMETER);
assert_eq!(p("4.0000001cm"), 4 * CENTIMETER + NANOMETER);
assert_eq!(p("4.0000000cm"), 4 * CENTIMETER);
assert_eq!(p("5.00000001dm"), 5 * DECIMETER + NANOMETER);
assert_eq!(p("5.00000000dm"), 5 * DECIMETER);
assert_eq!(p("6.000000001m"), 6 * METER + NANOMETER);
assert_eq!(p("6.000000000m"), 6 * METER);
}
#[test]
fn compound_funk() {
const R: &str = " 34,966' 11 1⁄4\" ";
assert_eq!(p(R), 34_966 * FOOT + 11 * INCH + 1 * QUARTER);
const S: &str = " 34,966' 111⁄4\" ";
assert_eq!(p(S), 34_966 * FOOT + 11 * INCH + 1 * QUARTER);
const P: &str = " 34,966 ' 111⁄4\" ";
assert_eq!(p(P), 34_966 * FOOT + 11 * INCH + 1 * QUARTER);
}
#[test]
fn whole_picas_parse_dim() {
const R5PC: Target = p("5pc");
assert_eq!(R5PC, 5 * PICA);
const R1PC: Target = p("1pc");
assert_eq!(R1PC, 1 * PICA);
const R10MPC: Target = p("10,000,000 pc");
assert_eq!(R10MPC, 10_000_000 * PICA);
}
#[test]
fn whole_points_parse_dim() {
const R5PT: Target = p("5pt");
assert_eq!(R5PT, 5 * POINT);
const R1PT: Target = p("1pt");
assert_eq!(R1PT, 1 * POINT);
const R10MPT: Target = p("10,000,000 pt");
assert_eq!(R10MPT, 10_000_000 * POINT);
}
#[test]
fn assorted_whitespace() {
const W1: Target = p("320,333\u{00A0}\u{FEFF}\u{2009}\u{200A}\u{202f}\u{2033}");
assert_eq!(W1, 320_333 * INCH);
const W2: Target = p("\u{2005}17⁄32\u{2000}″\u{2001}\u{2002}\u{2003}\u{2004}");
assert_eq!(W2, 34 * SIXTY_FOURTH);
const W3: Target = p("\u{2006}3⁄8\u{2007}″\u{2008}\u{2009}\u{200A}\u{200B}");
assert_eq!(W3, 24 * SIXTY_FOURTH);
}
#[test]
fn grouping_separators() {
const G1: Target = p("67\u{2008}000,000\u{2008}000\u{202f}io");
assert_eq!(G1, 67_000_000_000);
}
#[test]
fn parse_diagnostics() {
extern crate alloc;
use alloc::format;
const P1: Target = p("69in");
assert_eq!(P1, INCH * 69);
const P2: Target = p("21'11\"");
assert_eq!(P2, FOOT * 21 + INCH * 11);
const P3: Target = p("21'1137⁄64\"");
assert_eq!(P3, FOOT * 21 + INCH * 11 + 37 * SIXTY_FOURTH);
const P4: Target = p("21'11.25000000\"");
assert_eq!(P4, FOOT * 21 + INCH * 11 + 1 * QUARTER);
const P5: Target = p("9.0000 nm");
assert_eq!(P5, 9 * NANOMETER);
const P6: Target = p("9 nm");
assert_eq!(P6, 9 * NANOMETER);
const P7: Result<Target, ParseError> = parse_dim_diagnostic("9.1 nm");
assert_eq!(
P7,
Err(ParseError::TooPrecise {
unit: Unit::Nanometer,
at: 3
})
);
const P8: Target = p("5mm");
assert_eq!(P8, 5 * MILLIMETER);
const P9: Target = p("5.0mm");
assert_eq!(P9, 5 * MILLIMETER);
const P10: Target = p("0.5cm");
assert_eq!(P10, 5 * MILLIMETER);
const P11: Target = p("2.5cm");
assert_eq!(P11, 25 * MILLIMETER);
const P12: Result<Target, ParseError> = parse_dim_diagnostic("nm");
assert_eq!(
P12,
Err(ParseError::EmptyQuantity {
unit: Unit::Nanometer,
at: 0
})
);
const P13: Target = p("0nm");
assert_eq!(P13, 0);
const P14: Result<Target, ParseError> = parse_dim_diagnostic("nm");
assert_eq!(
P14,
Err(ParseError::EmptyQuantity {
unit: Unit::Nanometer,
at: 0
})
);
const P15: Result<Target, ParseError> = parse_dim_diagnostic("39");
assert_eq!(P15, Err(ParseError::NoUnit { at: 2 }));
const P16: Result<Target, ParseError> = parse_dim_diagnostic("");
assert_eq!(P16, Err(ParseError::Empty));
let p17: Result<Target, ParseError> =
parse_dim_diagnostic(format!("{}nm", (Target::MAX / 9) + 1).as_ref());
assert_eq!(
p17,
Err(ParseError::TooBig {
unit: Unit::Nanometer,
at: 1
})
);
let p18: Target = p(format!("{}io", Target::MAX).as_ref());
assert_eq!(p18, Target::MAX);
const P19: Result<Target, ParseError> = parse_dim_diagnostic(" ");
assert_eq!(P19, Err(ParseError::Empty));
const P20: Result<Target, ParseError> = parse_dim_diagnostic("1foo");
assert_eq!(P20, Err(ParseError::NoUnit { at: 4 }));
const P21: Result<Target, ParseError> = parse_dim_diagnostic(" mm ");
assert_eq!(
P21,
Err(ParseError::EmptyQuantity {
unit: Unit::Millimeter,
at: 1
})
);
const P22: Result<Target, ParseError> = parse_dim_diagnostic("1.0000001mm");
assert_eq!(
P22,
Err(ParseError::TooPrecise {
unit: Unit::Millimeter,
at: 9
})
);
const P23: Result<Target, ParseError> = parse_dim_diagnostic("1/3\"");
assert_eq!(
P23,
Err(ParseError::BadDenominator {
unit: Unit::Inch,
at: 3
})
);
const P24: Result<Target, ParseError> = parse_dim_diagnostic("3/2\"");
assert_eq!(
P24,
Err(ParseError::BadNumerator {
unit: Unit::Inch,
at: 1
})
);
const P25: Result<Target, ParseError> = parse_dim_diagnostic("0/2\"");
assert_eq!(
P25,
Err(ParseError::BadNumerator {
unit: Unit::Inch,
at: 1
})
);
let p26: Result<Target, ParseError> = parse_dim_diagnostic("1m1\"");
assert_eq!(
p26,
Err(ParseError::InvalidCompound {
inferior: Unit::Inch,
found: Unit::Meter,
at: 2
})
);
let digits = (Target::MAX.ilog10() as usize) + 1;
let p27_str = format!("1{}io", "0".repeat(digits));
let p27: Result<Target, ParseError> = parse_dim_diagnostic(&p27_str);
assert_eq!(
p27,
Err(ParseError::TooBig {
unit: Unit::Iota,
at: 1
})
);
let p28_str = format!("1{}io", "9".repeat(digits));
let p28: Result<Target, ParseError> = parse_dim_diagnostic(&p28_str);
assert_eq!(
p28,
Err(ParseError::TooBig {
unit: Unit::Iota,
at: 2
})
);
let p29_str = format!("9{}io", "0".repeat(digits));
let p29: Result<Target, ParseError> = parse_dim_diagnostic(&p29_str);
assert_eq!(
p29,
Err(ParseError::TooBig {
unit: Unit::Iota,
at: 1
})
);
}
#[test]
fn parse_as_sanity() {
use super::super::$T::parse_as;
use super::super::Unit::*;
use joto_constants::length::$T as l;
assert_eq!(parse_as("(unrelated) 1 ", Iota), Some(l::IOTA));
assert_eq!(parse_as("(unrelated) 1 ", Inch), Some(l::INCH));
assert_eq!(parse_as("(unrelated) 1 ", Foot), Some(l::FOOT));
assert_eq!(parse_as("(unrelated) 1 ", Yard), Some(l::YARD));
assert_eq!(parse_as("(unrelated) 1 ", Point), Some(l::POINT));
assert_eq!(parse_as("(unrelated) 1 ", Pica), Some(l::PICA));
assert_eq!(parse_as("(unrelated) 1 ", Nanometer), Some(l::NANOMETER));
assert_eq!(parse_as("(unrelated) 1 ", Micrometer), Some(l::MICROMETER));
assert_eq!(parse_as("(unrelated) 1 ", Millimeter), Some(l::MILLIMETER));
assert_eq!(parse_as("(unrelated) 1 ", Centimeter), Some(l::CENTIMETER));
assert_eq!(parse_as("(unrelated) 1 ", Decimeter), Some(l::DECIMETER));
assert_eq!(parse_as("(unrelated) 1 ", Meter), Some(l::METER));
assert_eq!(parse_as("(unrelated) 1 ", Q), Some(l::QUARTER_MILLIMETER));
assert_eq!(parse_as(".000000001", Meter), Some(l::NANOMETER));
assert_eq!(parse_as(".001", Micrometer), Some(l::NANOMETER));
assert_eq!(parse_as("3/8", Inch), Some(3 * (l::EIGHTH)));
assert_eq!(parse_as(" ", Millimeter), None);
assert_eq!(parse_as(".0001", Micrometer), None);
assert_eq!(parse_as("foo37", Centimeter), Some(37 * l::CENTIMETER));
}
#[test]
fn parse_as_diagnostic_sanity() {
use super::super::$T::parse_as_diagnostic;
use super::super::Unit;
assert_eq!(
parse_as_diagnostic(" ", Unit::Millimeter),
Err(ParseError::EmptyQuantity {
unit: Unit::Millimeter,
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::length::f64::{QUARTER, SIXTY_FOURTH};
#[test]
fn parse_sanity() {
assert_eq!(parse_dim("1⁄4in").unwrap(), QUARTER);
assert_eq!(parse_dim("1⁄64in").unwrap(), SIXTY_FOURTH);
assert_eq!(parse_dim("37⁄64in").unwrap(), 37. * SIXTY_FOURTH);
}
#[test]
fn parse_check_overflow() {
extern crate alloc;
use alloc::format;
assert_eq!(
parse_dim(format!("{}io", (1u64 << f64::MANTISSA_DIGITS) + 1).as_ref()),
None
);
}
}
#[test]
fn basic_trim_end() {
assert_eq!(super::trim_end("foo \u{202F} "), "foo");
}
}