Add Decimal number type.

This commit is contained in:
Stephen Chung 2021-02-13 20:57:56 +08:00
parent 4fdd58f220
commit 3650b04c38
15 changed files with 477 additions and 16 deletions

View File

@ -24,6 +24,8 @@ jobs:
- "--features no_optimize" - "--features no_optimize"
- "--features no_float" - "--features no_float"
- "--features f32_float" - "--features f32_float"
- "--features decimal"
- "--features no_float,decimal"
- "--tests --features only_i32" - "--tests --features only_i32"
- "--features only_i64" - "--features only_i64"
- "--features no_index" - "--features no_index"

View File

@ -37,6 +37,7 @@ no_float = [] # no floating-point
f32_float = [] # set FLOAT=f32 f32_float = [] # set FLOAT=f32
only_i32 = [] # set INT=i32 (useful for 32-bit systems) only_i32 = [] # set INT=i32 (useful for 32-bit systems)
only_i64 = [] # set INT=i64 (default) and disable support for all other integer types only_i64 = [] # set INT=i64 (default) and disable support for all other integer types
decimal = [ "rust_decimal" ] # add the Decimal number type
no_index = [] # no arrays and indexing no_index = [] # no arrays and indexing
no_object = [] # no custom objects no_object = [] # no custom objects
no_function = [ "no_closure" ] # no script-defined functions (meaning no closures) no_function = [ "no_closure" ] # no script-defined functions (meaning no closures)
@ -98,6 +99,11 @@ version = "0.2"
default_features = false default_features = false
optional = true optional = true
[dependencies.rust_decimal]
version = "1.10"
default_features = false
optional = true
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
instant= { version = "0.1" } # WASM implementation of std::time::Instant instant= { version = "0.1" } # WASM implementation of std::time::Instant

View File

