diff --git a/src/ast.rs b/src/ast.rs index 01baba7f..c964b214 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -25,6 +25,9 @@ use crate::{ #[cfg(not(feature = "no_float"))] use crate::{stdlib::str::FromStr, FLOAT}; +#[cfg(not(feature = "no_float"))] +use num_traits::Float; + #[cfg(not(feature = "no_index"))] use crate::Array; @@ -776,7 +779,7 @@ pub struct Ident { impl fmt::Debug for Ident { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Ident({:?} @ {:?})", self.name, self.pos) + write!(f, "{:?} @ {:?}", self.name, self.pos) } } @@ -1440,13 +1443,13 @@ impl FnCallExpr { } } -/// A type that wraps a [`FLOAT`] and implements [`Hash`]. +/// A type that wraps a floating-point number and implements [`Hash`]. #[cfg(not(feature = "no_float"))] #[derive(Clone, Copy, PartialEq, PartialOrd)] -pub struct FloatWrapper(FLOAT); +pub struct FloatWrapper(F); #[cfg(not(feature = "no_float"))] -impl Hash for FloatWrapper { +impl Hash for FloatWrapper { #[inline(always)] fn hash(&self, state: &mut H) { self.0.to_ne_bytes().hash(state); @@ -1454,24 +1457,24 @@ impl Hash for FloatWrapper { } #[cfg(not(feature = "no_float"))] -impl AsRef for FloatWrapper { +impl AsRef for FloatWrapper { #[inline(always)] - fn as_ref(&self) -> &FLOAT { + fn as_ref(&self) -> &F { &self.0 } } #[cfg(not(feature = "no_float"))] -impl AsMut for FloatWrapper { +impl AsMut for FloatWrapper { #[inline(always)] - fn as_mut(&mut self) -> &mut FLOAT { + fn as_mut(&mut self) -> &mut F { &mut self.0 } } #[cfg(not(feature = "no_float"))] -impl crate::stdlib::ops::Deref for FloatWrapper { - type Target = FLOAT; +impl crate::stdlib::ops::Deref for FloatWrapper { + type Target = F; #[inline(always)] fn deref(&self) -> &Self::Target { @@ -1480,7 +1483,7 @@ impl crate::stdlib::ops::Deref for FloatWrapper { } #[cfg(not(feature = "no_float"))] -impl crate::stdlib::ops::DerefMut for FloatWrapper { +impl crate::stdlib::ops::DerefMut for FloatWrapper { #[inline(always)] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 @@ -1488,52 +1491,67 @@ impl crate::stdlib::ops::DerefMut for FloatWrapper { } #[cfg(not(feature = "no_float"))] -impl fmt::Debug for FloatWrapper { +impl fmt::Debug for FloatWrapper { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self, f) + fmt::Display::fmt(&self.0, f) } } #[cfg(not(feature = "no_float"))] -impl fmt::Display for FloatWrapper { +impl> fmt::Display for FloatWrapper { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - #[cfg(feature = "no_std")] - #[cfg(not(feature = "no_float"))] - use num_traits::Float; - let abs = self.0.abs(); - if abs > 10000000000000.0 || abs < 0.0000000000001 { + if abs > Self::MAX_NATURAL_FLOAT_FOR_DISPLAY.into() + || abs < Self::MIN_NATURAL_FLOAT_FOR_DISPLAY.into() + { write!(f, "{:e}", self.0) } else { - self.0.fmt(f) + fmt::Display::fmt(&self.0, f)?; + if abs.fract().is_zero() { + f.write_str(".0")?; + } + Ok(()) } } } #[cfg(not(feature = "no_float"))] -impl From for FloatWrapper { +impl From for FloatWrapper { #[inline(always)] - fn from(value: FLOAT) -> Self { + fn from(value: F) -> Self { Self::new(value) } } #[cfg(not(feature = "no_float"))] -impl FromStr for FloatWrapper { - type Err = ::Err; +impl FromStr for FloatWrapper { + type Err = ::Err; #[inline(always)] fn from_str(s: &str) -> Result { - FLOAT::from_str(s).map(Into::::into) + F::from_str(s).map(Into::::into) } } #[cfg(not(feature = "no_float"))] -impl FloatWrapper { +impl FloatWrapper { + /// Maximum floating-point number for natural display before switching to scientific notation. + pub const MAX_NATURAL_FLOAT_FOR_DISPLAY: f32 = 10000000000000.0; + + /// Minimum floating-point number for natural display before switching to scientific notation. + pub const MIN_NATURAL_FLOAT_FOR_DISPLAY: f32 = 0.0000000000001; + #[inline(always)] - pub const fn new(value: FLOAT) -> Self { + pub fn new(value: F) -> Self { + Self(value) + } +} + +impl FloatWrapper { + #[inline(always)] + pub(crate) const fn const_new(value: FLOAT) -> Self { Self(value) } } @@ -1544,7 +1562,7 @@ impl FloatWrapper { /// # Volatile Data Structure /// /// This type is volatile and may change. -#[derive(Debug, Clone, Hash)] +#[derive(Clone, Hash)] pub enum Expr { /// Dynamic constant. /// Used to hold either an [`Array`] or [`Map`][crate::Map] literal for quick cloning. @@ -1556,7 +1574,7 @@ pub enum Expr { IntegerConstant(INT, Position), /// Floating-point constant. #[cfg(not(feature = "no_float"))] - FloatConstant(FloatWrapper, Position), + FloatConstant(FloatWrapper, Position), /// Character constant. CharConstant(char, Position), /// [String][ImmutableString] constant. @@ -1613,6 +1631,80 @@ impl Default for Expr { } } +impl fmt::Debug for Expr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::DynamicConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos), + Self::BoolConstant(value, pos) => write!(f, "{} @ {:?}", value, pos), + Self::IntegerConstant(value, pos) => write!(f, "{} @ {:?}", value, pos), + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(value, pos) => write!(f, "{} @ {:?}", value, pos), + Self::CharConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos), + Self::StringConstant(value, pos) => write!(f, "{:?} @ {:?}", value, pos), + Self::FnPointer(value, pos) => write!(f, "Fn({:?}) @ {:?}", value, pos), + Self::Unit(pos) => write!(f, "() @ {:?}", pos), + Self::InterpolatedString(x) => { + f.write_str("InterpolatedString")?; + f.debug_list().entries(x.iter()).finish() + } + Self::Array(x, pos) => { + f.write_str("Array")?; + f.debug_list().entries(x.iter()).finish()?; + write!(f, " @ {:?}", pos) + } + Self::Map(x, pos) => { + f.write_str("Map")?; + f.debug_map() + .entries(x.0.iter().map(|(k, v)| (k, v))) + .finish()?; + write!(f, " @ {:?}", pos) + } + Self::Variable(i, pos, x) => { + f.write_str("Variable(")?; + match x.1 { + Some((_, ref namespace)) => write!(f, "{}::", namespace)?, + _ => (), + } + write!(f, "{}", x.2)?; + match i.map_or_else(|| x.0, |n| NonZeroUsize::new(n.get() as usize)) { + Some(n) => write!(f, " [{}]", n)?, + _ => (), + } + write!(f, ") @ {:?}", pos) + } + Self::Property(x) => write!(f, "Property({:?} @ {:?})", x.2.name, x.2.pos), + Self::Stmt(x) => { + f.write_str("Stmt")?; + f.debug_list().entries(x.statements.iter()).finish()?; + write!(f, " @ {:?}", x.pos) + } + Self::FnCall(x, pos) => { + f.debug_tuple("FnCall").field(x).finish()?; + write!(f, " @ {:?}", pos) + } + Self::Dot(x, pos) | Self::Index(x, pos) | Self::And(x, pos) | Self::Or(x, pos) => { + let op = match self { + Self::Dot(_, _) => "Dot", + Self::Index(_, _) => "Index", + Self::And(_, _) => "And", + Self::Or(_, _) => "Or", + _ => unreachable!(), + }; + + f.debug_struct(op) + .field("lhs", &x.lhs) + .field("rhs", &x.rhs) + .finish()?; + write!(f, " @ {:?}", pos) + } + Self::Custom(x, pos) => { + f.debug_tuple("Custom").field(x).finish()?; + write!(f, " @ {:?}", pos) + } + } + } +} + impl Expr { /// Get the [`Dynamic`] value of a constant expression. /// @@ -1914,7 +2006,7 @@ mod tests { assert_eq!(size_of::>(), 16); assert_eq!(size_of::(), 32); assert_eq!(size_of::>(), 32); - assert_eq!(size_of::(), 80); + assert_eq!(size_of::(), 96); assert_eq!(size_of::(), 288); assert_eq!(size_of::(), 56); assert_eq!(size_of::(), 16); diff --git a/src/dynamic.rs b/src/dynamic.rs index 3c0c23d4..92025c68 100644 --- a/src/dynamic.rs +++ b/src/dynamic.rs @@ -159,7 +159,7 @@ pub enum Union { Int(INT, AccessMode), /// A floating-point value. #[cfg(not(feature = "no_float"))] - Float(FloatWrapper, AccessMode), + Float(FloatWrapper, AccessMode), /// A fixed-precision decimal value. #[cfg(feature = "decimal")] Decimal(Box, AccessMode), @@ -682,16 +682,22 @@ impl Dynamic { pub const NEGATIVE_ONE: Dynamic = Self(Union::Int(-1, AccessMode::ReadWrite)); /// A [`Dynamic`] containing the floating-point zero. #[cfg(not(feature = "no_float"))] - pub const FLOAT_ZERO: Dynamic = - Self(Union::Float(FloatWrapper::new(0.0), AccessMode::ReadWrite)); + pub const FLOAT_ZERO: Dynamic = Self(Union::Float( + FloatWrapper::const_new(0.0), + AccessMode::ReadWrite, + )); /// A [`Dynamic`] containing the floating-point one. #[cfg(not(feature = "no_float"))] - pub const FLOAT_ONE: Dynamic = - Self(Union::Float(FloatWrapper::new(1.0), AccessMode::ReadWrite)); + pub const FLOAT_ONE: Dynamic = Self(Union::Float( + FloatWrapper::const_new(1.0), + AccessMode::ReadWrite, + )); /// A [`Dynamic`] containing the floating-point negative one. #[cfg(not(feature = "no_float"))] - pub const FLOAT_NEGATIVE_ONE: Dynamic = - Self(Union::Float(FloatWrapper::new(-1.0), AccessMode::ReadWrite)); + pub const FLOAT_NEGATIVE_ONE: Dynamic = Self(Union::Float( + FloatWrapper::const_new(-1.0), + AccessMode::ReadWrite, + )); /// Get the [`AccessMode`] for this [`Dynamic`]. pub(crate) fn access_mode(&self) -> AccessMode { @@ -1670,9 +1676,9 @@ impl From for Dynamic { } } #[cfg(not(feature = "no_float"))] -impl From for Dynamic { +impl From> for Dynamic { #[inline(always)] - fn from(value: FloatWrapper) -> Self { + fn from(value: FloatWrapper) -> Self { Self(Union::Float(value, AccessMode::ReadWrite)) } } diff --git a/src/engine.rs b/src/engine.rs index 17a84051..dfc1225c 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -52,7 +52,7 @@ pub type Precedence = NonZeroU8; // We cannot use Cow here because `eval` may load a [module][Module] and // the module name will live beyond the AST of the eval script text. // The best we can do is a shared reference. -#[derive(Debug, Clone, Default)] +#[derive(Clone, Default)] pub struct Imports(StaticVec, StaticVec>); impl Imports { @@ -147,6 +147,20 @@ impl Imports { } } +impl fmt::Debug for Imports { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Imports")?; + + if self.is_empty() { + f.debug_map().finish() + } else { + f.debug_map() + .entries(self.0.iter().zip(self.1.iter())) + .finish() + } + } +} + #[cfg(not(feature = "unchecked"))] #[cfg(debug_assertions)] #[cfg(not(feature = "no_function"))] diff --git a/src/module/mod.rs b/src/module/mod.rs index 34b23693..aa20b2df 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -7,8 +7,8 @@ use crate::fn_register::RegisterNativeFunction; use crate::stdlib::{ any::TypeId, boxed::Box, - collections::BTreeMap, - fmt, format, + collections::{BTreeMap, BTreeSet}, + fmt, iter::empty, num::NonZeroUsize, ops::{Add, AddAssign, Deref, DerefMut}, @@ -173,50 +173,36 @@ impl Default for Module { impl fmt::Debug for Module { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Module({}\n{}{}{})", - self.id - .as_ref() - .map(|id| format!("id: {:?},", id)) - .unwrap_or_default(), - if !self.modules.is_empty() { - format!( - " modules: {}\n", - self.modules - .keys() - .map(|m| m.as_str()) - .collect::>() - .join(", ") - ) - } else { - Default::default() - }, - if !self.variables.is_empty() { - format!( - " vars: {}\n", - self.variables - .iter() - .map(|(k, v)| format!("{}={:?}", k, v)) - .collect::>() - .join(", ") - ) - } else { - Default::default() - }, - if !self.functions.is_empty() { - format!( - " functions: {}\n", - self.functions - .values() - .map(|f| crate::stdlib::string::ToString::to_string(&f.func)) - .collect::>() - .join(", ") - ) - } else { - Default::default() - } - ) + let mut d = f.debug_struct("Module"); + + if let Some(ref id) = self.id { + d.field("id", id); + } + + if !self.modules.is_empty() { + d.field( + "modules", + &self + .modules + .keys() + .map(|m| m.as_str()) + .collect::>(), + ); + } + if !self.variables.is_empty() { + d.field("vars", &self.variables); + } + if !self.functions.is_empty() { + d.field( + "functions", + &self + .functions + .values() + .map(|f| crate::stdlib::string::ToString::to_string(&f.func)) + .collect::>(), + ); + } + d.finish() } } diff --git a/src/optimize.rs b/src/optimize.rs index 22eb64e6..03814584 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -17,8 +17,8 @@ use crate::stdlib::{ }; use crate::utils::get_hasher; use crate::{ - calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, Module, Position, Scope, - StaticVec, AST, + calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, ImmutableString, Module, + Position, Scope, StaticVec, AST, }; /// Level of optimization performed. @@ -799,15 +799,12 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { if x.namespace.is_none() // Non-qualified && state.optimization_level == OptimizationLevel::Simple // simple optimizations && x.num_args() == 1 - && matches!(x.args[0], Expr::StringConstant(_, _)) + && x.constant_args.len() == 1 + && x.constant_args[0].0.is::() && x.name == KEYWORD_FN_PTR => { - if let Expr::StringConstant(s, _) = mem::take(&mut x.args[0]) { - state.set_dirty(); - *expr = Expr::FnPointer(s, *pos); - } else { - unreachable!(); - } + state.set_dirty(); + *expr = Expr::FnPointer(mem::take(&mut x.constant_args[0].0).take_immutable_string().unwrap(), *pos); } // Do not call some special keywords diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index 3331f32c..ab589415 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -9,7 +9,7 @@ def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, { mod fn_ptr_functions { #[rhai_fn(name = "name", get = "name", pure)] pub fn name(f: &mut FnPtr) -> ImmutableString { - f.get_fn_name().clone() + f.get_fn_name().as_str().into() } #[cfg(not(feature = "no_function"))] diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 2cceda30..5d05da0b 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -73,31 +73,23 @@ mod print_debug_functions { #[cfg(not(feature = "no_float"))] use num_traits::Float; + use crate::ast::FloatWrapper; + #[rhai_fn(name = "print", name = "to_string")] pub fn print_f64(number: f64) -> ImmutableString { - let abs = number.abs(); - if abs > 10000000000000.0 || abs < 0.0000000000001 { - format!("{:e}", number).into() - } else { - number.to_string().into() - } + FloatWrapper::new(number).to_string().into() } #[rhai_fn(name = "print", name = "to_string")] pub fn print_f32(number: f32) -> ImmutableString { - let abs = number.abs(); - if abs > 10000000000000.0 || abs < 0.0000000000001 { - format!("{:e}", number).into() - } else { - number.to_string().into() - } + FloatWrapper::new(number).to_string().into() } #[rhai_fn(name = "debug", name = "to_debug")] pub fn debug_f64(number: f64) -> ImmutableString { - number.to_string().into() + format!("{:?}", FloatWrapper::new(number)).into() } #[rhai_fn(name = "debug", name = "to_debug")] pub fn debug_f32(number: f32) -> ImmutableString { - number.to_string().into() + format!("{:?}", FloatWrapper::new(number)).into() } } diff --git a/src/parser.rs b/src/parser.rs index 409b6723..ab0aa0bc 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1271,16 +1271,16 @@ fn parse_primary( } // Cache the hash key for namespace-qualified variables - match &mut root_expr { - Expr::Variable(_, _, x) if x.1.is_some() => Some(x), - Expr::Index(x, _) | Expr::Dot(x, _) => match &mut x.lhs { + match root_expr { + Expr::Variable(_, _, ref mut x) if x.1.is_some() => Some(x), + Expr::Index(ref mut x, _) | Expr::Dot(ref mut x, _) => match &mut x.lhs { Expr::Variable(_, _, x) if x.1.is_some() => Some(x), _ => None, }, _ => None, } .map(|x| match x.as_mut() { - (_, Some((ref mut hash, ref mut namespace)), name) => { + (_, Some((hash, namespace)), name) => { *hash = calc_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0); #[cfg(not(feature = "no_module"))] diff --git a/src/token.rs b/src/token.rs index 9d0884c0..cf5b06f2 100644 --- a/src/token.rs +++ b/src/token.rs @@ -18,7 +18,7 @@ use crate::stdlib::{ use crate::{Engine, LexError, StaticVec, INT}; #[cfg(not(feature = "no_float"))] -use crate::ast::FloatWrapper; +use crate::{ast::FloatWrapper, FLOAT}; #[cfg(feature = "decimal")] use rust_decimal::Decimal; @@ -210,7 +210,7 @@ pub enum Token { /// /// Reserved under the `no_float` feature. #[cfg(not(feature = "no_float"))] - FloatConstant(FloatWrapper), + FloatConstant(FloatWrapper), /// A [`Decimal`] constant. /// /// Requires the `decimal` feature. diff --git a/tests/optimizer.rs b/tests/optimizer.rs index caa98dcc..69c884fd 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -55,24 +55,33 @@ fn test_optimizer_parse() -> Result<(), Box> { let ast = engine.compile("{ const DECISION = false; if DECISION { 42 } else { 123 } }")?; - assert!(format!("{:?}", ast).starts_with( - r#"AST { source: None, body: [Expr(IntegerConstant(123, 1:53))], functions: Module("# - )); + assert_eq!( + format!("{:?}", ast), + "AST { source: None, body: [Expr(123 @ 1:53)], functions: Module, resolver: None }" + ); let ast = engine.compile("const DECISION = false; if DECISION { 42 } else { 123 }")?; - assert!(format!("{:?}", ast).starts_with(r#"AST { source: None, body: [Const(BoolConstant(false, 1:18), Ident("DECISION" @ 1:7), false, 1:1), Expr(IntegerConstant(123, 1:51))], functions: Module("#)); + assert_eq!( + format!("{:?}", ast), + r#"AST { source: None, body: [Const(false @ 1:18, "DECISION" @ 1:7, false, 1:1), Expr(123 @ 1:51)], functions: Module, resolver: None }"# + ); let ast = engine.compile("if 1 == 2 { 42 }")?; - assert!(format!("{:?}", ast).starts_with("AST { source: None, body: [], functions: Module(")); + assert_eq!( + format!("{:?}", ast), + "AST { source: None, body: [], functions: Module, resolver: None }" + ); engine.set_optimization_level(OptimizationLevel::Full); let ast = engine.compile("abs(-42)")?; - assert!(format!("{:?}", ast) - .starts_with(r"AST { source: None, body: [Expr(IntegerConstant(42, 1:1))]")); + assert_eq!( + format!("{:?}", ast), + "AST { source: None, body: [Expr(42 @ 1:1)], functions: Module, resolver: None }" + ); Ok(()) }