diff --git a/src/sass/functions/color/hsl.rs b/src/sass/functions/color/hsl.rs index 583d6896f..74c85ac1c 100644 --- a/src/sass/functions/color/hsl.rs +++ b/src/sass/functions/color/hsl.rs @@ -1,15 +1,14 @@ use super::channels::Channels; use super::{ - bad_arg, check_alpha, check_pct_rational_range, check_rational, - get_checked, get_color, get_opt_check, is_special, make_call, CheckedArg, - FunctionMap, + bad_arg, check_alpha, check_pct_range, check_rational, get_checked, + get_color, get_opt_check, is_special, make_call, CheckedArg, FunctionMap, }; use crate::css::{CallArgs, Value}; use crate::output::Format; use crate::sass::{ArgsError, FormalArgs, Name}; use crate::value::{Hsla, Numeric, Rational, Unit}; use crate::{Error, Scope, ScopeRef}; -use num_traits::{one, zero}; +use num_traits::zero; use std::convert::TryFrom; pub fn register(f: &mut Scope) { @@ -70,15 +69,15 @@ pub fn expose(m: &Scope, global: &mut FunctionMap) { def!(f, darken(color, amount), |s| { let col = get_color(s, "color")?; let hsla = col.to_hsla(); - let lum = hsla.lum() - - get_checked(s, name!(amount), check_pct_rational_range)?; + let lum = + hsla.lum() - get_checked(s, name!(amount), check_pct_range)?; Ok(Hsla::new(hsla.hue(), hsla.sat(), lum, hsla.alpha()).into()) }); def!(f, desaturate(color, amount), |s| { let col = get_color(s, "color")?; let hsla = col.to_hsla(); - let sat = hsla.sat() - - get_checked(s, name!(amount), check_pct_rational_range)?; + let sat = + hsla.sat() - get_checked(s, name!(amount), check_pct_range)?; Ok(Hsla::new(hsla.hue(), sat, hsla.lum(), hsla.alpha()).into()) }); def_va!(f, saturate(kwargs), |s| { @@ -88,8 +87,7 @@ pub fn expose(m: &Scope, global: &mut FunctionMap) { match a1.eval(s.clone(), args.clone()) { Ok(s) => { let col = get_color(&s, "color")?; - let sat = - get_checked(&s, name!(amount), check_pct_rational_range)?; + let sat = get_checked(&s, name!(amount), check_pct_range)?; let hsla = col.to_hsla(); let sat = hsla.sat() + sat; Ok(Hsla::new(hsla.hue(), sat, hsla.lum(), hsla.alpha()) @@ -100,7 +98,7 @@ pub fn expose(m: &Scope, global: &mut FunctionMap) { .eval(s.clone(), args) .map_err(|e| bad_arg(e, &name!(saturate), &a2))?; let sat = s.get("amount")?; - check_pct_rational_range(sat.clone()).named(name!(amount))?; + check_pct_range(sat.clone()).named(name!(amount))?; Ok(make_call("saturate", vec![sat])) } Err(ae) => Err(bad_arg(ae, &name!(saturate), &a1)), @@ -109,8 +107,8 @@ pub fn expose(m: &Scope, global: &mut FunctionMap) { def!(f, lighten(color, amount), |s| { let col = get_color(s, "color")?; let hsla = col.to_hsla(); - let lum = hsla.lum() - + get_checked(s, name!(amount), check_pct_rational_range)?; + let lum = + hsla.lum() + get_checked(s, name!(amount), check_pct_range)?; Ok(Hsla::new(hsla.hue(), hsla.sat(), lum, hsla.alpha()).into()) }); for (gname, lname) in &[ @@ -181,8 +179,8 @@ fn hsla_from_values( } else { Ok(Hsla::new( check_rational(h).named(name!(hue))?, - check_rational_percent(s).named(name!(saturation))?, - check_rational_percent(l).named(name!(lightness))?, + check_pct_opt(s).named(name!(saturation))?, + check_pct_opt(l).named(name!(lightness))?, check_alpha(a).named(name!(alpha))?, ) .into()) @@ -195,22 +193,17 @@ pub fn percentage(v: Rational) -> Value { /// Gets a percentage as a fraction 0 .. 1. /// If v is not a percentage, keep it as it is. -fn check_rational_percent(v: Value) -> Result { - match v { - Value::Null => Ok(zero()), - Value::Numeric(v, ..) => { - let r = v.value.as_ratio().map_err(|e| e.to_string())?; - if v.unit.is_percent() || r > one() { - Ok(r / 100) - } else { - Ok(r) - } - } - v => Err(format!( - "{} is not a number", +fn check_pct_opt(v: Value) -> Result { + let v = super::check::numeric(v)?; + if !v.unit.is_percent() { + // Note: The deprecation warning should include the parameter name + // and line of call, but we don't have that here. + dep_warn!( + "Passing a number without unit % ({}) is deprecated.", v.format(Format::introspect()) - )), + ); } + Ok(v.as_ratio()? / 100) } #[test] diff --git a/src/sass/functions/color/hwb.rs b/src/sass/functions/color/hwb.rs index 1f424e306..7633b5fc5 100644 --- a/src/sass/functions/color/hwb.rs +++ b/src/sass/functions/color/hwb.rs @@ -5,7 +5,7 @@ use super::{ use crate::css::{CallArgs, Value}; use crate::output::Format; use crate::sass::FormalArgs; -use crate::value::{Hwba, ListSeparator, Number, Unit}; +use crate::value::{Hwba, ListSeparator, Rational, Unit}; use crate::{Error, Scope, ScopeRef}; pub fn register(f: &mut Scope) { @@ -43,11 +43,11 @@ fn hwb(s: &ScopeRef) -> Result { )) })? }; - let hue = as_hue(hue).named(name!(hue))?.as_ratio()?; - let w = check_expl_pct(w).named(name!(whiteness))?.as_ratio()?; - let b = check_expl_pct(b).named(name!(blackness))?.as_ratio()?; + let hue = check_hue(hue).named(name!(hue))?; + let w = check_expl_pct(w).named(name!(whiteness))?; + let b = check_expl_pct(b).named(name!(blackness))?; let a = check_alpha(a).named(name!(alpha))?; - Ok(Hwba::new(hue, w / 100, b / 100, a).into()) + Ok(Hwba::new(hue, w, b, a).into()) } fn hwb_from_channels( @@ -106,10 +106,10 @@ fn badchannels(v: &Value) -> Error { )) } -fn as_hue(v: Value) -> Result { +fn check_hue(v: Value) -> Result { let vv = check::numeric(v)?; if let Some(scaled) = vv.as_unit_def(Unit::Deg) { - Ok(scaled) + Ok(scaled.as_ratio()?) } else { Err(format!( "{} is not an angle", diff --git a/src/sass/functions/color/mod.rs b/src/sass/functions/color/mod.rs index 3257991e2..f9dc5502a 100644 --- a/src/sass/functions/color/mod.rs +++ b/src/sass/functions/color/mod.rs @@ -6,7 +6,7 @@ use crate::css::{CallArgs, Value}; use crate::output::Format; use crate::parser::SourcePos; use crate::sass::{ArgsError, FormalArgs, Name}; -use crate::value::{Color, Number, Quotes, Rational, Unit}; +use crate::value::{Color, Number, Numeric, Quotes, Rational, Unit}; use crate::Scope; use num_traits::{one, zero, Signed}; mod channels; @@ -73,19 +73,35 @@ fn check_color(v: Value) -> Result { /// For the alpha parameter of rgba, hsla, hwba functions /// Special perk: Defaults to 1.0 if the value is null. fn check_alpha(v: Value) -> Result { - match v { - Value::Null => Ok(one()), + Ok(match v { + Value::Null => one(), v => { let num = check::numeric(v)?; - let r = num.value.as_ratio().map_err(|e| e.to_string())?; - if num.unit.is_percent() { - Ok(r / 100) - } else if num.unit.is_none() { - Ok(r) - } else { - Err(expected_to(&num, "have no units or \"%\"")) - } + num.as_unit(Unit::None) + .ok_or_else(|| expected_to(&num, "have no units or \"%\""))? + .as_ratio()? } + }) +} +/// Get a rational number in the 0..1 range. +fn check_alpha_range(v: Value) -> Result { + let val = check::numeric(v)?; + let a = val + .as_unit_def(Unit::None) + .ok_or_else(|| expected_to(&val, "have no units or \"%\""))? + .as_ratio()?; + if a < zero() || a > one() { + Err(expected_to(&val, "be within 0 and 1")) + } else { + Ok(a) + } +} +fn check_alpha_pm(v: Value) -> Result { + let v = check_rational(v)?; + if v.abs() > one() { + Err(expected_to(&Number::from(v), "be within -1 and 1")) + } else { + Ok(v) } } @@ -95,81 +111,57 @@ fn check_pct(v: Value) -> Result { .ok_or_else(|| expected_to(&val, "have unit \"%\"")) } -fn check_pct_expl_rational_pm1(v: Value) -> Result { - let val = check::numeric(v)?; - if !val.unit.is_percent() { - return Err(expected_to(&val, "have unit \"%\"")); - } - if val.value.clone().abs() > 100.into() { - Err(expected_to(&val, "be within -100% and 100%")) - } else { - let r = val.value.as_ratio().map_err(|e| e.to_string())?; - Ok(r / 100) - } -} - -fn check_expl_pct(v: Value) -> Result { +fn check_expl_pct(v: Value) -> Result { let val = check::numeric(v)?; if !val.unit.is_percent() { return Err(expected_to(&val, "have unit \"%\"")); } - if val.value < 0.into() || val.value > 100.into() { + if val.value < zero() || val.value > 100.into() { Err(expected_to(&val, "be within 0% and 100%")) } else { - Ok(val.value) + Ok(val.as_ratio()? / 100) } } -fn check_expl_pct_r(v: Value) -> Result { - check_expl_pct(v)? - .as_ratio() - .map(|r| r / 100) - .map_err(|e| e.to_string()) -} -fn check_pct_rational_range(v: Value) -> Result { +fn check_pct_range(v: Value) -> Result { let val = check_pct(v)?; - if val < 0.into() || val > 100.into() { + if val < zero() || val > 100.into() { Err(expected_to(&val, "be within 0 and 100")) } else { - val.as_ratio().map_err(|e| e.to_string()).map(|v| v / 100) + Ok(val.as_ratio()? / 100) } } fn check_rational(v: Value) -> Result { - check::numeric(v)?.as_ratio().map_err(|e| e.to_string()) + Ok(check::numeric(v)?.as_ratio()?) } -fn check_rational_pm1(v: Value) -> Result { - let v = check_rational(v)?; - if v.abs() > one() { - Err(expected_to(&Number::from(v), "be within -1 and 1")) - } else { - Ok(v) - } -} -fn check_rational_byte(v: Value) -> Result { - let v = check_rational(v)?; - if v > Rational::from_integer(255) || v < zero() { - Err(expected_to(&Number::from(v), "be within 0 and 255")) + +fn check_channel(v: Value) -> Result { + num2chan(&check::numeric(v)?) +} +fn check_channel_range(v: Value) -> Result { + let v = check::numeric(v)?; + let r = num2chan(&v)?; + if r > Rational::from_integer(255) || r < zero() { + Err(expected_to(&v, "be within 0 and 255")) } else { - Ok(v) + Ok(r) } } -fn check_rational_pmbyte(v: Value) -> Result { - let v = check_rational(v)?; - if v > Rational::from_integer(255) || v < Rational::from_integer(-255) { - Err(expected_to(&Number::from(v), "be within -255 and 255")) +fn check_channel_pm(v: Value) -> Result { + let v = check::numeric(v)?; + let r = num2chan(&v)?; + if r.abs() > Rational::from_integer(255) { + Err(expected_to(&v, "be within -255 and 255")) } else { - Ok(v) + Ok(r) } } -/// Get a rational number in the 0..1 range. -fn check_rational_fract(v: Value) -> Result { - let val = check::numeric(v)? - .as_unit_def(Unit::None) - .ok_or_else(|| "xyzzy".to_string())?; - if val < 0.into() || val > 1.into() { - Err(expected_to(&val, "be within 0 and 1")) +fn num2chan(v: &Numeric) -> Result { + let r = v.as_ratio()?; + if v.unit.is_percent() { + Ok(r * 255 / 100) } else { - val.as_ratio().map_err(|e| e.to_string()) + Ok(r) } } diff --git a/src/sass/functions/color/other.rs b/src/sass/functions/color/other.rs index 355b7ee6b..69c9d4745 100644 --- a/src/sass/functions/color/other.rs +++ b/src/sass/functions/color/other.rs @@ -1,13 +1,13 @@ use super::{ - check, check_color, check_expl_pct_r, check_pct_expl_rational_pm1, - check_rational, check_rational_byte, check_rational_fract, - check_rational_pm1, check_rational_pmbyte, expected_to, get_checked, - get_color, make_call, CheckedArg, Error, FunctionMap, Name, + check, check_alpha_pm, check_alpha_range, check_channel_pm, + check_channel_range, check_color, check_expl_pct, check_rational, + expected_to, get_checked, get_color, make_call, CheckedArg, Error, + FunctionMap, Name, }; use crate::css::{CallArgs, Value}; use crate::value::{Hsla, Hwba, Quotes, Rational, Rgba}; use crate::Scope; -use num_traits::{one, Signed}; +use num_traits::{one, zero, Signed}; pub fn register(f: &mut Scope) { def_va!(f, adjust(color, kwargs), |s| { @@ -24,25 +24,15 @@ pub fn register(f: &mut Scope) { return Err(Error::error("Only one positional argument is allowed. \ All other arguments must be passed by name")); } - let red = take_opt(&mut args, name!(red), check_rational_pmbyte)?; - let gre = take_opt(&mut args, name!(green), check_rational_pmbyte)?; - let blu = take_opt(&mut args, name!(blue), check_rational_pmbyte)?; + let red = take_opt(&mut args, name!(red), check_channel_pm)?; + let gre = take_opt(&mut args, name!(green), check_channel_pm)?; + let blu = take_opt(&mut args, name!(blue), check_channel_pm)?; let hue = take_opt(&mut args, name!(hue), check_rational)?; - let sat = - take_opt(&mut args, name!(saturation), check_pct_rational_pm1)?; - let lig = - take_opt(&mut args, name!(lightness), check_pct_rational_pm1)?; - let bla = take_opt( - &mut args, - name!(blackness), - check_pct_expl_rational_pm1, - )?; - let whi = take_opt( - &mut args, - name!(whiteness), - check_pct_expl_rational_pm1, - )?; - let a_adj = take_opt(&mut args, name!(alpha), check_rational_pm1)?; + let sat = take_opt(&mut args, name!(saturation), check_pct_pm)?; + let lig = take_opt(&mut args, name!(lightness), check_pct_pm)?; + let bla = take_opt(&mut args, name!(blackness), check_pct_expl_pm)?; + let whi = take_opt(&mut args, name!(whiteness), check_pct_expl_pm)?; + let a_adj = take_opt(&mut args, name!(alpha), check_alpha_pm)?; if let Some(extra) = args.named.keys().iter().next() { return Err(Error::error(format!( @@ -111,34 +101,14 @@ pub fn register(f: &mut Scope) { return Err(Error::error("Only one positional argument is allowed. \ All other arguments must be passed by name")); } - let red = - take_opt(&mut args, name!(red), check_pct_expl_rational_pm1)?; - let gre = - take_opt(&mut args, name!(green), check_pct_expl_rational_pm1)?; - let blu = - take_opt(&mut args, name!(blue), check_pct_expl_rational_pm1)?; - let sat = take_opt( - &mut args, - name!(saturation), - check_pct_expl_rational_pm1, - )?; - let lig = take_opt( - &mut args, - name!(lightness), - check_pct_expl_rational_pm1, - )?; - let bla = take_opt( - &mut args, - name!(blackness), - check_pct_expl_rational_pm1, - )?; - let whi = take_opt( - &mut args, - name!(whiteness), - check_pct_expl_rational_pm1, - )?; - let a_adj = - take_opt(&mut args, name!(alpha), check_pct_expl_rational_pm1)?; + let red = take_opt(&mut args, name!(red), check_pct_expl_pm)?; + let gre = take_opt(&mut args, name!(green), check_pct_expl_pm)?; + let blu = take_opt(&mut args, name!(blue), check_pct_expl_pm)?; + let sat = take_opt(&mut args, name!(saturation), check_pct_expl_pm)?; + let lig = take_opt(&mut args, name!(lightness), check_pct_expl_pm)?; + let bla = take_opt(&mut args, name!(blackness), check_pct_expl_pm)?; + let whi = take_opt(&mut args, name!(whiteness), check_pct_expl_pm)?; + let a_adj = take_opt(&mut args, name!(alpha), check_pct_expl_pm)?; if let Some(extra) = args.named.keys().iter().next() { return Err(Error::error(format!( @@ -207,17 +177,16 @@ pub fn register(f: &mut Scope) { return Err(Error::error("Only one positional argument is allowed. \ All other arguments must be passed by name")); } - let red = take_opt(&mut args, name!(red), check_rational_byte)?; - let gre = take_opt(&mut args, name!(green), check_rational_byte)?; - let blu = take_opt(&mut args, name!(blue), check_rational_byte)?; + let red = take_opt(&mut args, name!(red), check_channel_range)?; + let gre = take_opt(&mut args, name!(green), check_channel_range)?; + let blu = take_opt(&mut args, name!(blue), check_channel_range)?; let hue = take_opt(&mut args, name!(hue), check_rational)?; let sat = - take_opt(&mut args, name!(saturation), check_pct_rational_p1)?; - let lig = - take_opt(&mut args, name!(lightness), check_pct_rational_p1)?; - let bla = take_opt(&mut args, name!(blackness), check_expl_pct_r)?; - let whi = take_opt(&mut args, name!(whiteness), check_expl_pct_r)?; - let alp = take_opt(&mut args, name!(alpha), check_rational_fract)?; + take_opt(&mut args, name!(saturation), check_pct_opt_range)?; + let lig = take_opt(&mut args, name!(lightness), check_pct_opt_range)?; + let bla = take_opt(&mut args, name!(blackness), check_expl_pct)?; + let whi = take_opt(&mut args, name!(whiteness), check_expl_pct)?; + let alp = take_opt(&mut args, name!(alpha), check_alpha_range)?; if let Some(extra) = args.named.keys().iter().next() { return Err(Error::error(format!( @@ -285,13 +254,13 @@ pub fn expose(m: &Scope, global: &mut FunctionMap) { let mut f = Scope::builtin_module("sass:color"); def!(f, fade_in(color, amount), |s| { let mut col = get_color(s, "color")?; - let amount = get_checked(s, name!(amount), check_rational_fract)?; + let amount = get_checked(s, name!(amount), check_alpha_range)?; col.set_alpha(col.get_alpha() + amount); Ok(col.into()) }); def!(f, fade_out(color, amount), |s| { let mut col = get_color(s, "color")?; - let amount = get_checked(s, name!(amount), check_rational_fract)?; + let amount = get_checked(s, name!(amount), check_alpha_range)?; col.set_alpha(col.get_alpha() - amount); Ok(col.into()) }); @@ -319,22 +288,32 @@ where .transpose() } -fn check_pct_rational_pm1(v: Value) -> Result { +fn check_pct_pm(v: Value) -> Result { let val = check::numeric(v)?; if val.value.clone().abs() > 100.into() { Err(expected_to(&val, "be within -100% and 100%")) } else { - let r = val.value.as_ratio().map_err(|e| e.to_string())?; - Ok(r / 100) + Ok(val.as_ratio()? / 100) } } -fn check_pct_rational_p1(v: Value) -> Result { +fn check_pct_expl_pm(v: Value) -> Result { + let val = check::numeric(v)?; + if !val.unit.is_percent() { + return Err(expected_to(&val, "have unit \"%\"")); + } + if val.value.clone().abs() > 100.into() { + Err(expected_to(&val, "be within -100% and 100%")) + } else { + Ok(val.as_ratio()? / 100) + } +} + +fn check_pct_opt_range(v: Value) -> Result { let val = check::numeric(v)?; - if val.value < 0.into() || val.value > 100.into() { + if val.value < zero() || val.value > 100.into() { Err(expected_to(&val, "be within 0% and 100%")) } else { - let r = val.value.as_ratio().map_err(|e| e.to_string())?; - Ok(r / 100) + Ok(val.as_ratio()? / 100) } } diff --git a/src/sass/functions/color/rgb.rs b/src/sass/functions/color/rgb.rs index 83feccbfd..53f56b106 100644 --- a/src/sass/functions/color/rgb.rs +++ b/src/sass/functions/color/rgb.rs @@ -1,6 +1,6 @@ use super::channels::Channels; use super::{ - bad_arg, check, check_alpha, check_color, check_pct_rational_range, + bad_arg, check_alpha, check_channel, check_color, check_pct_range, get_checked, get_color, is_special, make_call, CheckedArg, Error, FunctionMap, }; @@ -29,7 +29,7 @@ pub fn register(f: &mut Scope) { let a = a.to_rgba(); let b = get_color(s, "color2")?; let b = b.to_rgba(); - let w = get_checked(s, name!(weight), check_pct_rational_range)?; + let w = get_checked(s, name!(weight), check_pct_range)?; let one: Rational = one(); let w_a = { @@ -57,8 +57,7 @@ pub fn register(f: &mut Scope) { match s.get("color")? { Value::Color(col, _) => { let rgba = col.to_rgba(); - let w = - get_checked(s, name!(weight), check_pct_rational_range)?; + let w = get_checked(s, name!(weight), check_pct_range)?; let inv = |v: Rational| -(v - 255) * w + v * -(w - 1); Ok(Rgba::new( inv(rgba.red()), @@ -69,8 +68,7 @@ pub fn register(f: &mut Scope) { .into()) } col => { - let w = - get_checked(s, name!(weight), check_pct_rational_range)?; + let w = get_checked(s, name!(weight), check_pct_range)?; if w == one() { match col { v @ Value::Numeric(..) => { @@ -172,25 +170,15 @@ fn rgba_from_values( Ok(make_call(fn_name.as_ref(), vec![r, g, b, a])) } else { Ok(Rgba::new( - to_int(r).named(name!(red))?, - to_int(g).named(name!(green))?, - to_int(b).named(name!(blue))?, + check_channel(r).named(name!(red))?, + check_channel(g).named(name!(green))?, + check_channel(b).named(name!(blue))?, check_alpha(a).named(name!(alpha))?, ) .into()) } } -fn to_int(v: Value) -> Result { - let v = check::numeric(v)?; - let r = v.value.as_ratio().map_err(|e| e.to_string())?; - if v.unit.is_percent() { - Ok(r * 255 / 100) - } else { - Ok(r) - } -} - #[cfg(test)] mod test { use crate::variablescope::test::do_evaluate; diff --git a/src/sass/functions/math.rs b/src/sass/functions/math.rs index 700dfed5a..a17009a4d 100644 --- a/src/sass/functions/math.rs +++ b/src/sass/functions/math.rs @@ -23,7 +23,9 @@ pub fn create_module() -> Scope { use crate::value::Operator; match (a, b) { (Value::Color(a, _), Value::Numeric(b, _)) if b.is_no_unit() => { - let bn = b.as_ratio()?; + let bn = b + .as_ratio() + .map_err(|e| Error::BadValue(e.to_string()))?; Ok((a.to_rgba().as_ref() / bn).into()) } (Value::Numeric(ref a, _), Value::Numeric(ref b, _)) => { diff --git a/src/value/mod.rs b/src/value/mod.rs index 6b480a0d6..546f70c49 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -11,7 +11,7 @@ mod unitset; pub use self::colors::{Color, Hsla, Hwba, Rgba}; pub use self::list_separator::ListSeparator; -pub use self::number::{Number, Rational}; +pub use self::number::{BadNumber, Number, Rational}; pub use self::numeric::Numeric; pub use self::operator::Operator; pub use self::quotes::Quotes; diff --git a/src/value/number.rs b/src/value/number.rs index 392b6278b..1f03c4a2a 100644 --- a/src/value/number.rs +++ b/src/value/number.rs @@ -350,7 +350,7 @@ impl NumValue { NumValue::Float(r) => r.round().into(), } } - pub fn as_ratio(&self) -> Result { + pub fn as_ratio(&self) -> Result { match self { NumValue::Rational(r) => Ok(*r), NumValue::BigRational(r) => { @@ -365,14 +365,12 @@ impl NumValue { numer /= 32; denom /= 32; if denom.is_zero() { - return Err(crate::Error::BadValue( - "Number too large".into(), - )); + return Err(BadNumber::TooLarge); } } } NumValue::Float(r) => Ratio::approximate_float(*r) - .ok_or_else(|| crate::Error::BadValue(r.to_string())), + .ok_or_else(|| BadNumber::BadFloat(*r)), } } } @@ -387,7 +385,7 @@ impl Number { /// If the value is bignum rational or floating point, it is /// approximated as long as it is withing range, otherwises an /// error is returned. - pub fn as_ratio(&self) -> Result { + pub fn as_ratio(&self) -> Result { self.value.as_ratio() } @@ -831,6 +829,30 @@ impl fmt::Debug for Number { } } +/// Error signifying that a number could not be approximated as +/// a rational. +pub enum BadNumber { + /// The number was to large. + TooLarge, + /// The number was a "special" float value. + BadFloat(f64), +} + +impl From for String { + fn from(n: BadNumber) -> String { + n.to_string() + } +} + +impl fmt::Display for BadNumber { + fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result { + match self { + BadNumber::TooLarge => out.write_str("Number too large"), + BadNumber::BadFloat(f) => write!(out, "Bad float: {}", f), + } + } +} + #[test] fn debug_integer() { assert_eq!(format!("{:?}", Number::from(17)), "Number 17 / 1",); diff --git a/src/value/numeric.rs b/src/value/numeric.rs index 3513cc090..a0b83834a 100644 --- a/src/value/numeric.rs +++ b/src/value/numeric.rs @@ -1,6 +1,5 @@ -use super::{Number, Rational, Unit, UnitSet}; +use super::{BadNumber, Number, Rational, Unit, UnitSet}; use crate::output::{Format, Formatted}; -use crate::Error; use std::fmt::{self, Display}; use std::ops::{Div, Mul, Neg}; @@ -77,7 +76,7 @@ impl Numeric { /// The unit is ignored. If the value is bignum rational or /// floating point, it is approximated as long as it is withing /// range, otherwises an error is returned. - pub fn as_ratio(&self) -> Result { + pub fn as_ratio(&self) -> Result { self.value.as_ratio() }