@ -25,6 +25,7 @@ New features
------------ ------------
* Scientific notation is supported for floating-point number literals. * Scientific notation is supported for floating-point number literals.
* A new feature, `decimal`, enables the [`Decimal`](https://crates.io/crates/rust_decimal) data type. When both `no_float` and `decimal` features are enabled, floating-point literals parse to `Decimal`.
Enhancements Enhancements
------------ ------------

View File

@ -10,7 +10,6 @@ use crate::stdlib::{
hash::Hash, hash::Hash,
num::{NonZeroU64, NonZeroUsize}, num::{NonZeroU64, NonZeroUsize},
ops::{Add, AddAssign}, ops::{Add, AddAssign},
str::FromStr,
string::String, string::String,
vec, vec,
vec::Vec, vec::Vec,
@ -22,7 +21,7 @@ use crate::{
}; };
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::FLOAT; use crate::{stdlib::str::FromStr, FLOAT};
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::Array; use crate::Array;

View File

@ -15,6 +15,9 @@ use crate::{FnPtr, ImmutableString, INT};
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::{ast::FloatWrapper, FLOAT}; use crate::{ast::FloatWrapper, FLOAT};
#[cfg(feature = "decimal")]
use rust_decimal::Decimal;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::Array; use crate::Array;
@ -25,6 +28,7 @@ use crate::Map;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use crate::stdlib::time::Instant; use crate::stdlib::time::Instant;
use fmt::Debug;
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use instant::Instant; use instant::Instant;
@ -155,6 +159,8 @@ pub enum Union {
Int(INT, AccessMode), Int(INT, AccessMode),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Float(FloatWrapper, AccessMode), Float(FloatWrapper, AccessMode),
#[cfg(feature = "decimal")]
Decimal(Box<Decimal>, AccessMode),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Array(Box<Array>, AccessMode), Array(Box<Array>, AccessMode),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -305,6 +311,8 @@ impl Dynamic {
Union::Int(_, _) => TypeId::of::<INT>(), Union::Int(_, _) => TypeId::of::<INT>(),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(_, _) => TypeId::of::<FLOAT>(), Union::Float(_, _) => TypeId::of::<FLOAT>(),
#[cfg(feature = "decimal")]
Union::Decimal(_, _) => TypeId::of::<Decimal>(),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(_, _) => TypeId::of::<Array>(), Union::Array(_, _) => TypeId::of::<Array>(),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -338,6 +346,8 @@ impl Dynamic {
Union::Int(_, _) => type_name::<INT>(), Union::Int(_, _) => type_name::<INT>(),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(_, _) => type_name::<FLOAT>(), Union::Float(_, _) => type_name::<FLOAT>(),
#[cfg(feature = "decimal")]
Union::Decimal(_, _) => "decimal",
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(_, _) => "array", Union::Array(_, _) => "array",
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -408,6 +418,10 @@ pub(crate) fn map_std_type_name(name: &str) -> &str {
} else if name == type_name::<FnPtr>() { } else if name == type_name::<FnPtr>() {
"Fn" "Fn"
} else { } else {
#[cfg(feature = "decimal")]
if name == type_name::<Decimal>() {
return "decimal";
}
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
if name == type_name::<Array>() { if name == type_name::<Array>() {
return "array"; return "array";
@ -435,6 +449,8 @@ impl fmt::Display for Dynamic {
Union::Int(value, _) => fmt::Display::fmt(value, f), Union::Int(value, _) => fmt::Display::fmt(value, f),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(value, _) => fmt::Display::fmt(value, f), Union::Float(value, _) => fmt::Display::fmt(value, f),
#[cfg(feature = "decimal")]
Union::Decimal(value, _) => fmt::Display::fmt(value, f),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(value, _) => fmt::Debug::fmt(value, f), Union::Array(value, _) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -474,6 +490,8 @@ impl fmt::Debug for Dynamic {
Union::Int(value, _) => fmt::Debug::fmt(value, f), Union::Int(value, _) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(value, _) => fmt::Debug::fmt(value, f), Union::Float(value, _) => fmt::Debug::fmt(value, f),
#[cfg(feature = "decimal")]
Union::Decimal(value, _) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(value, _) => fmt::Debug::fmt(value, f), Union::Array(value, _) => fmt::Debug::fmt(value, f),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -518,6 +536,10 @@ impl Clone for Dynamic {
Union::Int(value, _) => Self(Union::Int(value, AccessMode::ReadWrite)), Union::Int(value, _) => Self(Union::Int(value, AccessMode::ReadWrite)),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(value, _) => Self(Union::Float(value, AccessMode::ReadWrite)), Union::Float(value, _) => Self(Union::Float(value, AccessMode::ReadWrite)),
#[cfg(feature = "decimal")]
Union::Decimal(ref value, _) => {
Self(Union::Decimal(value.clone(), AccessMode::ReadWrite))
}
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(ref value, _) => Self(Union::Array(value.clone(), AccessMode::ReadWrite)), Union::Array(ref value, _) => Self(Union::Array(value.clone(), AccessMode::ReadWrite)),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -582,6 +604,8 @@ impl Dynamic {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(_, access) => access, Union::Float(_, access) => access,
#[cfg(feature = "decimal")]
Union::Decimal(_, access) => access,
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(_, access) => access, Union::Array(_, access) => access,
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -605,6 +629,8 @@ impl Dynamic {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
Union::Float(_, access) => *access = typ, Union::Float(_, access) => *access = typ,
#[cfg(feature = "decimal")]
Union::Decimal(_, access) => *access = typ,
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(_, access) => *access = typ, Union::Array(_, access) => *access = typ,
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -687,6 +713,13 @@ impl Dynamic {
.clone() .clone()
.into(); .into();
} }
#[cfg(feature = "decimal")]
if TypeId::of::<T>() == TypeId::of::<Decimal>() {
return <dyn Any>::downcast_ref::<Decimal>(&value)
.unwrap()
.clone()
.into();
}
if TypeId::of::<T>() == TypeId::of::<bool>() { if TypeId::of::<T>() == TypeId::of::<bool>() {
return <dyn Any>::downcast_ref::<bool>(&value) return <dyn Any>::downcast_ref::<bool>(&value)
.unwrap() .unwrap()
@ -845,6 +878,14 @@ impl Dynamic {
}; };
} }
#[cfg(feature = "decimal")]
if TypeId::of::<T>() == TypeId::of::<Decimal>() {
return match self.0 {
Union::Decimal(value, _) => unsafe_try_cast(*value),
_ => None,
};
}
if TypeId::of::<T>() == TypeId::of::<bool>() { if TypeId::of::<T>() == TypeId::of::<bool>() {
return match self.0 { return match self.0 {
Union::Bool(value, _) => unsafe_try_cast(value), Union::Bool(value, _) => unsafe_try_cast(value),
@ -1113,6 +1154,13 @@ impl Dynamic {
_ => None, _ => None,
}; };
} }
#[cfg(feature = "decimal")]
if TypeId::of::<T>() == TypeId::of::<Decimal>() {
return match &self.0 {
Union::Decimal(value, _) => <dyn Any>::downcast_ref::<T>(value.as_ref()),
_ => None,
};
}
if TypeId::of::<T>() == TypeId::of::<bool>() { if TypeId::of::<T>() == TypeId::of::<bool>() {
return match &self.0 { return match &self.0 {
Union::Bool(value, _) => <dyn Any>::downcast_ref::<T>(value), Union::Bool(value, _) => <dyn Any>::downcast_ref::<T>(value),
@ -1202,6 +1250,13 @@ impl Dynamic {
_ => None, _ => None,
}; };
} }
#[cfg(feature = "decimal")]
if TypeId::of::<T>() == TypeId::of::<Decimal>() {
return match &mut self.0 {
Union::Decimal(value, _) => <dyn Any>::downcast_mut::<T>(value.as_mut()),
_ => None,
};
}
if TypeId::of::<T>() == TypeId::of::<bool>() { if TypeId::of::<T>() == TypeId::of::<bool>() {
return match &mut self.0 { return match &mut self.0 {
Union::Bool(value, _) => <dyn Any>::downcast_mut::<T>(value), Union::Bool(value, _) => <dyn Any>::downcast_mut::<T>(value),
@ -1277,6 +1332,8 @@ impl Dynamic {
} }
/// Cast the [`Dynamic`] as the system floating-point type [`FLOAT`] and return it. /// Cast the [`Dynamic`] as the system floating-point type [`FLOAT`] and return it.
/// Returns the name of the actual type if the cast fails. /// Returns the name of the actual type if the cast fails.
///
/// Not available under `no_float`.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[inline(always)] #[inline(always)]
pub fn as_float(&self) -> Result<FLOAT, &'static str> { pub fn as_float(&self) -> Result<FLOAT, &'static str> {
@ -1287,6 +1344,20 @@ impl Dynamic {
_ => Err(self.type_name()), _ => Err(self.type_name()),
} }
} }
/// Cast the [`Dynamic`] as a [`Decimal`] and return it.
/// Returns the name of the actual type if the cast fails.
///
/// Available only under `decimal`.
#[cfg(feature = "decimal")]
#[inline(always)]
pub fn as_decimal(self) -> Result<Decimal, &'static str> {
match self.0 {
Union::Decimal(n, _) => Ok(*n),
#[cfg(not(feature = "no_closure"))]
Union::Shared(_, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()),
_ => Err(self.type_name()),
}
}
/// Cast the [`Dynamic`] as a [`bool`] and return it. /// Cast the [`Dynamic`] as a [`bool`] and return it.
/// Returns the name of the actual type if the cast fails. /// Returns the name of the actual type if the cast fails.
#[inline(always)] #[inline(always)]
@ -1312,7 +1383,7 @@ impl Dynamic {
/// Cast the [`Dynamic`] as a [`String`] and return the string slice. /// Cast the [`Dynamic`] as a [`String`] and return the string slice.
/// Returns the name of the actual type if the cast fails. /// Returns the name of the actual type if the cast fails.
/// ///
/// Cast is failing if `self` is Shared Dynamic /// Fails if `self` is _shared_.
#[inline(always)] #[inline(always)]
pub fn as_str(&self) -> Result<&str, &'static str> { pub fn as_str(&self) -> Result<&str, &'static str> {
match &self.0 { match &self.0 {
@ -1386,6 +1457,16 @@ impl From<FloatWrapper> for Dynamic {
Self(Union::Float(value, AccessMode::ReadWrite)) Self(Union::Float(value, AccessMode::ReadWrite))
} }
} }
#[cfg(feature = "decimal")]
impl From<Decimal> for Dynamic {
#[inline(always)]
fn from(value: Decimal) -> Self {
Self(Union::Decimal(
Box::new(value.into()),
AccessMode::ReadWrite,
))
}
}
impl From<char> for Dynamic { impl From<char> for Dynamic {
#[inline(always)] #[inline(always)]
fn from(value: char) -> Self { fn from(value: char) -> Self {

View File

@ -197,6 +197,10 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
combine_with_exported_module!(lib, "f32", f32_functions); combine_with_exported_module!(lib, "f32", f32_functions);
combine_with_exported_module!(lib, "f64", f64_functions); combine_with_exported_module!(lib, "f64", f64_functions);
} }
// Decimal functions
#[cfg(feature = "decimal")]
combine_with_exported_module!(lib, "decimal", decimal_functions);
}); });
gen_arithmetic_functions!(arith_basic => INT); gen_arithmetic_functions!(arith_basic => INT);
@ -257,7 +261,7 @@ mod f32_functions {
} }
#[rhai_fn(name = "+")] #[rhai_fn(name = "+")]
pub fn plus(x: f32) -> f32 { pub fn plus(x: f32) -> f32 {
-x x
} }
pub fn abs(x: f32) -> f32 { pub fn abs(x: f32) -> f32 {
x.abs() x.abs()
@ -320,7 +324,7 @@ mod f64_functions {
} }
#[rhai_fn(name = "+")] #[rhai_fn(name = "+")]
pub fn plus(x: f64) -> f64 { pub fn plus(x: f64) -> f64 {
-x x
} }
pub fn abs(x: f64) -> f64 { pub fn abs(x: f64) -> f64 {
x.abs() x.abs()
@ -346,3 +350,130 @@ mod f64_functions {
} }
} }
} }
#[cfg(feature = "decimal")]
#[export_module]
mod decimal_functions {
use rust_decimal::{prelude::Zero, Decimal};
#[rhai_fn(name = "+", return_raw)]
pub fn add_dd(x: Decimal, y: Decimal) -> Result<Dynamic, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) {
x.checked_add(y)
.ok_or_else(|| make_err(format!("Addition overflow: {} + {}", x, y)))
.map(Dynamic::from)
} else {
Ok(Dynamic::from(x + y))
}
}
#[rhai_fn(name = "+", return_raw)]
pub fn add_id(x: INT, y: Decimal) -> Result<Dynamic, Box<EvalAltResult>> {
add_dd(x.into(), y)
}
#[rhai_fn(name = "+", return_raw)]
pub fn add_di(x: Decimal, y: INT) -> Result<Dynamic, Box<EvalAltResult>> {
add_dd(x, y.into())
}
#[rhai_fn(name = "-", return_raw)]
pub fn subtract_dd(x: Decimal, y: Decimal) -> Result<Dynamic, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) {
x.checked_sub(y)
.ok_or_else(|| make_err(format!("Subtraction overflow: {} - {}", x, y)))
.map(Dynamic::from)
} else {
Ok(Dynamic::from(x - y))
}
}
#[rhai_fn(name = "-", return_raw)]
pub fn subtract_id(x: INT, y: Decimal) -> Result<Dynamic, Box<EvalAltResult>> {
subtract_dd(x.into(), y)
}
#[rhai_fn(name = "-", return_raw)]
pub fn subtract_di(x: Decimal, y: INT) -> Result<Dynamic, Box<EvalAltResult>> {
subtract_dd(x, y.into())
}
#[rhai_fn(name = "*", return_raw)]
pub fn multiply_dd(x: Decimal, y: Decimal) -> Result<Dynamic, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) {
x.checked_mul(y)
.ok_or_else(|| make_err(format!("Multiplication overflow: {} * {}", x, y)))
.map(Dynamic::from)
} else {
Ok(Dynamic::from(x * y))
}
}
#[rhai_fn(name = "*", return_raw)]
pub fn multiply_id(x: INT, y: Decimal) -> Result<Dynamic, Box<EvalAltResult>> {
multiply_dd(x.into(), y)
}
#[rhai_fn(name = "*", return_raw)]
pub fn multiply_di(x: Decimal, y: INT) -> Result<Dynamic, Box<EvalAltResult>> {
multiply_dd(x, y.into())
}
#[rhai_fn(name = "/", return_raw)]
pub fn divide_dd(x: Decimal, y: Decimal) -> Result<Dynamic, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) {
// Detect division by zero
if y == Decimal::zero() {
Err(make_err(format!("Division by zero: {} / {}", x, y)))
} else {
x.checked_div(y)
.ok_or_else(|| make_err(format!("Division overflow: {} / {}", x, y)))
.map(Dynamic::from)
}
} else {
Ok(Dynamic::from(x / y))
}
}
#[rhai_fn(name = "/", return_raw)]
pub fn divide_id(x: INT, y: Decimal) -> Result<Dynamic, Box<EvalAltResult>> {
divide_dd(x.into(), y)
}
#[rhai_fn(name = "/", return_raw)]
pub fn divide_di(x: Decimal, y: INT) -> Result<Dynamic, Box<EvalAltResult>> {
divide_dd(x, y.into())
}
#[rhai_fn(name = "%", return_raw)]
pub fn modulo_dd(x: Decimal, y: Decimal) -> Result<Dynamic, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) {
x.checked_rem(y)
.ok_or_else(|| {
make_err(format!(
"Modulo division by zero or overflow: {} % {}",
x, y
))
})
.map(Dynamic::from)
} else {
Ok(Dynamic::from(x % y))
}
}
#[rhai_fn(name = "%", return_raw)]
pub fn modulo_id(x: INT, y: Decimal) -> Result<Dynamic, Box<EvalAltResult>> {
modulo_dd(x.into(), y)
}
#[rhai_fn(name = "%", return_raw)]
pub fn modulo_di(x: Decimal, y: INT) -> Result<Dynamic, Box<EvalAltResult>> {
modulo_dd(x, y.into())
}
#[rhai_fn(name = "-")]
pub fn neg(x: Decimal) -> Decimal {
-x
}
#[rhai_fn(name = "+")]
pub fn plus(x: Decimal) -> Decimal {
x
}
pub fn abs(x: Decimal) -> Decimal {
x.abs()
}
pub fn sign(x: Decimal) -> INT {
if x == Decimal::zero() {
0
} else if x.is_sign_negative() {
-1
} else {
1
}
}
}

