diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index ff485154..661afd26 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -302,7 +302,10 @@ mod float_functions { #[cfg(feature = "decimal")] #[export_module] mod decimal_functions { - use rust_decimal::Decimal; + use rust_decimal::{ + prelude::{FromStr, RoundingStrategy}, + Decimal, + }; #[rhai_fn(name = "floor", get = "floor")] pub fn floor(x: Decimal) -> Decimal { @@ -316,14 +319,11 @@ mod decimal_functions { pub fn round(x: Decimal) -> Decimal { x.round() } - #[rhai_fn(return_raw)] + #[rhai_fn(name = "round", return_raw)] pub fn round_dp(x: Decimal, dp: INT) -> Result> { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { - return Err(make_err(format!( - "Decimal value {} round to too many decimal points: {}", - x, dp - ))); + return Ok(x.into()); } if dp < 0 { return Err(make_err(format!( @@ -333,16 +333,13 @@ mod decimal_functions { } } - Ok(Dynamic::from(x.round_dp(dp as u32))) + Ok(x.round_dp(dp as u32).into()) } #[rhai_fn(return_raw)] pub fn round_up(x: Decimal, dp: INT) -> Result> { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { - return Err(make_err(format!( - "Decimal value {} round to too many decimal points: {}", - x, dp - ))); + return Ok(x.into()); } if dp < 0 { return Err(make_err(format!( @@ -352,19 +349,16 @@ mod decimal_functions { } } - Ok(Dynamic::from(x.round_dp_with_strategy( - dp as u32, - rust_decimal::prelude::RoundingStrategy::RoundUp, - ))) + Ok( + x.round_dp_with_strategy(dp as u32, RoundingStrategy::RoundUp) + .into(), + ) } #[rhai_fn(return_raw)] pub fn round_down(x: Decimal, dp: INT) -> Result> { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { - return Err(make_err(format!( - "Decimal value {} round to too many decimal points: {}", - x, dp - ))); + return Ok(x.into()); } if dp < 0 { return Err(make_err(format!( @@ -374,19 +368,16 @@ mod decimal_functions { } } - Ok(Dynamic::from(x.round_dp_with_strategy( - dp as u32, - rust_decimal::prelude::RoundingStrategy::RoundDown, - ))) + Ok( + x.round_dp_with_strategy(dp as u32, RoundingStrategy::RoundDown) + .into(), + ) } #[rhai_fn(return_raw)] pub fn round_half_up(x: Decimal, dp: INT) -> Result> { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { - return Err(make_err(format!( - "Decimal value {} round to too many decimal points: {}", - x, dp - ))); + return Ok(x.into()); } if dp < 0 { return Err(make_err(format!( @@ -396,19 +387,16 @@ mod decimal_functions { } } - Ok(Dynamic::from(x.round_dp_with_strategy( - dp as u32, - rust_decimal::prelude::RoundingStrategy::RoundHalfUp, - ))) + Ok( + x.round_dp_with_strategy(dp as u32, RoundingStrategy::RoundHalfUp) + .into(), + ) } #[rhai_fn(return_raw)] pub fn round_half_down(x: Decimal, dp: INT) -> Result> { if cfg!(not(feature = "unchecked")) { if cfg!(not(feature = "only_i32")) && dp > (u32::MAX as INT) { - return Err(make_err(format!( - "Decimal value {} round to too many decimal points: {}", - x, dp - ))); + return Ok(x.into()); } if dp < 0 { return Err(make_err(format!( @@ -418,10 +406,10 @@ mod decimal_functions { } } - Ok(Dynamic::from(x.round_dp_with_strategy( - dp as u32, - rust_decimal::prelude::RoundingStrategy::RoundHalfDown, - ))) + Ok( + x.round_dp_with_strategy(dp as u32, RoundingStrategy::RoundHalfDown) + .into(), + ) } #[rhai_fn(name = "int", get = "int")] pub fn int(x: Decimal) -> Decimal { @@ -433,8 +421,8 @@ mod decimal_functions { } #[rhai_fn(return_raw)] pub fn parse_decimal(s: &str) -> Result> { - s.trim() - .parse::() + Decimal::from_str(s) + .or_else(|_| Decimal::from_scientific(s)) .map(Into::::into) .map_err(|err| { EvalAltResult::ErrorArithmetic( diff --git a/src/token.rs b/src/token.rs index 3aaa7f72..0717c632 100644 --- a/src/token.rs +++ b/src/token.rs @@ -22,6 +22,10 @@ use rust_decimal::Decimal; type LERR = LexError; +/// Separator character for numbers. +const NUM_SEP: char = '_'; + +/// A stream of tokens. pub type TokenStream<'a, 't> = Peekable>; /// A location (line number + character position) in the input script. @@ -1079,7 +1083,7 @@ fn get_next_token_inner( while let Some(next_char) = stream.peek_next() { match next_char { - ch if valid(ch) || ch == '_' => { + ch if valid(ch) || ch == NUM_SEP => { result.push(next_char); eat_next(stream, pos); } @@ -1174,7 +1178,7 @@ fn get_next_token_inner( // Parse number if let Some(radix) = radix_base { - let out: String = result.iter().skip(2).filter(|&&c| c != '_').collect(); + let out: String = result.iter().skip(2).filter(|&&c| c != NUM_SEP).collect(); return Some(( INT::from_str_radix(&out, radix) @@ -1185,7 +1189,7 @@ fn get_next_token_inner( start_pos, )); } else { - let out: String = result.iter().filter(|&&c| c != '_').collect(); + let out: String = result.iter().filter(|&&c| c != NUM_SEP).collect(); let num = INT::from_str(&out).map(Token::IntegerConstant); // If integer parsing is unnecessary, try float instead @@ -1197,6 +1201,11 @@ fn get_next_token_inner( #[cfg(feature = "decimal")] let num = num.or_else(|_| Decimal::from_str(&out).map(Token::DecimalConstant)); + // Then try decimal in scientific notation + #[cfg(feature = "decimal")] + let num = + num.or_else(|_| Decimal::from_scientific(&out).map(Token::DecimalConstant)); + return Some(( num.unwrap_or_else(|_| { Token::LexError(LERR::MalformedNumber(result.into_iter().collect()))