Skip to content

Commit

Permalink
changed to function impl
Browse files Browse the repository at this point in the history
  • Loading branch information
feefladder committed Feb 7, 2024
1 parent 960bfef commit 29207bb
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 77 deletions.
2 changes: 1 addition & 1 deletion src/fraction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub use self::sign::Sign;
pub mod approx;

pub mod display;
pub mod unicode_fromto_str;
mod unicode_fromto_str;

#[cfg(feature = "with-juniper-support")]
pub mod juniper_support;
Expand Down
180 changes: 104 additions & 76 deletions src/fraction/unicode_fromto_str.rs
Original file line number Diff line number Diff line change
@@ -1,65 +1,84 @@
use std::fmt::Display;

use crate::fraction::GenericFraction;
extern crate core;
use self::core::{fmt, str};
use std::{fmt, str};
use crate::{error::ParseError, Sign};
use num::rational::Ratio;
use Integer;

#[derive(Debug, PartialEq)]
pub struct UnicodeFromToStr<T: Clone + Integer>(GenericFraction<T>);

impl<T> fmt::Display for UnicodeFromToStr<T>
where
T: Clone + Integer + fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
GenericFraction::NaN => write!(f, "NaN"),
GenericFraction::Infinity(s) => {
write!(f, "{}∞", s)
impl<T: Clone + Integer + Display> GenericFraction<T> {
pub fn unicode_display(&self) -> impl fmt::Display + '_ {
struct UnicodeDisplay<'a, T: Clone + Integer>(&'a GenericFraction<T>);
impl<'a, T> fmt::Display for UnicodeDisplay<'a, T>
where
T: Clone + Integer + fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
GenericFraction::NaN => write!(f, "NaN"),
GenericFraction::Infinity(s) => {
write!(f, "{}∞", s)
}
GenericFraction::Rational(s, r) => {
write!(f, "{}{}", s, r.numer())?;
if !r.denom().is_one() {
write!(f, "⁄{}", r.denom())?;
}
Ok(())
}
}
}
GenericFraction::Rational(s, r) => {
write!(f, "{}{}", s, r.numer())?;
if !r.denom().is_one() {
write!(f, "⁄{}", r.denom())?;
}
UnicodeDisplay(self)
}

pub fn unicode_display_mixed(&self) -> impl Display + '_ {
struct S<'a, T: Clone + Integer + fmt::Display>(&'a GenericFraction<T>);
impl<'a, T> fmt::Display for S<'a, T>
where
T: Clone + Integer + fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
GenericFraction::Rational(s, r) if r.fract() != *r && !r.denom().is_one() => {
write!(f, "{}{}\u{2064}{}⁄{}", s, r.trunc().numer(), r.fract().numer(), r.fract().denom())
}
_ => write!(f, "{}", self.0.unicode_display())
}
Ok(())
}
}
S(self)
}
}