View File

@ -16,6 +16,9 @@ use num_traits::float::Float;
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::stdlib::format; use crate::stdlib::format;
#[cfg(feature = "decimal")]
use rust_decimal::Decimal;
#[allow(dead_code)] #[allow(dead_code)]
#[cfg(feature = "only_i32")] #[cfg(feature = "only_i32")]
pub const MAX_INT: INT = i32::MAX; pub const MAX_INT: INT = i32::MAX;
@ -23,7 +26,7 @@ pub const MAX_INT: INT = i32::MAX;
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
pub const MAX_INT: INT = i64::MAX; pub const MAX_INT: INT = i64::MAX;
macro_rules! gen_conversion_functions { macro_rules! gen_conversion_as_functions {
($root:ident => $func_name:ident ( $($arg_type:ident),+ ) -> $result_type:ty) => { ($root:ident => $func_name:ident ( $($arg_type:ident),+ ) -> $result_type:ty) => {
pub mod $root { $(pub mod $arg_type { pub mod $root { $(pub mod $arg_type {
use super::super::*; use super::super::*;
@ -36,6 +39,19 @@ macro_rules! gen_conversion_functions {
} }
} }
macro_rules! gen_conversion_into_functions {
($root:ident => $func_name:ident ( $($arg_type:ident),+ ) -> $result_type:ty) => {
pub mod $root { $(pub mod $arg_type {
use super::super::*;
#[export_fn]
pub fn $func_name(x: $arg_type) -> $result_type {
x.into()
}
})* }
}
}
macro_rules! reg_functions { macro_rules! reg_functions {
($mod_name:ident += $root:ident :: $func_name:ident ( $($arg_type:ident),+ ) ) => { $( ($mod_name:ident += $root:ident :: $func_name:ident ( $($arg_type:ident),+ ) ) => { $(
set_exported_fn!($mod_name, stringify!($func_name), $root::$arg_type::$func_name); set_exported_fn!($mod_name, stringify!($func_name), $root::$arg_type::$func_name);
@ -76,6 +92,18 @@ def_package!(crate:BasicMathPackage:"Basic mathematic functions.", lib, {
reg_functions!(lib += num_128_to_float::to_float(i128, u128)); reg_functions!(lib += num_128_to_float::to_float(i128, u128));
} }
} }
// Decimal functions
#[cfg(feature = "decimal")]
{
combine_with_exported_module!(lib, "decimal", decimal_functions);
reg_functions!(lib += basic_to_decimal::to_decimal(INT));
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
reg_functions!(lib += numbers_to_decimal::to_decimal(i8, u8, i16, u16, i32, u32, i64, u64));
}
}); });
#[export_module] #[export_module]
@ -267,27 +295,75 @@ mod float_functions {
} }
} }
#[cfg(feature = "decimal")]
#[export_module]
mod decimal_functions {
use rust_decimal::Decimal;
#[rhai_fn(name = "floor", get = "floor")]
pub fn floor(x: Decimal) -> Decimal {
x.floor()
}
#[rhai_fn(name = "ceiling", get = "ceiling")]
pub fn ceiling(x: Decimal) -> Decimal {
x.ceil()
}
#[rhai_fn(name = "round", get = "round")]
pub fn round(x: Decimal) -> Decimal {
x.ceil()
}
#[rhai_fn(name = "int", get = "int")]
pub fn int(x: Decimal) -> Decimal {
x.trunc()
}
#[rhai_fn(name = "fraction", get = "fraction")]
pub fn fraction(x: Decimal) -> Decimal {
x.fract()
}
#[rhai_fn(return_raw)]
pub fn parse_decimal(s: &str) -> Result<Dynamic, Box<EvalAltResult>> {
s.trim()
.parse::<Decimal>()
.map(Into::<Dynamic>::into)
.map_err(|err| {
EvalAltResult::ErrorArithmetic(
format!("Error parsing decimal number '{}': {}", s, err),
Position::NONE,
)
.into()
})
}
}
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
gen_conversion_functions!(basic_to_float => to_float (INT) -> FLOAT); gen_conversion_as_functions!(basic_to_float => to_float (INT) -> FLOAT);
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
gen_conversion_functions!(numbers_to_float => to_float (i8, u8, i16, u16, i32, u32, i64, u64) -> FLOAT); gen_conversion_as_functions!(numbers_to_float => to_float (i8, u8, i16, u16, i32, u32, i64, u64) -> FLOAT);
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
gen_conversion_functions!(num_128_to_float => to_float (i128, u128) -> FLOAT); gen_conversion_as_functions!(num_128_to_float => to_float (i128, u128) -> FLOAT);
gen_conversion_functions!(basic_to_int => to_int (char) -> INT); gen_conversion_as_functions!(basic_to_int => to_int (char) -> INT);
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
gen_conversion_functions!(numbers_to_int => to_int (i8, u8, i16, u16, i32, u32, i64, u64) -> INT); gen_conversion_as_functions!(numbers_to_int => to_int (i8, u8, i16, u16, i32, u32, i64, u64) -> INT);
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
gen_conversion_functions!(num_128_to_int => to_int (i128, u128) -> INT); gen_conversion_as_functions!(num_128_to_int => to_int (i128, u128) -> INT);
#[cfg(feature = "decimal")]
gen_conversion_into_functions!(basic_to_decimal => to_decimal (INT) -> Decimal);
#[cfg(feature = "decimal")]
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
gen_conversion_into_functions!(numbers_to_decimal => to_decimal (i8, u8, i16, u16, i32, u32, i64, u64) -> Decimal);

View File

@ -15,6 +15,9 @@ use crate::Array;
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::Map; use crate::Map;
#[cfg(feature = "decimal")]
use rust_decimal::Decimal;
const FUNC_TO_STRING: &'static str = "to_string"; const FUNC_TO_STRING: &'static str = "to_string";
const FUNC_TO_DEBUG: &'static str = "to_debug"; const FUNC_TO_DEBUG: &'static str = "to_debug";
@ -73,6 +76,12 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin
reg_print_functions!(lib += print_float_32; f32); reg_print_functions!(lib += print_float_32; f32);
reg_debug_functions!(lib += print_float_32; f32); reg_debug_functions!(lib += print_float_32; f32);
} }
#[cfg(feature = "decimal")]
{
reg_print_functions!(lib += print_decimal; Decimal);
reg_debug_functions!(lib += debug_decimal; Decimal);
}
}); });
fn to_string<T: Display>(x: &mut T) -> ImmutableString { fn to_string<T: Display>(x: &mut T) -> ImmutableString {
@ -133,6 +142,12 @@ gen_functions!(print_float_64 => print_f64(f64));
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
gen_functions!(print_float_32 => print_f32(f32)); gen_functions!(print_float_32 => print_f32(f32));
#[cfg(feature = "decimal")]
gen_functions!(print_decimal => to_string(Decimal));
#[cfg(feature = "decimal")]
gen_functions!(debug_decimal => to_debug(Decimal));
// Register print and debug // Register print and debug
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]

View File

@ -958,6 +958,12 @@ fn parse_primary(
input.next().unwrap(); input.next().unwrap();
Expr::FloatConstant(x, settings.pos) Expr::FloatConstant(x, settings.pos)
} }
#[cfg(feature = "decimal")]
Token::DecimalConstant(x) => {
let x = (*x).into();
input.next().unwrap();
Expr::DynamicConstant(Box::new(x), settings.pos)
}
// { - block statement as expression // { - block statement as expression
Token::LeftBrace if settings.allow_stmt_expr => { Token::LeftBrace if settings.allow_stmt_expr => {

View File

@ -128,12 +128,26 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
Union::Bool(_, _) => self.deserialize_bool(visitor), Union::Bool(_, _) => self.deserialize_bool(visitor),
Union::Str(_, _) => self.deserialize_str(visitor), Union::Str(_, _) => self.deserialize_str(visitor),
Union::Char(_, _) => self.deserialize_char(visitor), Union::Char(_, _) => self.deserialize_char(visitor),
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
Union::Int(_, _) => self.deserialize_i64(visitor), Union::Int(_, _) => self.deserialize_i64(visitor),
#[cfg(feature = "only_i32")] #[cfg(feature = "only_i32")]
Union::Int(_, _) => self.deserialize_i32(visitor), Union::Int(_, _) => self.deserialize_i32(visitor),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[cfg(not(feature = "f32_float"))]
Union::Float(_, _) => self.deserialize_f64(visitor), Union::Float(_, _) => self.deserialize_f64(visitor),
#[cfg(not(feature = "no_float"))]
#[cfg(feature = "f32_float")]
Union::Float(_, _) => self.deserialize_f32(visitor),
#[cfg(feature = "decimal")]
#[cfg(not(feature = "f32_float"))]
Union::Decimal(_, _) => self.deserialize_f64(visitor),
#[cfg(feature = "decimal")]
#[cfg(feature = "f32_float")]
Union::Decimal(_, _) => self.deserialize_f32(visitor),
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(_, _) => self.deserialize_seq(visitor), Union::Array(_, _) => self.deserialize_seq(visitor),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -278,6 +292,19 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
.map_or_else(|| self.type_error(), |&x| _visitor.visit_f32(x)); .map_or_else(|| self.type_error(), |&x| _visitor.visit_f32(x));
#[cfg(feature = "no_float")] #[cfg(feature = "no_float")]
#[cfg(feature = "decimal")]
{
use rust_decimal::prelude::ToPrimitive;
return self
.value
.downcast_ref::<rust_decimal::Decimal>()
.and_then(|&x| x.to_f32())
.map_or_else(|| self.type_error(), |v| _visitor.visit_f32(v));
}
#[cfg(feature = "no_float")]
#[cfg(not(feature = "decimal"))]
return self.type_error_str("f32"); return self.type_error_str("f32");
} }
@ -289,6 +316,19 @@ impl<'de> Deserializer<'de> for &mut DynamicDeserializer<'de> {
.map_or_else(|| self.type_error(), |&x| _visitor.visit_f64(x)); .map_or_else(|| self.type_error(), |&x| _visitor.visit_f64(x));
#[cfg(feature = "no_float")] #[cfg(feature = "no_float")]
#[cfg(feature = "decimal")]
{
use rust_decimal::prelude::ToPrimitive;
return self
.value
.downcast_ref::<rust_decimal::Decimal>()
.and_then(|&x| x.to_f64())
.map_or_else(|| self.type_error(), |v| _visitor.visit_f64(v));
}
#[cfg(feature = "no_float")]
#[cfg(not(feature = "decimal"))]
return self.type_error_str("f64"); return self.type_error_str("f64");
} }