impl<T> str::FromStr for UnicodeFromToStr<T>
where
T: Clone + Integer + fmt::Display,
{
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (sign, start) = if s.starts_with('-') {
(Sign::Minus, 1)
} else if s.starts_with('+') {
(Sign::Plus, 1)
pub fn unicode_parse(input: &str) -> Result<Self, ParseError> {
let s: &str;
let sign = if input.starts_with('-') {
s = &input[1..];
Sign::Minus

} else if input.starts_with('+') {
s = &input[1..];
Sign::Plus
} else {
(Sign::Plus, 0)
s = input;
Sign::Plus
};
if s[start..].to_lowercase().starts_with("nan") {
Ok(Self(GenericFraction::nan()))
} else if s[start..].starts_with("∞")
|| s[start..].starts_with("inf")
|| s[start..].starts_with("infty")
if s.to_lowercase().starts_with("nan") {
Ok(GenericFraction::nan())
} else if s.starts_with("∞")
|| s.starts_with("inf")
|| s.starts_with("infty")
{
Ok(Self(GenericFraction::Infinity(sign)))
Ok(GenericFraction::Infinity(sign))
} else if let Some((first, denom_str)) = s.split_once(&['/', '⁄', '∕', '÷'][..]) {
// also allow for mixed fractions to be parsed: `1+1/2` or `1⁤1⁄2`
// also allow for mixed fractions to be parsed: `1⁤1⁄2`
// allowed invisible separators: \u{2064} \u{2063}
// '+' is disallowed, bc it would be confusing with -1+1/2
let mut numer: T;
let denom: T;
if let Some((trunc_str, numer_str)) =
first[start..].split_once(&['\u{2064}', '\u{2063}'][..])
first.split_once(&['\u{2064}', '\u{2063}'][..])
{
// let Ok(nu)
println!("mixed!");
let Ok(n) = T::from_str_radix(numer_str, 10) else {
return Err(ParseError::ParseIntError);
};
Expand All @@ -74,7 +93,7 @@ where
denom = d;
numer = numer + trunc * denom.clone();
} else {
let Ok(n) = T::from_str_radix(&first[start..], 10) else {
let Ok(n) = T::from_str_radix(&first, 10) else {
return Err(ParseError::ParseIntError);
};
numer = n;
Expand All @@ -83,42 +102,29 @@ where
};
denom = d;
}
Ok(Self(GenericFraction::Rational(
Ok(GenericFraction::Rational(
sign,
Ratio::new(numer, denom),
)))
))
} else {
let Ok(val) = T::from_str_radix(&s[start..], 10) else {
let Ok(val) = T::from_str_radix(&s, 10) else {
return Err(ParseError::ParseIntError);
};
Ok(Self(GenericFraction::Rational(
Ok(GenericFraction::Rational(
sign,
Ratio::new(val, T::one()),
)))
))
}
}
}

impl<T: Clone + Integer> From<UnicodeFromToStr<T>> for GenericFraction<T> {
fn from(value: UnicodeFromToStr<T>) -> Self {
value.0
}
}

impl<T: Clone + Integer> From<GenericFraction<T>> for UnicodeFromToStr<T> {
fn from(value: GenericFraction<T>) -> Self {
Self(value)
}
}

#[cfg(test)]
mod tests {
use std::{num::ParseIntError, str::FromStr};

use num::{One, Zero};

use crate::{
error::ParseError, unicode_fromto_str::UnicodeFromToStr, Fraction, GenericFraction,
error::ParseError, Fraction,
};

#[test]
Expand All @@ -138,28 +144,35 @@ mod tests {
];
for (string, frac) in test_vec {
println!("{} ?= {}", string, frac);
assert_eq!(UnicodeFromToStr::from_str(string).unwrap().0, frac);
assert_eq!(Fraction::unicode_parse(string), Ok(frac));
println!("{} ?= {}", string, frac);
assert_eq!(format!("{}", UnicodeFromToStr(frac)), string);
assert_eq!(format!("{}", frac.unicode_display()), string);
}
}

#[test]
fn test_from_str() {
fn test_fromto_str_mixed() {
let test_vec = vec![
("Nan", Fraction::nan()),
("nan", Fraction::nan()),
("+∞", Fraction::infinity()),
("+1", Fraction::one()),
("+5", Fraction::from(5)),
("1⁤1⁄2", Fraction::new(3u8, 2u8)),
("1⁣1⁄2", Fraction::new(3u8, 2u8)),
("-1⁤1⁄2", Fraction::new_neg(3u8, 2u8)),
("NaN", Fraction::nan()),
("∞", Fraction::infinity()),
("-∞", Fraction::neg_infinity()),
("0", Fraction::zero()),
("1", Fraction::one()),
("-1", -Fraction::one()),
("5", Fraction::from(5)),
// ("nan", Fraction::nan()),
// ("+∞", Fraction::infinity()),
// ("+1", Fraction::one()),
// ("+5", Fraction::from(5)),
("1\u{2064}1⁄2", Fraction::new(3u8, 2u8)),
// ("1⁣1⁄2", Fraction::new(3u8, 2u8)),
("-1\u{2064}1⁄2", Fraction::new_neg(3u8, 2u8)),
];
for (string, frac) in test_vec {
println!("{} ?= {}", string, frac);
let f_test = UnicodeFromToStr::from_str(string).unwrap().0;
assert_eq!(f_test, frac);
let f_test = Fraction::unicode_parse(string);
assert_eq!(f_test, Ok(frac));
assert_eq!(format!("{}", frac.unicode_display_mixed()), string);
}
}

Expand All @@ -175,13 +188,28 @@ mod tests {
"1⁤1⁄2BOGUS",
"1⁣1⁄2BOGUS",
"-1⁤1⁄2BOGUS",
"1⁢1⁄2" // uses INVISIBLE_TIMES
];
for s in test_vec {
println!("{}", s);
assert_eq!(
UnicodeFromToStr::<u64>::from_str(s).err(),
Some(ParseError::ParseIntError)
Fraction::unicode_parse(s),
Err(ParseError::ParseIntError)
)
}
}

#[test]
fn test_fromstr_fraction_ops() {
let test_vec = vec![
"1",
"1/2",
"3/2",
];
for s in test_vec {
let f = Fraction::unicode_parse(s).unwrap();
assert_eq!(f*Fraction::one(), f);
assert_eq!(f+Fraction::zero(), f);
}
}
}

0 comments on commit 29207bb

Please sign in to comment.