View File

@ -86,6 +86,27 @@ impl<'d> Visitor<'d> for DynamicVisitor {
return self.visit_f32(v as f32); return self.visit_f32(v as f32);
} }
#[cfg(feature = "no_float")]
#[cfg(feature = "decimal")]
fn visit_f32<E: Error>(self, v: f32) -> Result<Self::Value, E> {
use crate::stdlib::convert::TryFrom;
use rust_decimal::Decimal;
Decimal::try_from(v)
.map(|v| v.into())
.map_err(Error::custom)
}
#[cfg(feature = "no_float")]
#[cfg(feature = "decimal")]
fn visit_f64<E: Error>(self, v: f64) -> Result<Self::Value, E> {
use crate::stdlib::convert::TryFrom;
use rust_decimal::Decimal;
Decimal::try_from(v)
.map(|v| v.into())
.map_err(Error::custom)
}
fn visit_char<E: Error>(self, v: char) -> Result<Self::Value, E> { fn visit_char<E: Error>(self, v: char) -> Result<Self::Value, E> {
self.visit_string(v.to_string()) self.visit_string(v.to_string())
} }

View File

@ -214,11 +214,35 @@ impl Serializer for &mut DynamicSerializer {
} }
fn serialize_f32(self, v: f32) -> Result<Self::Ok, Box<EvalAltResult>> { fn serialize_f32(self, v: f32) -> Result<Self::Ok, Box<EvalAltResult>> {
Ok(Dynamic::from(v)) #[cfg(any(not(feature = "no_float"), not(feature = "decimal")))]
return Ok(Dynamic::from(v));
#[cfg(feature = "no_float")]
#[cfg(feature = "decimal")]
{
use crate::stdlib::convert::TryFrom;
use rust_decimal::Decimal;
Decimal::try_from(v)
.map(|v| v.into())
.map_err(Error::custom)
}
} }
fn serialize_f64(self, v: f64) -> Result<Self::Ok, Box<EvalAltResult>> { fn serialize_f64(self, v: f64) -> Result<Self::Ok, Box<EvalAltResult>> {
Ok(Dynamic::from(v)) #[cfg(any(not(feature = "no_float"), not(feature = "decimal")))]
return Ok(Dynamic::from(v));
#[cfg(feature = "no_float")]
#[cfg(feature = "decimal")]
{
use crate::stdlib::convert::TryFrom;
use rust_decimal::Decimal;
Decimal::try_from(v)
.map(|v| v.into())
.map_err(Error::custom)
}
} }
fn serialize_char(self, v: char) -> Result<Self::Ok, Box<EvalAltResult>> { fn serialize_char(self, v: char) -> Result<Self::Ok, Box<EvalAltResult>> {

View File

@ -12,16 +12,42 @@ impl Serialize for Dynamic {
Union::Bool(x, _) => ser.serialize_bool(*x), Union::Bool(x, _) => ser.serialize_bool(*x),
Union::Str(s, _) => ser.serialize_str(s.as_str()), Union::Str(s, _) => ser.serialize_str(s.as_str()),
Union::Char(c, _) => ser.serialize_str(&c.to_string()), Union::Char(c, _) => ser.serialize_str(&c.to_string()),
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
Union::Int(x, _) => ser.serialize_i64(*x), Union::Int(x, _) => ser.serialize_i64(*x),
#[cfg(feature = "only_i32")] #[cfg(feature = "only_i32")]
Union::Int(x, _) => ser.serialize_i32(*x), Union::Int(x, _) => ser.serialize_i32(*x),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[cfg(not(feature = "f32_float"))] #[cfg(not(feature = "f32_float"))]
Union::Float(x, _) => ser.serialize_f64(**x), Union::Float(x, _) => ser.serialize_f64(**x),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[cfg(feature = "f32_float")] #[cfg(feature = "f32_float")]
Union::Float(x, _) => ser.serialize_f32(*x), Union::Float(x, _) => ser.serialize_f32(*x),
#[cfg(feature = "decimal")]
#[cfg(not(feature = "f32_float"))]
Union::Decimal(x, _) => {
use rust_decimal::prelude::ToPrimitive;
if let Some(v) = x.to_f64() {
ser.serialize_f64(v)
} else {
ser.serialize_str(&x.to_string())
}
}
#[cfg(feature = "decimal")]
#[cfg(feature = "f32_float")]
Union::Decimal(x, _) => {
use rust_decimal::prelude::ToPrimitive;
if let Some(v) = x.to_f32() {
ser.serialize_f32(v)
} else {
ser.serialize_str(&x.to_string())
}
}
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(a, _) => (**a).serialize(ser), Union::Array(a, _) => (**a).serialize(ser),
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]

View File

@ -17,6 +17,9 @@ use crate::{Engine, LexError, StaticVec, INT};
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
use crate::ast::FloatWrapper; use crate::ast::FloatWrapper;
#[cfg(feature = "decimal")]
use rust_decimal::Decimal;
type LERR = LexError; type LERR = LexError;
pub type TokenStream<'a, 't> = Peekable<TokenIterator<'a, 't>>; pub type TokenStream<'a, 't> = Peekable<TokenIterator<'a, 't>>;
@ -162,6 +165,11 @@ pub enum Token {
/// Reserved under the `no_float` feature. /// Reserved under the `no_float` feature.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
FloatConstant(FloatWrapper), FloatConstant(FloatWrapper),
/// A [`Decimal`] constant.
///
/// Requires the `decimal` feature.
#[cfg(feature = "decimal")]
DecimalConstant(Decimal),
/// An identifier. /// An identifier.
Identifier(String), Identifier(String),
/// A character constant. /// A character constant.
@ -348,6 +356,8 @@ impl Token {
IntegerConstant(i) => i.to_string().into(), IntegerConstant(i) => i.to_string().into(),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
FloatConstant(f) => f.to_string().into(), FloatConstant(f) => f.to_string().into(),
#[cfg(feature = "decimal")]
DecimalConstant(d) => d.to_string().into(),
StringConstant(_) => "string".into(), StringConstant(_) => "string".into(),
CharConstant(c) => c.to_string().into(), CharConstant(c) => c.to_string().into(),
Identifier(s) => s.clone().into(), Identifier(s) => s.clone().into(),
@ -1073,7 +1083,7 @@ fn get_next_token_inner(
result.push(next_char); result.push(next_char);
eat_next(stream, pos); eat_next(stream, pos);
} }
#[cfg(not(feature = "no_float"))] #[cfg(any(not(feature = "no_float"), feature = "decimal"))]
'.' => { '.' => {
stream.get_next().unwrap(); stream.get_next().unwrap();
@ -1183,6 +1193,10 @@ fn get_next_token_inner(
let num = let num =
num.or_else(|_| FloatWrapper::from_str(&out).map(Token::FloatConstant)); num.or_else(|_| FloatWrapper::from_str(&out).map(Token::FloatConstant));
// Then try decimal
#[cfg(feature = "decimal")]
let num = num.or_else(|_| Decimal::from_str(&out).map(Token::DecimalConstant));
return Some(( return Some((
num.unwrap_or_else(|_| { num.unwrap_or_else(|_| {
Token::LexError(LERR::MalformedNumber(result.into_iter().collect())) Token::LexError(LERR::MalformedNumber(result.into_iter().collect()))

View File

@ -5,11 +5,14 @@ use rhai::{
Dynamic, Engine, EvalAltResult, ImmutableString, INT, Dynamic, Engine, EvalAltResult, ImmutableString, INT,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use rhai::Array; use rhai::Array;
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use rhai::Map; use rhai::Map;
#[cfg(feature = "decimal")]
use rust_decimal::Decimal;
#[test] #[test]
fn test_serde_ser_primary_types() -> Result<(), Box<EvalAltResult>> { fn test_serde_ser_primary_types() -> Result<(), Box<EvalAltResult>> {
@ -25,6 +28,13 @@ fn test_serde_ser_primary_types() -> Result<(), Box<EvalAltResult>> {
assert!(to_dynamic(123.456_f32)?.is::<f32>()); assert!(to_dynamic(123.456_f32)?.is::<f32>());
} }
#[cfg(feature = "no_float")]
#[cfg(feature = "decimal")]
{
assert!(to_dynamic(123.456_f64)?.is::<Decimal>());
assert!(to_dynamic(123.456_f32)?.is::<Decimal>());
}
assert!(to_dynamic("hello".to_string())?.is::<String>()); assert!(to_dynamic("hello".to_string())?.is::<String>());
Ok(()) Ok(())
@ -301,6 +311,15 @@ fn test_serde_de_primary_types() -> Result<(), Box<EvalAltResult>> {
assert_eq!(123.456, from_dynamic::<f32>(&Dynamic::from(123.456_f32))?); assert_eq!(123.456, from_dynamic::<f32>(&Dynamic::from(123.456_f32))?);
} }
#[cfg(feature = "no_float")]
#[cfg(feature = "decimal")]
{
let d: Dynamic = Decimal::from_str("123.456").unwrap().into();
assert_eq!(123.456, from_dynamic::<f64>(&d)?);
assert_eq!(123.456, from_dynamic::<f32>(&d)?);
}
assert_eq!( assert_eq!(
"hello", "hello",
from_dynamic::<String>(&"hello".to_string().into())? from_dynamic::<String>(&"hello".to_string().into())?