From 5152a40e935fc628fb7fd4c57e53f1dcb1fd475e Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 12 Apr 2020 23:00:06 +0800 Subject: [PATCH 01/34] Change Dynamic to enum. --- README.md | 23 +- src/any.rs | 508 ++++++++++++++++++++++++++++++++++++--------- src/api.rs | 50 ++--- src/builtin.rs | 48 +++-- src/engine.rs | 352 ++++++++++++++----------------- src/fn_call.rs | 5 +- src/fn_func.rs | 4 +- src/fn_register.rs | 24 +-- src/lib.rs | 2 +- src/optimize.rs | 8 +- src/parser.rs | 148 ++++++------- src/scope.rs | 35 ++-- tests/maps.rs | 2 +- 13 files changed, 727 insertions(+), 482 deletions(-) diff --git a/README.md b/README.md index c073d244..7977dbc3 100644 --- a/README.md +++ b/README.md @@ -439,12 +439,10 @@ There is no easy way for Rust to decide, at run-time, what type the `Dynamic` va function and match against the name). A `Dynamic` value's actual type can be checked via the `is` method. -The `cast` method (from the `rhai::AnyExt` trait) then converts the value into a specific, known type. +The `cast` method then converts the value into a specific, known type. Alternatively, use the `try_cast` method which does not panic but returns an error when the cast fails. ```rust -use rhai::AnyExt; // pull in the trait. - let list: Array = engine.eval("...")?; // return type is 'Array' let item = list[0]; // an element in an 'Array' is 'Dynamic' @@ -459,8 +457,6 @@ let value = item.try_cast::()?; // 'try_cast' does not panic whe The `type_name` method gets the name of the actual type as a static string slice, which you may match against. ```rust -use rhai::Any; // pull in the trait. - let list: Array = engine.eval("...")?; // return type is 'Array' let item = list[0]; // an element in an 'Array' is 'Dynamic' @@ -499,8 +495,6 @@ A number of traits, under the `rhai::` module namespace, provide additional func | Trait | Description | Methods | | ------------------- | --------------------------------------------------------------------------------- | --------------------------------------- | -| `Any` | Generic trait that represents a [`Dynamic`] type | `type_id`, `type_name`, `into_dynamic` | -| `AnyExt` | Extension trait to allows casting of a [`Dynamic`] value to Rust types | `cast`, `try_cast` | | `RegisterFn` | Trait for registering functions | `register_fn` | | `RegisterDynamicFn` | Trait for registering functions returning [`Dynamic`] | `register_dynamic_fn` | | `RegisterResultFn` | Trait for registering fallible functions returning `Result<`_T_`, EvalAltResult>` | `register_result_fn` | @@ -513,9 +507,9 @@ Rhai's scripting engine is very lightweight. It gets most of its abilities from To call these functions, they need to be registered with the [`Engine`]. ```rust -use rhai::{Engine, EvalAltResult}; +use rhai::{Dynamic, Engine, EvalAltResult}; use rhai::RegisterFn; // use 'RegisterFn' trait for 'register_fn' -use rhai::{Any, Dynamic, RegisterDynamicFn}; // use 'RegisterDynamicFn' trait for 'register_dynamic_fn' +use rhai::{Dynamic, RegisterDynamicFn}; // use 'RegisterDynamicFn' trait for 'register_dynamic_fn' // Normal function fn add(x: i64, y: i64) -> i64 { @@ -524,7 +518,7 @@ fn add(x: i64, y: i64) -> i64 { // Function that returns a Dynamic value fn get_an_any() -> Dynamic { - (42_i64).into_dynamic() // 'into_dynamic' is defined by the 'rhai::Any' trait + Dynamic::from(42_i64) } fn main() -> Result<(), EvalAltResult> @@ -548,17 +542,16 @@ fn main() -> Result<(), EvalAltResult> } ``` -To return a [`Dynamic`] value from a Rust function, use the `into_dynamic()` method -(under the `rhai::Any` trait) to convert it. +To return a [`Dynamic`] value from a Rust function, use the `Dynamic::from` method. ```rust -use rhai::Any; // pull in the trait +use rhai::Dynamic; fn decide(yes_no: bool) -> Dynamic { if yes_no { - (42_i64).into_dynamic() + Dynamic::from(42_i64) } else { - String::from("hello!").into_dynamic() // remember &str is not supported by Rhai + Dynamic::from(String::from("hello!")) // remember &str is not supported by Rhai } } ``` diff --git a/src/any.rs b/src/any.rs index 1a7bda22..803fe2d2 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1,70 +1,38 @@ //! Helper module which defines the `Any` trait to to allow dynamic value handling. +use crate::engine::{Array, Map}; +use crate::parser::INT; + +#[cfg(not(feature = "no_float"))] +use crate::parser::FLOAT; + use crate::stdlib::{ - any::{type_name, TypeId}, + any::{type_name, Any, TypeId}, boxed::Box, fmt, + time::Instant, }; -/// An raw value of any type. +/// A trait to represent any type. /// /// Currently, `Variant` is not `Send` nor `Sync`, so it can practically be any type. /// Turn on the `sync` feature to restrict it to only types that implement `Send + Sync`. -pub type Variant = dyn Any; - -/// A boxed dynamic type containing any value. -/// -/// Currently, `Dynamic` is not `Send` nor `Sync`, so it can practically be any type. -/// Turn on the `sync` feature to restrict it to only types that implement `Send + Sync`. -pub type Dynamic = Box; - -/// A trait covering any type. -#[cfg(feature = "sync")] -pub trait Any: crate::stdlib::any::Any + Send + Sync { - /// Get the `TypeId` of this type. - fn type_id(&self) -> TypeId; - - /// Get the name of this type. - fn type_name(&self) -> &'static str; - - /// Convert into `Dynamic`. - fn into_dynamic(&self) -> Dynamic; - - /// This trait may only be implemented by `rhai`. - #[doc(hidden)] - fn _closed(&self) -> _Private; -} - -#[cfg(feature = "sync")] -impl Any for T { - fn type_id(&self) -> TypeId { - TypeId::of::() - } - - fn type_name(&self) -> &'static str { - type_name::() - } - - fn into_dynamic(&self) -> Dynamic { - Box::new(self.clone()) - } - - fn _closed(&self) -> _Private { - _Private - } -} - -/// A trait covering any type. #[cfg(not(feature = "sync"))] -pub trait Any: crate::stdlib::any::Any { - /// Get the `TypeId` of this type. - fn type_id(&self) -> TypeId; +pub trait Variant: Any { + /// Convert this `Variant` trait object to `&dyn Any`. + fn as_any(&self) -> &dyn Any; + + /// Convert this `Variant` trait object to `&mut dyn Any`. + fn as_mut_any(&mut self) -> &mut dyn Any; /// Get the name of this type. fn type_name(&self) -> &'static str; /// Convert into `Dynamic`. - fn into_dynamic(&self) -> Dynamic; + fn into_dynamic(self) -> Dynamic; + + /// Clone into `Dynamic`. + fn clone_into_dynamic(&self) -> Dynamic; /// This trait may only be implemented by `rhai`. #[doc(hidden)] @@ -72,101 +40,312 @@ pub trait Any: crate::stdlib::any::Any { } #[cfg(not(feature = "sync"))] -impl Any for T { - fn type_id(&self) -> TypeId { - TypeId::of::() +impl Variant for T { + fn as_any(&self) -> &dyn Any { + self as &dyn Any + } + fn as_mut_any(&mut self) -> &mut dyn Any { + self as &mut dyn Any } - fn type_name(&self) -> &'static str { type_name::() } - - fn into_dynamic(&self) -> Dynamic { - Box::new(self.clone()) + fn into_dynamic(self) -> Dynamic { + Dynamic::from(self) + } + fn clone_into_dynamic(&self) -> Dynamic { + Dynamic::from(self.clone()) } - fn _closed(&self) -> _Private { _Private } } -impl Variant { +/// A trait to represent any type. +#[cfg(feature = "sync")] +pub trait Variant: Any + Send + Sync { + /// Convert this `Variant` trait object to `&dyn Any`. + fn as_any(&self) -> &dyn Any; + + /// Convert this `Variant` trait object to `&mut dyn Any`. + fn as_mut_any(&mut self) -> &mut dyn Any; + + /// Get the name of this type. + fn type_name(&self) -> &'static str; + + /// Convert into `Dynamic`. + fn into_dynamic(self) -> Dynamic; + + /// Clone into `Dynamic`. + fn clone_into_dynamic(&self) -> Dynamic; + + /// This trait may only be implemented by `rhai`. + #[doc(hidden)] + fn _closed(&self) -> _Private; +} + +#[cfg(feature = "sync")] +impl Variant for T { + fn as_any(&self) -> &dyn Any { + self as &dyn Any + } + fn as_mut_any(&mut self) -> &mut dyn Any { + self as &mut dyn Any + } + fn type_name(&self) -> &'static str { + type_name::() + } + fn into_dynamic(self) -> Dynamic { + Dynamic::from(self) + } + fn clone_into_dynamic(&self) -> Dynamic { + Dynamic::from(self.clone()) + } + fn _closed(&self) -> _Private { + _Private + } +} + +impl dyn Variant { /// Is this `Variant` a specific type? pub fn is(&self) -> bool { - TypeId::of::() == ::type_id(self) + TypeId::of::() == self.type_id() } /// Get a reference of a specific type to the `Variant`. /// Returns `None` if the cast fails. pub fn downcast_ref(&self) -> Option<&T> { - if self.is::() { - unsafe { Some(&*(self as *const Variant as *const T)) } - } else { - None - } + Any::downcast_ref::(self.as_any()) } /// Get a mutable reference of a specific type to the `Variant`. /// Returns `None` if the cast fails. pub fn downcast_mut(&mut self) -> Option<&mut T> { - if self.is::() { - unsafe { Some(&mut *(self as *mut Variant as *mut T)) } - } else { - None + Any::downcast_mut::(self.as_mut_any()) + } +} + +/// A dynamic type containing any value. +pub struct Dynamic(pub(crate) Union); + +/// Internal `Dynamic` representation. +pub enum Union { + Unit(()), + Bool(bool), + Str(String), + Char(char), + Int(INT), + #[cfg(not(feature = "no_float"))] + Float(FLOAT), + Array(Array), + Map(Box), // Box it to reduce size + Variant(Box), +} + +impl Dynamic { + /// Is the value held by this `Dynamic` a particular type? + pub fn is(&self) -> bool { + self.type_id() == TypeId::of::() + } + + /// Get the TypeId of the value held by this `Dynamic`. + pub fn type_id(&self) -> TypeId { + match &self.0 { + Union::Unit(_) => TypeId::of::<()>(), + Union::Bool(_) => TypeId::of::(), + Union::Str(_) => TypeId::of::(), + Union::Char(_) => TypeId::of::(), + Union::Int(_) => TypeId::of::(), + #[cfg(not(feature = "no_float"))] + Union::Float(_) => TypeId::of::(), + Union::Array(_) => TypeId::of::(), + Union::Map(_) => TypeId::of::(), + Union::Variant(value) => (**value).type_id(), + } + } + + /// Get the name of the type of the value held by this `Dynamic`. + pub fn type_name(&self) -> &'static str { + match &self.0 { + Union::Unit(_) => "()", + Union::Bool(_) => "bool", + Union::Str(_) => "string", + Union::Char(_) => "char", + Union::Int(_) => type_name::(), + #[cfg(not(feature = "no_float"))] + Union::Float(_) => type_name::(), + Union::Array(_) => "array", + Union::Map(_) => "map", + + Union::Variant(value) if value.is::() => "timestamp", + Union::Variant(value) => (**value).type_name(), } } } -impl fmt::Debug for Variant { +impl fmt::Display for Dynamic { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.pad("?") + match &self.0 { + Union::Unit(_) => write!(f, ""), + Union::Bool(value) => write!(f, "{}", value), + Union::Str(value) => write!(f, "{}", value), + Union::Char(value) => write!(f, "{}", value), + Union::Int(value) => write!(f, "{}", value), + #[cfg(not(feature = "no_float"))] + Union::Float(value) => write!(f, "{}", value), + Union::Array(value) => write!(f, "{:?}", value), + Union::Map(value) => write!(f, "{:?}", value), + Union::Variant(_) => write!(f, "?"), + } + } +} + +impl fmt::Debug for Dynamic { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.0 { + Union::Unit(value) => write!(f, "{:?}", value), + Union::Bool(value) => write!(f, "{:?}", value), + Union::Str(value) => write!(f, "{:?}", value), + Union::Char(value) => write!(f, "{:?}", value), + Union::Int(value) => write!(f, "{:?}", value), + #[cfg(not(feature = "no_float"))] + Union::Float(value) => write!(f, "{:?}", value), + Union::Array(value) => write!(f, "{:?}", value), + Union::Map(value) => write!(f, "{:?}", value), + Union::Variant(_) => write!(f, "?"), + } } } impl Clone for Dynamic { fn clone(&self) -> Self { - self.as_ref().into_dynamic() + match &self.0 { + Union::Unit(value) => Self(Union::Unit(value.clone())), + Union::Bool(value) => Self(Union::Bool(value.clone())), + Union::Str(value) => Self(Union::Str(value.clone())), + Union::Char(value) => Self(Union::Char(value.clone())), + Union::Int(value) => Self(Union::Int(value.clone())), + #[cfg(not(feature = "no_float"))] + Union::Float(value) => Self(Union::Float(value.clone())), + Union::Array(value) => Self(Union::Array(value.clone())), + Union::Map(value) => Self(Union::Map(value.clone())), + Union::Variant(value) => (**value).clone_into_dynamic(), + } } } -/// An extension trait that allows down-casting a `Dynamic` value to a specific type. -pub trait AnyExt: Sized { - /// Get a copy of a `Dynamic` value as a specific type. - fn try_cast(self) -> Result; - - /// Get a copy of a `Dynamic` value as a specific type. +impl Dynamic { + /// Create a `Dynamic` from any type. /// - /// # Panics + /// # Examples /// - /// Panics if the cast fails (e.g. the type of the actual value is not the same as the specified type). - fn cast(self) -> T; + /// ``` + /// use rhai::Dynamic; + /// + /// let result = Dynamic::from(42_i64); + /// assert_eq!(result.type_name(), "i64"); + /// assert_eq!(result.to_string(), "42"); + /// + /// let result = Dynamic::from("hello".to_string()); + /// assert_eq!(result.type_name(), "string"); + /// assert_eq!(result.to_string(), "hello"); + /// ``` + pub fn from(value: T) -> Self { + if let Some(result) = (&value as &dyn Variant) + .downcast_ref::<()>() + .cloned() + .map(Union::Unit) + .or_else(|| { + (&value as &dyn Variant) + .downcast_ref::() + .cloned() + .map(Union::Bool) + .or_else(|| { + (&value as &dyn Variant) + .downcast_ref::() + .cloned() + .map(Union::Int) + .or_else(|| { + (&value as &dyn Variant) + .downcast_ref::() + .cloned() + .map(Union::Char) + }) + }) + }) + { + return Self(result); + } - /// This trait may only be implemented by `rhai`. - #[doc(hidden)] - fn _closed(&self) -> _Private; -} + #[cfg(not(feature = "no_float"))] + { + if let Some(result) = (&value as &dyn Variant) + .downcast_ref::() + .cloned() + .map(Union::Char) + { + return Self(result); + } + } + + fn cast_box(item: Box) -> Result> { + if TypeId::of::() == TypeId::of::() { + unsafe { + let raw: *mut dyn Any = Box::into_raw(item as Box); + Ok(*Box::from_raw(raw as *mut T)) + } + } else { + Err(item) + } + } + + let var = Box::new(value); + + Self( + cast_box::<_, String>(var) + .map(Union::Str) + .or_else(|var| { + cast_box::<_, Array>(var).map(Union::Array).or_else(|var| { + cast_box::<_, Map>(var) + .map(|v| Union::Map(Box::new(v))) + .or_else(|var| -> Result { + Ok(Union::Variant(var as Box)) + }) + }) + }) + .unwrap(), + ) + } -impl AnyExt for Dynamic { /// Get a copy of the `Dynamic` value as a specific type. /// + /// Returns an error with the name of the value's actual type when the cast fails. + /// /// # Example /// /// ``` - /// use rhai::{Dynamic, Any, AnyExt}; + /// use rhai::Dynamic; /// - /// let x: Dynamic = 42_u32.into_dynamic(); + /// let x = Dynamic::from(42_u32); /// /// assert_eq!(x.try_cast::().unwrap(), 42); /// ``` - fn try_cast(self) -> Result { - if self.is::() { - unsafe { - let raw: *mut Variant = Box::into_raw(self); - Ok(*Box::from_raw(raw as *mut T)) - } - } else { - Err(self) + pub fn try_cast(self) -> Result { + match &self.0 { + Union::Unit(value) => (value as &dyn Variant).downcast_ref::().cloned(), + Union::Bool(value) => (value as &dyn Variant).downcast_ref::().cloned(), + Union::Str(value) => (value as &dyn Variant).downcast_ref::().cloned(), + Union::Char(value) => (value as &dyn Variant).downcast_ref::().cloned(), + Union::Int(value) => (value as &dyn Variant).downcast_ref::().cloned(), + #[cfg(not(feature = "no_float"))] + Union::Float(value) => (value as &dyn Variant).downcast_ref::().cloned(), + Union::Array(value) => (value as &dyn Variant).downcast_ref::().cloned(), + Union::Map(value) => (value.as_ref() as &dyn Variant) + .downcast_ref::() + .cloned(), + Union::Variant(value) => value.as_ref().downcast_ref::().cloned(), } + .ok_or(self) } /// Get a copy of the `Dynamic` value as a specific type. @@ -178,18 +357,141 @@ impl AnyExt for Dynamic { /// # Example /// /// ``` - /// use rhai::{Dynamic, Any, AnyExt}; + /// use rhai::Dynamic; /// - /// let x: Dynamic = 42_u32.into_dynamic(); + /// let x = Dynamic::from(42_u32); /// /// assert_eq!(x.cast::(), 42); /// ``` - fn cast(self) -> T { - self.try_cast::().expect("cast failed") + pub fn cast(self) -> T { + self.try_cast::().unwrap() } - fn _closed(&self) -> _Private { - _Private + /// Get a reference of a specific type to the `Dynamic`. + /// Returns `None` if the cast fails. + pub fn downcast_ref(&self) -> Option<&T> { + match &self.0 { + Union::Unit(value) => (value as &dyn Variant).downcast_ref::(), + Union::Bool(value) => (value as &dyn Variant).downcast_ref::(), + Union::Str(value) => (value as &dyn Variant).downcast_ref::(), + Union::Char(value) => (value as &dyn Variant).downcast_ref::(), + Union::Int(value) => (value as &dyn Variant).downcast_ref::(), + #[cfg(not(feature = "no_float"))] + Union::Float(value) => (value as &dyn Variant).downcast_ref::(), + Union::Array(value) => (value as &dyn Variant).downcast_ref::(), + Union::Map(value) => (value.as_ref() as &dyn Variant).downcast_ref::(), + Union::Variant(value) => value.as_ref().downcast_ref::(), + } + } + + /// Get a mutable reference of a specific type to the `Dynamic`. + /// Returns `None` if the cast fails. + pub fn downcast_mut(&mut self) -> Option<&mut T> { + match &mut self.0 { + Union::Unit(value) => (value as &mut dyn Variant).downcast_mut::(), + Union::Bool(value) => (value as &mut dyn Variant).downcast_mut::(), + Union::Str(value) => (value as &mut dyn Variant).downcast_mut::(), + Union::Char(value) => (value as &mut dyn Variant).downcast_mut::(), + Union::Int(value) => (value as &mut dyn Variant).downcast_mut::(), + #[cfg(not(feature = "no_float"))] + Union::Float(value) => (value as &mut dyn Variant).downcast_mut::(), + Union::Array(value) => (value as &mut dyn Variant).downcast_mut::(), + Union::Map(value) => (value.as_mut() as &mut dyn Variant).downcast_mut::(), + Union::Variant(value) => value.as_mut().downcast_mut::(), + } + } + + /// Cast the `Dynamic` as the system integer type `INT` and return it. + /// Returns the name of the actual type if the cast fails. + pub(crate) fn as_int(&self) -> Result { + match self.0 { + Union::Int(n) => Ok(n), + _ => Err(self.type_name()), + } + } + + /// Cast the `Dynamic` as the system integer type `INT` and return it. + /// Returns the name of the actual type if the cast fails. + #[cfg(not(feature = "no_float"))] + pub(crate) fn as_float(&self) -> Result { + match self.0 { + Union::Float(n) => Ok(n), + _ => Err(self.type_name()), + } + } + + /// Cast the `Dynamic` as a `bool` and return it. + /// Returns the name of the actual type if the cast fails. + pub(crate) fn as_bool(&self) -> Result { + match self.0 { + Union::Bool(b) => Ok(b), + _ => Err(self.type_name()), + } + } + + /// Cast the `Dynamic` as a `char` and return it. + /// Returns the name of the actual type if the cast fails. + pub(crate) fn as_char(&self) -> Result { + match self.0 { + Union::Char(n) => Ok(n), + _ => Err(self.type_name()), + } + } + + /// Cast the `Dynamic` as a string and return the string slice. + /// Returns the name of the actual type if the cast fails. + pub(crate) fn as_str(&self) -> Result<&str, &'static str> { + match &self.0 { + Union::Str(s) => Ok(s), + _ => Err(self.type_name()), + } + } + + /// Convert the `Dynamic` into `String` and return it. + /// Returns the name of the actual type if the cast fails. + pub(crate) fn take_string(self) -> Result { + match self.0 { + Union::Str(s) => Ok(s), + _ => Err(self.type_name()), + } + } + + /// Cast the `Dynamic` as an `Array` and return a reference to it. + /// Returns the name of the actual type if the cast fails. + pub(crate) fn as_array(&self) -> Result<&Array, &'static str> { + match &self.0 { + Union::Array(array) => Ok(array), + _ => Err(self.type_name()), + } + } + + /// Cast the `Dynamic` as a `Map` and return a reference to it. + /// Returns the name of the actual type if the cast fails. + pub(crate) fn as_map(&self) -> Result<&Map, &'static str> { + match &self.0 { + Union::Map(map) => Ok(map), + _ => Err(self.type_name()), + } + } + + pub(crate) fn from_unit() -> Self { + Self(Union::Unit(())) + } + pub(crate) fn from_bool(value: bool) -> Self { + Self(Union::Bool(value)) + } + pub(crate) fn from_int(value: INT) -> Self { + Self(Union::Int(value)) + } + #[cfg(not(feature = "no_float"))] + pub(crate) fn from_float(value: FLOAT) -> Self { + Self(Union::Float(value)) + } + pub(crate) fn from_char(value: char) -> Self { + Self(Union::Char(value)) + } + pub(crate) fn from_string(value: String) -> Self { + Self(Union::Str(value)) } } diff --git a/src/api.rs b/src/api.rs index fde9b5d4..9dc84298 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,6 +1,6 @@ //! Module that defines the extern API of `Engine`. -use crate::any::{Any, AnyExt, Dynamic}; +use crate::any::{Dynamic, Variant}; use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec, Map}; use crate::error::ParseError; use crate::fn_call::FuncArgs; @@ -109,7 +109,7 @@ impl<'e> Engine<'e> { /// # } /// ``` #[cfg(not(feature = "no_object"))] - pub fn register_type(&mut self) { + pub fn register_type(&mut self) { self.register_type_with_name::(type_name::()); } @@ -157,7 +157,7 @@ impl<'e> Engine<'e> { /// # } /// ``` #[cfg(not(feature = "no_object"))] - pub fn register_type_with_name(&mut self, name: &str) { + pub fn register_type_with_name(&mut self, name: &str) { if self.type_names.is_none() { self.type_names = Some(HashMap::new()); } @@ -171,7 +171,7 @@ impl<'e> Engine<'e> { /// Register an iterator adapter for a type with the `Engine`. /// This is an advanced feature. - pub fn register_iterator(&mut self, f: F) { + pub fn register_iterator(&mut self, f: F) { if self.type_iterators.is_none() { self.type_iterators = Some(HashMap::new()); } @@ -221,8 +221,8 @@ impl<'e> Engine<'e> { #[cfg(not(feature = "no_object"))] pub fn register_get(&mut self, name: &str, callback: F) where - T: Any + Clone, - U: Any + Clone, + T: Variant + Clone, + U: Variant + Clone, F: ObjectGetCallback, { self.register_fn(&make_getter(name), callback); @@ -267,8 +267,8 @@ impl<'e> Engine<'e> { #[cfg(not(feature = "no_object"))] pub fn register_set(&mut self, name: &str, callback: F) where - T: Any + Clone, - U: Any + Clone, + T: Variant + Clone, + U: Variant + Clone, F: ObjectSetCallback, { self.register_fn(&make_setter(name), callback); @@ -315,8 +315,8 @@ impl<'e> Engine<'e> { #[cfg(not(feature = "no_object"))] pub fn register_get_set(&mut self, name: &str, get_fn: G, set_fn: S) where - T: Any + Clone, - U: Any + Clone, + T: Variant + Clone, + U: Variant + Clone, G: ObjectGetCallback, S: ObjectSetCallback, { @@ -487,7 +487,7 @@ impl<'e> Engine<'e> { /// /// ``` /// # fn main() -> Result<(), rhai::EvalAltResult> { - /// use rhai::{Engine, AnyExt}; + /// use rhai::Engine; /// /// let engine = Engine::new(); /// @@ -612,7 +612,7 @@ impl<'e> Engine<'e> { /// # } /// ``` #[cfg(not(feature = "no_std"))] - pub fn eval_file(&self, path: PathBuf) -> Result { + pub fn eval_file(&self, path: PathBuf) -> Result { Self::read_file(path).and_then(|contents| self.eval::(&contents)) } @@ -636,7 +636,7 @@ impl<'e> Engine<'e> { /// # } /// ``` #[cfg(not(feature = "no_std"))] - pub fn eval_file_with_scope( + pub fn eval_file_with_scope( &self, scope: &mut Scope, path: PathBuf, @@ -658,7 +658,7 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` - pub fn eval(&self, script: &str) -> Result { + pub fn eval(&self, script: &str) -> Result { self.eval_with_scope(&mut Scope::new(), script) } @@ -684,7 +684,7 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` - pub fn eval_with_scope( + pub fn eval_with_scope( &self, scope: &mut Scope, script: &str, @@ -709,7 +709,7 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` - pub fn eval_expression(&self, script: &str) -> Result { + pub fn eval_expression(&self, script: &str) -> Result { self.eval_expression_with_scope(&mut Scope::new(), script) } @@ -731,7 +731,7 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` - pub fn eval_expression_with_scope( + pub fn eval_expression_with_scope( &self, scope: &mut Scope, script: &str, @@ -761,7 +761,7 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` - pub fn eval_ast(&self, ast: &AST) -> Result { + pub fn eval_ast(&self, ast: &AST) -> Result { self.eval_ast_with_scope(&mut Scope::new(), ast) } @@ -794,7 +794,7 @@ impl<'e> Engine<'e> { /// # Ok(()) /// # } /// ``` - pub fn eval_ast_with_scope( + pub fn eval_ast_with_scope( &self, scope: &mut Scope, ast: &AST, @@ -803,7 +803,7 @@ impl<'e> Engine<'e> { .try_cast::() .map_err(|a| { EvalAltResult::ErrorMismatchOutputType( - self.map_type_name((*a).type_name()).to_string(), + self.map_type_name(a.type_name()).to_string(), Position::none(), ) }) @@ -816,7 +816,7 @@ impl<'e> Engine<'e> { ) -> Result { ast.0 .iter() - .try_fold(().into_dynamic(), |_, stmt| { + .try_fold(Dynamic::from_unit(), |_, stmt| { self.eval_stmt(scope, Some(ast.1.as_ref()), stmt, 0) }) .or_else(|err| match err { @@ -875,7 +875,7 @@ impl<'e> Engine<'e> { ) -> Result<(), EvalAltResult> { ast.0 .iter() - .try_fold(().into_dynamic(), |_, stmt| { + .try_fold(Dynamic::from_unit(), |_, stmt| { self.eval_stmt(scope, Some(ast.1.as_ref()), stmt, 0) }) .map(|_| ()) @@ -921,7 +921,7 @@ impl<'e> Engine<'e> { /// # } /// ``` #[cfg(not(feature = "no_function"))] - pub fn call_fn( + pub fn call_fn( &self, scope: &mut Scope, ast: &AST, @@ -929,7 +929,7 @@ impl<'e> Engine<'e> { args: A, ) -> Result { let mut arg_values = args.into_vec(); - let mut args: Vec<_> = arg_values.iter_mut().map(Dynamic::as_mut).collect(); + let mut args: Vec<_> = arg_values.iter_mut().collect(); let fn_lib = Some(ast.1.as_ref()); let pos = Position::none(); @@ -937,7 +937,7 @@ impl<'e> Engine<'e> { .try_cast() .map_err(|a| { EvalAltResult::ErrorMismatchOutputType( - self.map_type_name((*a).type_name()).into(), + self.map_type_name(a.type_name()).into(), pos, ) }) diff --git a/src/builtin.rs b/src/builtin.rs index f77b4f4a..dfdda748 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -1,7 +1,7 @@ //! Helper module that allows registration of the _core library_ and //! _standard library_ of utility functions. -use crate::any::{Any, Dynamic}; +use crate::any::{Dynamic, Variant}; use crate::engine::{Engine, FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT}; use crate::fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; use crate::parser::{Position, INT}; @@ -628,8 +628,8 @@ impl Engine<'_> { // Register map access functions #[cfg(not(feature = "no_index"))] self.register_fn("keys", |map: Map| { - map.into_iter() - .map(|(k, _)| k.into_dynamic()) + map.iter() + .map(|(k, _)| Dynamic::from(k.clone())) .collect::>() }); @@ -641,15 +641,16 @@ impl Engine<'_> { } // Register range function - fn reg_range(engine: &mut Engine) + fn reg_range(engine: &mut Engine) where Range: Iterator, { - engine.register_iterator::, _>(|a: &Dynamic| { + engine.register_iterator::, _>(|source: &Dynamic| { Box::new( - a.downcast_ref::>() + source + .downcast_ref::>() + .cloned() .unwrap() - .clone() .map(|x| x.into_dynamic()), ) as Box> }); @@ -678,12 +679,12 @@ impl Engine<'_> { struct StepRange(T, T, T) where for<'a> &'a T: Add<&'a T, Output = T>, - T: Any + Clone + PartialOrd; + T: Variant + Clone + PartialOrd; impl Iterator for StepRange where for<'a> &'a T: Add<&'a T, Output = T>, - T: Any + Clone + PartialOrd, + T: Variant + Clone + PartialOrd, { type Item = T; @@ -701,14 +702,15 @@ impl Engine<'_> { fn reg_step(engine: &mut Engine) where for<'a> &'a T: Add<&'a T, Output = T>, - T: Any + Clone + PartialOrd, + T: Variant + Clone + PartialOrd, StepRange: Iterator, { - engine.register_iterator::, _>(|a: &Dynamic| { + engine.register_iterator::, _>(|source: &Dynamic| { Box::new( - a.downcast_ref::>() + source + .downcast_ref::>() + .cloned() .unwrap() - .clone() .map(|x| x.into_dynamic()), ) as Box> }); @@ -866,19 +868,19 @@ impl Engine<'_> { } // Register array utility functions - fn push(list: &mut Array, item: T) { - list.push(Box::new(item)); + fn push(list: &mut Array, item: T) { + list.push(Dynamic::from(item)); } - fn ins(list: &mut Array, position: INT, item: T) { + fn ins(list: &mut Array, position: INT, item: T) { if position <= 0 { - list.insert(0, Box::new(item)); + list.insert(0, Dynamic::from(item)); } else if (position as usize) >= list.len() - 1 { push(list, item); } else { - list.insert(position as usize, Box::new(item)); + list.insert(position as usize, Dynamic::from(item)); } } - fn pad(list: &mut Array, len: INT, item: T) { + fn pad(list: &mut Array, len: INT, item: T) { if len >= 0 { while list.len() < len as usize { push(list, item.clone()); @@ -919,18 +921,18 @@ impl Engine<'_> { } self.register_dynamic_fn("pop", |list: &mut Array| { - list.pop().unwrap_or_else(|| ().into_dynamic()) + list.pop().unwrap_or_else(|| Dynamic::from_unit()) }); self.register_dynamic_fn("shift", |list: &mut Array| { if !list.is_empty() { - ().into_dynamic() + Dynamic::from_unit() } else { list.remove(0) } }); self.register_dynamic_fn("remove", |list: &mut Array, len: INT| { if len < 0 || (len as usize) >= list.len() { - ().into_dynamic() + Dynamic::from_unit() } else { list.remove(len as usize) } @@ -951,7 +953,7 @@ impl Engine<'_> { self.register_fn("len", |map: &mut Map| map.len() as INT); self.register_fn("clear", |map: &mut Map| map.clear()); self.register_dynamic_fn("remove", |x: &mut Map, name: String| { - x.remove(&name).unwrap_or(().into_dynamic()) + x.remove(&name).unwrap_or_else(|| Dynamic::from_unit()) }); self.register_fn("mixin", |map1: &mut Map, map2: Map| { map2.into_iter().for_each(|(key, value)| { diff --git a/src/engine.rs b/src/engine.rs index 73b1e800..59906675 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,6 +1,6 @@ //! Main module defining the script evaluation `Engine`. -use crate::any::{Any, AnyExt, Dynamic, Variant}; +use crate::any::{Dynamic, Union}; use crate::error::ParseErrorType; use crate::optimize::OptimizationLevel; use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt, INT}; @@ -8,7 +8,7 @@ use crate::result::EvalAltResult; use crate::scope::{EntryRef as ScopeSource, EntryType as ScopeEntryType, Scope}; use crate::stdlib::{ - any::{type_name, TypeId}, + any::TypeId, borrow::Cow, boxed::Box, cmp::Ordering, @@ -19,7 +19,6 @@ use crate::stdlib::{ rc::Rc, string::{String, ToString}, sync::Arc, - time::Instant, vec, vec::Vec, }; @@ -34,7 +33,7 @@ pub type Array = Vec; /// Not available under the `no_object` feature. pub type Map = HashMap; -pub type FnCallArgs<'a> = [&'a mut Variant]; +pub type FnCallArgs<'a> = [&'a mut Dynamic]; #[cfg(feature = "sync")] pub type FnAny = dyn Fn(&mut FnCallArgs, Position) -> Result + Send + Sync; @@ -98,20 +97,20 @@ impl IndexValue { #[derive(Debug)] enum Target<'a> { Scope(ScopeSource<'a>), - Value(&'a mut Variant), + Value(&'a mut Dynamic), } impl<'a> Target<'a> { - fn from(value: &'a mut Variant) -> Self { + fn from(value: &'a mut Dynamic) -> Self { Self::Value(value) } fn from_src(src: ScopeSource<'a>) -> Self { Self::Scope(src) } - fn get_mut(self, scope: &'a mut Scope) -> &'a mut Variant { + fn get_mut(self, scope: &'a mut Scope) -> &'a mut Dynamic { match self { Self::Value(t) => t, - Self::Scope(src) => scope.get_mut(src).as_mut(), + Self::Scope(src) => scope.get_mut(src), } } } @@ -300,7 +299,6 @@ impl Default for Engine<'_> { max_call_stack_depth: MAX_CALL_STACK_DEPTH, }; - engine.fill_type_names(); engine.register_core_lib(); #[cfg(not(feature = "no_stdlib"))] @@ -353,25 +351,6 @@ fn extract_prop_from_setter(fn_name: &str) -> Option<&str> { } impl Engine<'_> { - fn fill_type_names(&mut self) { - // User-friendly names for built-in types - self.type_names = Some( - [ - #[cfg(not(feature = "no_index"))] - (type_name::(), "array"), - #[cfg(not(feature = "no_object"))] - (type_name::(), "map"), - (type_name::(), "string"), - (type_name::(), "timestamp"), - (type_name::(), "dynamic"), - (type_name::(), "variant"), - ] - .iter() - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect(), - ); - } - /// Create a new `Engine` pub fn new() -> Self { Default::default() @@ -400,7 +379,6 @@ impl Engine<'_> { max_call_stack_depth: MAX_CALL_STACK_DEPTH, }; - engine.fill_type_names(); engine.register_core_lib(); engine @@ -452,7 +430,7 @@ impl Engine<'_> { fn_def .params .iter() - .zip(args.into_iter().map(|x| (*x).into_dynamic())) + .zip(args.into_iter().map(|v| v.clone())) .map(|(name, value)| (name.clone(), ScopeEntryType::Normal, value)), ); @@ -478,7 +456,7 @@ impl Engine<'_> { fn_def .params .iter() - .zip(args.into_iter().map(|x| (*x).into_dynamic())) + .zip(args.into_iter().map(|v| v.clone())) .map(|(name, value)| (name, ScopeEntryType::Normal, value)), ); @@ -496,14 +474,13 @@ impl Engine<'_> { let spec = FnSpec { name: fn_name.into(), - args: args.iter().map(|a| Any::type_id(*a)).collect(), + args: args.iter().map(|a| a.type_id()).collect(), }; // Argument must be a string - fn cast_to_string(r: &Variant, pos: Position) -> Result<&str, EvalAltResult> { - r.downcast_ref::() - .map(String::as_str) - .ok_or_else(|| EvalAltResult::ErrorMismatchOutputType(r.type_name().into(), pos)) + fn cast_to_string(r: &Dynamic, pos: Position) -> Result<&str, EvalAltResult> { + r.as_str() + .map_err(|type_name| EvalAltResult::ErrorMismatchOutputType(type_name.into(), pos)) } // Search built-in's and external functions @@ -513,23 +490,26 @@ impl Engine<'_> { // See if the function match print/debug (which requires special processing) return match fn_name { - KEYWORD_PRINT if self.on_print.is_some() => Ok(self.on_print.as_ref().unwrap()( - cast_to_string(result.as_ref(), pos)?, - ) - .into_dynamic()), - KEYWORD_DEBUG if self.on_debug.is_some() => Ok(self.on_debug.as_ref().unwrap()( - cast_to_string(result.as_ref(), pos)?, - ) - .into_dynamic()), - KEYWORD_PRINT | KEYWORD_DEBUG => Ok(().into_dynamic()), + KEYWORD_PRINT if self.on_print.is_some() => { + self.on_print.as_ref().unwrap()(cast_to_string(&result, pos)?); + Ok(Dynamic::from_unit()) + } + KEYWORD_DEBUG if self.on_debug.is_some() => { + self.on_debug.as_ref().unwrap()(cast_to_string(&result, pos)?); + Ok(Dynamic::from_unit()) + } + KEYWORD_PRINT | KEYWORD_DEBUG => Ok(Dynamic::from_unit()), _ => Ok(result), }; } if let Some(prop) = extract_prop_from_getter(fn_name) { // Map property access - if let Some(map) = args[0].downcast_ref::() { - return Ok(map.get(prop).cloned().unwrap_or_else(|| ().into_dynamic())); + if let Ok(map) = args[0].as_map() { + return Ok(map + .get(prop) + .cloned() + .unwrap_or_else(|| Dynamic::from_unit())); } // Getter function not found @@ -540,12 +520,12 @@ impl Engine<'_> { } if let Some(prop) = extract_prop_from_setter(fn_name) { - let value = args[1].into_dynamic(); + let value = args[1].clone(); // Map property update - if let Some(map) = args[0].downcast_mut::() { + if let Dynamic(Union::Map(map)) = args[0] { map.insert(prop.to_string(), value); - return Ok(().into_dynamic()); + return Ok(Dynamic::from_unit()); } // Setter function not found @@ -592,9 +572,7 @@ impl Engine<'_> { let this_ptr = target.get_mut(scope); - let mut args: Vec<_> = once(this_ptr) - .chain(values.iter_mut().map(Dynamic::as_mut)) - .collect(); + let mut args: Vec<_> = once(this_ptr).chain(values.iter_mut()).collect(); let def_val = def_val.as_ref(); @@ -639,7 +617,7 @@ impl Engine<'_> { let mut args = [target.get_mut(scope)]; self.call_fn_raw(None, fn_lib, &make_getter(id), &mut args, None, *pos, 0) .and_then(|mut val| { - let target = Target::from(val.as_mut()); + let target = Target::from(&mut val); self.get_dot_val_helper(scope, fn_lib, target, rhs, level) }) } @@ -667,7 +645,7 @@ impl Engine<'_> { self.get_indexed_value(scope, fn_lib, &val, idx_expr, *op_pos, level) .and_then(|(mut val, _, _)| { - let target = Target::from(val.as_mut()); + let target = Target::from(&mut val); self.get_dot_val_helper(scope, fn_lib, target, rhs, level) }) } @@ -712,7 +690,7 @@ impl Engine<'_> { Expr::Index(idx_lhs, idx_expr, op_pos) => { let (idx_src_type, src, index, mut val) = self.eval_index_expr(scope, fn_lib, idx_lhs, idx_expr, *op_pos, level)?; - let target = Target::from(val.as_mut()); + let target = Target::from(&mut val); let value = self.get_dot_val_helper(scope, fn_lib, target, dot_rhs, level); // In case the expression mutated `target`, we need to update it back into the scope because it is cloned. @@ -743,7 +721,7 @@ impl Engine<'_> { // {expr}.??? expr => { let mut val = self.eval_expr(scope, fn_lib, expr, level)?; - self.get_dot_val_helper(scope, fn_lib, Target::from(val.as_mut()), dot_rhs, level) + self.get_dot_val_helper(scope, fn_lib, Target::from(&mut val), dot_rhs, level) } } } @@ -772,16 +750,21 @@ impl Engine<'_> { let idx_pos = idx_expr.position(); // val_array[idx] - if let Some(arr) = val.downcast_ref::() { + if let Ok(arr) = val.as_array() { let index = self .eval_expr(scope, fn_lib, idx_expr, level)? - .try_cast::() + .as_int() .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; return if index >= 0 { arr.get(index as usize) - .cloned() - .map(|v| (v, IndexSourceType::Array, IndexValue::from_num(index))) + .map(|v| { + ( + v.clone(), + IndexSourceType::Array, + IndexValue::from_num(index), + ) + }) .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), index, idx_pos)) } else { Err(EvalAltResult::ErrorArrayBounds(arr.len(), index, idx_pos)) @@ -789,26 +772,26 @@ impl Engine<'_> { } // val_map[idx] - if let Some(map) = val.downcast_ref::() { + if let Ok(map) = val.as_map() { let index = self .eval_expr(scope, fn_lib, idx_expr, level)? - .try_cast::() + .take_string() .map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_expr.position()))?; return Ok(( map.get(&index) .cloned() - .unwrap_or_else(|| ().into_dynamic()), + .unwrap_or_else(|| Dynamic::from_unit()), IndexSourceType::Map, IndexValue::from_str(index), )); } // val_string[idx] - if let Some(s) = val.downcast_ref::() { + if let Ok(s) = val.as_str() { let index = self .eval_expr(scope, fn_lib, idx_expr, level)? - .try_cast::() + .as_int() .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; return if index >= 0 { @@ -816,7 +799,7 @@ impl Engine<'_> { .nth(index as usize) .map(|ch| { ( - ch.into_dynamic(), + Dynamic::from_char(ch), IndexSourceType::String, IndexValue::from_num(index), ) @@ -921,14 +904,14 @@ impl Engine<'_> { IndexSourceType::Array => { let arr = scope.get_mut_by_type::(src); arr[idx.as_num()] = new_val.0; - Ok(().into_dynamic()) + Ok(Dynamic::from_unit()) } // map_id[idx] = val IndexSourceType::Map => { let arr = scope.get_mut_by_type::(src); arr.insert(idx.as_str(), new_val.0); - Ok(().into_dynamic()) + Ok(Dynamic::from_unit()) } // string_id[idx] = val @@ -938,10 +921,10 @@ impl Engine<'_> { // Value must be a character let ch = new_val .0 - .try_cast::() + .as_char() .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; Self::str_replace_char(s, idx.as_num(), ch); - Ok(().into_dynamic()) + Ok(Dynamic::from_unit()) } IndexSourceType::Expression => panic!("expression cannot be indexed for update"), @@ -955,27 +938,26 @@ impl Engine<'_> { new_val: Dynamic, pos: Position, ) -> Result { - if let Some(arr) = target.downcast_mut::() { - arr[idx.as_num()] = new_val; - return Ok(target); + match target { + Dynamic(Union::Array(ref mut arr)) => { + arr[idx.as_num()] = new_val; + } + Dynamic(Union::Map(ref mut map)) => { + map.insert(idx.as_str(), new_val); + } + Dynamic(Union::Str(ref mut s)) => { + // Value must be a character + let ch = new_val + .as_char() + .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; + + Self::str_replace_char(s, idx.as_num(), ch); + } + // All other variable types should be an error + _ => panic!("array, map or string source type expected for indexing"), } - if let Some(map) = target.downcast_mut::() { - map.insert(idx.as_str(), new_val); - return Ok(target); - } - - if let Some(s) = target.downcast_mut::() { - // Value must be a character - let ch = new_val - .try_cast::() - .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; - Self::str_replace_char(s, idx.as_num(), ch); - return Ok(target); - } - - // All other variable types should be an error - panic!("array, map or string source type expected for indexing") + Ok(target) } /// Chain-evaluate a dot setter @@ -983,7 +965,7 @@ impl Engine<'_> { &self, scope: &mut Scope, fn_lib: Option<&FunctionsLib>, - this_ptr: &mut Variant, + this_ptr: &mut Dynamic, dot_rhs: &Expr, new_val: (&mut Dynamic, Position), level: usize, @@ -991,7 +973,7 @@ impl Engine<'_> { match dot_rhs { // xxx.id Expr::Property(id, pos) => { - let mut args = [this_ptr, new_val.0.as_mut()]; + let mut args = [this_ptr, new_val.0]; self.call_fn_raw(None, fn_lib, &make_setter(id), &mut args, None, *pos, 0) } @@ -1010,7 +992,7 @@ impl Engine<'_> { }) .and_then(|mut val| { let fn_name = make_setter(id); - let mut args = [this_ptr, val.as_mut()]; + let mut args = [this_ptr, &mut val]; self.call_fn_raw(None, fn_lib, &fn_name, &mut args, None, *pos, 0) }) } @@ -1029,13 +1011,12 @@ impl Engine<'_> { let fn_name = make_getter(id); self.call_fn_raw(None, fn_lib, &fn_name, &mut [this_ptr], None, *pos, 0) .and_then(|mut val| { - let value = val.as_mut(); - self.set_dot_val_helper(scope, fn_lib, value, rhs, new_val, level) + self.set_dot_val_helper(scope, fn_lib, &mut val, rhs, new_val, level) .map(|_| val) // Discard Ok return value }) .and_then(|mut val| { let fn_name = make_setter(id); - let mut args = [this_ptr, val.as_mut()]; + let mut args = [this_ptr, &mut val]; self.call_fn_raw(None, fn_lib, &fn_name, &mut args, None, *pos, 0) }) } @@ -1053,7 +1034,7 @@ impl Engine<'_> { )?; let val_pos = new_val.1; - let this_ptr = value.as_mut(); + let this_ptr = &mut value; self.set_dot_val_helper( scope, fn_lib, this_ptr, rhs, new_val, level, )?; @@ -1063,7 +1044,7 @@ impl Engine<'_> { }) .and_then(|mut v| { let fn_name = make_setter(id); - let mut args = [this_ptr, v.as_mut()]; + let mut args = [this_ptr, &mut v]; self.call_fn_raw(None, fn_lib, &fn_name, &mut args, None, *pos, 0) }) } @@ -1114,7 +1095,7 @@ impl Engine<'_> { _ => { // Avoid referencing scope which is used below as mut let entry = ScopeSource { name: id, ..src }; - let this_ptr = target.as_mut(); + let this_ptr = &mut target; let value = self .set_dot_val_helper(scope, fn_lib, this_ptr, dot_rhs, new_val, level); @@ -1132,7 +1113,7 @@ impl Engine<'_> { let (idx_src_type, src, index, mut target) = self.eval_index_expr(scope, fn_lib, lhs, idx_expr, *op_pos, level)?; let val_pos = new_val.1; - let this_ptr = target.as_mut(); + let this_ptr = &mut target; let value = self.set_dot_val_helper(scope, fn_lib, this_ptr, dot_rhs, new_val, level); @@ -1181,56 +1162,46 @@ impl Engine<'_> { let mut lhs_value = self.eval_expr(scope, fn_lib, lhs, level)?; let rhs_value = self.eval_expr(scope, fn_lib, rhs, level)?; - if rhs_value.is::() { - let mut rhs_value = rhs_value.cast::(); - let def_value = false.into_dynamic(); - let mut result = false; + match rhs_value { + Dynamic(Union::Array(mut rhs_value)) => { + let def_value = Dynamic::from_bool(false); + let mut result = false; - // Call the '==' operator to compare each value - for value in rhs_value.iter_mut() { - let args = &mut [lhs_value.as_mut(), value.as_mut()]; - let def_value = Some(&def_value); - if self - .call_fn_raw(None, fn_lib, "==", args, def_value, rhs.position(), level)? - .try_cast::() - .unwrap_or(false) - { - result = true; - break; + // Call the '==' operator to compare each value + for value in rhs_value.iter_mut() { + let args = &mut [&mut lhs_value, value]; + let def_value = Some(&def_value); + if self + .call_fn_raw(None, fn_lib, "==", args, def_value, rhs.position(), level)? + .as_bool() + .unwrap_or(false) + { + result = true; + break; + } + } + + Ok(Dynamic::from_bool(result)) + } + Dynamic(Union::Map(rhs_value)) => { + // Only allows String or char + match lhs_value { + Dynamic(Union::Str(s)) => Ok(Dynamic::from_bool(rhs_value.contains_key(&s))), + Dynamic(Union::Char(c)) => { + Ok(Dynamic::from_bool(rhs_value.contains_key(&c.to_string()))) + } + _ => Err(EvalAltResult::ErrorInExpr(lhs.position())), } } - - Ok(result.into_dynamic()) - } else if rhs_value.is::() { - let rhs_value = rhs_value.cast::(); - - // Only allows String or char - if lhs_value.is::() { - Ok(rhs_value - .contains_key(&lhs_value.cast::()) - .into_dynamic()) - } else if lhs_value.is::() { - Ok(rhs_value - .contains_key(&lhs_value.cast::().to_string()) - .into_dynamic()) - } else { - Err(EvalAltResult::ErrorInExpr(lhs.position())) + Dynamic(Union::Str(rhs_value)) => { + // Only allows String or char + match lhs_value { + Dynamic(Union::Str(s)) => Ok(Dynamic::from_bool(rhs_value.contains(&s))), + Dynamic(Union::Char(c)) => Ok(Dynamic::from_bool(rhs_value.contains(c))), + _ => Err(EvalAltResult::ErrorInExpr(lhs.position())), + } } - } else if rhs_value.is::() { - let rhs_value = rhs_value.cast::(); - - // Only allows String or char - if lhs_value.is::() { - Ok(rhs_value - .contains(&lhs_value.cast::()) - .into_dynamic()) - } else if lhs_value.is::() { - Ok(rhs_value.contains(lhs_value.cast::()).into_dynamic()) - } else { - Err(EvalAltResult::ErrorInExpr(lhs.position())) - } - } else { - Err(EvalAltResult::ErrorInExpr(rhs.position())) + _ => Err(EvalAltResult::ErrorInExpr(rhs.position())), } } @@ -1243,10 +1214,11 @@ impl Engine<'_> { level: usize, ) -> Result { match expr { - Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()), - Expr::FloatConstant(f, _) => Ok(f.into_dynamic()), - Expr::StringConstant(s, _) => Ok(s.clone().into_owned().into_dynamic()), - Expr::CharConstant(c, _) => Ok(c.into_dynamic()), + Expr::IntegerConstant(i, _) => Ok(Dynamic::from_int(*i)), + #[cfg(not(feature = "no_float"))] + Expr::FloatConstant(f, _) => Ok(Dynamic::from_float(*f)), + Expr::StringConstant(s, _) => Ok(Dynamic::from_string(s.to_string())), + Expr::CharConstant(c, _) => Ok(Dynamic::from_char(*c)), Expr::Variable(id, pos) => Self::search_scope(scope, id, *pos).map(|(_, val)| val), Expr::Property(_, _) => panic!("unexpected property."), @@ -1262,7 +1234,7 @@ impl Engine<'_> { Expr::Variable(name, pos) => match scope.get(name) { None => { return Err(EvalAltResult::ErrorVariableNotFound( - name.clone().into_owned(), + name.to_string(), *pos, )) } @@ -1358,7 +1330,7 @@ impl Engine<'_> { .map(|val| arr.push(val)) })?; - Ok((arr).into_dynamic()) + Ok(Dynamic(Union::Array(arr))) } #[cfg(not(feature = "no_object"))] @@ -1371,7 +1343,7 @@ impl Engine<'_> { }) })?; - Ok((map).into_dynamic()) + Ok(Dynamic(Union::Map(Box::new(map)))) } Expr::FunctionCall(fn_name, args_expr_list, def_val, pos) => { @@ -1396,10 +1368,9 @@ impl Engine<'_> { && !has_override(self, fn_lib, KEYWORD_TYPE_OF) => { let result = self.eval_expr(scope, fn_lib, &args_expr_list[0], level)?; - Ok(self - .map_type_name((*result).type_name()) - .to_string() - .into_dynamic()) + Ok(Dynamic::from_string( + self.map_type_name(result.type_name()).to_string(), + )) } // eval @@ -1411,15 +1382,9 @@ impl Engine<'_> { let result = self.eval_expr(scope, fn_lib, &args_expr_list[0], level)?; // Get the script text by evaluating the expression - let script = result - .downcast_ref::() - .map(String::as_str) - .ok_or_else(|| { - EvalAltResult::ErrorMismatchOutputType( - result.type_name().into(), - pos, - ) - })?; + let script = result.as_str().map_err(|type_name| { + EvalAltResult::ErrorMismatchOutputType(type_name.into(), pos) + })?; // Compile the script text // No optimizations because we only run it once @@ -1461,7 +1426,7 @@ impl Engine<'_> { .map(|expr| self.eval_expr(scope, fn_lib, expr, level)) .collect::, _>>()?; - let mut args: Vec<_> = arg_values.iter_mut().map(Dynamic::as_mut).collect(); + let mut args: Vec<_> = arg_values.iter_mut().collect(); let def_val = def_val.as_ref(); self.call_fn_raw(None, fn_lib, fn_name, &mut args, def_val, *pos, level) } @@ -1472,41 +1437,41 @@ impl Engine<'_> { self.eval_in_expr(scope, fn_lib, lhs.as_ref(), rhs.as_ref(), level) } - Expr::And(lhs, rhs, _) => Ok(Box::new( + Expr::And(lhs, rhs, _) => Ok(Dynamic::from_bool( self .eval_expr(scope, fn_lib,lhs.as_ref(), level)? - .try_cast::() + .as_bool() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position()) })? && // Short-circuit using && self .eval_expr(scope, fn_lib,rhs.as_ref(), level)? - .try_cast::() + .as_bool() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position()) })?, )), - Expr::Or(lhs, rhs, _) => Ok(Box::new( + Expr::Or(lhs, rhs, _) => Ok(Dynamic::from_bool( self .eval_expr(scope,fn_lib, lhs.as_ref(), level)? - .try_cast::() + .as_bool() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position()) })? || // Short-circuit using || self .eval_expr(scope,fn_lib, rhs.as_ref(), level)? - .try_cast::() + .as_bool() .map_err(|_| { EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position()) })?, )), - Expr::True(_) => Ok(true.into_dynamic()), - Expr::False(_) => Ok(false.into_dynamic()), - Expr::Unit(_) => Ok(().into_dynamic()), + Expr::True(_) => Ok(Dynamic::from_bool(true)), + Expr::False(_) => Ok(Dynamic::from_bool(false)), + Expr::Unit(_) => Ok(Dynamic::from_unit()), _ => panic!("should not appear: {:?}", expr), } @@ -1522,7 +1487,7 @@ impl Engine<'_> { ) -> Result { match stmt { // No-op - Stmt::Noop(_) => Ok(().into_dynamic()), + Stmt::Noop(_) => Ok(Dynamic::from_unit()), // Expression as statement Stmt::Expr(expr) => { @@ -1532,7 +1497,7 @@ impl Engine<'_> { result } else { // If it is an assignment, erase the result at the root - ().into_dynamic() + Dynamic::from_unit() }) } @@ -1540,7 +1505,7 @@ impl Engine<'_> { Stmt::Block(block, _) => { let prev_len = scope.len(); - let result = block.iter().try_fold(().into_dynamic(), |_, stmt| { + let result = block.iter().try_fold(Dynamic::from_unit(), |_, stmt| { self.eval_stmt(scope, fn_lib, stmt, level) }); @@ -1552,7 +1517,7 @@ impl Engine<'_> { // If-else statement Stmt::IfThenElse(guard, if_body, else_body) => self .eval_expr(scope, fn_lib, guard, level)? - .try_cast::() + .as_bool() .map_err(|_| EvalAltResult::ErrorLogicGuard(guard.position())) .and_then(|guard_val| { if guard_val { @@ -1560,26 +1525,23 @@ impl Engine<'_> { } else if let Some(stmt) = else_body { self.eval_stmt(scope, fn_lib, stmt.as_ref(), level) } else { - Ok(().into_dynamic()) + Ok(Dynamic::from_unit()) } }), // While loop Stmt::While(guard, body) => loop { - match self - .eval_expr(scope, fn_lib, guard, level)? - .try_cast::() - { + match self.eval_expr(scope, fn_lib, guard, level)?.as_bool() { Ok(guard_val) if guard_val => { match self.eval_stmt(scope, fn_lib, body, level) { Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), Err(EvalAltResult::ErrorLoopBreak(true, _)) => { - return Ok(().into_dynamic()) + return Ok(Dynamic::from_unit()) } Err(x) => return Err(x), } } - Ok(_) => return Ok(().into_dynamic()), + Ok(_) => return Ok(Dynamic::from_unit()), Err(_) => return Err(EvalAltResult::ErrorLogicGuard(guard.position())), } }, @@ -1588,7 +1550,7 @@ impl Engine<'_> { Stmt::Loop(body) => loop { match self.eval_stmt(scope, fn_lib, body, level) { Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), - Err(EvalAltResult::ErrorLoopBreak(true, _)) => return Ok(().into_dynamic()), + Err(EvalAltResult::ErrorLoopBreak(true, _)) => return Ok(Dynamic::from_unit()), Err(x) => return Err(x), } }, @@ -1596,7 +1558,7 @@ impl Engine<'_> { // For loop Stmt::For(name, expr, body) => { let arr = self.eval_expr(scope, fn_lib, expr, level)?; - let tid = Any::type_id(arr.as_ref()); + let tid = arr.type_id(); if let Some(iter_fn) = self.type_iterators.as_ref().and_then(|t| t.get(&tid)) { // Add the loop variable - variable name is copied @@ -1620,7 +1582,7 @@ impl Engine<'_> { } scope.rewind(scope.len() - 1); - Ok(().into_dynamic()) + Ok(Dynamic::from_unit()) } else { Err(EvalAltResult::ErrorFor(expr.position())) } @@ -1634,7 +1596,7 @@ impl Engine<'_> { // Empty return Stmt::ReturnWithVal(None, ReturnType::Return, pos) => { - Err(EvalAltResult::Return(().into_dynamic(), *pos)) + Err(EvalAltResult::Return(Dynamic::from_unit(), *pos)) } // Return value @@ -1652,7 +1614,7 @@ impl Engine<'_> { Stmt::ReturnWithVal(Some(a), ReturnType::Exception, pos) => { let val = self.eval_expr(scope, fn_lib, a, level)?; Err(EvalAltResult::ErrorRuntime( - val.try_cast::().unwrap_or_else(|_| "".to_string()), + val.take_string().unwrap_or_else(|_| "".to_string()), *pos, )) } @@ -1662,13 +1624,13 @@ impl Engine<'_> { let val = self.eval_expr(scope, fn_lib, expr, level)?; // TODO - avoid copying variable name in inner block? scope.push_dynamic_value(name.clone(), ScopeEntryType::Normal, val, false); - Ok(().into_dynamic()) + Ok(Dynamic::from_unit()) } Stmt::Let(name, None, _) => { // TODO - avoid copying variable name in inner block? scope.push(name.clone(), ()); - Ok(().into_dynamic()) + Ok(Dynamic::from_unit()) } // Const statement @@ -1676,7 +1638,7 @@ impl Engine<'_> { let val = self.eval_expr(scope, fn_lib, expr, level)?; // TODO - avoid copying variable name in inner block? scope.push_dynamic_value(name.clone(), ScopeEntryType::Constant, val, true); - Ok(().into_dynamic()) + Ok(Dynamic::from_unit()) } Stmt::Const(_, _, _) => panic!("constant expression not constant!"), diff --git a/src/fn_call.rs b/src/fn_call.rs index c81b25bc..bdb2ec4b 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -2,8 +2,7 @@ #![allow(non_snake_case)] -use crate::any::{Any, Dynamic}; - +use crate::any::{Dynamic, Variant}; use crate::stdlib::{string::String, vec, vec::Vec}; /// Trait that represent arguments to a function call. @@ -18,7 +17,7 @@ pub trait FuncArgs { /// converted into `Dynamic`). macro_rules! impl_args { ($($p:ident),*) => { - impl<$($p: Any + Clone),*> FuncArgs for ($($p,)*) + impl<$($p: Variant + Clone),*> FuncArgs for ($($p,)*) { fn into_vec(self) -> Vec { let ($($p,)*) = self; diff --git a/src/fn_func.rs b/src/fn_func.rs index 7350d6d5..af45dae8 100644 --- a/src/fn_func.rs +++ b/src/fn_func.rs @@ -2,7 +2,7 @@ #![cfg(not(feature = "no_function"))] #![allow(non_snake_case)] -use crate::any::Any; +use crate::any::Variant; use crate::engine::Engine; use crate::error::ParseError; use crate::parser::AST; @@ -88,7 +88,7 @@ macro_rules! def_anonymous_fn { def_anonymous_fn!(imp); }; (imp $($par:ident),*) => { - impl<'e, $($par: Any + Clone,)* RET: Any + Clone> Func<($($par,)*), RET> for Engine<'e> + impl<'e, $($par: Variant + Clone,)* RET: Variant + Clone> Func<($($par,)*), RET> for Engine<'e> { #[cfg(feature = "sync")] type Output = Box Result + Send + Sync + 'e>; diff --git a/src/fn_register.rs b/src/fn_register.rs index 9030066a..a85dc094 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -2,7 +2,7 @@ #![allow(non_snake_case)] -use crate::any::{Any, Dynamic}; +use crate::any::{Dynamic, Variant}; use crate::engine::{Engine, FnCallArgs}; use crate::parser::Position; use crate::result::EvalAltResult; @@ -53,7 +53,7 @@ pub trait RegisterDynamicFn { /// /// // Function that returns a Dynamic value /// fn return_the_same_as_dynamic(x: i64) -> Dynamic { - /// Box::new(x) + /// Dynamic::from(x) /// } /// /// let mut engine = Engine::new(); @@ -137,7 +137,7 @@ macro_rules! def_register { // ^ function parameter actual type (T, &T or &mut T) // ^ dereferencing function impl< - $($par: Any + Clone,)* + $($par: Variant + Clone,)* #[cfg(feature = "sync")] FN: Fn($($param),*) -> RET + Send + Sync + 'static, @@ -145,7 +145,7 @@ macro_rules! def_register { #[cfg(not(feature = "sync"))] FN: Fn($($param),*) -> RET + 'static, - RET: Any + RET: Variant + Clone > RegisterFn for Engine<'_> { fn register_fn(&mut self, name: &str, f: FN) { @@ -163,13 +163,13 @@ macro_rules! def_register { let mut drain = args.iter_mut(); $( // Downcast every element, return in case of a type mismatch - let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); + let $par: &mut $par = drain.next().unwrap().downcast_mut().unwrap(); )* // Call the user-supplied function using ($clone) to // potentially clone the value, otherwise pass the reference. let r = f($(($clone)($par)),*); - Ok(Box::new(r) as Dynamic) + Ok(r.into_dynamic()) }; self.register_fn_raw(name, vec![$(TypeId::of::<$par>()),*], Box::new(func)); @@ -177,7 +177,7 @@ macro_rules! def_register { } impl< - $($par: Any + Clone,)* + $($par: Variant + Clone,)* #[cfg(feature = "sync")] FN: Fn($($param),*) -> Dynamic + Send + Sync + 'static, @@ -201,7 +201,7 @@ macro_rules! def_register { let mut drain = args.iter_mut(); $( // Downcast every element, return in case of a type mismatch - let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); + let $par: &mut $par = drain.next().unwrap().downcast_mut().unwrap(); )* // Call the user-supplied function using ($clone) to @@ -213,14 +213,14 @@ macro_rules! def_register { } impl< - $($par: Any + Clone,)* + $($par: Variant + Clone,)* #[cfg(feature = "sync")] FN: Fn($($param),*) -> Result + Send + Sync + 'static, #[cfg(not(feature = "sync"))] FN: Fn($($param),*) -> Result + 'static, - RET: Any + RET: Variant + Clone > RegisterResultFn for Engine<'_> { fn register_result_fn(&mut self, name: &str, f: FN) { @@ -238,12 +238,12 @@ macro_rules! def_register { let mut drain = args.iter_mut(); $( // Downcast every element, return in case of a type mismatch - let $par = drain.next().unwrap().downcast_mut::<$par>().unwrap(); + let $par: &mut $par = drain.next().unwrap().downcast_mut().unwrap(); )* // Call the user-supplied function using ($clone) to // potentially clone the value, otherwise pass the reference. - f($(($clone)($par)),*).map(|r| Box::new(r) as Dynamic) + f($(($clone)($par)),*).map(|r| r.into_dynamic()) .map_err(|err| err.set_position(pos)) }; self.register_fn_raw(name, vec![$(TypeId::of::<$par>()),*], Box::new(func)); diff --git a/src/lib.rs b/src/lib.rs index dd4155ec..66dc90af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,7 +83,7 @@ mod result; mod scope; mod stdlib; -pub use any::{Any, AnyExt, Dynamic, Variant}; +pub use any::Dynamic; pub use engine::Engine; pub use error::{ParseError, ParseErrorType}; pub use fn_call::FuncArgs; diff --git a/src/optimize.rs b/src/optimize.rs index fc5a4577..9852b649 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -1,4 +1,4 @@ -use crate::any::{Any, Dynamic}; +use crate::any::Dynamic; use crate::engine::{ Engine, FnAny, FnCallArgs, FnSpec, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF, @@ -116,7 +116,7 @@ fn call_fn( ) -> Result, EvalAltResult> { let spec = FnSpec { name: fn_name.into(), - args: args.iter().map(|a| Any::type_id(*a)).collect(), + args: args.iter().map(|a| a.type_id()).collect(), }; // Search built-in's and external functions @@ -570,7 +570,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { } let mut arg_values: Vec<_> = args.iter().map(Expr::get_constant_value).collect(); - let mut call_args: Vec<_> = arg_values.iter_mut().map(Dynamic::as_mut).collect(); + let mut call_args: Vec<_> = arg_values.iter_mut().collect(); // Save the typename of the first argument if it is `type_of()` // This is to avoid `call_args` being passed into the closure @@ -585,7 +585,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { result.or_else(|| { if !arg_for_type_of.is_empty() { // Handle `type_of()` - Some(arg_for_type_of.to_string().into_dynamic()) + Some(Dynamic::from_string(arg_for_type_of.to_string())) } else { // Otherwise use the default value, if any def_value.clone() diff --git a/src/parser.rs b/src/parser.rs index 9874edcf..98cc9b55 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,7 +1,7 @@ //! Main module defining the lexer and parser. -use crate::any::{Any, AnyExt, Dynamic}; -use crate::engine::{Array, Engine, FunctionsLib, Map}; +use crate::any::{Dynamic, Union}; +use crate::engine::{Engine, FunctionsLib}; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::scope::{EntryType as ScopeEntryType, Scope}; @@ -440,25 +440,30 @@ impl Expr { /// Panics when the expression is not constant. pub fn get_constant_value(&self) -> Dynamic { match self { - Self::IntegerConstant(i, _) => i.into_dynamic(), - Self::FloatConstant(f, _) => f.into_dynamic(), - Self::CharConstant(c, _) => c.into_dynamic(), - Self::StringConstant(s, _) => s.clone().into_owned().into_dynamic(), - Self::True(_) => true.into_dynamic(), - Self::False(_) => false.into_dynamic(), - Self::Unit(_) => ().into_dynamic(), + Self::IntegerConstant(i, _) => Dynamic::from_int(*i), + #[cfg(not(feature = "no_float"))] + Self::FloatConstant(f, _) => Dynamic::from_float(*f), + Self::CharConstant(c, _) => Dynamic::from_char(*c), + Self::StringConstant(s, _) => Dynamic::from_string(s.to_string()), + Self::True(_) => Dynamic::from_bool(true), + Self::False(_) => Dynamic::from_bool(false), + Self::Unit(_) => Dynamic::from_unit(), - Self::Array(items, _) if items.iter().all(Self::is_constant) => items - .iter() - .map(Self::get_constant_value) - .collect::>() - .into_dynamic(), + Self::Array(items, _) if items.iter().all(Self::is_constant) => Dynamic(Union::Array( + items + .iter() + .map(Self::get_constant_value) + .collect::>(), + )), - Self::Map(items, _) if items.iter().all(|(_, v, _)| v.is_constant()) => items - .iter() - .map(|(k, v, _)| (k.clone(), v.get_constant_value())) - .collect::>() - .into_dynamic(), + Self::Map(items, _) if items.iter().all(|(_, v, _)| v.is_constant()) => { + Dynamic(Union::Map(Box::new( + items + .iter() + .map(|(k, v, _)| (k.clone(), v.get_constant_value())) + .collect::>(), + ))) + } _ => panic!("cannot get value of non-constant expression"), } @@ -1958,7 +1963,7 @@ fn parse_unary<'a>( Ok(Expr::FunctionCall( "!".into(), vec![parse_primary(input, allow_stmt_expr)?], - Some(Box::new(false)), // NOT operator, when operating on invalid operand, defaults to false + Some(Dynamic::from_bool(false)), // NOT operator, when operating on invalid operand, defaults to false pos, )) } @@ -2270,37 +2275,37 @@ fn parse_binary_op<'a>( Token::EqualsTo => Expr::FunctionCall( "==".into(), vec![current_lhs, rhs], - Some((false).into_dynamic()), + Some(Dynamic::from_bool(false)), pos, ), Token::NotEqualsTo => Expr::FunctionCall( "!=".into(), vec![current_lhs, rhs], - Some((false).into_dynamic()), + Some(Dynamic::from_bool(false)), pos, ), Token::LessThan => Expr::FunctionCall( "<".into(), vec![current_lhs, rhs], - Some((false).into_dynamic()), + Some(Dynamic::from_bool(false)), pos, ), Token::LessThanEqualsTo => Expr::FunctionCall( "<=".into(), vec![current_lhs, rhs], - Some((false).into_dynamic()), + Some(Dynamic::from_bool(false)), pos, ), Token::GreaterThan => Expr::FunctionCall( ">".into(), vec![current_lhs, rhs], - Some((false).into_dynamic()), + Some(Dynamic::from_bool(false)), pos, ), Token::GreaterThanEqualsTo => Expr::FunctionCall( ">=".into(), vec![current_lhs, rhs], - Some((false).into_dynamic()), + Some(Dynamic::from_bool(false)), pos, ), @@ -2845,67 +2850,50 @@ pub fn parse<'a, 'e>( /// /// Returns Some(expression) if conversion is successful. Otherwise None. pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option { - if value.is::() { - Some(Expr::IntegerConstant(value.cast(), pos)) - } else if value.is::() { - Some(Expr::CharConstant(value.cast(), pos)) - } else if value.is::() { - Some(Expr::StringConstant(value.cast::().into(), pos)) - } else if value.is::() { - Some(if value.cast::() { - Expr::True(pos) - } else { - Expr::False(pos) - }) - } else { + match value.0 { + Union::Unit(_) => Some(Expr::Unit(pos)), + Union::Int(value) => Some(Expr::IntegerConstant(value, pos)), + Union::Char(value) => Some(Expr::CharConstant(value, pos)), + Union::Str(value) => Some(Expr::StringConstant(value.into(), pos)), + Union::Bool(true) => Some(Expr::True(pos)), + Union::Bool(false) => Some(Expr::False(pos)), #[cfg(not(feature = "no_index"))] - { - if value.is::() { - let array = value.cast::(); - let items: Vec<_> = array - .into_iter() - .map(|x| map_dynamic_to_expr(x, pos)) - .collect(); - if items.iter().all(Option::is_some) { - return Some(Expr::Array( - items.into_iter().map(Option::unwrap).collect(), - pos, - )); - } else { - return None; - } + Union::Array(array) => { + let items: Vec<_> = array + .into_iter() + .map(|x| map_dynamic_to_expr(x, pos)) + .collect(); + + if items.iter().all(Option::is_some) { + Some(Expr::Array( + items.into_iter().map(Option::unwrap).collect(), + pos, + )) + } else { + None } } - #[cfg(not(feature = "no_object"))] - { - if value.is::() { - let map = value.cast::(); - let items: Vec<_> = map - .into_iter() - .map(|(k, v)| (k, map_dynamic_to_expr(v, pos), pos)) - .collect(); - if items.iter().all(|(_, expr, _)| expr.is_some()) { - return Some(Expr::Map( - items - .into_iter() - .map(|(k, expr, pos)| (k, expr.unwrap(), pos)) - .collect(), - pos, - )); - } else { - return None; - } + Union::Map(map) => { + let items: Vec<_> = map + .into_iter() + .map(|(k, v)| (k, map_dynamic_to_expr(v, pos), pos)) + .collect(); + if items.iter().all(|(_, expr, _)| expr.is_some()) { + Some(Expr::Map( + items + .into_iter() + .map(|(k, expr, pos)| (k, expr.unwrap(), pos)) + .collect(), + pos, + )) + } else { + None } } - #[cfg(not(feature = "no_float"))] - { - if value.is::() { - return Some(Expr::FloatConstant(value.cast(), pos)); - } - } + Union::Float(value) => Some(Expr::FloatConstant(value, pos)), - None + _ => None, } } diff --git a/src/scope.rs b/src/scope.rs index b5e22170..203d2402 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,6 +1,6 @@ //! Module that defines the `Scope` type representing a function call-stack scope. -use crate::any::{Any, Dynamic}; +use crate::any::{Dynamic, Variant}; use crate::parser::{map_dynamic_to_expr, Expr, Position}; use crate::stdlib::{ @@ -20,7 +20,7 @@ pub enum EntryType { } /// An entry in the Scope. -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct Entry<'a> { /// Name of the entry. pub name: Cow<'a, str>, @@ -68,7 +68,7 @@ pub(crate) struct EntryRef<'a> { /// allowing for automatic _shadowing_. /// /// Currently, `Scope` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct Scope<'a>(Vec>); impl<'a> Scope<'a> { @@ -157,8 +157,8 @@ impl<'a> Scope<'a> { /// my_scope.push("x", 42_i64); /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` - pub fn push>, T: Any + Clone>(&mut self, name: K, value: T) { - self.push_dynamic_value(name, EntryType::Normal, value.into_dynamic(), false); + pub fn push>, T: Variant + Clone>(&mut self, name: K, value: T) { + self.push_dynamic_value(name, EntryType::Normal, Dynamic::from(value), false); } /// Add (push) a new `Dynamic` entry to the Scope. @@ -166,11 +166,11 @@ impl<'a> Scope<'a> { /// # Examples /// /// ``` - /// use rhai::{Any, Scope}; + /// use rhai::{Dynamic, Scope}; /// /// let mut my_scope = Scope::new(); /// - /// my_scope.push_dynamic("x", (42_i64).into_dynamic()); + /// my_scope.push_dynamic("x", Dynamic::from(42_i64)); /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` pub fn push_dynamic>>(&mut self, name: K, value: Dynamic) { @@ -195,8 +195,8 @@ impl<'a> Scope<'a> { /// my_scope.push_constant("x", 42_i64); /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` - pub fn push_constant>, T: Any + Clone>(&mut self, name: K, value: T) { - self.push_dynamic_value(name, EntryType::Constant, value.into_dynamic(), true); + pub fn push_constant>, T: Variant + Clone>(&mut self, name: K, value: T) { + self.push_dynamic_value(name, EntryType::Constant, Dynamic::from(value), true); } /// Add (push) a new constant with a `Dynamic` value to the Scope. @@ -211,11 +211,11 @@ impl<'a> Scope<'a> { /// # Examples /// /// ``` - /// use rhai::{Any, Scope}; + /// use rhai::{Dynamic, Scope}; /// /// let mut my_scope = Scope::new(); /// - /// my_scope.push_constant_dynamic("x", (42_i64).into_dynamic()); + /// my_scope.push_constant_dynamic("x", Dynamic::from(42_i64)); /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` pub fn push_constant_dynamic>>(&mut self, name: K, value: Dynamic) { @@ -336,13 +336,12 @@ impl<'a> Scope<'a> { /// my_scope.push("x", 42_i64); /// assert_eq!(my_scope.get_value::("x").unwrap(), 42); /// ``` - pub fn get_value(&self, name: &str) -> Option { + pub fn get_value(&self, name: &str) -> Option { self.0 .iter() .rev() .find(|Entry { name: key, .. }| name == key) - .and_then(|Entry { value, .. }| value.downcast_ref::()) - .map(T::clone) + .and_then(|Entry { value, .. }| value.downcast_ref::().cloned()) } /// Update the value of the named entry. @@ -366,7 +365,7 @@ impl<'a> Scope<'a> { /// my_scope.set_value("x", 0_i64); /// assert_eq!(my_scope.get_value::("x").unwrap(), 0); /// ``` - pub fn set_value(&mut self, name: &'a str, value: T) { + pub fn set_value(&mut self, name: &'a str, value: T) { match self.get(name) { Some(( EntryRef { @@ -382,8 +381,8 @@ impl<'a> Scope<'a> { .. }, _, - )) => self.0.get_mut(index).unwrap().value = value.into_dynamic(), - None => self.push(name, value.into_dynamic()), + )) => self.0.get_mut(index).unwrap().value = Dynamic::from(value), + None => self.push(name, value), } } @@ -403,7 +402,7 @@ impl<'a> Scope<'a> { } /// Get a mutable reference to an entry in the Scope and downcast it to a specific type - pub(crate) fn get_mut_by_type(&mut self, key: EntryRef) -> &mut T { + pub(crate) fn get_mut_by_type(&mut self, key: EntryRef) -> &mut T { self.get_mut(key) .downcast_mut::() .expect("wrong type cast") diff --git a/tests/maps.rs b/tests/maps.rs index ac591966..c1b91187 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -1,6 +1,6 @@ #![cfg(not(feature = "no_object"))] -use rhai::{AnyExt, Engine, EvalAltResult, Map, Scope, INT}; +use rhai::{Engine, EvalAltResult, Map, Scope, INT}; #[test] fn test_map_indexing() -> Result<(), EvalAltResult> { From adaf086e9053c2531a80a5fa589aa88eeabfc47a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 13 Apr 2020 09:49:12 +0800 Subject: [PATCH 02/34] Add i128, u128 and make timestamp functions safe. --- src/builtin.rs | 138 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 101 insertions(+), 37 deletions(-) diff --git a/src/builtin.rs b/src/builtin.rs index dfdda748..256361a0 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -32,6 +32,11 @@ use crate::stdlib::{ {i32, i64, u32}, }; +#[cfg(feature = "only_i32")] +const MAX_INT: INT = i32::MAX; +#[cfg(not(feature = "only_i32"))] +const MAX_INT: INT = i64::MAX; + macro_rules! reg_op { ($self:expr, $x:expr, $op:expr, $( $y:ty ),*) => ( $( @@ -371,10 +376,10 @@ impl Engine<'_> { #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { - reg_op_result!(self, "+", add, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op_result!(self, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op_result!(self, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op_result!(self, "/", div, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op_result!(self, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op_result!(self, "-", sub, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op_result!(self, "*", mul, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op_result!(self, "/", div, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); } } @@ -388,10 +393,10 @@ impl Engine<'_> { #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { - reg_op!(self, "+", add_u, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(self, "-", sub_u, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(self, "*", mul_u, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(self, "/", div_u, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "+", add_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(self, "-", sub_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(self, "*", mul_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(self, "/", div_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); } } @@ -414,12 +419,12 @@ impl Engine<'_> { #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { - reg_cmp!(self, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64); - reg_cmp!(self, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64); - reg_cmp!(self, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64); - reg_cmp!(self, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64); - reg_cmp!(self, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64); - reg_cmp!(self, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64); + reg_cmp!(self, "<", lt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_cmp!(self, "<=", lte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_cmp!(self, ">", gt, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_cmp!(self, ">=", gte, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_cmp!(self, "==", eq, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_cmp!(self, "!=", ne, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); } #[cfg(not(feature = "no_float"))] @@ -448,9 +453,9 @@ impl Engine<'_> { #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { - reg_op!(self, "|", binary_or, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(self, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(self, "^", binary_xor, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "|", binary_or, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(self, "&", binary_and, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(self, "^", binary_xor, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); } #[cfg(not(feature = "unchecked"))] @@ -462,9 +467,13 @@ impl Engine<'_> { #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { - reg_op_result1!(self, "<<", shl, i64, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op_result1!(self, ">>", shr, i64, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op_result!(self, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op_result1!( + self, "<<", shl, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128 + ); + reg_op_result1!( + self, ">>", shr, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128 + ); + reg_op_result!(self, "%", modulo, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); } } @@ -477,9 +486,9 @@ impl Engine<'_> { #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { - reg_op!(self, "<<", shl_u, i64, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(self, ">>", shr_u, i64, i8, u8, i16, u16, i32, i64, u32, u64); - reg_op!(self, "%", modulo_u, i8, u8, i16, u16, i32, i64, u32, u64); + reg_op!(self, "<<", shl_u, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(self, ">>", shr_u, i64, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); + reg_op!(self, "%", modulo_u, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); } } @@ -595,8 +604,11 @@ impl Engine<'_> { reg_fn1!(self, FUNC_TO_STRING, to_string, String, i8, u8, i16, u16); reg_fn1!(self, KEYWORD_PRINT, to_string, String, i32, i64, u32, u64); reg_fn1!(self, FUNC_TO_STRING, to_string, String, i32, i64, u32, u64); + reg_fn1!(self, KEYWORD_PRINT, to_string, String, i128, u128); + reg_fn1!(self, FUNC_TO_STRING, to_string, String, i128, u128); reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, i8, u8, i16, u16); reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, i32, i64, u32, u64); + reg_fn1!(self, KEYWORD_DEBUG, to_debug, String, i128, u128); } #[cfg(not(feature = "no_float"))] @@ -671,7 +683,7 @@ impl Engine<'_> { ) } - reg_range!(self, "range", i8, u8, i16, u16, i32, i64, u32, u64); + reg_range!(self, "range", i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); } // Register range function with step @@ -733,7 +745,7 @@ impl Engine<'_> { ) } - reg_step!(self, "range", i8, u8, i16, u16, i32, i64, u32, u64); + reg_step!(self, "range", i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); } } } @@ -801,6 +813,8 @@ impl Engine<'_> { self.register_fn("to_float", |x: u32| x as FLOAT); self.register_fn("to_float", |x: i64| x as FLOAT); self.register_fn("to_float", |x: u64| x as FLOAT); + self.register_fn("to_float", |x: i128| x as FLOAT); + self.register_fn("to_float", |x: u128| x as FLOAT); } } @@ -829,7 +843,7 @@ impl Engine<'_> { #[cfg(not(feature = "unchecked"))] { self.register_result_fn("to_int", |x: f32| { - if x > (i64::MAX as f32) { + if x > (MAX_INT as f32) { return Err(EvalAltResult::ErrorArithmetic( format!("Integer overflow: to_int({})", x), Position::none(), @@ -839,7 +853,7 @@ impl Engine<'_> { Ok(x.trunc() as INT) }); self.register_result_fn("to_int", |x: FLOAT| { - if x > (i64::MAX as FLOAT) { + if x > (MAX_INT as FLOAT) { return Err(EvalAltResult::ErrorArithmetic( format!("Integer overflow: to_int({})", x), Position::none(), @@ -907,10 +921,13 @@ impl Engine<'_> { { reg_fn2x!(self, "push", push, &mut Array, (), i8, u8, i16, u16); reg_fn2x!(self, "push", push, &mut Array, (), i32, i64, u32, u64); + reg_fn2x!(self, "push", push, &mut Array, (), i128, u128); reg_fn3!(self, "pad", pad, &mut Array, INT, (), i8, u8, i16, u16); reg_fn3!(self, "pad", pad, &mut Array, INT, (), i32, u32, i64, u64); + reg_fn3!(self, "pad", pad, &mut Array, INT, (), i128, u128); reg_fn3!(self, "insert", ins, &mut Array, INT, (), i8, u8, i16, u16); reg_fn3!(self, "insert", ins, &mut Array, INT, (), i32, i64, u32, u64); + reg_fn3!(self, "insert", ins, &mut Array, INT, (), i128, u128); } #[cfg(not(feature = "no_float"))] @@ -985,8 +1002,13 @@ impl Engine<'_> { #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { - reg_fn2x!(self, "+", append, String, String, i8, u8, i16, u16, i32, i64, u32, u64); - reg_fn2y!(self, "+", prepend, String, String, i8, u8, i16, u16, i32, i64, u32, u64); + reg_fn2x!( + self, "+", append, String, String, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128 + ); + reg_fn2y!( + self, "+", prepend, String, String, i8, u8, i16, u16, i32, i64, u32, u64, i128, + u128 + ); } #[cfg(not(feature = "no_float"))] @@ -1038,19 +1060,48 @@ impl Engine<'_> { // Register date/time functions self.register_fn("timestamp", || Instant::now()); - self.register_fn("-", |ts1: Instant, ts2: Instant| { + self.register_result_fn("-", |ts1: Instant, ts2: Instant| { if ts2 > ts1 { #[cfg(not(feature = "no_float"))] - return -(ts2 - ts1).as_secs_f64(); + return Ok(-(ts2 - ts1).as_secs_f64()); #[cfg(feature = "no_float")] - return -((ts2 - ts1).as_secs() as INT); + { + let seconds = (ts2 - ts1).as_secs(); + + #[cfg(not(feature = "unchecked"))] + { + if seconds > (MAX_INT as u64) { + return Err(EvalAltResult::ErrorArithmetic( + format!( + "Integer overflow for timestamp duration: {}", + -(seconds as i64) + ), + Position::none(), + )); + } + } + return Ok(-(seconds as INT)); + } } else { #[cfg(not(feature = "no_float"))] - return (ts1 - ts2).as_secs_f64(); + return Ok((ts1 - ts2).as_secs_f64()); #[cfg(feature = "no_float")] - return (ts1 - ts2).as_secs() as INT; + { + let seconds = (ts1 - ts2).as_secs(); + + #[cfg(not(feature = "unchecked"))] + { + if seconds > (MAX_INT as u64) { + return Err(EvalAltResult::ErrorArithmetic( + format!("Integer overflow for timestamp duration: {}", seconds), + Position::none(), + )); + } + } + return Ok(seconds as INT); + } } }); @@ -1061,12 +1112,25 @@ impl Engine<'_> { reg_cmp!(self, "==", eq, Instant); reg_cmp!(self, "!=", ne, Instant); - self.register_fn("elapsed", |timestamp: Instant| { + self.register_result_fn("elapsed", |timestamp: Instant| { #[cfg(not(feature = "no_float"))] - return timestamp.elapsed().as_secs_f64(); + return Ok(timestamp.elapsed().as_secs_f64()); #[cfg(feature = "no_float")] - return timestamp.elapsed().as_secs() as INT; + { + let seconds = timestamp.elapsed().as_secs(); + + #[cfg(not(feature = "unchecked"))] + { + if seconds > (MAX_INT as u64) { + return Err(EvalAltResult::ErrorArithmetic( + format!("Integer overflow for timestamp.elapsed(): {}", seconds), + Position::none(), + )); + } + } + return Ok(seconds as INT); + } }); } } From 5796e520eca04db44ff2a7655ea6484bae3ff6ce Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 13 Apr 2020 10:27:08 +0800 Subject: [PATCH 03/34] Support Dynamic return values. --- README.md | 11 +++++-- examples/repl.rs | 34 ++++++++++----------- src/any.rs | 79 ++++++++++++++++++++++++++++-------------------- src/api.rs | 26 +++++++--------- src/engine.rs | 2 +- 5 files changed, 82 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index 7977dbc3..136ec458 100644 --- a/README.md +++ b/README.md @@ -180,13 +180,18 @@ fn main() -> Result<(), EvalAltResult> ### Script evaluation The type parameter is used to specify the type of the return value, which _must_ match the actual type or an error is returned. -Rhai is very strict here. There are two ways to specify the return type - _turbofish_ notation, or type inference. +Rhai is very strict here. Use [`Dynamic`] for uncertain return types. +There are two ways to specify the return type - _turbofish_ notation, or type inference. ```rust let result = engine.eval::("40 + 2")?; // return type is i64, specified using 'turbofish' notation let result: i64 = engine.eval("40 + 2")?; // return type is inferred to be i64 +result.is::() == true; + +let result: Dynamic = engine.eval("boo()")?; // use 'Dynamic' if you're not sure what type it'll be! + let result = engine.eval::("40 + 2")?; // returns an error because the actual return type is i64, not String ``` @@ -440,7 +445,7 @@ function and match against the name). A `Dynamic` value's actual type can be checked via the `is` method. The `cast` method then converts the value into a specific, known type. -Alternatively, use the `try_cast` method which does not panic but returns an error when the cast fails. +Alternatively, use the `try_cast` method which does not panic but returns `None` when the cast fails. ```rust let list: Array = engine.eval("...")?; // return type is 'Array' @@ -451,7 +456,7 @@ item.is::() == true; // 'is' returns whether a 'Dynam let value = item.cast::(); // if the element is 'i64', this succeeds; otherwise it panics let value: i64 = item.cast(); // type can also be inferred -let value = item.try_cast::()?; // 'try_cast' does not panic when the cast fails, but returns an error +let value = item.try_cast::().unwrap(); // 'try_cast' does not panic when the cast fails, but returns 'None' ``` The `type_name` method gets the name of the actual type as a static string slice, which you may match against. diff --git a/examples/repl.rs b/examples/repl.rs index 56f0ac69..0c513a59 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult, Position, Scope, AST}; +use rhai::{Dynamic, Engine, EvalAltResult, Position, Scope, AST}; #[cfg(not(feature = "no_optimize"))] use rhai::OptimizationLevel; @@ -137,7 +137,7 @@ fn main() { _ => (), } - if let Err(err) = engine + match engine .compile_with_scope(&scope, &script) .map_err(EvalAltResult::ErrorParsing) .and_then(|r| { @@ -157,22 +157,20 @@ fn main() { main_ast = main_ast.merge(&ast); // Evaluate - let result = engine - .consume_ast_with_scope(&mut scope, &main_ast) - .or_else(|err| match err { - EvalAltResult::Return(_, _) => Ok(()), - err => Err(err), - }); - - // Throw away all the statements, leaving only the functions - main_ast.retain_functions(); - - result - }) - { - println!(); - print_error(&input, err); - println!(); + engine.eval_ast_with_scope::(&mut scope, &main_ast) + }) { + Ok(result) => { + println!("=> {:?}", result); + println!(); + } + Err(err) => { + println!(); + print_error(&input, err); + println!(); + } } + + // Throw away all the statements, leaving only the functions + main_ast.retain_functions(); } } diff --git a/src/any.rs b/src/any.rs index 803fe2d2..65f68cbd 100644 --- a/src/any.rs +++ b/src/any.rs @@ -143,6 +143,15 @@ pub enum Union { } impl Dynamic { + /// Does this `Dynamic` hold a variant data type + /// instead of one of the support system primitive types? + pub fn is_variant(&self) -> bool { + match self.0 { + Union::Variant(_) => true, + _ => false, + } + } + /// Is the value held by this `Dynamic` a particular type? pub fn is(&self) -> bool { self.type_id() == TypeId::of::() @@ -212,7 +221,7 @@ impl fmt::Debug for Dynamic { Union::Float(value) => write!(f, "{:?}", value), Union::Array(value) => write!(f, "{:?}", value), Union::Map(value) => write!(f, "{:?}", value), - Union::Variant(_) => write!(f, "?"), + Union::Variant(_) => write!(f, ""), } } } @@ -234,8 +243,23 @@ impl Clone for Dynamic { } } +/// Cast a Boxed type into another type. +fn cast_box(item: Box) -> Result> { + // Only allow casting to the exact same type + if TypeId::of::() == TypeId::of::() { + // SAFETY: just checked whether we are pointing to the correct type + unsafe { + let raw: *mut dyn Any = Box::into_raw(item as Box); + Ok(*Box::from_raw(raw as *mut T)) + } + } else { + // Return the consumed item for chaining. + Err(item) + } +} + impl Dynamic { - /// Create a `Dynamic` from any type. + /// Create a `Dynamic` from any type. A `Dynamic` value is simply returned as is. /// /// # Examples /// @@ -249,6 +273,10 @@ impl Dynamic { /// let result = Dynamic::from("hello".to_string()); /// assert_eq!(result.type_name(), "string"); /// assert_eq!(result.to_string(), "hello"); + /// + /// let new_result = Dynamic::from(result); + /// assert_eq!(new_result.type_name(), "string"); + /// assert_eq!(new_result.to_string(), "hello"); /// ``` pub fn from(value: T) -> Self { if let Some(result) = (&value as &dyn Variant) @@ -288,29 +316,20 @@ impl Dynamic { } } - fn cast_box(item: Box) -> Result> { - if TypeId::of::() == TypeId::of::() { - unsafe { - let raw: *mut dyn Any = Box::into_raw(item as Box); - Ok(*Box::from_raw(raw as *mut T)) - } - } else { - Err(item) - } - } - let var = Box::new(value); Self( - cast_box::<_, String>(var) - .map(Union::Str) + cast_box::<_, Dynamic>(var) + .map(|x| x.0) .or_else(|var| { - cast_box::<_, Array>(var).map(Union::Array).or_else(|var| { - cast_box::<_, Map>(var) - .map(|v| Union::Map(Box::new(v))) - .or_else(|var| -> Result { - Ok(Union::Variant(var as Box)) - }) + cast_box::<_, String>(var).map(Union::Str).or_else(|var| { + cast_box::<_, Array>(var).map(Union::Array).or_else(|var| { + cast_box::<_, Map>(var) + .map(|v| Union::Map(Box::new(v))) + .or_else(|var| -> Result { + Ok(Union::Variant(var as Box)) + }) + }) }) }) .unwrap(), @@ -318,6 +337,7 @@ impl Dynamic { } /// Get a copy of the `Dynamic` value as a specific type. + /// Casting to a `Dynamic` just returns as is. /// /// Returns an error with the name of the value's actual type when the cast fails. /// @@ -330,7 +350,11 @@ impl Dynamic { /// /// assert_eq!(x.try_cast::().unwrap(), 42); /// ``` - pub fn try_cast(self) -> Result { + pub fn try_cast(self) -> Option { + if TypeId::of::() == TypeId::of::() { + return cast_box::<_, T>(Box::new(self)).ok(); + } + match &self.0 { Union::Unit(value) => (value as &dyn Variant).downcast_ref::().cloned(), Union::Bool(value) => (value as &dyn Variant).downcast_ref::().cloned(), @@ -345,7 +369,6 @@ impl Dynamic { .cloned(), Union::Variant(value) => value.as_ref().downcast_ref::().cloned(), } - .ok_or(self) } /// Get a copy of the `Dynamic` value as a specific type. @@ -410,16 +433,6 @@ impl Dynamic { } } - /// Cast the `Dynamic` as the system integer type `INT` and return it. - /// Returns the name of the actual type if the cast fails. - #[cfg(not(feature = "no_float"))] - pub(crate) fn as_float(&self) -> Result { - match self.0 { - Union::Float(n) => Ok(n), - _ => Err(self.type_name()), - } - } - /// Cast the `Dynamic` as a `bool` and return it. /// Returns the name of the actual type if the cast fails. pub(crate) fn as_bool(&self) -> Result { diff --git a/src/api.rs b/src/api.rs index 9dc84298..837c0bc0 100644 --- a/src/api.rs +++ b/src/api.rs @@ -799,14 +799,12 @@ impl<'e> Engine<'e> { scope: &mut Scope, ast: &AST, ) -> Result { - self.eval_ast_with_scope_raw(scope, ast)? - .try_cast::() - .map_err(|a| { - EvalAltResult::ErrorMismatchOutputType( - self.map_type_name(a.type_name()).to_string(), - Position::none(), - ) - }) + let result = self.eval_ast_with_scope_raw(scope, ast)?; + let return_type = self.map_type_name(result.type_name()); + + return result.try_cast::().ok_or_else(|| { + EvalAltResult::ErrorMismatchOutputType(return_type.to_string(), Position::none()) + }); } pub(crate) fn eval_ast_with_scope_raw( @@ -933,14 +931,12 @@ impl<'e> Engine<'e> { let fn_lib = Some(ast.1.as_ref()); let pos = Position::none(); - self.call_fn_raw(Some(scope), fn_lib, name, &mut args, None, pos, 0)? + let result = self.call_fn_raw(Some(scope), fn_lib, name, &mut args, None, pos, 0)?; + let return_type = self.map_type_name(result.type_name()); + + return result .try_cast() - .map_err(|a| { - EvalAltResult::ErrorMismatchOutputType( - self.map_type_name(a.type_name()).into(), - pos, - ) - }) + .ok_or_else(|| EvalAltResult::ErrorMismatchOutputType(return_type.into(), pos)); } /// Optimize the `AST` with constants defined in an external Scope. diff --git a/src/engine.rs b/src/engine.rs index 59906675..ea223926 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -543,7 +543,7 @@ impl Engine<'_> { // Raise error let types_list: Vec<_> = args .iter() - .map(|x| (*x).type_name()) + .map(|x| x.type_name()) .map(|name| self.map_type_name(name)) .collect(); From 2e9a5f7a8945b2cce2b066c52caf467a3cb0eb29 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 13 Apr 2020 12:29:22 +0800 Subject: [PATCH 04/34] Add sub_string, crop and index_of to String. --- README.md | 40 ++++++++++++------ src/builtin.rs | 98 ++++++++++++++++++++++++++++++++++++++++++- tests/string.rs | 108 +++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 232 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index c073d244..26d12ee8 100644 --- a/README.md +++ b/README.md @@ -1151,16 +1151,19 @@ record == "Bob X. Davis: age 42 ❤\n"; The following standard methods (defined in the standard library but excluded if using a [raw `Engine`]) operate on strings: -| Function | Parameter(s) | Description | -| ---------- | ------------------------------------- | -------------------------------------------------------------------- | -| `len` | _none_ | returns the number of characters (not number of bytes) in the string | -| `pad` | character to pad, target length | pads the string with an character to a specified length | -| `append` | character/string to append | Adds a character or a string to the end of another string | -| `clear` | _none_ | empties the string | -| `truncate` | target length | cuts off the string at exactly a specified number of characters | -| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string | -| `replace` | target sub-string, replacement string | replaces a substring with another | -| `trim` | _none_ | trims the string of whitespace at the beginning and end | +| Function | Parameter(s) | Description | +| ------------ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | +| `len` | _none_ | returns the number of characters (not number of bytes) in the string | +| `pad` | character to pad, target length | pads the string with an character to at least a specified length | +| `append` | character/string to append | Adds a character or a string to the end of another string | +| `clear` | _none_ | empties the string | +| `truncate` | target length | cuts off the string at exactly a specified number of characters | +| `contains` | character/sub-string to search for | checks if a certain character or sub-string occurs in the string | +| `index_of` | character/sub-string to search for, start index _(optional)_ | returns the index that a certain character or sub-string occurs in the string, or -1 if not found | +| `sub_string` | start index, length _(optional)_ | extracts a sub-string (to the end of the string if length is not specified) | +| `crop` | start index, length _(optional)_ | retains only a portion of the string (to the end of the string if length is not specified) | +| `replace` | target sub-string, replacement string | replaces a sub-string with another | +| `trim` | _none_ | trims the string of whitespace at the beginning and end | ### Examples @@ -1176,17 +1179,30 @@ full_name.pad(15, '$'); full_name.len() == 15; full_name == "Bob C. Davis$$$"; +let n = full_name.index_of('$'); +n == 12; + +full_name.index_of("$$", n + 1) == 13; + +full_name.sub_string(n, 3) == "$$$"; + full_name.truncate(6); full_name.len() == 6; full_name == "Bob C."; full_name.replace("Bob", "John"); full_name.len() == 7; -full_name = "John C."; +full_name == "John C."; full_name.contains('C') == true; full_name.contains("John") == true; +full_name.crop(5); +full_name == "C."; + +full_name.crop(0, 1); +full_name == "C"; + full_name.clear(); full_name.len() == 0; ``` @@ -1220,7 +1236,7 @@ The following methods (defined in the standard library but excluded if using a [ | `shift` | _none_ | removes the first element and returns it ([`()`] if empty) | | `remove` | index | removes an element at a particular index and returns it, or returns [`()`] if the index is not valid | | `len` | _none_ | returns the number of elements | -| `pad` | element to pad, target length | pads the array with an element until a specified length | +| `pad` | element to pad, target length | pads the array with an element to at least a specified length | | `clear` | _none_ | empties the array | | `truncate` | target length | cuts off the array at exactly a specified length (discarding all subsequent elements) | diff --git a/src/builtin.rs b/src/builtin.rs index f77b4f4a..c312a980 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -1000,17 +1000,113 @@ impl Engine<'_> { } // Register string utility functions + fn sub_string(s: &mut String, start: INT, len: INT) -> String { + let offset = if s.is_empty() || len <= 0 { + return "".to_string(); + } else if start < 0 { + 0 + } else if (start as usize) >= s.chars().count() { + return "".to_string(); + } else { + start as usize + }; + + let chars: Vec<_> = s.chars().collect(); + + let len = if offset + (len as usize) > chars.len() { + chars.len() - offset + } else { + len as usize + }; + + chars[offset..][..len].into_iter().collect::() + } + + fn crop_string(s: &mut String, start: INT, len: INT) { + let offset = if s.is_empty() || len <= 0 { + s.clear(); + return; + } else if start < 0 { + 0 + } else if (start as usize) >= s.chars().count() { + s.clear(); + return; + } else { + start as usize + }; + + let chars: Vec<_> = s.chars().collect(); + + let len = if offset + (len as usize) > chars.len() { + chars.len() - offset + } else { + len as usize + }; + + s.clear(); + + chars[offset..][..len] + .into_iter() + .for_each(|&ch| s.push(ch)); + } + self.register_fn("len", |s: &mut String| s.chars().count() as INT); self.register_fn("contains", |s: &mut String, ch: char| s.contains(ch)); self.register_fn("contains", |s: &mut String, find: String| s.contains(&find)); + self.register_fn("index_of", |s: &mut String, ch: char, start: INT| { + let start = if start < 0 { + 0 + } else if (start as usize) >= s.chars().count() { + return -1 as INT; + } else { + s.chars().take(start as usize).collect::().len() + }; + + s[start..] + .find(ch) + .map(|index| s[0..start + index].chars().count() as INT) + .unwrap_or(-1 as INT) + }); + self.register_fn("index_of", |s: &mut String, ch: char| { + s.find(ch) + .map(|index| s[0..index].chars().count() as INT) + .unwrap_or(-1 as INT) + }); + self.register_fn("index_of", |s: &mut String, find: String, start: INT| { + let start = if start < 0 { + 0 + } else if (start as usize) >= s.chars().count() { + return -1 as INT; + } else { + s.chars().take(start as usize).collect::().len() + }; + + s[start..] + .find(&find) + .map(|index| s[0..start + index].chars().count() as INT) + .unwrap_or(-1 as INT) + }); + self.register_fn("index_of", |s: &mut String, find: String| { + s.find(&find) + .map(|index| s[0..index].chars().count() as INT) + .unwrap_or(-1 as INT) + }); self.register_fn("clear", |s: &mut String| s.clear()); self.register_fn("append", |s: &mut String, ch: char| s.push(ch)); self.register_fn("append", |s: &mut String, add: String| s.push_str(&add)); + self.register_fn("sub_string", sub_string); + self.register_fn("sub_string", |s: &mut String, start: INT| { + sub_string(s, start, s.len() as INT) + }); + self.register_fn("crop", crop_string); + self.register_fn("crop", |s: &mut String, start: INT| { + crop_string(s, start, s.len() as INT) + }); self.register_fn("truncate", |s: &mut String, len: INT| { if len >= 0 { let chars: Vec<_> = s.chars().take(len as usize).collect(); s.clear(); - chars.iter().for_each(|&ch| s.push(ch)); + chars.into_iter().for_each(|ch| s.push(ch)); } else { s.clear(); } diff --git a/tests/string.rs b/tests/string.rs index 7454dcf2..af974ae1 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_string() -> Result<(), EvalAltResult> { @@ -33,3 +33,109 @@ fn test_string() -> Result<(), EvalAltResult> { Ok(()) } + +#[cfg(not(feature = "no_stdlib"))] +#[test] +fn test_string_substring() -> Result<(), EvalAltResult> { + let engine = Engine::new(); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(-1, 2)"# + )?, + "❤❤" + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(1, 5)"# + )?, + "❤❤ he" + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(1)"# + )?, + "❤❤ hello! ❤❤❤" + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(99)"# + )?, + "" + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(1, -1)"# + )?, + "" + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.sub_string(1, 999)"# + )?, + "❤❤ hello! ❤❤❤" + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.crop(1, -1); x"# + )?, + "" + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.crop(4, 6); x"# + )?, + "hello!" + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.crop(1, 999); x"# + )?, + "❤❤ hello! ❤❤❤" + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.index_of('\u2764')"# + )?, + 0 + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.index_of('\u2764', 5)"# + )?, + 11 + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.index_of('\u2764', -1)"# + )?, + 0 + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.index_of('\u2764', 999)"# + )?, + -1 + ); + + assert_eq!( + engine.eval::( + r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.index_of('x')"# + )?, + -1 + ); + + Ok(()) +} From 254522b268492e031fa50d186552f92ca151e5ee Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 13 Apr 2020 14:26:53 +0800 Subject: [PATCH 05/34] Do not run test_string_substring if no_object. --- README.md | 2 -- tests/string.rs | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 26d12ee8..2f21cc07 100644 --- a/README.md +++ b/README.md @@ -317,8 +317,6 @@ Use `Engine::new_raw` to create a _raw_ `Engine`, in which: let mut engine = Engine::new_raw(); // create a 'raw' Engine engine.register_stdlib(); // register the standard library manually - -engine. ``` Evaluate expressions only diff --git a/tests/string.rs b/tests/string.rs index af974ae1..1ec77e2e 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -35,6 +35,7 @@ fn test_string() -> Result<(), EvalAltResult> { } #[cfg(not(feature = "no_stdlib"))] +#[cfg(not(feature = "no_object"))] #[test] fn test_string_substring() -> Result<(), EvalAltResult> { let engine = Engine::new(); From 9ed9df1e96cf067e99e4a414c932d59f767493c9 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 13 Apr 2020 17:26:16 +0800 Subject: [PATCH 06/34] Add benchmarks. --- benches/eval_expression.rs | 43 ++++++++++++++++ benches/eval_scope.rs | 77 ++++++++++++++++++++++++++++ benches/eval_type.rs | 100 +++++++++++++++++++++++++++++++++++++ benches/iterations.rs | 25 ++++++++++ benches/parsing.rs | 83 ++++++++++++++++++++++++++++++ 5 files changed, 328 insertions(+) create mode 100644 benches/eval_expression.rs create mode 100644 benches/eval_scope.rs create mode 100644 benches/eval_type.rs create mode 100644 benches/iterations.rs create mode 100644 benches/parsing.rs diff --git a/benches/eval_expression.rs b/benches/eval_expression.rs new file mode 100644 index 00000000..131d3bb5 --- /dev/null +++ b/benches/eval_expression.rs @@ -0,0 +1,43 @@ +#![feature(test)] + +///! Test evaluating expressions +extern crate test; + +use rhai::{Engine, OptimizationLevel}; +use test::Bencher; + +#[bench] +fn bench_eval_expression_single(bench: &mut Bencher) { + let script = "1"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile_expression(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} + +#[bench] +fn bench_eval_expression_number_literal(bench: &mut Bencher) { + let script = "2 > 1"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile_expression(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} + +#[bench] +fn bench_eval_expression_number_operators(bench: &mut Bencher) { + let script = "2 + 2 == 4"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile_expression(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} diff --git a/benches/eval_scope.rs b/benches/eval_scope.rs new file mode 100644 index 00000000..5d38f7d7 --- /dev/null +++ b/benches/eval_scope.rs @@ -0,0 +1,77 @@ +#![feature(test)] + +///! Test evaluating with scope +extern crate test; + +use rhai::{Engine, OptimizationLevel, Scope, INT}; +use test::Bencher; + +#[bench] +fn bench_eval_scope_single(bench: &mut Bencher) { + let script = "requests_made == requests_made"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let mut scope = Scope::new(); + scope.push("requests_made", 99 as INT); + + let ast = engine.compile_expression(script).unwrap(); + + bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); +} + +#[bench] +fn bench_eval_scope_multiple(bench: &mut Bencher) { + let script = "requests_made > requests_succeeded"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let mut scope = Scope::new(); + scope.push("requests_made", 99 as INT); + scope.push("requests_succeeded", 90 as INT); + + let ast = engine.compile_expression(script).unwrap(); + + bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); +} + +#[bench] +fn bench_eval_scope_longer(bench: &mut Bencher) { + let script = "(requests_made * requests_succeeded / 100) >= 90"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let mut scope = Scope::new(); + scope.push("requests_made", 99 as INT); + scope.push("requests_succeeded", 90 as INT); + + let ast = engine.compile_expression(script).unwrap(); + + bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); +} + +#[bench] +fn bench_eval_scope_complex(bench: &mut Bencher) { + let script = r#" + 2 > 1 && + "something" != "nothing" || + "2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && + Variable_name_with_spaces <= variableName && + modifierTest + 1000 / 2 > (80 * 100 % 2) + "#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let mut scope = Scope::new(); + scope.push("Variable_name_with_spaces", 99 as INT); + scope.push("variableName", 90 as INT); + scope.push("modifierTest", 5 as INT); + + let ast = engine.compile_expression(script).unwrap(); + + bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); +} diff --git a/benches/eval_type.rs b/benches/eval_type.rs new file mode 100644 index 00000000..1e5a70c8 --- /dev/null +++ b/benches/eval_type.rs @@ -0,0 +1,100 @@ +#![feature(test)] + +///! Test evaluating expressions +extern crate test; + +use rhai::{Engine, OptimizationLevel, RegisterFn, Scope, INT}; +use test::Bencher; + +#[derive(Debug, Clone)] +struct Test { + x: INT, +} + +impl Test { + pub fn get_x(&mut self) -> INT { + self.x + } + pub fn action(&mut self) { + self.x = 0; + } + pub fn update(&mut self, val: INT) { + self.x = val; + } + pub fn get_nest(&mut self) -> Test { + Test { x: 9 } + } +} + +#[bench] +fn bench_type_field(bench: &mut Bencher) { + let script = "foo.field"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + engine.register_type_with_name::("Test"); + engine.register_get("field", Test::get_x); + + let ast = engine.compile_expression(script).unwrap(); + + let mut scope = Scope::new(); + scope.push("foo", Test { x: 42 }); + + bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); +} + +#[bench] +fn bench_type_method(bench: &mut Bencher) { + let script = "foo.action()"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + engine.register_type_with_name::("Test"); + engine.register_fn("action", Test::action); + + let ast = engine.compile_expression(script).unwrap(); + + let mut scope = Scope::new(); + scope.push("foo", Test { x: 42 }); + + bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); +} + +#[bench] +fn bench_type_method_with_params(bench: &mut Bencher) { + let script = "foo.update(1)"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + engine.register_type_with_name::("Test"); + engine.register_fn("update", Test::update); + + let ast = engine.compile_expression(script).unwrap(); + + let mut scope = Scope::new(); + scope.push("foo", Test { x: 42 }); + + bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); +} + +#[bench] +fn bench_type_method_nested(bench: &mut Bencher) { + let script = "foo.nest.field"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + engine.register_type_with_name::("Test"); + engine.register_get("field", Test::get_x); + engine.register_get("nest", Test::get_nest); + + let ast = engine.compile_expression(script).unwrap(); + + let mut scope = Scope::new(); + scope.push("foo", Test { x: 42 }); + + bench.iter(|| engine.consume_ast_with_scope(&mut scope, &ast).unwrap()); +} diff --git a/benches/iterations.rs b/benches/iterations.rs new file mode 100644 index 00000000..31da75f3 --- /dev/null +++ b/benches/iterations.rs @@ -0,0 +1,25 @@ +#![feature(test)] + +///! Test 1,000 iterations +extern crate test; + +use rhai::{Engine, OptimizationLevel}; +use test::Bencher; + +#[bench] +fn bench_iterations_1000(bench: &mut Bencher) { + let script = r#" + let x = 1_000; + + while x > 0 { + x = x - 1; + } + "#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} diff --git a/benches/parsing.rs b/benches/parsing.rs new file mode 100644 index 00000000..280b9f45 --- /dev/null +++ b/benches/parsing.rs @@ -0,0 +1,83 @@ +#![feature(test)] + +///! Test parsing expressions +extern crate test; + +use rhai::{Engine, OptimizationLevel}; +use test::Bencher; + +#[bench] +fn bench_parse_single(bench: &mut Bencher) { + let script = "1"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + bench.iter(|| engine.compile_expression(script).unwrap()); +} + +#[bench] +fn bench_parse_simple(bench: &mut Bencher) { + let script = "(requests_made * requests_succeeded / 100) >= 90"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + bench.iter(|| engine.compile_expression(script).unwrap()); +} + +#[bench] +fn bench_parse_full(bench: &mut Bencher) { + let script = r#" + 2 > 1 && + "something" != "nothing" || + "2014-01-20" < "Wed Jul 8 23:07:35 MDT 2015" && + [array, with, spaces].len() <= #{prop:name}.len() && + modifierTest + 1000 / 2 > (80 * 100 % 2) + "#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + bench.iter(|| engine.compile_expression(script).unwrap()); +} + +#[bench] +fn bench_parse_primes(bench: &mut Bencher) { + let script = r#" + // This script uses the Sieve of Eratosthenes to calculate prime numbers. + + let now = timestamp(); + + const MAX_NUMBER_TO_CHECK = 10_000; // 1229 primes <= 10000 + + let prime_mask = []; + prime_mask.pad(MAX_NUMBER_TO_CHECK, true); + + prime_mask[0] = prime_mask[1] = false; + + let total_primes_found = 0; + + for p in range(2, MAX_NUMBER_TO_CHECK) { + if prime_mask[p] { + print(p); + + total_primes_found += 1; + let i = 2 * p; + + while i < MAX_NUMBER_TO_CHECK { + prime_mask[i] = false; + i += p; + } + } + } + + print("Total " + total_primes_found + " primes."); + print("Run time = " + now.elapsed() + " seconds."); + "#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + bench.iter(|| engine.compile(script).unwrap()); +} From f600e59401dd97a619d70aec207e16ad7f0732f7 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 13 Apr 2020 23:31:05 +0800 Subject: [PATCH 07/34] Fix bug with casting from float. --- src/any.rs | 49 +++++++++++++++++++++---------------------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/src/any.rs b/src/any.rs index 65f68cbd..a056179b 100644 --- a/src/any.rs +++ b/src/any.rs @@ -279,39 +279,21 @@ impl Dynamic { /// assert_eq!(new_result.to_string(), "hello"); /// ``` pub fn from(value: T) -> Self { - if let Some(result) = (&value as &dyn Variant) - .downcast_ref::<()>() - .cloned() - .map(Union::Unit) - .or_else(|| { - (&value as &dyn Variant) - .downcast_ref::() - .cloned() - .map(Union::Bool) - .or_else(|| { - (&value as &dyn Variant) - .downcast_ref::() - .cloned() - .map(Union::Int) - .or_else(|| { - (&value as &dyn Variant) - .downcast_ref::() - .cloned() - .map(Union::Char) - }) - }) - }) - { + let dyn_value = &value as &dyn Variant; + + if let Some(result) = dyn_value.downcast_ref::<()>().cloned().map(Union::Unit) { + return Self(result); + } else if let Some(result) = dyn_value.downcast_ref::().cloned().map(Union::Bool) { + return Self(result); + } else if let Some(result) = dyn_value.downcast_ref::().cloned().map(Union::Int) { + return Self(result); + } else if let Some(result) = dyn_value.downcast_ref::().cloned().map(Union::Char) { return Self(result); } #[cfg(not(feature = "no_float"))] { - if let Some(result) = (&value as &dyn Variant) - .downcast_ref::() - .cloned() - .map(Union::Char) - { + if let Some(result) = dyn_value.downcast_ref::().cloned().map(Union::Float) { return Self(result); } } @@ -372,6 +354,7 @@ impl Dynamic { } /// Get a copy of the `Dynamic` value as a specific type. + /// Casting to a `Dynamic` just returns as is. /// /// # Panics /// @@ -391,8 +374,13 @@ impl Dynamic { } /// Get a reference of a specific type to the `Dynamic`. + /// Casting to `Dynamic` just returns a reference to it. /// Returns `None` if the cast fails. pub fn downcast_ref(&self) -> Option<&T> { + if TypeId::of::() == TypeId::of::() { + return (self as &dyn Variant).downcast_ref::(); + } + match &self.0 { Union::Unit(value) => (value as &dyn Variant).downcast_ref::(), Union::Bool(value) => (value as &dyn Variant).downcast_ref::(), @@ -408,8 +396,13 @@ impl Dynamic { } /// Get a mutable reference of a specific type to the `Dynamic`. + /// Casting to `Dynamic` just returns a mutable reference to it. /// Returns `None` if the cast fails. pub fn downcast_mut(&mut self) -> Option<&mut T> { + if TypeId::of::() == TypeId::of::() { + return (self as &mut dyn Variant).downcast_mut::(); + } + match &mut self.0 { Union::Unit(value) => (value as &mut dyn Variant).downcast_mut::(), Union::Bool(value) => (value as &mut dyn Variant).downcast_mut::(), From 691541c176c26233c4e4b29620f4ec1b821b773e Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 13 Apr 2020 23:38:10 +0800 Subject: [PATCH 08/34] Add more benchmarks. --- README.md | 2 ++ benches/iterations.rs | 26 ++++++++++++++- scripts/fibonacci.rhai | 22 +++++++++++++ scripts/mat_mul.rhai | 73 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 scripts/fibonacci.rhai create mode 100644 scripts/mat_mul.rhai diff --git a/README.md b/README.md index e6279422..13376dbb 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,8 @@ There are also a number of examples scripts that showcase Rhai's features, all i | -------------------------------------------- | ---------------------------------------------------------------------------------- | | [`speed_test.rhai`](scripts/speed_test.rhai) | a simple program to measure the speed of Rhai's interpreter (1 million iterations) | | [`primes.rhai`](scripts/primes.rhai) | use Sieve of Eratosthenes to find all primes smaller than a limit | +| [`fibonacci.rhai`](scripts/fibonacci.rhai) | calculate the n-th Fibonacci number using a really dumb algorithm | +| [`mat_mul.rhai`](scripts/mat_mul.rhai) | matrix multiplication test to measure the speed of Rhai's interpreter | To run the scripts, either make a tiny program or use of the `rhai_runner` example: diff --git a/benches/iterations.rs b/benches/iterations.rs index 31da75f3..073f0a91 100644 --- a/benches/iterations.rs +++ b/benches/iterations.rs @@ -3,7 +3,7 @@ ///! Test 1,000 iterations extern crate test; -use rhai::{Engine, OptimizationLevel}; +use rhai::{Engine, OptimizationLevel, Scope, INT}; use test::Bencher; #[bench] @@ -23,3 +23,27 @@ fn bench_iterations_1000(bench: &mut Bencher) { bench.iter(|| engine.consume_ast(&ast).unwrap()); } + +#[bench] +fn bench_iterations_fibonacci(bench: &mut Bencher) { + let script = r#" + fn fibonacci(n) { + if n < 2 { + n + } else { + fibonacci(n-1) + fibonacci(n-2) + } + } + "#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + bench.iter(|| { + engine + .call_fn::<_, INT>(&mut Scope::new(), &ast, "fibonacci", (20 as INT,)) + .unwrap() + }); +} diff --git a/scripts/fibonacci.rhai b/scripts/fibonacci.rhai new file mode 100644 index 00000000..a9a54c3a --- /dev/null +++ b/scripts/fibonacci.rhai @@ -0,0 +1,22 @@ +// This script calculates the n-th Fibonacci number using a really dumb algorithm +// to test the speed of the scripting engine. + +const target = 30; + +let now = timestamp(); + +fn fib(n) { + if n < 2 { + n + } else { + fib(n-1) + fib(n-2) + } +} + +print("Ready... Go!"); + +let result = fib(target); + +print("Fibonacci number #" + target + " = " + result); + +print("Finished. Run time = " + now.elapsed() + " seconds."); diff --git a/scripts/mat_mul.rhai b/scripts/mat_mul.rhai new file mode 100644 index 00000000..c7c00ae9 --- /dev/null +++ b/scripts/mat_mul.rhai @@ -0,0 +1,73 @@ +const SIZE = 50; + +fn new_mat(x, y) { + let row = []; + row.pad(y, 0.0); + + let matrix = []; + matrix.pad(x, row); + + matrix +} + +fn mat_gen(n) { + let m = new_mat(n, n); + let tmp = 1.0 / n.to_float() / n.to_float(); + + for i in range(0, n) { + for j in range(0, n) { + let foo = m[i]; + foo[j] = tmp * (i.to_float() - j.to_float()) * (i.to_float() + j.to_float()); + m[i] = foo; + } + } + + m +} + +fn mat_mul(a, b) { + let m = a.len(); + let n = a[0].len(); + let p = b[0].len(); + + let b2 = new_mat(n, p); + + for i in range(0, n) { + for j in range(0, p) { + let foo = b2[j]; + foo[i] = b[i][j]; + b2[j] = foo; + } + } + + let c = new_mat(m, p); + + for i in range(0, c.len()) { + let ci = c[i]; + for j in range(0, ci.len()) { + let b2j = b2[j]; + ci[j] = 0.0; + + for z in range(0, a[i].len()) { + let x = a[i][z]; + let y = b2j[z]; + ci[j] += x * y; + } + } + c[i] = ci; + } + + c +} + +let now = timestamp(); + +let a = mat_gen(SIZE); +let b = mat_gen(SIZE); +let c = mat_mul(a, b); + +for i in range(0, SIZE) { + print(c[i]); +} + +print("Finished. Run time = " + now.elapsed() + " seconds."); From 78cd53db09485e0a0740e0abdeb8d5f9fc18396c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 15 Apr 2020 21:41:28 +0800 Subject: [PATCH 09/34] Streamline tokens reading and reformat code for easier reading. --- src/parser.rs | 436 ++++++++++++++++++++++++-------------------------- 1 file changed, 205 insertions(+), 231 deletions(-) diff --git a/src/parser.rs b/src/parser.rs index 98cc9b55..d57574b4 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -556,7 +556,9 @@ impl Expr { Self::Stmt(stmt, _) => stmt.is_pure(), - expr => expr.is_constant() || matches!(expr, Self::Variable(_, _)), + Self::Variable(_, _) => true, + + expr => expr.is_constant(), } } @@ -1435,14 +1437,45 @@ pub fn lex<'a>(input: &'a [&'a str]) -> TokenIterator<'a> { } } +/// Consume a particular token, checking that it is the expected one. +fn eat_token(input: &mut Peekable, token: Token) { + if let Some((t, pos)) = input.next() { + if t != token { + panic!( + "expecting {} (found {}) at {}", + token.syntax(), + t.syntax(), + pos + ); + } + } else { + panic!("expecting {} but already EOF", token.syntax()); + } +} + +/// Match a particular token, consuming it if matched. +fn match_token(input: &mut Peekable, token: Token) -> Result { + if let Some((t, _)) = input.peek() { + if *t == token { + eat_token(input, token); + Ok(true) + } else { + Ok(false) + } + } else { + Err(PERR::UnexpectedEOF.into_err_eof()) + } +} + /// Parse ( expr ) fn parse_paren_expr<'a>( input: &mut Peekable>, begin: Position, allow_stmt_expr: bool, ) -> Result { - if matches!(input.peek(), Some((Token::RightParen, _))) { - input.next(); + const MISSING_RPAREN: &str = "for a matching ( in this expression"; + + if match_token(input, Token::RightParen)? { return Ok(Expr::Unit(begin)); } @@ -1452,16 +1485,9 @@ fn parse_paren_expr<'a>( // ( xxx ) Some((Token::RightParen, _)) => Ok(expr), // ( xxx ??? - Some((_, pos)) => Err(PERR::MissingToken( - ")".into(), - "for a matching ( in this expression".into(), - ) - .into_err(pos)), + Some((_, pos)) => Err(PERR::MissingToken(")".into(), MISSING_RPAREN.into()).into_err(pos)), // ( xxx - None => Err( - PERR::MissingToken(")".into(), "for a matching ( in this expression".into()) - .into_err_eof(), - ), + None => Err(PERR::MissingToken(")".into(), MISSING_RPAREN.into()).into_err_eof()), } } @@ -1485,7 +1511,7 @@ fn parse_call_expr<'a, S: Into> + Display>( } // id() Some((Token::RightParen, _)) => { - input.next(); + eat_token(input, Token::RightParen); return Ok(Expr::FunctionCall(id.into(), args_expr_list, None, begin)); } // id... @@ -1504,10 +1530,10 @@ fn parse_call_expr<'a, S: Into> + Display>( .into_err_eof()) } Some((Token::RightParen, _)) => { - input.next(); + eat_token(input, Token::RightParen); return Ok(Expr::FunctionCall(id.into(), args_expr_list, None, begin)); } - Some((Token::Comma, _)) => (), + Some((Token::Comma, _)) => eat_token(input, Token::Comma), Some((_, pos)) => { return Err(PERR::MissingToken( ",".into(), @@ -1516,8 +1542,6 @@ fn parse_call_expr<'a, S: Into> + Display>( .into_err(*pos)) } } - - input.next(); } } @@ -1633,22 +1657,17 @@ fn parse_index_expr<'a>( } // Check if there is a closing bracket - match input.peek().ok_or_else(|| { - PERR::MissingToken( - "]".into(), - "for a matching [ in this index expression".into(), - ) - .into_err_eof() - })? { - (Token::RightBracket, _) => { - input.next(); + const MISSING_RBRACKET: &str = "for a matching [ in this index expression"; + + match input.peek() { + Some((Token::RightBracket, _)) => { + eat_token(input, Token::RightBracket); Ok(Expr::Index(lhs, Box::new(idx_expr), pos)) } - (_, pos) => Err(PERR::MissingToken( - "]".into(), - "for a matching [ in this index expression".into(), - ) - .into_err(*pos)), + Some((_, pos)) => { + Err(PERR::MissingToken("]".into(), MISSING_RBRACKET.into()).into_err(*pos)) + } + None => Err(PERR::MissingToken("]".into(), MISSING_RBRACKET.into()).into_err_eof()), } } @@ -1662,14 +1681,14 @@ fn parse_ident_expr<'a, S: Into> + Display>( match input.peek() { // id(...) - function call Some((Token::LeftParen, _)) => { - input.next(); + eat_token(input, Token::LeftParen); parse_call_expr(id, input, begin, allow_stmt_expr) } // id[...] - indexing #[cfg(not(feature = "no_index"))] Some((Token::LeftBracket, pos)) => { let pos = *pos; - input.next(); + eat_token(input, Token::LeftBracket); parse_index_expr( Box::new(Expr::Variable(id.into(), begin)), input, @@ -1679,7 +1698,7 @@ fn parse_ident_expr<'a, S: Into> + Display>( } // id - variable Some(_) => Ok(Expr::Variable(id.into(), begin)), - // EOF + // {EOF} None => Ok(Expr::Variable(id.into(), begin)), } } @@ -1692,37 +1711,34 @@ fn parse_array_literal<'a>( ) -> Result { let mut arr = Vec::new(); - if !matches!(input.peek(), Some((Token::RightBracket, _))) { + if !match_token(input, Token::RightBracket)? { while input.peek().is_some() { arr.push(parse_expr(input, allow_stmt_expr)?); - match input.peek().ok_or_else(|| { - PERR::MissingToken("]".into(), "to end this array literal".into()).into_err_eof() - })? { - (Token::Comma, _) => input.next(), - (Token::RightBracket, _) => break, - (_, pos) => { + match input.peek() { + Some((Token::Comma, _)) => eat_token(input, Token::Comma), + Some((Token::RightBracket, _)) => { + eat_token(input, Token::RightBracket); + break; + } + Some((_, pos)) => { return Err(PERR::MissingToken( ",".into(), "to separate the items of this array literal".into(), ) .into_err(*pos)) } + None => { + return Err( + PERR::MissingToken("]".into(), "to end this array literal".into()) + .into_err_eof(), + ) + } }; } } - match input.peek().ok_or_else(|| { - PERR::MissingToken("]".into(), "to end this array literal".into()).into_err_eof() - })? { - (Token::RightBracket, _) => { - input.next(); - Ok(Expr::Array(arr, begin)) - } - (_, pos) => { - Err(PERR::MissingToken("]".into(), "to end this array literal".into()).into_err(*pos)) - } - } + Ok(Expr::Array(arr, begin)) } /// Parse a map literal. @@ -1733,36 +1749,25 @@ fn parse_map_literal<'a>( ) -> Result { let mut map = Vec::new(); - if !matches!(input.peek(), Some((Token::RightBrace, _))) { + if !match_token(input, Token::RightBrace)? { while input.peek().is_some() { - let (name, pos) = match input.next().ok_or_else(|| { - PERR::MissingToken("}".into(), "to end this object map literal".into()) - .into_err_eof() - })? { - (Token::Identifier(s), pos) => (s, pos), - (Token::StringConst(s), pos) => (s, pos), - (_, pos) if map.is_empty() => { - return Err(PERR::MissingToken( - "}".into(), - "to end this object map literal".into(), - ) - .into_err(pos)) + const MISSING_RBRACE: &str = "to end this object map literal"; + + let (name, pos) = match input.next() { + Some((Token::Identifier(s), pos)) => (s, pos), + Some((Token::StringConst(s), pos)) => (s, pos), + Some((_, pos)) if map.is_empty() => { + return Err(PERR::MissingToken("}".into(), MISSING_RBRACE.into()).into_err(pos)) + } + Some((_, pos)) => return Err(PERR::PropertyExpected.into_err(pos)), + None => { + return Err(PERR::MissingToken("}".into(), MISSING_RBRACE.into()).into_err_eof()) } - (_, pos) => return Err(PERR::PropertyExpected.into_err(pos)), }; - match input.next().ok_or_else(|| { - PERR::MissingToken( - ":".into(), - format!( - "to follow the property '{}' in this object map literal", - name - ), - ) - .into_err_eof() - })? { - (Token::Colon, _) => (), - (_, pos) => { + match input.next() { + Some((Token::Colon, _)) => (), + Some((_, pos)) => { return Err(PERR::MissingToken( ":".into(), format!( @@ -1772,33 +1777,40 @@ fn parse_map_literal<'a>( ) .into_err(pos)) } + None => { + return Err(PERR::MissingToken( + ":".into(), + format!( + "to follow the property '{}' in this object map literal", + name + ), + ) + .into_err_eof()) + } }; let expr = parse_expr(input, allow_stmt_expr)?; map.push((name, expr, pos)); - match input.peek().ok_or_else(|| { - PERR::MissingToken("}".into(), "to end this object map literal".into()) - .into_err_eof() - })? { - (Token::Comma, _) => { - input.next(); + match input.peek() { + Some((Token::Comma, _)) => eat_token(input, Token::Comma), + Some((Token::RightBrace, _)) => { + eat_token(input, Token::RightBrace); + break; } - (Token::RightBrace, _) => break, - (Token::Identifier(_), pos) => { + Some((Token::Identifier(_), pos)) => { return Err(PERR::MissingToken( ",".into(), "to separate the items of this object map literal".into(), ) .into_err(*pos)) } - (_, pos) => { - return Err(PERR::MissingToken( - "}".into(), - "to end this object map literal".into(), - ) - .into_err(*pos)) + Some((_, pos)) => { + return Err(PERR::MissingToken("}".into(), MISSING_RBRACE.into()).into_err(*pos)) + } + None => { + return Err(PERR::MissingToken("}".into(), MISSING_RBRACE.into()).into_err_eof()) } } } @@ -1815,18 +1827,7 @@ fn parse_map_literal<'a>( }) .map_err(|(key, pos)| PERR::DuplicatedProperty(key.to_string()).into_err(pos))?; - // Ending brace - match input.peek().ok_or_else(|| { - PERR::MissingToken("}".into(), "to end this object map literal".into()).into_err_eof() - })? { - (Token::RightBrace, _) => { - input.next(); - Ok(Expr::Map(map, begin)) - } - (_, pos) => Err( - PERR::MissingToken("]".into(), "to end this object map literal".into()).into_err(*pos), - ), - } + Ok(Expr::Map(map, begin)) } /// Parse a primary expression. @@ -1834,17 +1835,15 @@ fn parse_primary<'a>( input: &mut Peekable>, allow_stmt_expr: bool, ) -> Result { - let token = match input - .peek() - .ok_or_else(|| PERR::UnexpectedEOF.into_err_eof())? - { + let token = match input.peek() { // { - block statement as expression - (Token::LeftBrace, pos) if allow_stmt_expr => { + Some((Token::LeftBrace, pos)) if allow_stmt_expr => { let pos = *pos; return parse_block(input, false, allow_stmt_expr) .map(|block| Expr::Stmt(Box::new(block), pos)); } - _ => input.next().expect("should be a token"), + Some(_) => input.next().expect("should be a token"), + None => return Err(PERR::UnexpectedEOF.into_err_eof()), }; let mut can_be_indexed = false; @@ -1890,7 +1889,7 @@ fn parse_primary<'a>( // Tail processing all possible indexing while let Some((Token::LeftBracket, pos)) = input.peek() { let pos = *pos; - input.next(); + eat_token(input, Token::LeftBracket); root_expr = parse_index_expr(Box::new(root_expr), input, pos, allow_stmt_expr)?; } } @@ -1903,12 +1902,9 @@ fn parse_unary<'a>( input: &mut Peekable>, allow_stmt_expr: bool, ) -> Result { - match input - .peek() - .ok_or_else(|| PERR::UnexpectedEOF.into_err_eof())? - { + match input.peek() { // If statement is allowed to act as expressions - (Token::If, pos) => { + Some((Token::If, pos)) => { let pos = *pos; Ok(Expr::Stmt( Box::new(parse_if(input, false, allow_stmt_expr)?), @@ -1916,10 +1912,9 @@ fn parse_unary<'a>( )) } // -expr - (Token::UnaryMinus, pos) => { + Some((Token::UnaryMinus, pos)) => { let pos = *pos; - - input.next(); + eat_token(input, Token::UnaryMinus); match parse_unary(input, allow_stmt_expr)? { // Negative integer @@ -1950,16 +1945,14 @@ fn parse_unary<'a>( } } // +expr - (Token::UnaryPlus, _) => { - input.next(); + Some((Token::UnaryPlus, _)) => { + eat_token(input, Token::UnaryPlus); parse_unary(input, allow_stmt_expr) } // !expr - (Token::Bang, pos) => { + Some((Token::Bang, pos)) => { let pos = *pos; - - input.next(); - + eat_token(input, Token::Bang); Ok(Expr::FunctionCall( "!".into(), vec![parse_primary(input, allow_stmt_expr)?], @@ -1968,7 +1961,9 @@ fn parse_unary<'a>( )) } // All other tokens - _ => parse_primary(input, allow_stmt_expr), + Some(_) => parse_primary(input, allow_stmt_expr), + // {EOF} + None => Err(PERR::UnexpectedEOF.into_err_eof()), } } @@ -1988,45 +1983,41 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result { - assert!(is_top, "property expected but gets variable"); - None - } - // property[...] - Expr::Index(idx_lhs, _, _) if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) => { - assert!(!is_top, "variable expected but gets property"); - None - } - // idx_lhs[...] Expr::Index(idx_lhs, _, pos) => match idx_lhs.as_ref() { + // var[...] + Expr::Variable(_, _) => { + assert!(is_top, "property expected but gets variable"); + None + } + // property[...] + Expr::Property(_, _) => { + assert!(!is_top, "variable expected but gets property"); + None + } + // ???[...][...] Expr::Index(_, _, _) => Some(ParseErrorType::AssignmentToCopy.into_err(*pos)), + // idx_lhs[...] _ => Some(ParseErrorType::AssignmentToInvalidLHS.into_err(*pos)), }, // dot_lhs.dot_rhs - Expr::Dot(dot_lhs, dot_rhs, _) => match dot_lhs.as_ref() { + Expr::Dot(dot_lhs, dot_rhs, pos) => match dot_lhs.as_ref() { // var.dot_rhs Expr::Variable(_, _) if is_top => valid_assignment_chain(dot_rhs, false), // property.dot_rhs Expr::Property(_, _) if !is_top => valid_assignment_chain(dot_rhs, false), - // var[...] - Expr::Index(idx_lhs, _, _) - if matches!(idx_lhs.as_ref(), &Expr::Variable(_, _)) && is_top => - { - valid_assignment_chain(dot_rhs, false) - } - // property[...] - Expr::Index(idx_lhs, _, _) - if matches!(idx_lhs.as_ref(), &Expr::Property(_, _)) && !is_top => - { - valid_assignment_chain(dot_rhs, false) - } - // idx_lhs[...] - Expr::Index(idx_lhs, _, _) => { - Some(ParseErrorType::AssignmentToCopy.into_err(idx_lhs.position())) - } + // idx_lhs[...].dot_rhs + Expr::Index(idx_lhs, _, _) => match idx_lhs.as_ref() { + // var[...].dot_rhs + Expr::Variable(_, _) if is_top => valid_assignment_chain(dot_rhs, false), + // property[...].dot_rhs + Expr::Property(_, _) if !is_top => valid_assignment_chain(dot_rhs, false), + // ???[...][...].dot_rhs + Expr::Index(_, _, _) => Some(ParseErrorType::AssignmentToCopy.into_err(*pos)), + // idx_lhs[...].dot_rhs + _ => Some(ParseErrorType::AssignmentToCopy.into_err(idx_lhs.position())), + }, expr => panic!("unexpected dot expression {:#?}", expr), }, @@ -2211,8 +2202,6 @@ fn parse_binary_op<'a>( } if let Some((op_token, pos)) = input.next() { - input.peek(); - let rhs = parse_unary(input, allow_stmt_expr)?; let next_precedence = if let Some((next_op, _)) = input.peek() { @@ -2375,7 +2364,7 @@ fn parse_if<'a>( allow_stmt_expr: bool, ) -> Result { // if ... - input.next(); + eat_token(input, Token::If); // if guard { if_body } ensure_not_statement_expr(input, "a boolean")?; @@ -2383,9 +2372,7 @@ fn parse_if<'a>( let if_body = parse_block(input, breakable, allow_stmt_expr)?; // if guard { if_body } else ... - let else_body = if matches!(input.peek(), Some((Token::Else, _))) { - input.next(); - + let else_body = if match_token(input, Token::Else).unwrap_or(false) { Some(Box::new(if matches!(input.peek(), Some((Token::If, _))) { // if guard { if_body } else if ... parse_if(input, breakable, allow_stmt_expr)? @@ -2410,7 +2397,7 @@ fn parse_while<'a>( allow_stmt_expr: bool, ) -> Result { // while ... - input.next(); + eat_token(input, Token::While); // while guard { body } ensure_not_statement_expr(input, "a boolean")?; @@ -2426,7 +2413,7 @@ fn parse_loop<'a>( allow_stmt_expr: bool, ) -> Result { // loop ... - input.next(); + eat_token(input, Token::Loop); // loop { body } let body = parse_block(input, true, allow_stmt_expr)?; @@ -2440,7 +2427,7 @@ fn parse_for<'a>( allow_stmt_expr: bool, ) -> Result { // for ... - input.next(); + eat_token(input, Token::For); // for name ... let name = match input @@ -2456,16 +2443,14 @@ fn parse_for<'a>( }; // for name in ... - match input.next().ok_or_else(|| { - PERR::MissingToken("in".into(), "after the iteration variable".into()).into_err_eof() - })? { - (Token::In, _) => (), - (_, pos) => { - return Err( - PERR::MissingToken("in".into(), "after the iteration variable".into()) - .into_err(pos), - ) + const ERROR_MSG: &str = "after the iteration variable"; + + match input.next() { + Some((Token::In, _)) => (), + Some((_, pos)) => { + return Err(PERR::MissingToken("in".into(), ERROR_MSG.into()).into_err(pos)) } + None => return Err(PERR::MissingToken("in".into(), ERROR_MSG.into()).into_err_eof()), } // for name in expr { body } @@ -2496,9 +2481,7 @@ fn parse_let<'a>( }; // let name = ... - if matches!(input.peek(), Some((Token::Equals, _))) { - input.next(); - + if match_token(input, Token::Equals)? { // let name = expr let init_value = parse_expr(input, allow_stmt_expr)?; @@ -2541,7 +2524,7 @@ fn parse_block<'a>( let mut statements = Vec::new(); - while !matches!(input.peek(), Some((Token::RightBrace, _))) { + while !match_token(input, Token::RightBrace)? { // Parse statements inside the block let stmt = parse_stmt(input, breakable, allow_stmt_expr)?; @@ -2551,13 +2534,14 @@ fn parse_block<'a>( statements.push(stmt); match input.peek() { - // EOF - None => break, // { ... stmt } - Some((Token::RightBrace, _)) => break, + Some((Token::RightBrace, _)) => { + eat_token(input, Token::RightBrace); + break; + } // { ... stmt; Some((Token::SemiColon, _)) if need_semicolon => { - input.next(); + eat_token(input, Token::SemiColon); } // { ... { stmt } ; Some((Token::SemiColon, _)) if !need_semicolon => (), @@ -2571,20 +2555,17 @@ fn parse_block<'a>( .into_err(*pos), ); } + // {EOF} + None => { + return Err( + PERR::MissingToken("}".into(), "to end this statement block".into()) + .into_err_eof(), + ) + } } } - match input.peek().ok_or_else(|| { - PERR::MissingToken("}".into(), "to end this statement block".into()).into_err_eof() - })? { - (Token::RightBrace, _) => { - input.next(); - Ok(Stmt::Block(statements, pos)) - } - (_, pos) => { - Err(PERR::MissingToken("}".into(), "to end this statement block".into()).into_err(*pos)) - } - } + Ok(Stmt::Block(statements, pos)) } /// Parse an expression as a statement. @@ -2623,28 +2604,27 @@ fn parse_stmt<'a>( (Token::Continue, pos) if breakable => { let pos = *pos; - input.next(); + eat_token(input, Token::Continue); Ok(Stmt::Continue(pos)) } (Token::Break, pos) if breakable => { let pos = *pos; - input.next(); + eat_token(input, Token::Break); Ok(Stmt::Break(pos)) } (Token::Continue, pos) | (Token::Break, pos) => Err(PERR::LoopBreak.into_err(*pos)), - (token @ Token::Return, pos) | (token @ Token::Throw, pos) => { - let return_type = match token { - Token::Return => ReturnType::Return, - Token::Throw => ReturnType::Exception, + (Token::Return, pos) | (Token::Throw, pos) => { + let pos = *pos; + + let return_type = match input.next() { + Some((Token::Return, _)) => ReturnType::Return, + Some((Token::Throw, _)) => ReturnType::Exception, _ => panic!("token should be return or throw"), }; - let pos = *pos; - input.next(); - match input.peek() { - // `return`/`throw` at EOF + // `return`/`throw` at {EOF} None => Ok(Stmt::ReturnWithVal(None, return_type, Position::eof())), // `return;` or `throw;` Some((Token::SemiColon, _)) => Ok(Stmt::ReturnWithVal(None, return_type, pos)), @@ -2671,49 +2651,43 @@ fn parse_fn<'a>( ) -> Result { let pos = input.next().expect("should be fn").1; - let name = match input - .next() - .ok_or_else(|| PERR::FnMissingName.into_err_eof())? - { - (Token::Identifier(s), _) => s, - (_, pos) => return Err(PERR::FnMissingName.into_err(pos)), + let name = match input.next() { + Some((Token::Identifier(s), _)) => s, + Some((_, pos)) => return Err(PERR::FnMissingName.into_err(pos)), + None => return Err(PERR::FnMissingName.into_err_eof()), }; - match input - .peek() - .ok_or_else(|| PERR::FnMissingParams(name.clone()).into_err_eof())? - { - (Token::LeftParen, _) => input.next(), - (_, pos) => return Err(PERR::FnMissingParams(name).into_err(*pos)), + match input.peek() { + Some((Token::LeftParen, _)) => eat_token(input, Token::LeftParen), + Some((_, pos)) => return Err(PERR::FnMissingParams(name).into_err(*pos)), + None => return Err(PERR::FnMissingParams(name.clone()).into_err_eof()), }; let mut params = Vec::new(); - if matches!(input.peek(), Some((Token::RightParen, _))) { - input.next(); - } else { + if !match_token(input, Token::RightParen)? { let end_err = format!("to close the parameters list of function '{}'", name); let sep_err = format!("to separate the parameters of function '{}'", name); loop { - match input - .next() - .ok_or_else(|| PERR::MissingToken(")".into(), end_err.to_string()).into_err_eof())? - { - (Token::Identifier(s), pos) => params.push((s, pos)), - (_, pos) => return Err(PERR::MissingToken(")".into(), end_err).into_err(pos)), + match input.next() { + Some((Token::Identifier(s), pos)) => params.push((s, pos)), + Some((_, pos)) => return Err(PERR::MissingToken(")".into(), end_err).into_err(pos)), + None => { + return Err(PERR::MissingToken(")".into(), end_err.to_string()).into_err_eof()) + } } - match input - .next() - .ok_or_else(|| PERR::MissingToken(")".into(), end_err.to_string()).into_err_eof())? - { - (Token::RightParen, _) => break, - (Token::Comma, _) => (), - (Token::Identifier(_), pos) => { + match input.next() { + Some((Token::RightParen, _)) => break, + Some((Token::Comma, _)) => (), + Some((Token::Identifier(_), pos)) => { return Err(PERR::MissingToken(",".into(), sep_err).into_err(pos)) } - (_, pos) => return Err(PERR::MissingToken(",".into(), sep_err).into_err(pos)), + Some((_, pos)) => return Err(PERR::MissingToken(",".into(), sep_err).into_err(pos)), + None => { + return Err(PERR::MissingToken(")".into(), end_err.to_string()).into_err_eof()) + } } } } @@ -2811,7 +2785,7 @@ fn parse_global_level<'a>( None => break, // stmt ; Some((Token::SemiColon, _)) if need_semicolon => { - input.next(); + eat_token(input, Token::SemiColon); } // stmt ; Some((Token::SemiColon, _)) if !need_semicolon => (), From a35518fe498981c8bcd3462f3cb4034610e7b713 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 15 Apr 2020 22:21:23 +0800 Subject: [PATCH 10/34] Split tokenizer into separate file, plus fix no_std feature. --- src/any.rs | 6 +- src/api.rs | 3 +- src/builtin.rs | 118 +++--- src/engine.rs | 3 +- src/error.rs | 2 +- src/fn_call.rs | 2 +- src/fn_register.rs | 2 +- src/lib.rs | 4 +- src/optimize.rs | 3 +- src/parser.rs | 971 +------------------------------------------- src/result.rs | 3 +- src/scope.rs | 10 +- src/stdlib.rs | 2 +- src/token.rs | 982 +++++++++++++++++++++++++++++++++++++++++++++ tests/time.rs | 1 + 15 files changed, 1071 insertions(+), 1041 deletions(-) create mode 100644 src/token.rs diff --git a/src/any.rs b/src/any.rs index a056179b..242969d7 100644 --- a/src/any.rs +++ b/src/any.rs @@ -10,9 +10,12 @@ use crate::stdlib::{ any::{type_name, Any, TypeId}, boxed::Box, fmt, - time::Instant, + string::String, }; +#[cfg(not(feature = "no_std"))] +use crate::stdlib::time::Instant; + /// A trait to represent any type. /// /// Currently, `Variant` is not `Send` nor `Sync`, so it can practically be any type. @@ -186,6 +189,7 @@ impl Dynamic { Union::Array(_) => "array", Union::Map(_) => "map", + #[cfg(not(feature = "no_std"))] Union::Variant(value) if value.is::() => "timestamp", Union::Variant(value) => (**value).type_name(), } diff --git a/src/api.rs b/src/api.rs index 837c0bc0..01d0a76f 100644 --- a/src/api.rs +++ b/src/api.rs @@ -6,9 +6,10 @@ use crate::error::ParseError; use crate::fn_call::FuncArgs; use crate::fn_register::RegisterFn; use crate::optimize::{optimize_into_ast, OptimizationLevel}; -use crate::parser::{lex, parse, parse_global_expr, Position, AST}; +use crate::parser::{parse, parse_global_expr, AST}; use crate::result::EvalAltResult; use crate::scope::Scope; +use crate::token::{lex, Position}; use crate::stdlib::{ any::{type_name, TypeId}, diff --git a/src/builtin.rs b/src/builtin.rs index 77abf74c..0fac046f 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -4,8 +4,9 @@ use crate::any::{Dynamic, Variant}; use crate::engine::{Engine, FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT}; use crate::fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; -use crate::parser::{Position, INT}; +use crate::parser::INT; use crate::result::EvalAltResult; +use crate::token::Position; #[cfg(not(feature = "no_index"))] use crate::engine::Array; @@ -27,11 +28,13 @@ use crate::stdlib::{ format, ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Range, Rem, Shl, Shr, Sub}, string::{String, ToString}, - time::Instant, vec::Vec, {i32, i64, u32}, }; +#[cfg(not(feature = "no_std"))] +use crate::stdlib::time::Instant; + #[cfg(feature = "only_i32")] const MAX_INT: INT = i32::MAX; #[cfg(not(feature = "only_i32"))] @@ -1153,80 +1156,83 @@ impl Engine<'_> { } }); - // Register date/time functions - self.register_fn("timestamp", || Instant::now()); + #[cfg(not(feature = "no_std"))] + { + // Register date/time functions + self.register_fn("timestamp", || Instant::now()); - self.register_result_fn("-", |ts1: Instant, ts2: Instant| { - if ts2 > ts1 { - #[cfg(not(feature = "no_float"))] - return Ok(-(ts2 - ts1).as_secs_f64()); + self.register_result_fn("-", |ts1: Instant, ts2: Instant| { + if ts2 > ts1 { + #[cfg(not(feature = "no_float"))] + return Ok(-(ts2 - ts1).as_secs_f64()); - #[cfg(feature = "no_float")] - { - let seconds = (ts2 - ts1).as_secs(); - - #[cfg(not(feature = "unchecked"))] + #[cfg(feature = "no_float")] { - if seconds > (MAX_INT as u64) { - return Err(EvalAltResult::ErrorArithmetic( - format!( - "Integer overflow for timestamp duration: {}", - -(seconds as i64) - ), - Position::none(), - )); + let seconds = (ts2 - ts1).as_secs(); + + #[cfg(not(feature = "unchecked"))] + { + if seconds > (MAX_INT as u64) { + return Err(EvalAltResult::ErrorArithmetic( + format!( + "Integer overflow for timestamp duration: {}", + -(seconds as i64) + ), + Position::none(), + )); + } } + return Ok(-(seconds as INT)); + } + } else { + #[cfg(not(feature = "no_float"))] + return Ok((ts1 - ts2).as_secs_f64()); + + #[cfg(feature = "no_float")] + { + let seconds = (ts1 - ts2).as_secs(); + + #[cfg(not(feature = "unchecked"))] + { + if seconds > (MAX_INT as u64) { + return Err(EvalAltResult::ErrorArithmetic( + format!("Integer overflow for timestamp duration: {}", seconds), + Position::none(), + )); + } + } + return Ok(seconds as INT); } - return Ok(-(seconds as INT)); } - } else { + }); + + reg_cmp!(self, "<", lt, Instant); + reg_cmp!(self, "<=", lte, Instant); + reg_cmp!(self, ">", gt, Instant); + reg_cmp!(self, ">=", gte, Instant); + reg_cmp!(self, "==", eq, Instant); + reg_cmp!(self, "!=", ne, Instant); + + self.register_result_fn("elapsed", |timestamp: Instant| { #[cfg(not(feature = "no_float"))] - return Ok((ts1 - ts2).as_secs_f64()); + return Ok(timestamp.elapsed().as_secs_f64()); #[cfg(feature = "no_float")] { - let seconds = (ts1 - ts2).as_secs(); + let seconds = timestamp.elapsed().as_secs(); #[cfg(not(feature = "unchecked"))] { if seconds > (MAX_INT as u64) { return Err(EvalAltResult::ErrorArithmetic( - format!("Integer overflow for timestamp duration: {}", seconds), + format!("Integer overflow for timestamp.elapsed(): {}", seconds), Position::none(), )); } } return Ok(seconds as INT); } - } - }); - - reg_cmp!(self, "<", lt, Instant); - reg_cmp!(self, "<=", lte, Instant); - reg_cmp!(self, ">", gt, Instant); - reg_cmp!(self, ">=", gte, Instant); - reg_cmp!(self, "==", eq, Instant); - reg_cmp!(self, "!=", ne, Instant); - - self.register_result_fn("elapsed", |timestamp: Instant| { - #[cfg(not(feature = "no_float"))] - return Ok(timestamp.elapsed().as_secs_f64()); - - #[cfg(feature = "no_float")] - { - let seconds = timestamp.elapsed().as_secs(); - - #[cfg(not(feature = "unchecked"))] - { - if seconds > (MAX_INT as u64) { - return Err(EvalAltResult::ErrorArithmetic( - format!("Integer overflow for timestamp.elapsed(): {}", seconds), - Position::none(), - )); - } - } - return Ok(seconds as INT); - } - }); + }); + } } } diff --git a/src/engine.rs b/src/engine.rs index ea223926..2dec3e5e 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -3,9 +3,10 @@ use crate::any::{Dynamic, Union}; use crate::error::ParseErrorType; use crate::optimize::OptimizationLevel; -use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt, INT}; +use crate::parser::{Expr, FnDef, ReturnType, Stmt, INT}; use crate::result::EvalAltResult; use crate::scope::{EntryRef as ScopeSource, EntryType as ScopeEntryType, Scope}; +use crate::token::Position; use crate::stdlib::{ any::TypeId, diff --git a/src/error.rs b/src/error.rs index 6ad21bbd..077ebd22 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,6 @@ //! Module containing error definitions for the parsing process. -use crate::parser::Position; +use crate::token::Position; use crate::stdlib::{char, error::Error, fmt, string::String}; diff --git a/src/fn_call.rs b/src/fn_call.rs index bdb2ec4b..29a9c056 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -3,7 +3,7 @@ #![allow(non_snake_case)] use crate::any::{Dynamic, Variant}; -use crate::stdlib::{string::String, vec, vec::Vec}; +use crate::stdlib::vec::Vec; /// Trait that represent arguments to a function call. /// Any data type that can be converted into a `Vec` of `Dynamic` values can be used diff --git a/src/fn_register.rs b/src/fn_register.rs index a85dc094..711c9e4b 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -4,8 +4,8 @@ use crate::any::{Dynamic, Variant}; use crate::engine::{Engine, FnCallArgs}; -use crate::parser::Position; use crate::result::EvalAltResult; +use crate::token::Position; use crate::stdlib::{any::TypeId, boxed::Box, string::ToString, vec}; diff --git a/src/lib.rs b/src/lib.rs index 66dc90af..de8e80db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,15 +82,17 @@ mod parser; mod result; mod scope; mod stdlib; +mod token; pub use any::Dynamic; pub use engine::Engine; pub use error::{ParseError, ParseErrorType}; pub use fn_call::FuncArgs; pub use fn_register::{RegisterDynamicFn, RegisterFn, RegisterResultFn}; -pub use parser::{Position, AST, INT}; +pub use parser::{AST, INT}; pub use result::EvalAltResult; pub use scope::Scope; +pub use token::Position; #[cfg(not(feature = "no_function"))] pub use fn_func::Func; diff --git a/src/optimize.rs b/src/optimize.rs index 9852b649..cd4c30b5 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -3,9 +3,10 @@ use crate::engine::{ Engine, FnAny, FnCallArgs, FnSpec, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF, }; -use crate::parser::{map_dynamic_to_expr, Expr, FnDef, Position, ReturnType, Stmt, AST}; +use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST}; use crate::result::EvalAltResult; use crate::scope::{Entry as ScopeEntry, EntryType as ScopeEntryType, Scope}; +use crate::token::Position; use crate::stdlib::{ boxed::Box, diff --git a/src/parser.rs b/src/parser.rs index d57574b4..755609af 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -5,23 +5,21 @@ use crate::engine::{Engine, FunctionsLib}; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::scope::{EntryType as ScopeEntryType, Scope}; +use crate::token::{Position, Token, TokenIterator}; use crate::stdlib::{ borrow::Cow, boxed::Box, char, collections::HashMap, - fmt, fmt::Display, format, iter::Peekable, ops::Add, rc::Rc, - str::Chars, - str::FromStr, string::{String, ToString}, sync::Arc, - usize, vec, + vec, vec::Vec, }; @@ -42,125 +40,8 @@ pub type INT = i32; /// Not available under the `no_float` feature. pub type FLOAT = f64; -type LERR = LexError; type PERR = ParseErrorType; -/// A location (line number + character position) in the input script. -#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] -pub struct Position { - /// Line number - 0 = none, MAX = EOF - line: usize, - /// Character position - 0 = BOL, MAX = EOF - pos: usize, -} - -impl Position { - /// Create a new `Position`. - pub fn new(line: usize, position: usize) -> Self { - assert!(line != 0, "line cannot be zero"); - assert!( - line != usize::MAX || position != usize::MAX, - "invalid position" - ); - - Self { - line, - pos: position, - } - } - - /// Get the line number (1-based), or `None` if no position or EOF. - pub fn line(&self) -> Option { - if self.is_none() || self.is_eof() { - None - } else { - Some(self.line) - } - } - - /// Get the character position (1-based), or `None` if at beginning of a line. - pub fn position(&self) -> Option { - if self.is_none() || self.is_eof() || self.pos == 0 { - None - } else { - Some(self.pos) - } - } - - /// Advance by one character position. - pub(crate) fn advance(&mut self) { - self.pos += 1; - } - - /// Go backwards by one character position. - /// - /// # Panics - /// - /// Panics if already at beginning of a line - cannot rewind to a previous line. - /// - pub(crate) fn rewind(&mut self) { - assert!(self.pos > 0, "cannot rewind at position 0"); - self.pos -= 1; - } - - /// Advance to the next line. - pub(crate) fn new_line(&mut self) { - self.line += 1; - self.pos = 0; - } - - /// Create a `Position` representing no position. - pub(crate) fn none() -> Self { - Self { line: 0, pos: 0 } - } - - /// Create a `Position` at EOF. - pub(crate) fn eof() -> Self { - Self { - line: usize::MAX, - pos: usize::MAX, - } - } - - /// Is there no `Position`? - pub fn is_none(&self) -> bool { - self.line == 0 && self.pos == 0 - } - - /// Is the `Position` at EOF? - pub fn is_eof(&self) -> bool { - self.line == usize::MAX && self.pos == usize::MAX - } -} - -impl Default for Position { - fn default() -> Self { - Self::new(1, 0) - } -} - -impl fmt::Display for Position { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.is_eof() { - write!(f, "EOF") - } else if self.is_none() { - write!(f, "none") - } else { - write!(f, "line {}, position {}", self.line, self.pos) - } - } -} - -impl fmt::Debug for Position { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.is_eof() { - write!(f, "(EOF)") - } else { - write!(f, "({}:{})", self.line, self.pos) - } - } -} - /// Compiled AST (abstract syntax tree) of a Rhai script. /// /// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. @@ -591,852 +472,6 @@ impl Expr { } } -/// Tokens. -#[derive(Debug, PartialEq, Clone)] -pub enum Token { - IntegerConstant(INT), - FloatConstant(FLOAT), - Identifier(String), - CharConstant(char), - StringConst(String), - LeftBrace, - RightBrace, - LeftParen, - RightParen, - LeftBracket, - RightBracket, - Plus, - UnaryPlus, - Minus, - UnaryMinus, - Multiply, - Divide, - Modulo, - PowerOf, - LeftShift, - RightShift, - SemiColon, - Colon, - Comma, - Period, - #[cfg(not(feature = "no_object"))] - MapStart, - Equals, - True, - False, - Let, - Const, - If, - Else, - While, - Loop, - For, - In, - LessThan, - GreaterThan, - LessThanEqualsTo, - GreaterThanEqualsTo, - EqualsTo, - NotEqualsTo, - Bang, - Pipe, - Or, - XOr, - Ampersand, - And, - #[cfg(not(feature = "no_function"))] - Fn, - Continue, - Break, - Return, - Throw, - PlusAssign, - MinusAssign, - MultiplyAssign, - DivideAssign, - LeftShiftAssign, - RightShiftAssign, - AndAssign, - OrAssign, - XOrAssign, - ModuloAssign, - PowerOfAssign, - LexError(Box), -} - -impl Token { - /// Get the syntax of the token. - pub fn syntax(&self) -> Cow { - use Token::*; - - match self { - IntegerConstant(i) => i.to_string().into(), - FloatConstant(f) => f.to_string().into(), - Identifier(s) => s.into(), - CharConstant(c) => c.to_string().into(), - LexError(err) => err.to_string().into(), - - token => (match token { - StringConst(_) => "string", - LeftBrace => "{", - RightBrace => "}", - LeftParen => "(", - RightParen => ")", - LeftBracket => "[", - RightBracket => "]", - Plus => "+", - UnaryPlus => "+", - Minus => "-", - UnaryMinus => "-", - Multiply => "*", - Divide => "/", - SemiColon => ";", - Colon => ":", - Comma => ",", - Period => ".", - #[cfg(not(feature = "no_object"))] - MapStart => "#{", - Equals => "=", - True => "true", - False => "false", - Let => "let", - Const => "const", - If => "if", - Else => "else", - While => "while", - Loop => "loop", - LessThan => "<", - GreaterThan => ">", - Bang => "!", - LessThanEqualsTo => "<=", - GreaterThanEqualsTo => ">=", - EqualsTo => "==", - NotEqualsTo => "!=", - Pipe => "|", - Or => "||", - Ampersand => "&", - And => "&&", - #[cfg(not(feature = "no_function"))] - Fn => "fn", - Continue => "continue", - Break => "break", - Return => "return", - Throw => "throw", - PlusAssign => "+=", - MinusAssign => "-=", - MultiplyAssign => "*=", - DivideAssign => "/=", - LeftShiftAssign => "<<=", - RightShiftAssign => ">>=", - AndAssign => "&=", - OrAssign => "|=", - XOrAssign => "^=", - LeftShift => "<<", - RightShift => ">>", - XOr => "^", - Modulo => "%", - ModuloAssign => "%=", - PowerOf => "~", - PowerOfAssign => "~=", - For => "for", - In => "in", - _ => panic!("operator should be match in outer scope"), - }) - .into(), - } - } - - // If another operator is after these, it's probably an unary operator - // (not sure about fn name). - pub fn is_next_unary(&self) -> bool { - use Token::*; - - match self { - LexError(_) | - LeftBrace | // (+expr) - is unary - // RightBrace | {expr} - expr not unary & is closing - LeftParen | // {-expr} - is unary - // RightParen | (expr) - expr not unary & is closing - LeftBracket | // [-expr] - is unary - // RightBracket | [expr] - expr not unary & is closing - Plus | - UnaryPlus | - Minus | - UnaryMinus | - Multiply | - Divide | - Colon | - Comma | - Period | - Equals | - LessThan | - GreaterThan | - Bang | - LessThanEqualsTo | - GreaterThanEqualsTo | - EqualsTo | - NotEqualsTo | - Pipe | - Or | - Ampersand | - And | - If | - While | - PlusAssign | - MinusAssign | - MultiplyAssign | - DivideAssign | - LeftShiftAssign | - RightShiftAssign | - AndAssign | - OrAssign | - XOrAssign | - LeftShift | - RightShift | - XOr | - Modulo | - ModuloAssign | - Return | - Throw | - PowerOf | - In | - PowerOfAssign => true, - - _ => false, - } - } - - /// Get the precedence number of the token. - pub fn precedence(&self) -> u8 { - use Token::*; - - match self { - Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign - | RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign - | PowerOfAssign => 10, - - Or | XOr | Pipe => 40, - - And | Ampersand => 50, - - LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo | EqualsTo - | NotEqualsTo => 60, - - In => 70, - - Plus | Minus => 80, - - Divide | Multiply | PowerOf => 90, - - LeftShift | RightShift => 100, - - Modulo => 110, - - Period => 120, - - _ => 0, - } - } - - /// Does an expression bind to the right (instead of left)? - pub fn is_bind_right(&self) -> bool { - use Token::*; - - match self { - // Assignments bind to the right - Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign - | RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign - | PowerOfAssign => true, - - // Property access binds to the right - Period => true, - - _ => false, - } - } -} - -/// An iterator on a `Token` stream. -pub struct TokenIterator<'a> { - /// Can the next token be a unary operator? - can_be_unary: bool, - /// Current position. - pos: Position, - /// The input character streams. - streams: Vec>>, -} - -impl<'a> TokenIterator<'a> { - /// Consume the next character. - fn eat_next(&mut self) { - self.get_next(); - self.advance(); - } - /// Get the next character - fn get_next(&mut self) -> Option { - loop { - if self.streams.is_empty() { - return None; - } else if let Some(ch) = self.streams[0].next() { - return Some(ch); - } else { - let _ = self.streams.remove(0); - } - } - } - /// Peek the next character - fn peek_next(&mut self) -> Option { - loop { - if self.streams.is_empty() { - return None; - } else if let Some(ch) = self.streams[0].peek() { - return Some(*ch); - } else { - let _ = self.streams.remove(0); - } - } - } - /// Move the current position one character ahead. - fn advance(&mut self) { - self.pos.advance(); - } - /// Move the current position back one character. - /// - /// # Panics - /// - /// Panics if already at the beginning of a line - cannot rewind to the previous line. - fn rewind(&mut self) { - self.pos.rewind(); - } - /// Move the current position to the next line. - fn new_line(&mut self) { - self.pos.new_line() - } - - /// Parse a string literal wrapped by `enclosing_char`. - pub fn parse_string_literal( - &mut self, - enclosing_char: char, - ) -> Result { - let mut result = Vec::new(); - let mut escape = String::with_capacity(12); - - loop { - let next_char = self.get_next(); - self.advance(); - - match next_char.ok_or((LERR::UnterminatedString, Position::eof()))? { - // \... - '\\' if escape.is_empty() => { - escape.push('\\'); - } - // \\ - '\\' if !escape.is_empty() => { - escape.clear(); - result.push('\\'); - } - // \t - 't' if !escape.is_empty() => { - escape.clear(); - result.push('\t'); - } - // \n - 'n' if !escape.is_empty() => { - escape.clear(); - result.push('\n'); - } - // \r - 'r' if !escape.is_empty() => { - escape.clear(); - result.push('\r'); - } - // \x??, \u????, \U???????? - ch @ 'x' | ch @ 'u' | ch @ 'U' if !escape.is_empty() => { - let mut seq = escape.clone(); - seq.push(ch); - escape.clear(); - - let mut out_val: u32 = 0; - let len = match ch { - 'x' => 2, - 'u' => 4, - 'U' => 8, - _ => panic!("should be 'x', 'u' or 'U'"), - }; - - for _ in 0..len { - let c = self.get_next().ok_or_else(|| { - (LERR::MalformedEscapeSequence(seq.to_string()), self.pos) - })?; - - seq.push(c); - self.advance(); - - out_val *= 16; - out_val += c.to_digit(16).ok_or_else(|| { - (LERR::MalformedEscapeSequence(seq.to_string()), self.pos) - })?; - } - - result.push( - char::from_u32(out_val) - .ok_or_else(|| (LERR::MalformedEscapeSequence(seq), self.pos))?, - ); - } - - // \{enclosing_char} - escaped - ch if enclosing_char == ch && !escape.is_empty() => { - escape.clear(); - result.push(ch) - } - - // Close wrapper - ch if enclosing_char == ch && escape.is_empty() => break, - - // Unknown escape sequence - _ if !escape.is_empty() => { - return Err((LERR::MalformedEscapeSequence(escape), self.pos)) - } - - // Cannot have new-lines inside string literals - '\n' => { - self.rewind(); - return Err((LERR::UnterminatedString, self.pos)); - } - - // All other characters - ch => { - escape.clear(); - result.push(ch); - } - } - } - - Ok(result.iter().collect()) - } - - /// Get the next token. - fn inner_next(&mut self) -> Option<(Token, Position)> { - let mut negated = false; - - while let Some(c) = self.get_next() { - self.advance(); - - let pos = self.pos; - - match (c, self.peek_next().unwrap_or('\0')) { - // \n - ('\n', _) => self.new_line(), - - // digit ... - ('0'..='9', _) => { - let mut result = Vec::new(); - let mut radix_base: Option = None; - result.push(c); - - while let Some(next_char) = self.peek_next() { - match next_char { - '0'..='9' | '_' => { - result.push(next_char); - self.eat_next(); - } - #[cfg(not(feature = "no_float"))] - '.' => { - result.push(next_char); - self.eat_next(); - while let Some(next_char_in_float) = self.peek_next() { - match next_char_in_float { - '0'..='9' | '_' => { - result.push(next_char_in_float); - self.eat_next(); - } - _ => break, - } - } - } - // 0x????, 0o????, 0b???? - ch @ 'x' | ch @ 'X' | ch @ 'o' | ch @ 'O' | ch @ 'b' | ch @ 'B' - if c == '0' => - { - result.push(next_char); - self.eat_next(); - - let valid = match ch { - 'x' | 'X' => [ - 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_', - ], - 'o' | 'O' => [ - '0', '1', '2', '3', '4', '5', '6', '7', '_', '_', '_', '_', - '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', - ], - 'b' | 'B' => [ - '0', '1', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', - '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', - ], - _ => panic!("unexpected character {}", ch), - }; - - radix_base = Some(match ch { - 'x' | 'X' => 16, - 'o' | 'O' => 8, - 'b' | 'B' => 2, - _ => panic!("unexpected character {}", ch), - }); - - while let Some(next_char_in_hex) = self.peek_next() { - if !valid.contains(&next_char_in_hex) { - break; - } - - result.push(next_char_in_hex); - self.eat_next(); - } - } - - _ => break, - } - } - - if negated { - result.insert(0, '-'); - } - - // Parse number - if let Some(radix) = radix_base { - let out: String = result.iter().skip(2).filter(|&&c| c != '_').collect(); - - return Some(( - INT::from_str_radix(&out, radix) - .map(Token::IntegerConstant) - .unwrap_or_else(|_| { - Token::LexError(Box::new(LERR::MalformedNumber( - result.iter().collect(), - ))) - }), - pos, - )); - } else { - let out: String = result.iter().filter(|&&c| c != '_').collect(); - let num = INT::from_str(&out).map(Token::IntegerConstant); - - // If integer parsing is unnecessary, try float instead - #[cfg(not(feature = "no_float"))] - let num = num.or_else(|_| FLOAT::from_str(&out).map(Token::FloatConstant)); - - return Some(( - num.unwrap_or_else(|_| { - Token::LexError(Box::new(LERR::MalformedNumber( - result.iter().collect(), - ))) - }), - pos, - )); - } - } - - // letter or underscore ... - ('A'..='Z', _) | ('a'..='z', _) | ('_', _) => { - let mut result = Vec::new(); - result.push(c); - - while let Some(next_char) = self.peek_next() { - match next_char { - x if x.is_ascii_alphanumeric() || x == '_' => { - result.push(x); - self.eat_next(); - } - _ => break, - } - } - - let is_valid_identifier = result - .iter() - .find(|&ch| char::is_ascii_alphanumeric(ch)) // first alpha-numeric character - .map(char::is_ascii_alphabetic) // is a letter - .unwrap_or(false); // if no alpha-numeric at all - syntax error - - let identifier: String = result.iter().collect(); - - if !is_valid_identifier { - return Some(( - Token::LexError(Box::new(LERR::MalformedIdentifier(identifier))), - pos, - )); - } - - return Some(( - match identifier.as_str() { - "true" => Token::True, - "false" => Token::False, - "let" => Token::Let, - "const" => Token::Const, - "if" => Token::If, - "else" => Token::Else, - "while" => Token::While, - "loop" => Token::Loop, - "continue" => Token::Continue, - "break" => Token::Break, - "return" => Token::Return, - "throw" => Token::Throw, - "for" => Token::For, - "in" => Token::In, - - #[cfg(not(feature = "no_function"))] - "fn" => Token::Fn, - - _ => Token::Identifier(identifier), - }, - pos, - )); - } - - // " - string literal - ('"', _) => { - return self.parse_string_literal('"').map_or_else( - |err| Some((Token::LexError(Box::new(err.0)), err.1)), - |out| Some((Token::StringConst(out), pos)), - ); - } - - // ' - character literal - ('\'', '\'') => { - return Some(( - Token::LexError(Box::new(LERR::MalformedChar("".to_string()))), - pos, - )); - } - ('\'', _) => { - return Some(self.parse_string_literal('\'').map_or_else( - |err| (Token::LexError(Box::new(err.0)), err.1), - |result| { - let mut chars = result.chars(); - let first = chars.next(); - - if chars.next().is_some() { - (Token::LexError(Box::new(LERR::MalformedChar(result))), pos) - } else { - (Token::CharConstant(first.expect("should be Some")), pos) - } - }, - )); - } - - // Braces - ('{', _) => return Some((Token::LeftBrace, pos)), - ('}', _) => return Some((Token::RightBrace, pos)), - - // Parentheses - ('(', _) => return Some((Token::LeftParen, pos)), - (')', _) => return Some((Token::RightParen, pos)), - - // Indexing - ('[', _) => return Some((Token::LeftBracket, pos)), - (']', _) => return Some((Token::RightBracket, pos)), - - // Map literal - #[cfg(not(feature = "no_object"))] - ('#', '{') => { - self.eat_next(); - return Some((Token::MapStart, pos)); - } - - // Operators - ('+', '=') => { - self.eat_next(); - return Some((Token::PlusAssign, pos)); - } - ('+', _) if self.can_be_unary => return Some((Token::UnaryPlus, pos)), - ('+', _) => return Some((Token::Plus, pos)), - - ('-', '0'..='9') if self.can_be_unary => negated = true, - ('-', '0'..='9') => return Some((Token::Minus, pos)), - ('-', '=') => { - self.eat_next(); - return Some((Token::MinusAssign, pos)); - } - ('-', _) if self.can_be_unary => return Some((Token::UnaryMinus, pos)), - ('-', _) => return Some((Token::Minus, pos)), - - ('*', '=') => { - self.eat_next(); - return Some((Token::MultiplyAssign, pos)); - } - ('*', _) => return Some((Token::Multiply, pos)), - - // Comments - ('/', '/') => { - self.eat_next(); - - while let Some(c) = self.get_next() { - if c == '\n' { - self.new_line(); - break; - } - - self.advance(); - } - } - ('/', '*') => { - let mut level = 1; - - self.eat_next(); - - while let Some(c) = self.get_next() { - self.advance(); - - match c { - '/' => { - if self.get_next() == Some('*') { - level += 1; - } - self.advance(); - } - '*' => { - if self.get_next() == Some('/') { - level -= 1; - } - self.advance(); - } - '\n' => self.new_line(), - _ => (), - } - - if level == 0 { - break; - } - } - } - - ('/', '=') => { - self.eat_next(); - return Some((Token::DivideAssign, pos)); - } - ('/', _) => return Some((Token::Divide, pos)), - - (';', _) => return Some((Token::SemiColon, pos)), - (':', _) => return Some((Token::Colon, pos)), - (',', _) => return Some((Token::Comma, pos)), - ('.', _) => return Some((Token::Period, pos)), - - ('=', '=') => { - self.eat_next(); - return Some((Token::EqualsTo, pos)); - } - ('=', _) => return Some((Token::Equals, pos)), - - ('<', '=') => { - self.eat_next(); - return Some((Token::LessThanEqualsTo, pos)); - } - ('<', '<') => { - self.eat_next(); - - return Some(( - if self.peek_next() == Some('=') { - self.eat_next(); - Token::LeftShiftAssign - } else { - Token::LeftShift - }, - pos, - )); - } - ('<', _) => return Some((Token::LessThan, pos)), - - ('>', '=') => { - self.eat_next(); - return Some((Token::GreaterThanEqualsTo, pos)); - } - ('>', '>') => { - self.eat_next(); - - return Some(( - if self.peek_next() == Some('=') { - self.eat_next(); - Token::RightShiftAssign - } else { - Token::RightShift - }, - pos, - )); - } - ('>', _) => return Some((Token::GreaterThan, pos)), - - ('!', '=') => { - self.eat_next(); - return Some((Token::NotEqualsTo, pos)); - } - ('!', _) => return Some((Token::Bang, pos)), - - ('|', '|') => { - self.eat_next(); - return Some((Token::Or, pos)); - } - ('|', '=') => { - self.eat_next(); - return Some((Token::OrAssign, pos)); - } - ('|', _) => return Some((Token::Pipe, pos)), - - ('&', '&') => { - self.eat_next(); - return Some((Token::And, pos)); - } - ('&', '=') => { - self.eat_next(); - return Some((Token::AndAssign, pos)); - } - ('&', _) => return Some((Token::Ampersand, pos)), - - ('^', '=') => { - self.eat_next(); - return Some((Token::XOrAssign, pos)); - } - ('^', _) => return Some((Token::XOr, pos)), - - ('%', '=') => { - self.eat_next(); - return Some((Token::ModuloAssign, pos)); - } - ('%', _) => return Some((Token::Modulo, pos)), - - ('~', '=') => { - self.eat_next(); - return Some((Token::PowerOfAssign, pos)); - } - ('~', _) => return Some((Token::PowerOf, pos)), - - (ch, _) if ch.is_whitespace() => (), - (ch, _) => return Some((Token::LexError(Box::new(LERR::UnexpectedChar(ch))), pos)), - } - } - - None - } -} - -impl<'a> Iterator for TokenIterator<'a> { - type Item = (Token, Position); - - fn next(&mut self) -> Option { - self.inner_next().map(|x| { - // Save the last token - self.can_be_unary = x.0.is_next_unary(); - x - }) - } -} - -/// Tokenize an input text stream. -pub fn lex<'a>(input: &'a [&'a str]) -> TokenIterator<'a> { - TokenIterator { - can_be_unary: true, - pos: Position::new(1, 0), - streams: input.iter().map(|s| s.chars().peekable()).collect(), - } -} - /// Consume a particular token, checking that it is the expected one. fn eat_token(input: &mut Peekable, token: Token) { if let Some((t, pos)) = input.next() { @@ -1932,7 +967,7 @@ fn parse_unary<'a>( } }) .ok_or_else(|| { - PERR::BadInput(LERR::MalformedNumber(format!("-{}", i)).to_string()) + PERR::BadInput(LexError::MalformedNumber(format!("-{}", i)).to_string()) .into_err(pos) }), diff --git a/src/result.rs b/src/result.rs index ddb56f0b..9d443f82 100644 --- a/src/result.rs +++ b/src/result.rs @@ -2,7 +2,8 @@ use crate::any::Dynamic; use crate::error::ParseError; -use crate::parser::{Position, INT}; +use crate::parser::INT; +use crate::token::Position; use crate::stdlib::{ error::Error, diff --git a/src/scope.rs b/src/scope.rs index 203d2402..3da54404 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,14 +1,10 @@ //! Module that defines the `Scope` type representing a function call-stack scope. use crate::any::{Dynamic, Variant}; -use crate::parser::{map_dynamic_to_expr, Expr, Position}; +use crate::parser::{map_dynamic_to_expr, Expr}; +use crate::token::Position; -use crate::stdlib::{ - borrow::Cow, - iter, - string::{String, ToString}, - vec::Vec, -}; +use crate::stdlib::{borrow::Cow, iter, vec::Vec}; /// Type of an entry in the Scope. #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] diff --git a/src/stdlib.rs b/src/stdlib.rs index 4ec71d21..1d1397d5 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -8,7 +8,7 @@ mod inner { panic, pin, prelude, ptr, result, slice, str, task, time, u128, u16, u32, u64, u8, usize, }; - pub use alloc::{borrow, boxed, format, string, sync, vec}; + pub use alloc::{borrow, boxed, format, rc, string, sync, vec}; pub use core_error as error; diff --git a/src/token.rs b/src/token.rs new file mode 100644 index 00000000..55563d0f --- /dev/null +++ b/src/token.rs @@ -0,0 +1,982 @@ +//! Main module defining the lexer and parser. + +use crate::error::LexError; +use crate::parser::INT; + +#[cfg(not(feature = "no_float"))] +use crate::parser::FLOAT; + +use crate::stdlib::{ + borrow::Cow, + boxed::Box, + char, fmt, + iter::Peekable, + str::{Chars, FromStr}, + string::{String, ToString}, + usize, + vec::Vec, +}; + +type LERR = LexError; + +/// A location (line number + character position) in the input script. +#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] +pub struct Position { + /// Line number - 0 = none, MAX = EOF + line: usize, + /// Character position - 0 = BOL, MAX = EOF + pos: usize, +} + +impl Position { + /// Create a new `Position`. + pub fn new(line: usize, position: usize) -> Self { + assert!(line != 0, "line cannot be zero"); + assert!( + line != usize::MAX || position != usize::MAX, + "invalid position" + ); + + Self { + line, + pos: position, + } + } + + /// Get the line number (1-based), or `None` if no position or EOF. + pub fn line(&self) -> Option { + if self.is_none() || self.is_eof() { + None + } else { + Some(self.line) + } + } + + /// Get the character position (1-based), or `None` if at beginning of a line. + pub fn position(&self) -> Option { + if self.is_none() || self.is_eof() || self.pos == 0 { + None + } else { + Some(self.pos) + } + } + + /// Advance by one character position. + pub(crate) fn advance(&mut self) { + self.pos += 1; + } + + /// Go backwards by one character position. + /// + /// # Panics + /// + /// Panics if already at beginning of a line - cannot rewind to a previous line. + /// + pub(crate) fn rewind(&mut self) { + assert!(self.pos > 0, "cannot rewind at position 0"); + self.pos -= 1; + } + + /// Advance to the next line. + pub(crate) fn new_line(&mut self) { + self.line += 1; + self.pos = 0; + } + + /// Create a `Position` representing no position. + pub(crate) fn none() -> Self { + Self { line: 0, pos: 0 } + } + + /// Create a `Position` at EOF. + pub(crate) fn eof() -> Self { + Self { + line: usize::MAX, + pos: usize::MAX, + } + } + + /// Is there no `Position`? + pub fn is_none(&self) -> bool { + self.line == 0 && self.pos == 0 + } + + /// Is the `Position` at EOF? + pub fn is_eof(&self) -> bool { + self.line == usize::MAX && self.pos == usize::MAX + } +} + +impl Default for Position { + fn default() -> Self { + Self::new(1, 0) + } +} + +impl fmt::Display for Position { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.is_eof() { + write!(f, "EOF") + } else if self.is_none() { + write!(f, "none") + } else { + write!(f, "line {}, position {}", self.line, self.pos) + } + } +} + +impl fmt::Debug for Position { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.is_eof() { + write!(f, "(EOF)") + } else { + write!(f, "({}:{})", self.line, self.pos) + } + } +} + +/// Tokens. +#[derive(Debug, PartialEq, Clone)] +pub enum Token { + IntegerConstant(INT), + FloatConstant(FLOAT), + Identifier(String), + CharConstant(char), + StringConst(String), + LeftBrace, + RightBrace, + LeftParen, + RightParen, + LeftBracket, + RightBracket, + Plus, + UnaryPlus, + Minus, + UnaryMinus, + Multiply, + Divide, + Modulo, + PowerOf, + LeftShift, + RightShift, + SemiColon, + Colon, + Comma, + Period, + #[cfg(not(feature = "no_object"))] + MapStart, + Equals, + True, + False, + Let, + Const, + If, + Else, + While, + Loop, + For, + In, + LessThan, + GreaterThan, + LessThanEqualsTo, + GreaterThanEqualsTo, + EqualsTo, + NotEqualsTo, + Bang, + Pipe, + Or, + XOr, + Ampersand, + And, + #[cfg(not(feature = "no_function"))] + Fn, + Continue, + Break, + Return, + Throw, + PlusAssign, + MinusAssign, + MultiplyAssign, + DivideAssign, + LeftShiftAssign, + RightShiftAssign, + AndAssign, + OrAssign, + XOrAssign, + ModuloAssign, + PowerOfAssign, + LexError(Box), +} + +impl Token { + /// Get the syntax of the token. + pub fn syntax(&self) -> Cow { + use Token::*; + + match self { + IntegerConstant(i) => i.to_string().into(), + FloatConstant(f) => f.to_string().into(), + Identifier(s) => s.into(), + CharConstant(c) => c.to_string().into(), + LexError(err) => err.to_string().into(), + + token => (match token { + StringConst(_) => "string", + LeftBrace => "{", + RightBrace => "}", + LeftParen => "(", + RightParen => ")", + LeftBracket => "[", + RightBracket => "]", + Plus => "+", + UnaryPlus => "+", + Minus => "-", + UnaryMinus => "-", + Multiply => "*", + Divide => "/", + SemiColon => ";", + Colon => ":", + Comma => ",", + Period => ".", + #[cfg(not(feature = "no_object"))] + MapStart => "#{", + Equals => "=", + True => "true", + False => "false", + Let => "let", + Const => "const", + If => "if", + Else => "else", + While => "while", + Loop => "loop", + LessThan => "<", + GreaterThan => ">", + Bang => "!", + LessThanEqualsTo => "<=", + GreaterThanEqualsTo => ">=", + EqualsTo => "==", + NotEqualsTo => "!=", + Pipe => "|", + Or => "||", + Ampersand => "&", + And => "&&", + #[cfg(not(feature = "no_function"))] + Fn => "fn", + Continue => "continue", + Break => "break", + Return => "return", + Throw => "throw", + PlusAssign => "+=", + MinusAssign => "-=", + MultiplyAssign => "*=", + DivideAssign => "/=", + LeftShiftAssign => "<<=", + RightShiftAssign => ">>=", + AndAssign => "&=", + OrAssign => "|=", + XOrAssign => "^=", + LeftShift => "<<", + RightShift => ">>", + XOr => "^", + Modulo => "%", + ModuloAssign => "%=", + PowerOf => "~", + PowerOfAssign => "~=", + For => "for", + In => "in", + _ => panic!("operator should be match in outer scope"), + }) + .into(), + } + } + + // If another operator is after these, it's probably an unary operator + // (not sure about fn name). + pub fn is_next_unary(&self) -> bool { + use Token::*; + + match self { + LexError(_) | + LeftBrace | // (+expr) - is unary + // RightBrace | {expr} - expr not unary & is closing + LeftParen | // {-expr} - is unary + // RightParen | (expr) - expr not unary & is closing + LeftBracket | // [-expr] - is unary + // RightBracket | [expr] - expr not unary & is closing + Plus | + UnaryPlus | + Minus | + UnaryMinus | + Multiply | + Divide | + Colon | + Comma | + Period | + Equals | + LessThan | + GreaterThan | + Bang | + LessThanEqualsTo | + GreaterThanEqualsTo | + EqualsTo | + NotEqualsTo | + Pipe | + Or | + Ampersand | + And | + If | + While | + PlusAssign | + MinusAssign | + MultiplyAssign | + DivideAssign | + LeftShiftAssign | + RightShiftAssign | + AndAssign | + OrAssign | + XOrAssign | + LeftShift | + RightShift | + XOr | + Modulo | + ModuloAssign | + Return | + Throw | + PowerOf | + In | + PowerOfAssign => true, + + _ => false, + } + } + + /// Get the precedence number of the token. + pub fn precedence(&self) -> u8 { + use Token::*; + + match self { + Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign + | RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign + | PowerOfAssign => 10, + + Or | XOr | Pipe => 40, + + And | Ampersand => 50, + + LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo | EqualsTo + | NotEqualsTo => 60, + + In => 70, + + Plus | Minus => 80, + + Divide | Multiply | PowerOf => 90, + + LeftShift | RightShift => 100, + + Modulo => 110, + + Period => 120, + + _ => 0, + } + } + + /// Does an expression bind to the right (instead of left)? + pub fn is_bind_right(&self) -> bool { + use Token::*; + + match self { + // Assignments bind to the right + Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign + | RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign + | PowerOfAssign => true, + + // Property access binds to the right + Period => true, + + _ => false, + } + } +} + +/// An iterator on a `Token` stream. +pub struct TokenIterator<'a> { + /// Can the next token be a unary operator? + can_be_unary: bool, + /// Current position. + pos: Position, + /// The input character streams. + streams: Vec>>, +} + +impl<'a> TokenIterator<'a> { + /// Consume the next character. + fn eat_next(&mut self) { + self.get_next(); + self.advance(); + } + /// Get the next character + fn get_next(&mut self) -> Option { + loop { + if self.streams.is_empty() { + return None; + } else if let Some(ch) = self.streams[0].next() { + return Some(ch); + } else { + let _ = self.streams.remove(0); + } + } + } + /// Peek the next character + fn peek_next(&mut self) -> Option { + loop { + if self.streams.is_empty() { + return None; + } else if let Some(ch) = self.streams[0].peek() { + return Some(*ch); + } else { + let _ = self.streams.remove(0); + } + } + } + /// Move the current position one character ahead. + fn advance(&mut self) { + self.pos.advance(); + } + /// Move the current position back one character. + /// + /// # Panics + /// + /// Panics if already at the beginning of a line - cannot rewind to the previous line. + fn rewind(&mut self) { + self.pos.rewind(); + } + /// Move the current position to the next line. + fn new_line(&mut self) { + self.pos.new_line() + } + + /// Parse a string literal wrapped by `enclosing_char`. + pub fn parse_string_literal( + &mut self, + enclosing_char: char, + ) -> Result { + let mut result = Vec::new(); + let mut escape = String::with_capacity(12); + + loop { + let next_char = self.get_next(); + self.advance(); + + match next_char.ok_or((LERR::UnterminatedString, Position::eof()))? { + // \... + '\\' if escape.is_empty() => { + escape.push('\\'); + } + // \\ + '\\' if !escape.is_empty() => { + escape.clear(); + result.push('\\'); + } + // \t + 't' if !escape.is_empty() => { + escape.clear(); + result.push('\t'); + } + // \n + 'n' if !escape.is_empty() => { + escape.clear(); + result.push('\n'); + } + // \r + 'r' if !escape.is_empty() => { + escape.clear(); + result.push('\r'); + } + // \x??, \u????, \U???????? + ch @ 'x' | ch @ 'u' | ch @ 'U' if !escape.is_empty() => { + let mut seq = escape.clone(); + seq.push(ch); + escape.clear(); + + let mut out_val: u32 = 0; + let len = match ch { + 'x' => 2, + 'u' => 4, + 'U' => 8, + _ => panic!("should be 'x', 'u' or 'U'"), + }; + + for _ in 0..len { + let c = self.get_next().ok_or_else(|| { + (LERR::MalformedEscapeSequence(seq.to_string()), self.pos) + })?; + + seq.push(c); + self.advance(); + + out_val *= 16; + out_val += c.to_digit(16).ok_or_else(|| { + (LERR::MalformedEscapeSequence(seq.to_string()), self.pos) + })?; + } + + result.push( + char::from_u32(out_val) + .ok_or_else(|| (LERR::MalformedEscapeSequence(seq), self.pos))?, + ); + } + + // \{enclosing_char} - escaped + ch if enclosing_char == ch && !escape.is_empty() => { + escape.clear(); + result.push(ch) + } + + // Close wrapper + ch if enclosing_char == ch && escape.is_empty() => break, + + // Unknown escape sequence + _ if !escape.is_empty() => { + return Err((LERR::MalformedEscapeSequence(escape), self.pos)) + } + + // Cannot have new-lines inside string literals + '\n' => { + self.rewind(); + return Err((LERR::UnterminatedString, self.pos)); + } + + // All other characters + ch => { + escape.clear(); + result.push(ch); + } + } + } + + Ok(result.iter().collect()) + } + + /// Get the next token. + fn inner_next(&mut self) -> Option<(Token, Position)> { + let mut negated = false; + + while let Some(c) = self.get_next() { + self.advance(); + + let pos = self.pos; + + match (c, self.peek_next().unwrap_or('\0')) { + // \n + ('\n', _) => self.new_line(), + + // digit ... + ('0'..='9', _) => { + let mut result = Vec::new(); + let mut radix_base: Option = None; + result.push(c); + + while let Some(next_char) = self.peek_next() { + match next_char { + '0'..='9' | '_' => { + result.push(next_char); + self.eat_next(); + } + #[cfg(not(feature = "no_float"))] + '.' => { + result.push(next_char); + self.eat_next(); + while let Some(next_char_in_float) = self.peek_next() { + match next_char_in_float { + '0'..='9' | '_' => { + result.push(next_char_in_float); + self.eat_next(); + } + _ => break, + } + } + } + // 0x????, 0o????, 0b???? + ch @ 'x' | ch @ 'X' | ch @ 'o' | ch @ 'O' | ch @ 'b' | ch @ 'B' + if c == '0' => + { + result.push(next_char); + self.eat_next(); + + let valid = match ch { + 'x' | 'X' => [ + 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_', + ], + 'o' | 'O' => [ + '0', '1', '2', '3', '4', '5', '6', '7', '_', '_', '_', '_', + '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', + ], + 'b' | 'B' => [ + '0', '1', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', + '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', + ], + _ => panic!("unexpected character {}", ch), + }; + + radix_base = Some(match ch { + 'x' | 'X' => 16, + 'o' | 'O' => 8, + 'b' | 'B' => 2, + _ => panic!("unexpected character {}", ch), + }); + + while let Some(next_char_in_hex) = self.peek_next() { + if !valid.contains(&next_char_in_hex) { + break; + } + + result.push(next_char_in_hex); + self.eat_next(); + } + } + + _ => break, + } + } + + if negated { + result.insert(0, '-'); + } + + // Parse number + if let Some(radix) = radix_base { + let out: String = result.iter().skip(2).filter(|&&c| c != '_').collect(); + + return Some(( + INT::from_str_radix(&out, radix) + .map(Token::IntegerConstant) + .unwrap_or_else(|_| { + Token::LexError(Box::new(LERR::MalformedNumber( + result.iter().collect(), + ))) + }), + pos, + )); + } else { + let out: String = result.iter().filter(|&&c| c != '_').collect(); + let num = INT::from_str(&out).map(Token::IntegerConstant); + + // If integer parsing is unnecessary, try float instead + #[cfg(not(feature = "no_float"))] + let num = num.or_else(|_| FLOAT::from_str(&out).map(Token::FloatConstant)); + + return Some(( + num.unwrap_or_else(|_| { + Token::LexError(Box::new(LERR::MalformedNumber( + result.iter().collect(), + ))) + }), + pos, + )); + } + } + + // letter or underscore ... + ('A'..='Z', _) | ('a'..='z', _) | ('_', _) => { + let mut result = Vec::new(); + result.push(c); + + while let Some(next_char) = self.peek_next() { + match next_char { + x if x.is_ascii_alphanumeric() || x == '_' => { + result.push(x); + self.eat_next(); + } + _ => break, + } + } + + let is_valid_identifier = result + .iter() + .find(|&ch| char::is_ascii_alphanumeric(ch)) // first alpha-numeric character + .map(char::is_ascii_alphabetic) // is a letter + .unwrap_or(false); // if no alpha-numeric at all - syntax error + + let identifier: String = result.iter().collect(); + + if !is_valid_identifier { + return Some(( + Token::LexError(Box::new(LERR::MalformedIdentifier(identifier))), + pos, + )); + } + + return Some(( + match identifier.as_str() { + "true" => Token::True, + "false" => Token::False, + "let" => Token::Let, + "const" => Token::Const, + "if" => Token::If, + "else" => Token::Else, + "while" => Token::While, + "loop" => Token::Loop, + "continue" => Token::Continue, + "break" => Token::Break, + "return" => Token::Return, + "throw" => Token::Throw, + "for" => Token::For, + "in" => Token::In, + + #[cfg(not(feature = "no_function"))] + "fn" => Token::Fn, + + _ => Token::Identifier(identifier), + }, + pos, + )); + } + + // " - string literal + ('"', _) => { + return self.parse_string_literal('"').map_or_else( + |err| Some((Token::LexError(Box::new(err.0)), err.1)), + |out| Some((Token::StringConst(out), pos)), + ); + } + + // ' - character literal + ('\'', '\'') => { + return Some(( + Token::LexError(Box::new(LERR::MalformedChar("".to_string()))), + pos, + )); + } + ('\'', _) => { + return Some(self.parse_string_literal('\'').map_or_else( + |err| (Token::LexError(Box::new(err.0)), err.1), + |result| { + let mut chars = result.chars(); + let first = chars.next(); + + if chars.next().is_some() { + (Token::LexError(Box::new(LERR::MalformedChar(result))), pos) + } else { + (Token::CharConstant(first.expect("should be Some")), pos) + } + }, + )); + } + + // Braces + ('{', _) => return Some((Token::LeftBrace, pos)), + ('}', _) => return Some((Token::RightBrace, pos)), + + // Parentheses + ('(', _) => return Some((Token::LeftParen, pos)), + (')', _) => return Some((Token::RightParen, pos)), + + // Indexing + ('[', _) => return Some((Token::LeftBracket, pos)), + (']', _) => return Some((Token::RightBracket, pos)), + + // Map literal + #[cfg(not(feature = "no_object"))] + ('#', '{') => { + self.eat_next(); + return Some((Token::MapStart, pos)); + } + + // Operators + ('+', '=') => { + self.eat_next(); + return Some((Token::PlusAssign, pos)); + } + ('+', _) if self.can_be_unary => return Some((Token::UnaryPlus, pos)), + ('+', _) => return Some((Token::Plus, pos)), + + ('-', '0'..='9') if self.can_be_unary => negated = true, + ('-', '0'..='9') => return Some((Token::Minus, pos)), + ('-', '=') => { + self.eat_next(); + return Some((Token::MinusAssign, pos)); + } + ('-', _) if self.can_be_unary => return Some((Token::UnaryMinus, pos)), + ('-', _) => return Some((Token::Minus, pos)), + + ('*', '=') => { + self.eat_next(); + return Some((Token::MultiplyAssign, pos)); + } + ('*', _) => return Some((Token::Multiply, pos)), + + // Comments + ('/', '/') => { + self.eat_next(); + + while let Some(c) = self.get_next() { + if c == '\n' { + self.new_line(); + break; + } + + self.advance(); + } + } + ('/', '*') => { + let mut level = 1; + + self.eat_next(); + + while let Some(c) = self.get_next() { + self.advance(); + + match c { + '/' => { + if self.get_next() == Some('*') { + level += 1; + } + self.advance(); + } + '*' => { + if self.get_next() == Some('/') { + level -= 1; + } + self.advance(); + } + '\n' => self.new_line(), + _ => (), + } + + if level == 0 { + break; + } + } + } + + ('/', '=') => { + self.eat_next(); + return Some((Token::DivideAssign, pos)); + } + ('/', _) => return Some((Token::Divide, pos)), + + (';', _) => return Some((Token::SemiColon, pos)), + (':', _) => return Some((Token::Colon, pos)), + (',', _) => return Some((Token::Comma, pos)), + ('.', _) => return Some((Token::Period, pos)), + + ('=', '=') => { + self.eat_next(); + return Some((Token::EqualsTo, pos)); + } + ('=', _) => return Some((Token::Equals, pos)), + + ('<', '=') => { + self.eat_next(); + return Some((Token::LessThanEqualsTo, pos)); + } + ('<', '<') => { + self.eat_next(); + + return Some(( + if self.peek_next() == Some('=') { + self.eat_next(); + Token::LeftShiftAssign + } else { + Token::LeftShift + }, + pos, + )); + } + ('<', _) => return Some((Token::LessThan, pos)), + + ('>', '=') => { + self.eat_next(); + return Some((Token::GreaterThanEqualsTo, pos)); + } + ('>', '>') => { + self.eat_next(); + + return Some(( + if self.peek_next() == Some('=') { + self.eat_next(); + Token::RightShiftAssign + } else { + Token::RightShift + }, + pos, + )); + } + ('>', _) => return Some((Token::GreaterThan, pos)), + + ('!', '=') => { + self.eat_next(); + return Some((Token::NotEqualsTo, pos)); + } + ('!', _) => return Some((Token::Bang, pos)), + + ('|', '|') => { + self.eat_next(); + return Some((Token::Or, pos)); + } + ('|', '=') => { + self.eat_next(); + return Some((Token::OrAssign, pos)); + } + ('|', _) => return Some((Token::Pipe, pos)), + + ('&', '&') => { + self.eat_next(); + return Some((Token::And, pos)); + } + ('&', '=') => { + self.eat_next(); + return Some((Token::AndAssign, pos)); + } + ('&', _) => return Some((Token::Ampersand, pos)), + + ('^', '=') => { + self.eat_next(); + return Some((Token::XOrAssign, pos)); + } + ('^', _) => return Some((Token::XOr, pos)), + + ('%', '=') => { + self.eat_next(); + return Some((Token::ModuloAssign, pos)); + } + ('%', _) => return Some((Token::Modulo, pos)), + + ('~', '=') => { + self.eat_next(); + return Some((Token::PowerOfAssign, pos)); + } + ('~', _) => return Some((Token::PowerOf, pos)), + + (ch, _) if ch.is_whitespace() => (), + (ch, _) => return Some((Token::LexError(Box::new(LERR::UnexpectedChar(ch))), pos)), + } + } + + None + } +} + +impl<'a> Iterator for TokenIterator<'a> { + type Item = (Token, Position); + + fn next(&mut self) -> Option { + self.inner_next().map(|x| { + // Save the last token + self.can_be_unary = x.0.is_next_unary(); + x + }) + } +} + +/// Tokenize an input text stream. +pub fn lex<'a>(input: &'a [&'a str]) -> TokenIterator<'a> { + TokenIterator { + can_be_unary: true, + pos: Position::new(1, 0), + streams: input.iter().map(|s| s.chars().peekable()).collect(), + } +} diff --git a/tests/time.rs b/tests/time.rs index 10b232fe..eddb652d 100644 --- a/tests/time.rs +++ b/tests/time.rs @@ -1,4 +1,5 @@ #![cfg(not(feature = "no_stdlib"))] +#![cfg(not(feature = "no_std"))] use rhai::{Engine, EvalAltResult, INT}; From 674d6c856fdb691eb32feec2acb67a4302ff50c2 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 15 Apr 2020 22:27:24 +0800 Subject: [PATCH 11/34] Bump version to 0.13.0. --- Cargo.toml | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 87cd6436..324e0a02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rhai" -version = "0.12.0" +version = "0.13.0" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"] description = "Embedded scripting for Rust" diff --git a/README.md b/README.md index 13376dbb..b5e84d7d 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Rhai's current features set: to do checked arithmetic operations); for [`no_std`] builds, a number of additional dependencies are pulled in to provide for functionalities that used to be in `std`. -**Note:** Currently, the version is 0.12.0, so the language and API's may change before they stabilize. +**Note:** Currently, the version is 0.13.0, so the language and API's may change before they stabilize. Installation ------------ @@ -36,7 +36,7 @@ Install the Rhai crate by adding this line to `dependencies`: ```toml [dependencies] -rhai = "0.12.0" +rhai = "0.13.0" ``` Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/): From c799a4567a68668058d81e6e84a601cb397eb715 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 16 Apr 2020 00:05:07 +0800 Subject: [PATCH 12/34] Remove IndexSourceType. --- src/engine.rs | 98 ++++++++++++++++----------------------------------- 1 file changed, 30 insertions(+), 68 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 2dec3e5e..7b4ba4f5 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -60,14 +60,6 @@ pub const FUNC_TO_STRING: &str = "to_string"; pub const FUNC_GETTER: &str = "get$"; pub const FUNC_SETTER: &str = "set$"; -#[derive(Debug, Eq, PartialEq, Hash, Clone, Copy)] -enum IndexSourceType { - Expression, - String, - Array, - Map, -} - #[derive(Debug, Eq, PartialEq, Hash, Clone)] enum IndexValue { Num(usize), @@ -608,7 +600,7 @@ impl Engine<'_> { }; self.get_indexed_value(scope, fn_lib, &value, idx_expr, *op_pos, level) - .map(|(val, _, _)| val) + .map(|(val, _)| val) } // xxx.dot_lhs.rhs @@ -645,7 +637,7 @@ impl Engine<'_> { }; self.get_indexed_value(scope, fn_lib, &val, idx_expr, *op_pos, level) - .and_then(|(mut val, _, _)| { + .and_then(|(mut val, _)| { let target = Target::from(&mut val); self.get_dot_val_helper(scope, fn_lib, target, rhs, level) }) @@ -689,7 +681,7 @@ impl Engine<'_> { // idx_lhs[idx_expr].??? Expr::Index(idx_lhs, idx_expr, op_pos) => { - let (idx_src_type, src, index, mut val) = + let (src, index, mut val) = self.eval_index_expr(scope, fn_lib, idx_lhs, idx_expr, *op_pos, level)?; let target = Target::from(&mut val); let value = self.get_dot_val_helper(scope, fn_lib, target, dot_rhs, level); @@ -707,7 +699,6 @@ impl Engine<'_> { Some(ScopeEntryType::Normal) => { Self::update_indexed_var_in_scope( - idx_src_type, scope, src.unwrap(), index, @@ -747,7 +738,7 @@ impl Engine<'_> { idx_expr: &Expr, op_pos: Position, level: usize, - ) -> Result<(Dynamic, IndexSourceType, IndexValue), EvalAltResult> { + ) -> Result<(Dynamic, IndexValue), EvalAltResult> { let idx_pos = idx_expr.position(); // val_array[idx] @@ -759,13 +750,7 @@ impl Engine<'_> { return if index >= 0 { arr.get(index as usize) - .map(|v| { - ( - v.clone(), - IndexSourceType::Array, - IndexValue::from_num(index), - ) - }) + .map(|v| (v.clone(), IndexValue::from_num(index))) .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), index, idx_pos)) } else { Err(EvalAltResult::ErrorArrayBounds(arr.len(), index, idx_pos)) @@ -783,7 +768,6 @@ impl Engine<'_> { map.get(&index) .cloned() .unwrap_or_else(|| Dynamic::from_unit()), - IndexSourceType::Map, IndexValue::from_str(index), )); } @@ -798,13 +782,7 @@ impl Engine<'_> { return if index >= 0 { s.chars() .nth(index as usize) - .map(|ch| { - ( - Dynamic::from_char(ch), - IndexSourceType::String, - IndexValue::from_num(index), - ) - }) + .map(|ch| (Dynamic::from_char(ch), IndexValue::from_num(index))) .ok_or_else(|| { EvalAltResult::ErrorStringBounds(s.chars().count(), index, idx_pos) }) @@ -833,15 +811,7 @@ impl Engine<'_> { idx_expr: &Expr, op_pos: Position, level: usize, - ) -> Result< - ( - IndexSourceType, - Option>, - IndexValue, - Dynamic, - ), - EvalAltResult, - > { + ) -> Result<(Option>, IndexValue, Dynamic), EvalAltResult> { match lhs { // id[idx_expr] Expr::Variable(id, _) => { @@ -854,11 +824,10 @@ impl Engine<'_> { val, ) = Self::search_scope(scope, &id, lhs.position())?; - let (val, idx_src_type, index) = + let (val, index) = self.get_indexed_value(scope, fn_lib, &val, idx_expr, op_pos, level)?; Ok(( - idx_src_type, Some(ScopeSource { name: &id, typ: src_type, @@ -874,7 +843,7 @@ impl Engine<'_> { let val = self.eval_expr(scope, fn_lib, expr, level)?; self.get_indexed_value(scope, fn_lib, &val, idx_expr, op_pos, level) - .map(|(val, _, index)| (IndexSourceType::Expression, None, index, val)) + .map(|(val, index)| (None, index, val)) } } } @@ -894,30 +863,24 @@ impl Engine<'_> { /// Update the value at an index position in a variable inside the scope fn update_indexed_var_in_scope( - idx_src_type: IndexSourceType, scope: &mut Scope, src: ScopeSource, idx: IndexValue, new_val: (Dynamic, Position), ) -> Result { - match idx_src_type { + let target = scope.get_mut(src); + + match &mut target.0 { // array_id[idx] = val - IndexSourceType::Array => { - let arr = scope.get_mut_by_type::(src); + Union::Array(arr) => { arr[idx.as_num()] = new_val.0; - Ok(Dynamic::from_unit()) } - // map_id[idx] = val - IndexSourceType::Map => { - let arr = scope.get_mut_by_type::(src); - arr.insert(idx.as_str(), new_val.0); - Ok(Dynamic::from_unit()) + Union::Map(map) => { + map.insert(idx.as_str(), new_val.0); } - // string_id[idx] = val - IndexSourceType::String => { - let s = scope.get_mut_by_type::(src); + Union::Str(s) => { let pos = new_val.1; // Value must be a character let ch = new_val @@ -925,11 +888,12 @@ impl Engine<'_> { .as_char() .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; Self::str_replace_char(s, idx.as_num(), ch); - Ok(Dynamic::from_unit()) } - - IndexSourceType::Expression => panic!("expression cannot be indexed for update"), + // All other variable types should be an error + _ => panic!("invalid type for indexing: {}", target.type_name()), } + + Ok(Dynamic::from_unit()) } /// Update the value at an index position @@ -939,14 +903,14 @@ impl Engine<'_> { new_val: Dynamic, pos: Position, ) -> Result { - match target { - Dynamic(Union::Array(ref mut arr)) => { + match &mut target.0 { + Union::Array(arr) => { arr[idx.as_num()] = new_val; } - Dynamic(Union::Map(ref mut map)) => { + Union::Map(map) => { map.insert(idx.as_str(), new_val); } - Dynamic(Union::Str(ref mut s)) => { + Union::Str(s) => { // Value must be a character let ch = new_val .as_char() @@ -955,7 +919,7 @@ impl Engine<'_> { Self::str_replace_char(s, idx.as_num(), ch); } // All other variable types should be an error - _ => panic!("array, map or string source type expected for indexing"), + _ => panic!("invalid type for indexing: {}", target.type_name()), } Ok(target) @@ -986,7 +950,7 @@ impl Engine<'_> { let fn_name = make_getter(id); self.call_fn_raw(None, fn_lib, &fn_name, &mut [this_ptr], None, *pos, 0) .and_then(|val| { - let (_, _, index) = self + let (_, index) = self .get_indexed_value(scope, fn_lib, &val, idx_expr, *op_pos, level)?; Self::update_indexed_value(val, index, new_val.0.clone(), new_val.1) @@ -1030,7 +994,7 @@ impl Engine<'_> { let fn_name = make_getter(id); self.call_fn_raw(None, fn_lib, &fn_name, &mut [this_ptr], None, *pos, 0) .and_then(|v| { - let (mut value, _, index) = self.get_indexed_value( + let (mut value, index) = self.get_indexed_value( scope, fn_lib, &v, idx_expr, *op_pos, level, )?; @@ -1111,7 +1075,7 @@ impl Engine<'_> { // lhs[idx_expr].??? // TODO - Allow chaining of indexing! Expr::Index(lhs, idx_expr, op_pos) => { - let (idx_src_type, src, index, mut target) = + let (src, index, mut target) = self.eval_index_expr(scope, fn_lib, lhs, idx_expr, *op_pos, level)?; let val_pos = new_val.1; let this_ptr = &mut target; @@ -1131,7 +1095,6 @@ impl Engine<'_> { Some(ScopeEntryType::Normal) => { Self::update_indexed_var_in_scope( - idx_src_type, scope, src.unwrap(), index, @@ -1270,7 +1233,7 @@ impl Engine<'_> { // idx_lhs[idx_expr] = rhs #[cfg(not(feature = "no_index"))] Expr::Index(idx_lhs, idx_expr, op_pos) => { - let (idx_src_type, src, index, _) = + let (src, index, _) = self.eval_index_expr(scope, fn_lib, idx_lhs, idx_expr, *op_pos, level)?; match src.map(|x| x.typ) { @@ -1286,7 +1249,6 @@ impl Engine<'_> { } Some(ScopeEntryType::Normal) => Ok(Self::update_indexed_var_in_scope( - idx_src_type, scope, src.unwrap(), index, @@ -1317,7 +1279,7 @@ impl Engine<'_> { #[cfg(not(feature = "no_index"))] Expr::Index(lhs, idx_expr, op_pos) => self .eval_index_expr(scope, fn_lib, lhs, idx_expr, *op_pos, level) - .map(|(_, _, _, x)| x), + .map(|(_, _, x)| x), #[cfg(not(feature = "no_object"))] Expr::Dot(lhs, rhs, _) => self.get_dot_val(scope, fn_lib, lhs, rhs, level), From 1ace4b474c0e3fd3332f69a720586e9ac0e4297d Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 16 Apr 2020 10:24:30 +0800 Subject: [PATCH 13/34] Favor matching on Union's instead of downcast::, as_xxx() or is::. --- src/any.rs | 34 +++--- src/engine.rs | 279 +++++++++++++++++++++++++------------------------- 2 files changed, 156 insertions(+), 157 deletions(-) diff --git a/src/any.rs b/src/any.rs index 242969d7..a02604f1 100644 --- a/src/any.rs +++ b/src/any.rs @@ -263,8 +263,24 @@ fn cast_box(item: Box) -> Result> { } impl Dynamic { + /// Get a reference to the inner `Union`. + pub(crate) fn get_ref(&self) -> &Union { + &self.0 + } + + /// Get a mutable reference to the inner `Union`. + pub(crate) fn get_mut(&mut self) -> &mut Union { + &mut self.0 + } + /// Create a `Dynamic` from any type. A `Dynamic` value is simply returned as is. /// + /// Beware that you need to pass in an `Array` type for it to be recognized as an `Array`. + /// A `Vec` does not get automatically converted to an `Array`, but will be a generic + /// restricted trait object instead, because `Vec` is not a supported standard type. + /// + /// Similarly, passing in a `HashMap` will not get a `Map` but a trait object. + /// /// # Examples /// /// ``` @@ -466,24 +482,6 @@ impl Dynamic { } } - /// Cast the `Dynamic` as an `Array` and return a reference to it. - /// Returns the name of the actual type if the cast fails. - pub(crate) fn as_array(&self) -> Result<&Array, &'static str> { - match &self.0 { - Union::Array(array) => Ok(array), - _ => Err(self.type_name()), - } - } - - /// Cast the `Dynamic` as a `Map` and return a reference to it. - /// Returns the name of the actual type if the cast fails. - pub(crate) fn as_map(&self) -> Result<&Map, &'static str> { - match &self.0 { - Union::Map(map) => Ok(map), - _ => Err(self.type_name()), - } - } - pub(crate) fn from_unit() -> Self { Self(Union::Unit(())) } diff --git a/src/engine.rs b/src/engine.rs index 7b4ba4f5..6b5df131 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -94,12 +94,6 @@ enum Target<'a> { } impl<'a> Target<'a> { - fn from(value: &'a mut Dynamic) -> Self { - Self::Value(value) - } - fn from_src(src: ScopeSource<'a>) -> Self { - Self::Scope(src) - } fn get_mut(self, scope: &'a mut Scope) -> &'a mut Dynamic { match self { Self::Value(t) => t, @@ -108,6 +102,18 @@ impl<'a> Target<'a> { } } +impl<'a> From> for Target<'a> { + fn from(src: ScopeSource<'a>) -> Self { + Self::Scope(src) + } +} + +impl<'a> From<&'a mut Dynamic> for Target<'a> { + fn from(value: &'a mut Dynamic) -> Self { + Self::Value(value) + } +} + #[derive(Debug, Eq, PartialEq, Hash, Clone)] pub struct FnSpec<'a> { pub name: Cow<'a, str>, @@ -497,35 +503,37 @@ impl Engine<'_> { } if let Some(prop) = extract_prop_from_getter(fn_name) { - // Map property access - if let Ok(map) = args[0].as_map() { - return Ok(map + return match args[0] { + // Map property access + Dynamic(Union::Map(map)) => Ok(map .get(prop) .cloned() - .unwrap_or_else(|| Dynamic::from_unit())); - } + .unwrap_or_else(|| Dynamic::from_unit())), - // Getter function not found - return Err(EvalAltResult::ErrorDotExpr( - format!("- property '{}' unknown or write-only", prop), - pos, - )); + // Getter function not found + _ => Err(EvalAltResult::ErrorDotExpr( + format!("- property '{}' unknown or write-only", prop), + pos, + )), + }; } if let Some(prop) = extract_prop_from_setter(fn_name) { let value = args[1].clone(); - // Map property update - if let Dynamic(Union::Map(map)) = args[0] { - map.insert(prop.to_string(), value); - return Ok(Dynamic::from_unit()); - } + return match args[0] { + // Map property update + Dynamic(Union::Map(map)) => { + map.insert(prop.to_string(), value); + Ok(Dynamic::from_unit()) + } - // Setter function not found - return Err(EvalAltResult::ErrorDotExpr( - format!("- property '{}' unknown or read-only", prop), - pos, - )); + // Setter function not found + _ => Err(EvalAltResult::ErrorDotExpr( + format!("- property '{}' unknown or read-only", prop), + pos, + )), + }; } if let Some(val) = def_val { @@ -610,8 +618,7 @@ impl Engine<'_> { let mut args = [target.get_mut(scope)]; self.call_fn_raw(None, fn_lib, &make_getter(id), &mut args, None, *pos, 0) .and_then(|mut val| { - let target = Target::from(&mut val); - self.get_dot_val_helper(scope, fn_lib, target, rhs, level) + self.get_dot_val_helper(scope, fn_lib, (&mut val).into(), rhs, level) }) } // xxx.idx_lhs[idx_expr].rhs @@ -638,8 +645,7 @@ impl Engine<'_> { self.get_indexed_value(scope, fn_lib, &val, idx_expr, *op_pos, level) .and_then(|(mut val, _)| { - let target = Target::from(&mut val); - self.get_dot_val_helper(scope, fn_lib, target, rhs, level) + self.get_dot_val_helper(scope, fn_lib, (&mut val).into(), rhs, level) }) } // Syntax error @@ -676,34 +682,34 @@ impl Engine<'_> { // This is a variable property access (potential function call). // Use a direct index into `scope` to directly mutate the variable value. - self.get_dot_val_helper(scope, fn_lib, Target::from_src(entry), dot_rhs, level) + self.get_dot_val_helper(scope, fn_lib, entry.into(), dot_rhs, level) } // idx_lhs[idx_expr].??? Expr::Index(idx_lhs, idx_expr, op_pos) => { let (src, index, mut val) = self.eval_index_expr(scope, fn_lib, idx_lhs, idx_expr, *op_pos, level)?; - let target = Target::from(&mut val); - let value = self.get_dot_val_helper(scope, fn_lib, target, dot_rhs, level); + let value = + self.get_dot_val_helper(scope, fn_lib, (&mut val).into(), dot_rhs, level); // In case the expression mutated `target`, we need to update it back into the scope because it is cloned. - match src.map(|s| s.typ) { - None => (), + if let Some(src) = src { + match src.typ { + ScopeEntryType::Constant => { + return Err(EvalAltResult::ErrorAssignmentToConstant( + src.name.to_string(), + idx_lhs.position(), + )); + } - Some(ScopeEntryType::Constant) => { - return Err(EvalAltResult::ErrorAssignmentToConstant( - src.unwrap().name.to_string(), - idx_lhs.position(), - )); - } - - Some(ScopeEntryType::Normal) => { - Self::update_indexed_var_in_scope( - scope, - src.unwrap(), - index, - (val, dot_rhs.position()), - )?; + ScopeEntryType::Normal => { + Self::update_indexed_var_in_scope( + scope, + src, + index, + (val, dot_rhs.position()), + )?; + } } } @@ -713,7 +719,7 @@ impl Engine<'_> { // {expr}.??? expr => { let mut val = self.eval_expr(scope, fn_lib, expr, level)?; - self.get_dot_val_helper(scope, fn_lib, Target::from(&mut val), dot_rhs, level) + self.get_dot_val_helper(scope, fn_lib, (&mut val).into(), dot_rhs, level) } } } @@ -740,66 +746,69 @@ impl Engine<'_> { level: usize, ) -> Result<(Dynamic, IndexValue), EvalAltResult> { let idx_pos = idx_expr.position(); + let type_name = self.map_type_name(val.type_name()); - // val_array[idx] - if let Ok(arr) = val.as_array() { - let index = self - .eval_expr(scope, fn_lib, idx_expr, level)? - .as_int() - .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; + match val.get_ref() { + Union::Array(arr) => { + // val_array[idx] + let index = self + .eval_expr(scope, fn_lib, idx_expr, level)? + .as_int() + .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; - return if index >= 0 { - arr.get(index as usize) - .map(|v| (v.clone(), IndexValue::from_num(index))) - .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), index, idx_pos)) - } else { - Err(EvalAltResult::ErrorArrayBounds(arr.len(), index, idx_pos)) - }; + return if index >= 0 { + arr.get(index as usize) + .map(|v| (v.clone(), IndexValue::from_num(index))) + .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), index, idx_pos)) + } else { + Err(EvalAltResult::ErrorArrayBounds(arr.len(), index, idx_pos)) + }; + } + + Union::Map(map) => { + // val_map[idx] + let index = self + .eval_expr(scope, fn_lib, idx_expr, level)? + .take_string() + .map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_expr.position()))?; + + return Ok(( + map.get(&index) + .cloned() + .unwrap_or_else(|| Dynamic::from_unit()), + IndexValue::from_str(index), + )); + } + + Union::Str(s) => { + // val_string[idx] + let index = self + .eval_expr(scope, fn_lib, idx_expr, level)? + .as_int() + .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; + + return if index >= 0 { + s.chars() + .nth(index as usize) + .map(|ch| (Dynamic::from_char(ch), IndexValue::from_num(index))) + .ok_or_else(|| { + EvalAltResult::ErrorStringBounds(s.chars().count(), index, idx_pos) + }) + } else { + Err(EvalAltResult::ErrorStringBounds( + s.chars().count(), + index, + idx_pos, + )) + }; + } + + // Error - cannot be indexed + _ => Err(EvalAltResult::ErrorIndexingType( + type_name.to_string(), + op_pos, + )), } - - // val_map[idx] - if let Ok(map) = val.as_map() { - let index = self - .eval_expr(scope, fn_lib, idx_expr, level)? - .take_string() - .map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_expr.position()))?; - - return Ok(( - map.get(&index) - .cloned() - .unwrap_or_else(|| Dynamic::from_unit()), - IndexValue::from_str(index), - )); - } - - // val_string[idx] - if let Ok(s) = val.as_str() { - let index = self - .eval_expr(scope, fn_lib, idx_expr, level)? - .as_int() - .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; - - return if index >= 0 { - s.chars() - .nth(index as usize) - .map(|ch| (Dynamic::from_char(ch), IndexValue::from_num(index))) - .ok_or_else(|| { - EvalAltResult::ErrorStringBounds(s.chars().count(), index, idx_pos) - }) - } else { - Err(EvalAltResult::ErrorStringBounds( - s.chars().count(), - index, - idx_pos, - )) - }; - } - - // Error - cannot be indexed - Err(EvalAltResult::ErrorIndexingType( - self.map_type_name(val.type_name()).to_string(), - op_pos, - )) } /// Evaluate an index expression @@ -815,25 +824,18 @@ impl Engine<'_> { match lhs { // id[idx_expr] Expr::Variable(id, _) => { - let ( - ScopeSource { - typ: src_type, - index: src_idx, - .. - }, - val, - ) = Self::search_scope(scope, &id, lhs.position())?; - - let (val, index) = + let (ScopeSource { typ, index, .. }, val) = + Self::search_scope(scope, &id, lhs.position())?; + let (val, idx) = self.get_indexed_value(scope, fn_lib, &val, idx_expr, op_pos, level)?; Ok(( Some(ScopeSource { name: &id, - typ: src_type, - index: src_idx, + typ, + index, }), - index, + idx, val, )) } @@ -841,7 +843,6 @@ impl Engine<'_> { // (expr)[idx_expr] expr => { let val = self.eval_expr(scope, fn_lib, expr, level)?; - self.get_indexed_value(scope, fn_lib, &val, idx_expr, op_pos, level) .map(|(val, index)| (None, index, val)) } @@ -870,7 +871,7 @@ impl Engine<'_> { ) -> Result { let target = scope.get_mut(src); - match &mut target.0 { + match target.get_mut() { // array_id[idx] = val Union::Array(arr) => { arr[idx.as_num()] = new_val.0; @@ -903,7 +904,7 @@ impl Engine<'_> { new_val: Dynamic, pos: Position, ) -> Result { - match &mut target.0 { + match target.get_mut() { Union::Array(arr) => { arr[idx.as_num()] = new_val; } @@ -1083,23 +1084,23 @@ impl Engine<'_> { self.set_dot_val_helper(scope, fn_lib, this_ptr, dot_rhs, new_val, level); // In case the expression mutated `target`, we need to update it back into the scope because it is cloned. - match src.map(|x| x.typ) { - None => (), + if let Some(src) = src { + match src.typ { + ScopeEntryType::Constant => { + return Err(EvalAltResult::ErrorAssignmentToConstant( + src.name.to_string(), + lhs.position(), + )); + } - Some(ScopeEntryType::Constant) => { - return Err(EvalAltResult::ErrorAssignmentToConstant( - src.unwrap().name.to_string(), - lhs.position(), - )); - } - - Some(ScopeEntryType::Normal) => { - Self::update_indexed_var_in_scope( - scope, - src.unwrap(), - index, - (target, val_pos), - )?; + ScopeEntryType::Normal => { + Self::update_indexed_var_in_scope( + scope, + src, + index, + (target, val_pos), + )?; + } } } From 090217e1cde728683533cfdac9fd86b179abf9a1 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 16 Apr 2020 11:57:08 +0800 Subject: [PATCH 14/34] Do not print result if (). --- examples/repl.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/repl.rs b/examples/repl.rs index 0c513a59..b6a0c9c4 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -159,10 +159,11 @@ fn main() { // Evaluate engine.eval_ast_with_scope::(&mut scope, &main_ast) }) { - Ok(result) => { + Ok(result) if !result.is::<()>() => { println!("=> {:?}", result); println!(); } + Ok(_) => (), Err(err) => { println!(); print_error(&input, err); From 4f2350734fa4682e68f36063a8e3ff71820079c0 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 16 Apr 2020 12:08:28 +0800 Subject: [PATCH 15/34] Avoid copying indexed value if not necessary. --- src/engine.rs | 56 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 6b5df131..1052d080 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -519,12 +519,12 @@ impl Engine<'_> { } if let Some(prop) = extract_prop_from_setter(fn_name) { - let value = args[1].clone(); + let (arg, value) = args.split_at_mut(0); - return match args[0] { + return match arg[0] { // Map property update Dynamic(Union::Map(map)) => { - map.insert(prop.to_string(), value); + map.insert(prop.to_string(), value[0].clone()); Ok(Dynamic::from_unit()) } @@ -588,7 +588,7 @@ impl Engine<'_> { // xxx.idx_lhs[idx_expr] Expr::Index(idx_lhs, idx_expr, op_pos) => { - let value = match idx_lhs.as_ref() { + let lhs_value = match idx_lhs.as_ref() { // xxx.id[idx_expr] Expr::Property(id, pos) => { let mut args = [target.get_mut(scope)]; @@ -596,6 +596,7 @@ impl Engine<'_> { } // xxx.???[???][idx_expr] Expr::Index(_, _, _) => { + // Chain the indexing self.get_dot_val_helper(scope, fn_lib, target, idx_lhs, level)? } // Syntax error @@ -607,7 +608,7 @@ impl Engine<'_> { } }; - self.get_indexed_value(scope, fn_lib, &value, idx_expr, *op_pos, level) + self.get_indexed_value(scope, fn_lib, &lhs_value, idx_expr, *op_pos, level, false) .map(|(val, _)| val) } @@ -643,7 +644,7 @@ impl Engine<'_> { } }; - self.get_indexed_value(scope, fn_lib, &val, idx_expr, *op_pos, level) + self.get_indexed_value(scope, fn_lib, &val, idx_expr, *op_pos, level, false) .and_then(|(mut val, _)| { self.get_dot_val_helper(scope, fn_lib, (&mut val).into(), rhs, level) }) @@ -744,6 +745,7 @@ impl Engine<'_> { idx_expr: &Expr, op_pos: Position, level: usize, + only_index: bool, ) -> Result<(Dynamic, IndexValue), EvalAltResult> { let idx_pos = idx_expr.position(); let type_name = self.map_type_name(val.type_name()); @@ -756,13 +758,22 @@ impl Engine<'_> { .as_int() .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; - return if index >= 0 { + if index >= 0 { arr.get(index as usize) - .map(|v| (v.clone(), IndexValue::from_num(index))) + .map(|v| { + ( + if only_index { + Dynamic::from_unit() + } else { + v.clone() + }, + IndexValue::from_num(index), + ) + }) .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), index, idx_pos)) } else { Err(EvalAltResult::ErrorArrayBounds(arr.len(), index, idx_pos)) - }; + } } Union::Map(map) => { @@ -772,12 +783,18 @@ impl Engine<'_> { .take_string() .map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_expr.position()))?; - return Ok(( + Ok(( map.get(&index) - .cloned() + .map(|v| { + if only_index { + Dynamic::from_unit() + } else { + v.clone() + } + }) .unwrap_or_else(|| Dynamic::from_unit()), IndexValue::from_str(index), - )); + )) } Union::Str(s) => { @@ -787,7 +804,7 @@ impl Engine<'_> { .as_int() .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; - return if index >= 0 { + if index >= 0 { s.chars() .nth(index as usize) .map(|ch| (Dynamic::from_char(ch), IndexValue::from_num(index))) @@ -800,7 +817,7 @@ impl Engine<'_> { index, idx_pos, )) - }; + } } // Error - cannot be indexed @@ -827,7 +844,7 @@ impl Engine<'_> { let (ScopeSource { typ, index, .. }, val) = Self::search_scope(scope, &id, lhs.position())?; let (val, idx) = - self.get_indexed_value(scope, fn_lib, &val, idx_expr, op_pos, level)?; + self.get_indexed_value(scope, fn_lib, &val, idx_expr, op_pos, level, false)?; Ok(( Some(ScopeSource { @@ -843,7 +860,7 @@ impl Engine<'_> { // (expr)[idx_expr] expr => { let val = self.eval_expr(scope, fn_lib, expr, level)?; - self.get_indexed_value(scope, fn_lib, &val, idx_expr, op_pos, level) + self.get_indexed_value(scope, fn_lib, &val, idx_expr, op_pos, level, false) .map(|(val, index)| (None, index, val)) } } @@ -951,8 +968,9 @@ impl Engine<'_> { let fn_name = make_getter(id); self.call_fn_raw(None, fn_lib, &fn_name, &mut [this_ptr], None, *pos, 0) .and_then(|val| { - let (_, index) = self - .get_indexed_value(scope, fn_lib, &val, idx_expr, *op_pos, level)?; + let (_, index) = self.get_indexed_value( + scope, fn_lib, &val, idx_expr, *op_pos, level, true, + )?; Self::update_indexed_value(val, index, new_val.0.clone(), new_val.1) }) @@ -996,7 +1014,7 @@ impl Engine<'_> { self.call_fn_raw(None, fn_lib, &fn_name, &mut [this_ptr], None, *pos, 0) .and_then(|v| { let (mut value, index) = self.get_indexed_value( - scope, fn_lib, &v, idx_expr, *op_pos, level, + scope, fn_lib, &v, idx_expr, *op_pos, level, false, )?; let val_pos = new_val.1; From f8e9d66a0b94f00bf752811cafcece3fdce19032 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 16 Apr 2020 23:31:48 +0800 Subject: [PATCH 16/34] Remove lifetime from Engine. --- src/api.rs | 86 +++++++++++++++++++++++-------------------- src/builtin.rs | 4 +- src/engine.rs | 51 ++++++++++++------------- src/fn_func.rs | 6 +-- src/fn_register.rs | 6 +-- src/optimize.rs | 19 ++++------ src/parser.rs | 8 ++-- tests/side_effects.rs | 14 +++---- 8 files changed, 95 insertions(+), 99 deletions(-) diff --git a/src/api.rs b/src/api.rs index 01d0a76f..40eef3c3 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,7 +1,7 @@ //! Module that defines the extern API of `Engine`. use crate::any::{Dynamic, Variant}; -use crate::engine::{make_getter, make_setter, Engine, FnAny, FnSpec, Map}; +use crate::engine::{calc_fn_spec, make_getter, make_setter, Engine, FnAny, Map}; use crate::error::ParseError; use crate::fn_call::FuncArgs; use crate::fn_register::RegisterFn; @@ -59,18 +59,16 @@ pub trait IteratorCallback: Fn(&Dynamic) -> Box> + impl Box> + 'static> IteratorCallback for F {} /// Engine public API -impl<'e> Engine<'e> { +impl Engine { /// Register a custom function. pub(crate) fn register_fn_raw(&mut self, fn_name: &str, args: Vec, f: Box) { - let spec = FnSpec { - name: fn_name.to_string().into(), - args, - }; - if self.functions.is_none() { self.functions = Some(HashMap::new()); } - self.functions.as_mut().unwrap().insert(spec, f); + self.functions + .as_mut() + .unwrap() + .insert(calc_fn_spec(fn_name, args.into_iter()), f); } /// Register a custom type for use with the `Engine`. @@ -969,23 +967,25 @@ impl<'e> Engine<'e> { /// ``` /// # fn main() -> Result<(), rhai::EvalAltResult> { /// # use std::sync::RwLock; + /// # use std::sync::Arc; /// use rhai::Engine; /// - /// let result = RwLock::new(String::from("")); - /// { - /// let mut engine = Engine::new(); + /// let result = Arc::new(RwLock::new(String::from(""))); /// - /// // Override action of 'print' function - /// engine.on_print(|s| result.write().unwrap().push_str(s)); + /// let mut engine = Engine::new(); + /// + /// // Override action of 'print' function + /// let logger = result.clone(); + /// engine.on_print(move |s| logger.write().unwrap().push_str(s)); + /// + /// engine.consume("print(40 + 2);")?; /// - /// engine.consume("print(40 + 2);")?; - /// } /// assert_eq!(*result.read().unwrap(), "42"); /// # Ok(()) /// # } /// ``` #[cfg(feature = "sync")] - pub fn on_print(&mut self, callback: impl Fn(&str) + Send + Sync + 'e) { + pub fn on_print(&mut self, callback: impl Fn(&str) + Send + Sync + 'static) { self.on_print = Some(Box::new(callback)); } /// Override default action of `print` (print to stdout using `println!`) @@ -995,23 +995,25 @@ impl<'e> Engine<'e> { /// ``` /// # fn main() -> Result<(), rhai::EvalAltResult> { /// # use std::sync::RwLock; + /// # use std::sync::Arc; /// use rhai::Engine; /// - /// let result = RwLock::new(String::from("")); - /// { - /// let mut engine = Engine::new(); + /// let result = Arc::new(RwLock::new(String::from(""))); /// - /// // Override action of 'print' function - /// engine.on_print(|s| result.write().unwrap().push_str(s)); + /// let mut engine = Engine::new(); + /// + /// // Override action of 'print' function + /// let logger = result.clone(); + /// engine.on_print(move |s| logger.write().unwrap().push_str(s)); + /// + /// engine.consume("print(40 + 2);")?; /// - /// engine.consume("print(40 + 2);")?; - /// } /// assert_eq!(*result.read().unwrap(), "42"); /// # Ok(()) /// # } /// ``` #[cfg(not(feature = "sync"))] - pub fn on_print(&mut self, callback: impl Fn(&str) + 'e) { + pub fn on_print(&mut self, callback: impl Fn(&str) + 'static) { self.on_print = Some(Box::new(callback)); } @@ -1022,23 +1024,25 @@ impl<'e> Engine<'e> { /// ``` /// # fn main() -> Result<(), rhai::EvalAltResult> { /// # use std::sync::RwLock; + /// # use std::sync::Arc; /// use rhai::Engine; /// - /// let result = RwLock::new(String::from("")); - /// { - /// let mut engine = Engine::new(); + /// let result = Arc::new(RwLock::new(String::from(""))); /// - /// // Override action of 'print' function - /// engine.on_debug(|s| result.write().unwrap().push_str(s)); + /// let mut engine = Engine::new(); + /// + /// // Override action of 'print' function + /// let logger = result.clone(); + /// engine.on_debug(move |s| logger.write().unwrap().push_str(s)); + /// + /// engine.consume(r#"debug("hello");"#)?; /// - /// engine.consume(r#"debug("hello");"#)?; - /// } /// assert_eq!(*result.read().unwrap(), r#""hello""#); /// # Ok(()) /// # } /// ``` #[cfg(feature = "sync")] - pub fn on_debug(&mut self, callback: impl Fn(&str) + Send + Sync + 'e) { + pub fn on_debug(&mut self, callback: impl Fn(&str) + Send + Sync + 'static) { self.on_debug = Some(Box::new(callback)); } /// Override default action of `debug` (print to stdout using `println!`) @@ -1048,23 +1052,25 @@ impl<'e> Engine<'e> { /// ``` /// # fn main() -> Result<(), rhai::EvalAltResult> { /// # use std::sync::RwLock; + /// # use std::sync::Arc; /// use rhai::Engine; /// - /// let result = RwLock::new(String::from("")); - /// { - /// let mut engine = Engine::new(); + /// let result = Arc::new(RwLock::new(String::from(""))); /// - /// // Override action of 'print' function - /// engine.on_debug(|s| result.write().unwrap().push_str(s)); + /// let mut engine = Engine::new(); + /// + /// // Override action of 'print' function + /// let logger = result.clone(); + /// engine.on_debug(move |s| logger.write().unwrap().push_str(s)); + /// + /// engine.consume(r#"debug("hello");"#)?; /// - /// engine.consume(r#"debug("hello");"#)?; - /// } /// assert_eq!(*result.read().unwrap(), r#""hello""#); /// # Ok(()) /// # } /// ``` #[cfg(not(feature = "sync"))] - pub fn on_debug(&mut self, callback: impl Fn(&str) + 'e) { + pub fn on_debug(&mut self, callback: impl Fn(&str) + 'static) { self.on_debug = Some(Box::new(callback)); } } diff --git a/src/builtin.rs b/src/builtin.rs index 0fac046f..428de9b7 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -94,7 +94,7 @@ fn ne(x: T, y: T) -> bool { x != y } -impl Engine<'_> { +impl Engine { /// Register the core built-in library. pub(crate) fn register_core_lib(&mut self) { // Checked add @@ -770,7 +770,7 @@ macro_rules! reg_fn2y { } /// Register the built-in library. -impl Engine<'_> { +impl Engine { pub fn register_stdlib(&mut self) { #[cfg(not(feature = "no_float"))] { diff --git a/src/engine.rs b/src/engine.rs index 1052d080..2e1462d9 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -10,17 +10,16 @@ use crate::token::Position; use crate::stdlib::{ any::TypeId, - borrow::Cow, boxed::Box, cmp::Ordering, - collections::HashMap, + collections::{hash_map::DefaultHasher, HashMap}, format, + hash::{Hash, Hasher}, iter::once, ops::{Deref, DerefMut}, rc::Rc, string::{String, ToString}, sync::Arc, - vec, vec::Vec, }; @@ -114,12 +113,6 @@ impl<'a> From<&'a mut Dynamic> for Target<'a> { } } -#[derive(Debug, Eq, PartialEq, Hash, Clone)] -pub struct FnSpec<'a> { - pub name: Cow<'a, str>, - pub args: Vec, -} - /// A type that holds a library of script-defined functions. /// /// Since script-defined functions have `Dynamic` parameters, functions with the same name @@ -239,9 +232,9 @@ impl DerefMut for FunctionsLib { /// ``` /// /// Currently, `Engine` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. -pub struct Engine<'e> { +pub struct Engine { /// A hashmap containing all compiled functions known to the engine. - pub(crate) functions: Option, Box>>, + pub(crate) functions: Option>>, /// A hashmap containing all iterators known to the engine. pub(crate) type_iterators: Option>>, @@ -250,17 +243,17 @@ pub struct Engine<'e> { /// Closure for implementing the `print` command. #[cfg(feature = "sync")] - pub(crate) on_print: Option>, + pub(crate) on_print: Option>, /// Closure for implementing the `print` command. #[cfg(not(feature = "sync"))] - pub(crate) on_print: Option>, + pub(crate) on_print: Option>, /// Closure for implementing the `debug` command. #[cfg(feature = "sync")] - pub(crate) on_debug: Option>, + pub(crate) on_debug: Option>, /// Closure for implementing the `debug` command. #[cfg(not(feature = "sync"))] - pub(crate) on_debug: Option>, + pub(crate) on_debug: Option>, /// Optimize the AST after compilation. pub(crate) optimization_level: OptimizationLevel, @@ -271,7 +264,7 @@ pub struct Engine<'e> { pub(crate) max_call_stack_depth: usize, } -impl Default for Engine<'_> { +impl Default for Engine { fn default() -> Self { // Create the new scripting Engine let mut engine = Engine { @@ -349,7 +342,14 @@ fn extract_prop_from_setter(fn_name: &str) -> Option<&str> { } } -impl Engine<'_> { +pub(crate) fn calc_fn_spec(fn_name: &str, params: impl Iterator) -> u64 { + let mut s = DefaultHasher::new(); + fn_name.hash(&mut s); + params.for_each(|t| t.hash(&mut s)); + s.finish() +} + +impl Engine { /// Create a new `Engine` pub fn new() -> Self { Default::default() @@ -471,11 +471,6 @@ impl Engine<'_> { } } - let spec = FnSpec { - name: fn_name.into(), - args: args.iter().map(|a| a.type_id()).collect(), - }; - // Argument must be a string fn cast_to_string(r: &Dynamic, pos: Position) -> Result<&str, EvalAltResult> { r.as_str() @@ -483,7 +478,9 @@ impl Engine<'_> { } // Search built-in's and external functions - if let Some(func) = self.functions.as_ref().and_then(|f| f.get(&spec)) { + let fn_spec = calc_fn_spec(fn_name, args.iter().map(|a| a.type_id())); + + if let Some(func) = self.functions.as_ref().and_then(|f| f.get(&fn_spec)) { // Run external function let result = func(args, pos)?; @@ -519,7 +516,7 @@ impl Engine<'_> { } if let Some(prop) = extract_prop_from_setter(fn_name) { - let (arg, value) = args.split_at_mut(0); + let (arg, value) = args.split_at_mut(1); return match arg[0] { // Map property update @@ -1336,10 +1333,8 @@ impl Engine<'_> { name: &str, ) -> bool { engine.functions.as_ref().map_or(false, |lib| { - lib.contains_key(&FnSpec { - name: name.into(), - args: vec![TypeId::of::()], - }) + let fn_spec = calc_fn_spec(name, [TypeId::of::()].iter().cloned()); + lib.contains_key(&fn_spec) }) || fn_lib.map_or(false, |lib| lib.has_function(name, 1)) } diff --git a/src/fn_func.rs b/src/fn_func.rs index af45dae8..4c3544de 100644 --- a/src/fn_func.rs +++ b/src/fn_func.rs @@ -88,13 +88,13 @@ macro_rules! def_anonymous_fn { def_anonymous_fn!(imp); }; (imp $($par:ident),*) => { - impl<'e, $($par: Variant + Clone,)* RET: Variant + Clone> Func<($($par,)*), RET> for Engine<'e> + impl<$($par: Variant + Clone,)* RET: Variant + Clone> Func<($($par,)*), RET> for Engine { #[cfg(feature = "sync")] - type Output = Box Result + Send + Sync + 'e>; + type Output = Box Result + Send + Sync>; #[cfg(not(feature = "sync"))] - type Output = Box Result + 'e>; + type Output = Box Result>; fn create_from_ast(self, ast: AST, entry_point: &str) -> Self::Output { let name = entry_point.to_string(); diff --git a/src/fn_register.rs b/src/fn_register.rs index 711c9e4b..08bf9109 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -146,7 +146,7 @@ macro_rules! def_register { FN: Fn($($param),*) -> RET + 'static, RET: Variant + Clone - > RegisterFn for Engine<'_> + > RegisterFn for Engine { fn register_fn(&mut self, name: &str, f: FN) { let fn_name = name.to_string(); @@ -184,7 +184,7 @@ macro_rules! def_register { #[cfg(not(feature = "sync"))] FN: Fn($($param),*) -> Dynamic + 'static, - > RegisterDynamicFn for Engine<'_> + > RegisterDynamicFn for Engine { fn register_dynamic_fn(&mut self, name: &str, f: FN) { let fn_name = name.to_string(); @@ -221,7 +221,7 @@ macro_rules! def_register { FN: Fn($($param),*) -> Result + 'static, RET: Variant + Clone - > RegisterResultFn for Engine<'_> + > RegisterResultFn for Engine { fn register_result_fn(&mut self, name: &str, f: FN) { let fn_name = name.to_string(); diff --git a/src/optimize.rs b/src/optimize.rs index cd4c30b5..1e8161e7 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -1,7 +1,7 @@ use crate::any::Dynamic; use crate::engine::{ - Engine, FnAny, FnCallArgs, FnSpec, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, - KEYWORD_TYPE_OF, + calc_fn_spec, Engine, FnAny, FnCallArgs, FunctionsLib, KEYWORD_DEBUG, KEYWORD_EVAL, + KEYWORD_PRINT, KEYWORD_TYPE_OF, }; use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST}; use crate::result::EvalAltResult; @@ -50,7 +50,7 @@ struct State<'a> { /// Collection of constants to use for eager function evaluations. constants: Vec<(String, Expr)>, /// An `Engine` instance for eager function evaluation. - engine: &'a Engine<'a>, + engine: &'a Engine, /// Library of script-defined functions. fn_lib: &'a [(&'a str, usize)], /// Optimization level. @@ -60,7 +60,7 @@ struct State<'a> { impl<'a> State<'a> { /// Create a new State. pub fn new( - engine: &'a Engine<'a>, + engine: &'a Engine, fn_lib: &'a [(&'a str, usize)], level: OptimizationLevel, ) -> Self { @@ -110,19 +110,14 @@ impl<'a> State<'a> { /// Call a registered function fn call_fn( - functions: Option<&HashMap>>, + functions: Option<&HashMap>>, fn_name: &str, args: &mut FnCallArgs, pos: Position, ) -> Result, EvalAltResult> { - let spec = FnSpec { - name: fn_name.into(), - args: args.iter().map(|a| a.type_id()).collect(), - }; - // Search built-in's and external functions functions - .and_then(|f| f.get(&spec)) + .and_then(|f| f.get(&calc_fn_spec(fn_name, args.iter().map(|a| a.type_id())))) .map(|func| func(args, pos)) .transpose() } @@ -621,7 +616,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { fn optimize<'a>( statements: Vec, - engine: &Engine<'a>, + engine: &Engine, scope: &Scope, fn_lib: &'a [(&'a str, usize)], level: OptimizationLevel, diff --git a/src/parser.rs b/src/parser.rs index 755609af..c8331d35 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1759,9 +1759,9 @@ fn parse_fn<'a>( }) } -pub fn parse_global_expr<'a, 'e>( +pub fn parse_global_expr<'a>( input: &mut Peekable>, - engine: &Engine<'e>, + engine: &Engine, scope: &Scope, optimization_level: OptimizationLevel, ) -> Result { @@ -1841,9 +1841,9 @@ fn parse_global_level<'a>( } /// Run the parser on an input stream, returning an AST. -pub fn parse<'a, 'e>( +pub fn parse<'a>( input: &mut Peekable>, - engine: &Engine<'e>, + engine: &Engine, scope: &Scope, optimization_level: OptimizationLevel, ) -> Result { diff --git a/tests/side_effects.rs b/tests/side_effects.rs index 0375d1af..259d1f63 100644 --- a/tests/side_effects.rs +++ b/tests/side_effects.rs @@ -81,18 +81,18 @@ fn test_side_effects_command() -> Result<(), EvalAltResult> { #[test] fn test_side_effects_print() -> Result<(), EvalAltResult> { + use std::sync::Arc; use std::sync::RwLock; - let result = RwLock::new(String::from("")); + let result = Arc::new(RwLock::new(String::from(""))); - { - let mut engine = Engine::new(); + let mut engine = Engine::new(); - // Override action of 'print' function - engine.on_print(|s| result.write().unwrap().push_str(s)); + // Override action of 'print' function + let logger = result.clone(); + engine.on_print(move |s| logger.write().unwrap().push_str(s)); - engine.consume("print(40 + 2);")?; - } + engine.consume("print(40 + 2);")?; assert_eq!(*result.read().unwrap(), "42"); Ok(()) From 3a93ab82402117a4f36f0939d26a02cbae53aa0f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 16 Apr 2020 23:58:57 +0800 Subject: [PATCH 17/34] Make FunctionsLib a HashMap. --- src/api.rs | 6 +++- src/engine.rs | 77 +++++++++++++++++++++++---------------------------- src/parser.rs | 17 ++++-------- src/scope.rs | 7 ----- 4 files changed, 45 insertions(+), 62 deletions(-) diff --git a/src/api.rs b/src/api.rs index 40eef3c3..90a918c7 100644 --- a/src/api.rs +++ b/src/api.rs @@ -956,7 +956,11 @@ impl Engine { ast: AST, optimization_level: OptimizationLevel, ) -> AST { - let fn_lib = ast.1.iter().map(|fn_def| fn_def.as_ref().clone()).collect(); + let fn_lib = ast + .1 + .iter() + .map(|(_, fn_def)| fn_def.as_ref().clone()) + .collect(); optimize_into_ast(self, scope, ast.0, fn_lib, optimization_level) } diff --git a/src/engine.rs b/src/engine.rs index 2e1462d9..b0b44a38 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -11,7 +11,6 @@ use crate::token::Position; use crate::stdlib::{ any::TypeId, boxed::Box, - cmp::Ordering, collections::{hash_map::DefaultHasher, HashMap}, format, hash::{Hash, Hasher}, @@ -125,48 +124,41 @@ impl<'a> From<&'a mut Dynamic> for Target<'a> { /// So instead this is implemented as a sorted list and binary searched. #[derive(Debug, Clone)] pub struct FunctionsLib( - #[cfg(feature = "sync")] Vec>, - #[cfg(not(feature = "sync"))] Vec>, + #[cfg(feature = "sync")] HashMap>, + #[cfg(not(feature = "sync"))] HashMap>, ); -impl FnDef { - /// Function to order two FnDef records, for binary search. - pub fn compare(&self, name: &str, params_len: usize) -> Ordering { - // First order by name - match self.name.as_str().cmp(name) { - // Then by number of parameters - Ordering::Equal => self.params.len().cmp(¶ms_len), - order => order, - } - } -} - impl FunctionsLib { /// Create a new `FunctionsLib`. pub fn new() -> Self { - FunctionsLib(Vec::new()) + FunctionsLib(HashMap::new()) } /// Create a new `FunctionsLib` from a collection of `FnDef`. pub fn from_vec(vec: Vec) -> Self { - #[cfg(feature = "sync")] - { - FunctionsLib(vec.into_iter().map(Arc::new).collect()) - } - #[cfg(not(feature = "sync"))] - { - FunctionsLib(vec.into_iter().map(Rc::new).collect()) - } + FunctionsLib( + vec.into_iter() + .map(|f| { + let hash = calc_fn_def(&f.name, f.params.len()); + + #[cfg(feature = "sync")] + { + (hash, Arc::new(f)) + } + #[cfg(not(feature = "sync"))] + { + (hash, Rc::new(f)) + } + }) + .collect(), + ) } /// Does a certain function exist in the `FunctionsLib`? pub fn has_function(&self, name: &str, params: usize) -> bool { - self.0.binary_search_by(|f| f.compare(name, params)).is_ok() + self.contains_key(&calc_fn_def(name, params)) } /// Get a function definition from the `FunctionsLib`. pub fn get_function(&self, name: &str, params: usize) -> Option<&FnDef> { - self.0 - .binary_search_by(|f| f.compare(name, params)) - .ok() - .map(|n| self.0[n].as_ref()) + self.get(&calc_fn_def(name, params)).map(|f| f.as_ref()) } /// Merge another `FunctionsLib` into this `FunctionsLib`. pub fn merge(&self, other: &Self) -> Self { @@ -177,16 +169,8 @@ impl FunctionsLib { } else { let mut functions = self.clone(); - other.iter().cloned().for_each(|fn_def| { - if let Some((n, _)) = functions - .iter() - .enumerate() - .find(|(_, f)| f.name == fn_def.name && f.params.len() == fn_def.params.len()) - { - functions[n] = fn_def; - } else { - functions.push(fn_def); - } + other.iter().for_each(|(hash, fn_def)| { + functions.insert(*hash, fn_def.clone()); }); functions @@ -196,9 +180,9 @@ impl FunctionsLib { impl Deref for FunctionsLib { #[cfg(feature = "sync")] - type Target = Vec>; + type Target = HashMap>; #[cfg(not(feature = "sync"))] - type Target = Vec>; + type Target = HashMap>; fn deref(&self) -> &Self::Target { &self.0 @@ -207,11 +191,11 @@ impl Deref for FunctionsLib { impl DerefMut for FunctionsLib { #[cfg(feature = "sync")] - fn deref_mut(&mut self) -> &mut Vec> { + fn deref_mut(&mut self) -> &mut HashMap> { &mut self.0 } #[cfg(not(feature = "sync"))] - fn deref_mut(&mut self) -> &mut Vec> { + fn deref_mut(&mut self) -> &mut HashMap> { &mut self.0 } } @@ -349,6 +333,13 @@ pub(crate) fn calc_fn_spec(fn_name: &str, params: impl Iterator) s.finish() } +pub(crate) fn calc_fn_def(fn_name: &str, params: usize) -> u64 { + let mut s = DefaultHasher::new(); + fn_name.hash(&mut s); + params.hash(&mut s); + s.finish() +} + impl Engine { /// Create a new `Engine` pub fn new() -> Self { diff --git a/src/parser.rs b/src/parser.rs index c8331d35..fe7b3191 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,7 +1,7 @@ //! Main module defining the lexer and parser. use crate::any::{Dynamic, Union}; -use crate::engine::{Engine, FunctionsLib}; +use crate::engine::{calc_fn_def, Engine, FunctionsLib}; use crate::error::{LexError, ParseError, ParseErrorType}; use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::scope::{EntryType as ScopeEntryType, Scope}; @@ -1787,9 +1787,9 @@ pub fn parse_global_expr<'a>( /// Parse the global level statements. fn parse_global_level<'a>( input: &mut Peekable>, -) -> Result<(Vec, Vec), ParseError> { +) -> Result<(Vec, HashMap), ParseError> { let mut statements = Vec::::new(); - let mut functions = Vec::::new(); + let mut functions = HashMap::::new(); while input.peek().is_some() { // Collect all the function definitions @@ -1797,13 +1797,7 @@ fn parse_global_level<'a>( { if matches!(input.peek().expect("should not be None"), (Token::Fn, _)) { let f = parse_fn(input, true)?; - - // Ensure list is sorted - match functions.binary_search_by(|fn_def| fn_def.compare(&f.name, f.params.len())) { - Ok(n) => functions[n] = f, // Override previous definition - Err(n) => functions.insert(n, f), // New function definition - } - + functions.insert(calc_fn_def(&f.name, f.params.len()), f); continue; } } @@ -1849,9 +1843,10 @@ pub fn parse<'a>( ) -> Result { let (statements, functions) = parse_global_level(input)?; + let fn_lib = functions.into_iter().map(|(_, v)| v).collect(); Ok( // Optimize AST - optimize_into_ast(engine, scope, statements, functions, optimization_level), + optimize_into_ast(engine, scope, statements, fn_lib, optimization_level), ) } diff --git a/src/scope.rs b/src/scope.rs index 3da54404..a9fd579c 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -397,13 +397,6 @@ impl<'a> Scope<'a> { &mut entry.value } - /// Get a mutable reference to an entry in the Scope and downcast it to a specific type - pub(crate) fn get_mut_by_type(&mut self, key: EntryRef) -> &mut T { - self.get_mut(key) - .downcast_mut::() - .expect("wrong type cast") - } - /// Get an iterator to entries in the Scope. pub fn iter(&self) -> impl Iterator { self.0.iter().rev() // Always search a Scope in reverse order From 5d9a99cefc415a30c46cb3a225ef4e04ae980873 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 17 Apr 2020 19:00:52 +0800 Subject: [PATCH 18/34] Refine postfix operators handling. --- src/engine.rs | 2 +- src/parser.rs | 165 ++++++++++++++++++++++++------------------------- tests/stack.rs | 4 +- 3 files changed, 83 insertions(+), 88 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index b0b44a38..e91489a4 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -45,7 +45,7 @@ type IteratorFn = dyn Fn(&Dynamic) -> Box> + Send + type IteratorFn = dyn Fn(&Dynamic) -> Box>; #[cfg(debug_assertions)] -pub const MAX_CALL_STACK_DEPTH: usize = 32; +pub const MAX_CALL_STACK_DEPTH: usize = 28; #[cfg(not(debug_assertions))] pub const MAX_CALL_STACK_DEPTH: usize = 256; diff --git a/src/parser.rs b/src/parser.rs index fe7b3191..68fda4ef 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -470,10 +470,42 @@ impl Expr { _ => false, } } + + /// Is a particular token allowed as a postfix operator to this expression? + pub fn is_valid_postfix(&self, token: &Token) -> bool { + match self { + Expr::IntegerConstant(_, _) + | Expr::FloatConstant(_, _) + | Expr::CharConstant(_, _) + | Expr::In(_, _, _) + | Expr::And(_, _, _) + | Expr::Or(_, _, _) + | Expr::True(_) + | Expr::False(_) + | Expr::Unit(_) => false, + + Expr::StringConstant(_, _) + | Expr::Stmt(_, _) + | Expr::FunctionCall(_, _, _, _) + | Expr::Assignment(_, _, _) + | Expr::Dot(_, _, _) + | Expr::Index(_, _, _) + | Expr::Array(_, _) + | Expr::Map(_, _) => match token { + Token::LeftBracket => true, + _ => false, + }, + + Expr::Variable(_, _) | Expr::Property(_, _) => match token { + Token::LeftBracket | Token::LeftParen => true, + _ => false, + }, + } + } } /// Consume a particular token, checking that it is the expected one. -fn eat_token(input: &mut Peekable, token: Token) { +fn eat_token(input: &mut Peekable, token: Token) -> Position { if let Some((t, pos)) = input.next() { if t != token { panic!( @@ -483,6 +515,7 @@ fn eat_token(input: &mut Peekable, token: Token) { pos ); } + pos } else { panic!("expecting {} but already EOF", token.syntax()); } @@ -568,7 +601,9 @@ fn parse_call_expr<'a, S: Into> + Display>( eat_token(input, Token::RightParen); return Ok(Expr::FunctionCall(id.into(), args_expr_list, None, begin)); } - Some((Token::Comma, _)) => eat_token(input, Token::Comma), + Some((Token::Comma, _)) => { + eat_token(input, Token::Comma); + } Some((_, pos)) => { return Err(PERR::MissingToken( ",".into(), @@ -706,38 +741,6 @@ fn parse_index_expr<'a>( } } -/// Parse an expression that begins with an identifier. -fn parse_ident_expr<'a, S: Into> + Display>( - id: S, - input: &mut Peekable>, - begin: Position, - allow_stmt_expr: bool, -) -> Result { - match input.peek() { - // id(...) - function call - Some((Token::LeftParen, _)) => { - eat_token(input, Token::LeftParen); - parse_call_expr(id, input, begin, allow_stmt_expr) - } - // id[...] - indexing - #[cfg(not(feature = "no_index"))] - Some((Token::LeftBracket, pos)) => { - let pos = *pos; - eat_token(input, Token::LeftBracket); - parse_index_expr( - Box::new(Expr::Variable(id.into(), begin)), - input, - pos, - allow_stmt_expr, - ) - } - // id - variable - Some(_) => Ok(Expr::Variable(id.into(), begin)), - // {EOF} - None => Ok(Expr::Variable(id.into(), begin)), - } -} - /// Parse an array literal. fn parse_array_literal<'a>( input: &mut Peekable>, @@ -829,7 +832,9 @@ fn parse_map_literal<'a>( map.push((name, expr, pos)); match input.peek() { - Some((Token::Comma, _)) => eat_token(input, Token::Comma), + Some((Token::Comma, _)) => { + eat_token(input, Token::Comma); + } Some((Token::RightBrace, _)) => { eat_token(input, Token::RightBrace); break; @@ -881,51 +886,45 @@ fn parse_primary<'a>( None => return Err(PERR::UnexpectedEOF.into_err_eof()), }; - let mut can_be_indexed = false; - let mut root_expr = match token { - (Token::IntegerConstant(x), pos) => Ok(Expr::IntegerConstant(x, pos)), - (Token::FloatConstant(x), pos) => Ok(Expr::FloatConstant(x, pos)), - (Token::CharConstant(c), pos) => Ok(Expr::CharConstant(c, pos)), - (Token::StringConst(s), pos) => { - can_be_indexed = true; - Ok(Expr::StringConstant(s.into(), pos)) - } - (Token::Identifier(s), pos) => { - can_be_indexed = true; - parse_ident_expr(s, input, pos, allow_stmt_expr) - } - (Token::LeftParen, pos) => { - can_be_indexed = true; - parse_paren_expr(input, pos, allow_stmt_expr) - } + (Token::IntegerConstant(x), pos) => Expr::IntegerConstant(x, pos), + (Token::FloatConstant(x), pos) => Expr::FloatConstant(x, pos), + (Token::CharConstant(c), pos) => Expr::CharConstant(c, pos), + (Token::StringConst(s), pos) => Expr::StringConstant(s.into(), pos), + (Token::Identifier(s), pos) => Expr::Variable(s.into(), pos), + (Token::LeftParen, pos) => parse_paren_expr(input, pos, allow_stmt_expr)?, #[cfg(not(feature = "no_index"))] - (Token::LeftBracket, pos) => { - can_be_indexed = true; - parse_array_literal(input, pos, allow_stmt_expr) - } + (Token::LeftBracket, pos) => parse_array_literal(input, pos, allow_stmt_expr)?, #[cfg(not(feature = "no_object"))] - (Token::MapStart, pos) => { - can_be_indexed = true; - parse_map_literal(input, pos, allow_stmt_expr) - } - (Token::True, pos) => Ok(Expr::True(pos)), - (Token::False, pos) => Ok(Expr::False(pos)), - (Token::LexError(err), pos) => Err(PERR::BadInput(err.to_string()).into_err(pos)), + (Token::MapStart, pos) => parse_map_literal(input, pos, allow_stmt_expr)?, + (Token::True, pos) => Expr::True(pos), + (Token::False, pos) => Expr::False(pos), + (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), (token, pos) => { - Err(PERR::BadInput(format!("Unexpected '{}'", token.syntax())).into_err(pos)) + return Err(PERR::BadInput(format!("Unexpected '{}'", token.syntax())).into_err(pos)) } - }?; + }; - #[cfg(feature = "no_index")] - let can_be_indexed = false; + // Tail processing all possible postfix operators + while let Some((token, _)) = input.peek() { + if !root_expr.is_valid_postfix(token) { + break; + } - if can_be_indexed { - // Tail processing all possible indexing - while let Some((Token::LeftBracket, pos)) = input.peek() { - let pos = *pos; - eat_token(input, Token::LeftBracket); - root_expr = parse_index_expr(Box::new(root_expr), input, pos, allow_stmt_expr)?; + let (token, pos) = input.next().unwrap(); + + root_expr = match (root_expr, token) { + // Function call + (Expr::Variable(id, pos), Token::LeftParen) + | (Expr::Property(id, pos), Token::LeftParen) => { + parse_call_expr(id, input, pos, allow_stmt_expr)? + } + // Indexing + (expr, Token::LeftBracket) => { + parse_index_expr(Box::new(expr), input, pos, allow_stmt_expr)? + } + // Unknown postfix operator + (expr, token) => panic!("unknown postfix operator {:?} for {:?}", token, expr), } } @@ -947,9 +946,8 @@ fn parse_unary<'a>( )) } // -expr - Some((Token::UnaryMinus, pos)) => { - let pos = *pos; - eat_token(input, Token::UnaryMinus); + Some((Token::UnaryMinus, _)) => { + let pos = eat_token(input, Token::UnaryMinus); match parse_unary(input, allow_stmt_expr)? { // Negative integer @@ -985,9 +983,8 @@ fn parse_unary<'a>( parse_unary(input, allow_stmt_expr) } // !expr - Some((Token::Bang, pos)) => { - let pos = *pos; - eat_token(input, Token::Bang); + Some((Token::Bang, _)) => { + let pos = eat_token(input, Token::Bang); Ok(Expr::FunctionCall( "!".into(), vec![parse_primary(input, allow_stmt_expr)?], @@ -1637,14 +1634,12 @@ fn parse_stmt<'a>( (Token::Loop, _) => parse_loop(input, allow_stmt_expr), (Token::For, _) => parse_for(input, allow_stmt_expr), - (Token::Continue, pos) if breakable => { - let pos = *pos; - eat_token(input, Token::Continue); + (Token::Continue, _) if breakable => { + let pos = eat_token(input, Token::Continue); Ok(Stmt::Continue(pos)) } - (Token::Break, pos) if breakable => { - let pos = *pos; - eat_token(input, Token::Break); + (Token::Break, _) if breakable => { + let pos = eat_token(input, Token::Break); Ok(Stmt::Break(pos)) } (Token::Continue, pos) | (Token::Break, pos) => Err(PERR::LoopBreak.into_err(*pos)), diff --git a/tests/stack.rs b/tests/stack.rs index 79a16221..8efc759e 100644 --- a/tests/stack.rs +++ b/tests/stack.rs @@ -9,10 +9,10 @@ fn test_stack_overflow() -> Result<(), EvalAltResult> { engine.eval::( r" fn foo(n) { if n == 0 { 0 } else { n + foo(n-1) } } - foo(30) + foo(25) ", )?, - 465 + 325 ); match engine.eval::<()>( From c5f66e932ba6a161bb426b8e3b022f98901a3673 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 17 Apr 2020 20:01:41 +0800 Subject: [PATCH 19/34] Simplify parsing by introducing an EOF token. --- examples/repl.rs | 9 -- examples/rhai_runner.rs | 8 +- src/error.rs | 11 +- src/parser.rs | 344 ++++++++++++++++++---------------------- src/token.rs | 61 +++---- 5 files changed, 189 insertions(+), 244 deletions(-) diff --git a/examples/repl.rs b/examples/repl.rs index b6a0c9c4..0b20988a 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -14,7 +14,6 @@ fn print_error(input: &str, err: EvalAltResult) { let line_no = if lines.len() > 1 { match err.position() { p if p.is_none() => "".to_string(), - p if p.is_eof() => format!("{}: ", lines.len()), p => format!("{}: ", p.line().unwrap()), } } else { @@ -25,15 +24,7 @@ fn print_error(input: &str, err: EvalAltResult) { let pos = err.position(); let pos_text = format!(" ({})", pos); - let pos = if pos.is_eof() { - let last = lines[lines.len() - 1]; - Position::new(lines.len(), last.len() + 1) - } else { - pos - }; - match pos { - p if p.is_eof() => panic!("should not be EOF"), p if p.is_none() => { // No position println!("{}", err); diff --git a/examples/rhai_runner.rs b/examples/rhai_runner.rs index 9a5cfe59..c2edf508 100644 --- a/examples/rhai_runner.rs +++ b/examples/rhai_runner.rs @@ -23,15 +23,9 @@ fn eprint_error(input: &str, err: EvalAltResult) { let lines: Vec<_> = input.split('\n').collect(); // Print error - let pos = if err.position().is_eof() { - let last = lines[lines.len() - 1]; - Position::new(lines.len(), last.len() + 1) - } else { - err.position() - }; + let pos = err.position(); match pos { - p if p.is_eof() => panic!("should not be EOF"), p if p.is_none() => { // No position eprintln!("{}", err); diff --git a/src/error.rs b/src/error.rs index 077ebd22..b3e6be13 100644 --- a/src/error.rs +++ b/src/error.rs @@ -110,11 +110,6 @@ impl ParseErrorType { pub(crate) fn into_err(self, pos: Position) -> ParseError { ParseError(self, pos) } - - /// Make a `ParseError` using the current type and EOF position. - pub(crate) fn into_err_eof(self) -> ParseError { - ParseError(self, Position::eof()) - } } /// Error when parsing a script. @@ -209,13 +204,11 @@ impl fmt::Display for ParseError { _ => write!(f, "{}", self.desc())?, } - if !self.1.is_eof() { - write!(f, " ({})", self.1) - } else if !self.1.is_none() { + if !self.1.is_none() { // Do not write any position if None Ok(()) } else { - write!(f, " at the end of the script but there is no more input") + write!(f, " ({})", self.1) } } } diff --git a/src/parser.rs b/src/parser.rs index 68fda4ef..b873e076 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -523,15 +523,12 @@ fn eat_token(input: &mut Peekable, token: Token) -> Position { /// Match a particular token, consuming it if matched. fn match_token(input: &mut Peekable, token: Token) -> Result { - if let Some((t, _)) = input.peek() { - if *t == token { - eat_token(input, token); - Ok(true) - } else { - Ok(false) - } + let (t, _) = input.peek().unwrap(); + if *t == token { + eat_token(input, token); + Ok(true) } else { - Err(PERR::UnexpectedEOF.into_err_eof()) + Ok(false) } } @@ -541,21 +538,21 @@ fn parse_paren_expr<'a>( begin: Position, allow_stmt_expr: bool, ) -> Result { - const MISSING_RPAREN: &str = "for a matching ( in this expression"; - if match_token(input, Token::RightParen)? { return Ok(Expr::Unit(begin)); } let expr = parse_expr(input, allow_stmt_expr)?; - match input.next() { + match input.next().unwrap() { // ( xxx ) - Some((Token::RightParen, _)) => Ok(expr), + (Token::RightParen, _) => Ok(expr), // ( xxx ??? - Some((_, pos)) => Err(PERR::MissingToken(")".into(), MISSING_RPAREN.into()).into_err(pos)), - // ( xxx - None => Err(PERR::MissingToken(")".into(), MISSING_RPAREN.into()).into_err_eof()), + (_, pos) => Err(PERR::MissingToken( + ")".into(), + "for a matching ( in this expression".into(), + ) + .into_err(pos)), } } @@ -568,17 +565,17 @@ fn parse_call_expr<'a, S: Into> + Display>( ) -> Result { let mut args_expr_list = Vec::new(); - match input.peek() { + match input.peek().unwrap() { //id {EOF} - None => { + (Token::EOF, pos) => { return Err(PERR::MissingToken( ")".into(), format!("to close the arguments list of this function call '{}'", id), ) - .into_err_eof()) + .into_err(*pos)) } // id() - Some((Token::RightParen, _)) => { + (Token::RightParen, _) => { eat_token(input, Token::RightParen); return Ok(Expr::FunctionCall(id.into(), args_expr_list, None, begin)); } @@ -589,22 +586,22 @@ fn parse_call_expr<'a, S: Into> + Display>( loop { args_expr_list.push(parse_expr(input, allow_stmt_expr)?); - match input.peek() { - None => { + match input.peek().unwrap() { + (Token::EOF, pos) => { return Err(PERR::MissingToken( ")".into(), format!("to close the arguments list of this function call '{}'", id), ) - .into_err_eof()) + .into_err(*pos)) } - Some((Token::RightParen, _)) => { + (Token::RightParen, _) => { eat_token(input, Token::RightParen); return Ok(Expr::FunctionCall(id.into(), args_expr_list, None, begin)); } - Some((Token::Comma, _)) => { + (Token::Comma, _) => { eat_token(input, Token::Comma); } - Some((_, pos)) => { + (_, pos) => { return Err(PERR::MissingToken( ",".into(), format!("to separate the arguments to function call '{}'", id), @@ -727,17 +724,16 @@ fn parse_index_expr<'a>( } // Check if there is a closing bracket - const MISSING_RBRACKET: &str = "for a matching [ in this index expression"; - - match input.peek() { - Some((Token::RightBracket, _)) => { + match input.peek().unwrap() { + (Token::RightBracket, _) => { eat_token(input, Token::RightBracket); Ok(Expr::Index(lhs, Box::new(idx_expr), pos)) } - Some((_, pos)) => { - Err(PERR::MissingToken("]".into(), MISSING_RBRACKET.into()).into_err(*pos)) - } - None => Err(PERR::MissingToken("]".into(), MISSING_RBRACKET.into()).into_err_eof()), + (_, pos) => Err(PERR::MissingToken( + "]".into(), + "for a matching [ in this index expression".into(), + ) + .into_err(*pos)), } } @@ -750,28 +746,28 @@ fn parse_array_literal<'a>( let mut arr = Vec::new(); if !match_token(input, Token::RightBracket)? { - while input.peek().is_some() { + while !input.peek().unwrap().0.is_eof() { arr.push(parse_expr(input, allow_stmt_expr)?); - match input.peek() { - Some((Token::Comma, _)) => eat_token(input, Token::Comma), - Some((Token::RightBracket, _)) => { + match input.peek().unwrap() { + (Token::EOF, pos) => { + return Err( + PERR::MissingToken("]".into(), "to end this array literal".into()) + .into_err(*pos), + ) + } + (Token::Comma, _) => eat_token(input, Token::Comma), + (Token::RightBracket, _) => { eat_token(input, Token::RightBracket); break; } - Some((_, pos)) => { + (_, pos) => { return Err(PERR::MissingToken( ",".into(), "to separate the items of this array literal".into(), ) .into_err(*pos)) } - None => { - return Err( - PERR::MissingToken("]".into(), "to end this array literal".into()) - .into_err_eof(), - ) - } }; } } @@ -788,24 +784,24 @@ fn parse_map_literal<'a>( let mut map = Vec::new(); if !match_token(input, Token::RightBrace)? { - while input.peek().is_some() { + while !input.peek().unwrap().0.is_eof() { const MISSING_RBRACE: &str = "to end this object map literal"; - let (name, pos) = match input.next() { - Some((Token::Identifier(s), pos)) => (s, pos), - Some((Token::StringConst(s), pos)) => (s, pos), - Some((_, pos)) if map.is_empty() => { + let (name, pos) = match input.next().unwrap() { + (Token::Identifier(s), pos) => (s, pos), + (Token::StringConst(s), pos) => (s, pos), + (_, pos) if map.is_empty() => { return Err(PERR::MissingToken("}".into(), MISSING_RBRACE.into()).into_err(pos)) } - Some((_, pos)) => return Err(PERR::PropertyExpected.into_err(pos)), - None => { - return Err(PERR::MissingToken("}".into(), MISSING_RBRACE.into()).into_err_eof()) + (Token::EOF, pos) => { + return Err(PERR::MissingToken("}".into(), MISSING_RBRACE.into()).into_err(pos)) } + (_, pos) => return Err(PERR::PropertyExpected.into_err(pos)), }; - match input.next() { - Some((Token::Colon, _)) => (), - Some((_, pos)) => { + match input.next().unwrap() { + (Token::Colon, _) => (), + (_, pos) => { return Err(PERR::MissingToken( ":".into(), format!( @@ -815,43 +811,30 @@ fn parse_map_literal<'a>( ) .into_err(pos)) } - None => { - return Err(PERR::MissingToken( - ":".into(), - format!( - "to follow the property '{}' in this object map literal", - name - ), - ) - .into_err_eof()) - } }; let expr = parse_expr(input, allow_stmt_expr)?; map.push((name, expr, pos)); - match input.peek() { - Some((Token::Comma, _)) => { + match input.peek().unwrap() { + (Token::Comma, _) => { eat_token(input, Token::Comma); } - Some((Token::RightBrace, _)) => { + (Token::RightBrace, _) => { eat_token(input, Token::RightBrace); break; } - Some((Token::Identifier(_), pos)) => { + (Token::Identifier(_), pos) => { return Err(PERR::MissingToken( ",".into(), "to separate the items of this object map literal".into(), ) .into_err(*pos)) } - Some((_, pos)) => { + (_, pos) => { return Err(PERR::MissingToken("}".into(), MISSING_RBRACE.into()).into_err(*pos)) } - None => { - return Err(PERR::MissingToken("}".into(), MISSING_RBRACE.into()).into_err_eof()) - } } } } @@ -875,38 +858,40 @@ fn parse_primary<'a>( input: &mut Peekable>, allow_stmt_expr: bool, ) -> Result { - let token = match input.peek() { + let (token, pos) = match input.peek().unwrap() { // { - block statement as expression - Some((Token::LeftBrace, pos)) if allow_stmt_expr => { + (Token::LeftBrace, pos) if allow_stmt_expr => { let pos = *pos; return parse_block(input, false, allow_stmt_expr) .map(|block| Expr::Stmt(Box::new(block), pos)); } - Some(_) => input.next().expect("should be a token"), - None => return Err(PERR::UnexpectedEOF.into_err_eof()), + (Token::EOF, pos) => return Err(PERR::UnexpectedEOF.into_err(*pos)), + _ => input.next().unwrap(), }; let mut root_expr = match token { - (Token::IntegerConstant(x), pos) => Expr::IntegerConstant(x, pos), - (Token::FloatConstant(x), pos) => Expr::FloatConstant(x, pos), - (Token::CharConstant(c), pos) => Expr::CharConstant(c, pos), - (Token::StringConst(s), pos) => Expr::StringConstant(s.into(), pos), - (Token::Identifier(s), pos) => Expr::Variable(s.into(), pos), - (Token::LeftParen, pos) => parse_paren_expr(input, pos, allow_stmt_expr)?, + Token::IntegerConstant(x) => Expr::IntegerConstant(x, pos), + Token::FloatConstant(x) => Expr::FloatConstant(x, pos), + Token::CharConstant(c) => Expr::CharConstant(c, pos), + Token::StringConst(s) => Expr::StringConstant(s.into(), pos), + Token::Identifier(s) => Expr::Variable(s.into(), pos), + Token::LeftParen => parse_paren_expr(input, pos, allow_stmt_expr)?, #[cfg(not(feature = "no_index"))] - (Token::LeftBracket, pos) => parse_array_literal(input, pos, allow_stmt_expr)?, + Token::LeftBracket => parse_array_literal(input, pos, allow_stmt_expr)?, #[cfg(not(feature = "no_object"))] - (Token::MapStart, pos) => parse_map_literal(input, pos, allow_stmt_expr)?, - (Token::True, pos) => Expr::True(pos), - (Token::False, pos) => Expr::False(pos), - (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), - (token, pos) => { + Token::MapStart => parse_map_literal(input, pos, allow_stmt_expr)?, + Token::True => Expr::True(pos), + Token::False => Expr::False(pos), + Token::LexError(err) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), + token => { return Err(PERR::BadInput(format!("Unexpected '{}'", token.syntax())).into_err(pos)) } }; // Tail processing all possible postfix operators - while let Some((token, _)) = input.peek() { + loop { + let (token, _) = input.peek().unwrap(); + if !root_expr.is_valid_postfix(token) { break; } @@ -936,9 +921,9 @@ fn parse_unary<'a>( input: &mut Peekable>, allow_stmt_expr: bool, ) -> Result { - match input.peek() { + match input.peek().unwrap() { // If statement is allowed to act as expressions - Some((Token::If, pos)) => { + (Token::If, pos) => { let pos = *pos; Ok(Expr::Stmt( Box::new(parse_if(input, false, allow_stmt_expr)?), @@ -946,7 +931,7 @@ fn parse_unary<'a>( )) } // -expr - Some((Token::UnaryMinus, _)) => { + (Token::UnaryMinus, _) => { let pos = eat_token(input, Token::UnaryMinus); match parse_unary(input, allow_stmt_expr)? { @@ -978,12 +963,12 @@ fn parse_unary<'a>( } } // +expr - Some((Token::UnaryPlus, _)) => { + (Token::UnaryPlus, _) => { eat_token(input, Token::UnaryPlus); parse_unary(input, allow_stmt_expr) } // !expr - Some((Token::Bang, _)) => { + (Token::Bang, _) => { let pos = eat_token(input, Token::Bang); Ok(Expr::FunctionCall( "!".into(), @@ -992,10 +977,10 @@ fn parse_unary<'a>( pos, )) } - // All other tokens - Some(_) => parse_primary(input, allow_stmt_expr), // {EOF} - None => Err(PERR::UnexpectedEOF.into_err_eof()), + (Token::EOF, pos) => Err(PERR::UnexpectedEOF.into_err(*pos)), + // All other tokens + _ => parse_primary(input, allow_stmt_expr), } } @@ -1378,12 +1363,11 @@ fn ensure_not_statement_expr<'a>( input: &mut Peekable>, type_name: &str, ) -> Result<(), ParseError> { - match input - .peek() - .ok_or_else(|| PERR::ExprExpected(type_name.to_string()).into_err_eof())? - { + match input.peek().unwrap() { // Disallow statement expressions - (Token::LeftBrace, pos) => Err(PERR::ExprExpected(type_name.to_string()).into_err(*pos)), + (Token::LeftBrace, pos) | (Token::EOF, pos) => { + Err(PERR::ExprExpected(type_name.to_string()).into_err(*pos)) + } // No need to check for others at this time - leave it for the expr parser _ => Ok(()), } @@ -1462,27 +1446,26 @@ fn parse_for<'a>( eat_token(input, Token::For); // for name ... - let name = match input - .next() - .ok_or_else(|| PERR::VariableExpected.into_err_eof())? - { + let name = match input.next().unwrap() { // Variable name (Token::Identifier(s), _) => s, // Bad identifier (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), + // EOF + (Token::EOF, pos) => return Err(PERR::VariableExpected.into_err(pos)), // Not a variable name (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), }; // for name in ... - const ERROR_MSG: &str = "after the iteration variable"; - - match input.next() { - Some((Token::In, _)) => (), - Some((_, pos)) => { - return Err(PERR::MissingToken("in".into(), ERROR_MSG.into()).into_err(pos)) + match input.next().unwrap() { + (Token::In, _) => (), + (_, pos) => { + return Err( + PERR::MissingToken("in".into(), "after the iteration variable".into()) + .into_err(pos), + ) } - None => return Err(PERR::MissingToken("in".into(), ERROR_MSG.into()).into_err_eof()), } // for name in expr { body } @@ -1503,10 +1486,7 @@ fn parse_let<'a>( input.next(); // let name ... - let (name, pos) = match input - .next() - .ok_or_else(|| PERR::VariableExpected.into_err_eof())? - { + let (name, pos) = match input.next().unwrap() { (Token::Identifier(s), pos) => (s, pos), (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), @@ -1542,10 +1522,7 @@ fn parse_block<'a>( allow_stmt_expr: bool, ) -> Result { // Must start with { - let pos = match input - .next() - .ok_or_else(|| PERR::UnexpectedEOF.into_err_eof())? - { + let pos = match input.next().unwrap() { (Token::LeftBrace, pos) => pos, (_, pos) => { return Err( @@ -1565,35 +1542,28 @@ fn parse_block<'a>( statements.push(stmt); - match input.peek() { + match input.peek().unwrap() { // { ... stmt } - Some((Token::RightBrace, _)) => { + (Token::RightBrace, _) => { eat_token(input, Token::RightBrace); break; } // { ... stmt; - Some((Token::SemiColon, _)) if need_semicolon => { + (Token::SemiColon, _) if need_semicolon => { eat_token(input, Token::SemiColon); } // { ... { stmt } ; - Some((Token::SemiColon, _)) if !need_semicolon => (), + (Token::SemiColon, _) if !need_semicolon => (), // { ... { stmt } ??? - Some((_, _)) if !need_semicolon => (), + (_, _) if !need_semicolon => (), // { ... stmt ??? - error - Some((_, pos)) => { + (_, pos) => { // Semicolons are not optional between statements return Err( PERR::MissingToken(";".into(), "to terminate this statement".into()) .into_err(*pos), ); } - // {EOF} - None => { - return Err( - PERR::MissingToken("}".into(), "to end this statement block".into()) - .into_err_eof(), - ) - } } } @@ -1614,37 +1584,37 @@ fn parse_stmt<'a>( breakable: bool, allow_stmt_expr: bool, ) -> Result { - let token = match input.peek() { - Some(token) => token, - None => return Ok(Stmt::Noop(Position::eof())), + let (token, pos) = match input.peek().unwrap() { + (Token::EOF, pos) => return Ok(Stmt::Noop(*pos)), + x => x, }; match token { // Semicolon - empty statement - (Token::SemiColon, pos) => Ok(Stmt::Noop(*pos)), + Token::SemiColon => Ok(Stmt::Noop(*pos)), - (Token::LeftBrace, _) => parse_block(input, breakable, allow_stmt_expr), + Token::LeftBrace => parse_block(input, breakable, allow_stmt_expr), // fn ... #[cfg(not(feature = "no_function"))] - (Token::Fn, pos) => Err(PERR::WrongFnDefinition.into_err(*pos)), + Token::Fn => Err(PERR::WrongFnDefinition.into_err(*pos)), - (Token::If, _) => parse_if(input, breakable, allow_stmt_expr), - (Token::While, _) => parse_while(input, allow_stmt_expr), - (Token::Loop, _) => parse_loop(input, allow_stmt_expr), - (Token::For, _) => parse_for(input, allow_stmt_expr), + Token::If => parse_if(input, breakable, allow_stmt_expr), + Token::While => parse_while(input, allow_stmt_expr), + Token::Loop => parse_loop(input, allow_stmt_expr), + Token::For => parse_for(input, allow_stmt_expr), - (Token::Continue, _) if breakable => { + Token::Continue if breakable => { let pos = eat_token(input, Token::Continue); Ok(Stmt::Continue(pos)) } - (Token::Break, _) if breakable => { + Token::Break if breakable => { let pos = eat_token(input, Token::Break); Ok(Stmt::Break(pos)) } - (Token::Continue, pos) | (Token::Break, pos) => Err(PERR::LoopBreak.into_err(*pos)), + Token::Continue | Token::Break => Err(PERR::LoopBreak.into_err(*pos)), - (Token::Return, pos) | (Token::Throw, pos) => { + Token::Return | Token::Throw => { let pos = *pos; let return_type = match input.next() { @@ -1653,13 +1623,13 @@ fn parse_stmt<'a>( _ => panic!("token should be return or throw"), }; - match input.peek() { + match input.peek().unwrap() { // `return`/`throw` at {EOF} - None => Ok(Stmt::ReturnWithVal(None, return_type, Position::eof())), + (Token::EOF, pos) => Ok(Stmt::ReturnWithVal(None, return_type, *pos)), // `return;` or `throw;` - Some((Token::SemiColon, _)) => Ok(Stmt::ReturnWithVal(None, return_type, pos)), + (Token::SemiColon, _) => Ok(Stmt::ReturnWithVal(None, return_type, pos)), // `return` or `throw` with expression - Some((_, _)) => { + (_, _) => { let expr = parse_expr(input, allow_stmt_expr)?; let pos = expr.position(); Ok(Stmt::ReturnWithVal(Some(Box::new(expr)), return_type, pos)) @@ -1667,8 +1637,8 @@ fn parse_stmt<'a>( } } - (Token::Let, _) => parse_let(input, ScopeEntryType::Normal, allow_stmt_expr), - (Token::Const, _) => parse_let(input, ScopeEntryType::Constant, allow_stmt_expr), + Token::Let => parse_let(input, ScopeEntryType::Normal, allow_stmt_expr), + Token::Const => parse_let(input, ScopeEntryType::Constant, allow_stmt_expr), _ => parse_expr_stmt(input, allow_stmt_expr), } @@ -1681,16 +1651,14 @@ fn parse_fn<'a>( ) -> Result { let pos = input.next().expect("should be fn").1; - let name = match input.next() { - Some((Token::Identifier(s), _)) => s, - Some((_, pos)) => return Err(PERR::FnMissingName.into_err(pos)), - None => return Err(PERR::FnMissingName.into_err_eof()), + let name = match input.next().unwrap() { + (Token::Identifier(s), _) => s, + (_, pos) => return Err(PERR::FnMissingName.into_err(pos)), }; - match input.peek() { - Some((Token::LeftParen, _)) => eat_token(input, Token::LeftParen), - Some((_, pos)) => return Err(PERR::FnMissingParams(name).into_err(*pos)), - None => return Err(PERR::FnMissingParams(name.clone()).into_err_eof()), + match input.peek().unwrap() { + (Token::LeftParen, _) => eat_token(input, Token::LeftParen), + (_, pos) => return Err(PERR::FnMissingParams(name).into_err(*pos)), }; let mut params = Vec::new(); @@ -1700,24 +1668,18 @@ fn parse_fn<'a>( let sep_err = format!("to separate the parameters of function '{}'", name); loop { - match input.next() { - Some((Token::Identifier(s), pos)) => params.push((s, pos)), - Some((_, pos)) => return Err(PERR::MissingToken(")".into(), end_err).into_err(pos)), - None => { - return Err(PERR::MissingToken(")".into(), end_err.to_string()).into_err_eof()) - } + match input.next().unwrap() { + (Token::Identifier(s), pos) => params.push((s, pos)), + (_, pos) => return Err(PERR::MissingToken(")".into(), end_err).into_err(pos)), } - match input.next() { - Some((Token::RightParen, _)) => break, - Some((Token::Comma, _)) => (), - Some((Token::Identifier(_), pos)) => { + match input.next().unwrap() { + (Token::RightParen, _) => break, + (Token::Comma, _) => (), + (Token::Identifier(_), pos) => { return Err(PERR::MissingToken(",".into(), sep_err).into_err(pos)) } - Some((_, pos)) => return Err(PERR::MissingToken(",".into(), sep_err).into_err(pos)), - None => { - return Err(PERR::MissingToken(")".into(), end_err.to_string()).into_err_eof()) - } + (_, pos) => return Err(PERR::MissingToken(",".into(), sep_err).into_err(pos)), } } } @@ -1738,10 +1700,9 @@ fn parse_fn<'a>( })?; // Parse function body - let body = match input.peek() { - Some((Token::LeftBrace, _)) => parse_block(input, false, allow_stmt_expr)?, - Some((_, pos)) => return Err(PERR::FnMissingBody(name).into_err(*pos)), - None => return Err(PERR::FnMissingBody(name).into_err_eof()), + let body = match input.peek().unwrap() { + (Token::LeftBrace, _) => parse_block(input, false, allow_stmt_expr)?, + (_, pos) => return Err(PERR::FnMissingBody(name).into_err(*pos)), }; let params = params.into_iter().map(|(p, _)| p).collect(); @@ -1762,9 +1723,12 @@ pub fn parse_global_expr<'a>( ) -> Result { let expr = parse_expr(input, false)?; - if let Some((token, pos)) = input.peek() { + match input.peek().unwrap() { + (Token::EOF, _) => (), // Return error if the expression doesn't end - return Err(PERR::BadInput(format!("Unexpected '{}'", token.syntax())).into_err(*pos)); + (token, pos) => { + return Err(PERR::BadInput(format!("Unexpected '{}'", token.syntax())).into_err(*pos)) + } } Ok( @@ -1786,7 +1750,7 @@ fn parse_global_level<'a>( let mut statements = Vec::::new(); let mut functions = HashMap::::new(); - while input.peek().is_some() { + while !input.peek().unwrap().0.is_eof() { // Collect all the function definitions #[cfg(not(feature = "no_function"))] { @@ -1804,19 +1768,19 @@ fn parse_global_level<'a>( statements.push(stmt); - match input.peek() { + match input.peek().unwrap() { // EOF - None => break, + (Token::EOF, _) => break, // stmt ; - Some((Token::SemiColon, _)) if need_semicolon => { + (Token::SemiColon, _) if need_semicolon => { eat_token(input, Token::SemiColon); } // stmt ; - Some((Token::SemiColon, _)) if !need_semicolon => (), + (Token::SemiColon, _) if !need_semicolon => (), // { stmt } ??? - Some((_, _)) if !need_semicolon => (), + (_, _) if !need_semicolon => (), // stmt ??? - error - Some((_, pos)) => { + (_, pos) => { // Semicolons are not optional between statements return Err( PERR::MissingToken(";".into(), "to terminate this statement".into()) diff --git a/src/token.rs b/src/token.rs index 55563d0f..fd46d10f 100644 --- a/src/token.rs +++ b/src/token.rs @@ -22,9 +22,9 @@ type LERR = LexError; /// A location (line number + character position) in the input script. #[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)] pub struct Position { - /// Line number - 0 = none, MAX = EOF + /// Line number - 0 = none line: usize, - /// Character position - 0 = BOL, MAX = EOF + /// Character position - 0 = BOL pos: usize, } @@ -43,9 +43,9 @@ impl Position { } } - /// Get the line number (1-based), or `None` if no position or EOF. + /// Get the line number (1-based), or `None` if no position. pub fn line(&self) -> Option { - if self.is_none() || self.is_eof() { + if self.is_none() { None } else { Some(self.line) @@ -54,7 +54,7 @@ impl Position { /// Get the character position (1-based), or `None` if at beginning of a line. pub fn position(&self) -> Option { - if self.is_none() || self.is_eof() || self.pos == 0 { + if self.is_none() || self.pos == 0 { None } else { Some(self.pos) @@ -88,23 +88,10 @@ impl Position { Self { line: 0, pos: 0 } } - /// Create a `Position` at EOF. - pub(crate) fn eof() -> Self { - Self { - line: usize::MAX, - pos: usize::MAX, - } - } - /// Is there no `Position`? pub fn is_none(&self) -> bool { self.line == 0 && self.pos == 0 } - - /// Is the `Position` at EOF? - pub fn is_eof(&self) -> bool { - self.line == usize::MAX && self.pos == usize::MAX - } } impl Default for Position { @@ -115,9 +102,7 @@ impl Default for Position { impl fmt::Display for Position { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.is_eof() { - write!(f, "EOF") - } else if self.is_none() { + if self.is_none() { write!(f, "none") } else { write!(f, "line {}, position {}", self.line, self.pos) @@ -127,11 +112,7 @@ impl fmt::Display for Position { impl fmt::Debug for Position { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.is_eof() { - write!(f, "(EOF)") - } else { - write!(f, "({}:{})", self.line, self.pos) - } + write!(f, "({}:{})", self.line, self.pos) } } @@ -206,6 +187,7 @@ pub enum Token { ModuloAssign, PowerOfAssign, LexError(Box), + EOF, } impl Token { @@ -284,12 +266,23 @@ impl Token { PowerOfAssign => "~=", For => "for", In => "in", + EOF => "{EOF}", _ => panic!("operator should be match in outer scope"), }) .into(), } } + // Is this token EOF? + pub fn is_eof(&self) -> bool { + use Token::*; + + match self { + EOF => true, + _ => false, + } + } + // If another operator is after these, it's probably an unary operator // (not sure about fn name). pub fn is_next_unary(&self) -> bool { @@ -420,10 +413,13 @@ impl<'a> TokenIterator<'a> { fn get_next(&mut self) -> Option { loop { if self.streams.is_empty() { + // No more streams return None; } else if let Some(ch) = self.streams[0].next() { + // Next character in current stream return Some(ch); } else { + // Jump to the next stream let _ = self.streams.remove(0); } } @@ -432,10 +428,13 @@ impl<'a> TokenIterator<'a> { fn peek_next(&mut self) -> Option { loop { if self.streams.is_empty() { + // No more streams return None; } else if let Some(ch) = self.streams[0].peek() { + // Next character in current stream return Some(*ch); } else { + // Jump to the next stream let _ = self.streams.remove(0); } } @@ -466,10 +465,13 @@ impl<'a> TokenIterator<'a> { let mut escape = String::with_capacity(12); loop { - let next_char = self.get_next(); + let next_char = self + .get_next() + .ok_or((LERR::UnterminatedString, self.pos))?; + self.advance(); - match next_char.ok_or((LERR::UnterminatedString, Position::eof()))? { + match next_char { // \... '\\' if escape.is_empty() => { escape.push('\\'); @@ -956,7 +958,8 @@ impl<'a> TokenIterator<'a> { } } - None + self.advance(); + Some((Token::EOF, self.pos)) } } From 65d611b976756c5b0b1e2b9d6df62fe6acfb0a06 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 17 Apr 2020 20:08:41 +0800 Subject: [PATCH 20/34] FIX: no_float errors. --- src/parser.rs | 1 + src/token.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/parser.rs b/src/parser.rs index b873e076..d50a5a60 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -871,6 +871,7 @@ fn parse_primary<'a>( let mut root_expr = match token { Token::IntegerConstant(x) => Expr::IntegerConstant(x, pos), + #[cfg(not(feature = "no_float"))] Token::FloatConstant(x) => Expr::FloatConstant(x, pos), Token::CharConstant(c) => Expr::CharConstant(c, pos), Token::StringConst(s) => Expr::StringConstant(s.into(), pos), diff --git a/src/token.rs b/src/token.rs index fd46d10f..aa11a3c6 100644 --- a/src/token.rs +++ b/src/token.rs @@ -120,6 +120,7 @@ impl fmt::Debug for Position { #[derive(Debug, PartialEq, Clone)] pub enum Token { IntegerConstant(INT), + #[cfg(not(feature = "no_float"))] FloatConstant(FLOAT), Identifier(String), CharConstant(char), @@ -197,6 +198,7 @@ impl Token { match self { IntegerConstant(i) => i.to_string().into(), + #[cfg(not(feature = "no_float"))] FloatConstant(f) => f.to_string().into(), Identifier(s) => s.into(), CharConstant(c) => c.to_string().into(), From 4a80997d6c844c3c0edb83428aa022439301ad94 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 17 Apr 2020 20:54:42 +0800 Subject: [PATCH 21/34] Removed unused imports. --- examples/repl.rs | 2 +- examples/rhai_runner.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/repl.rs b/examples/repl.rs index 0b20988a..3813d75d 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -1,4 +1,4 @@ -use rhai::{Dynamic, Engine, EvalAltResult, Position, Scope, AST}; +use rhai::{Dynamic, Engine, EvalAltResult, Scope, AST}; #[cfg(not(feature = "no_optimize"))] use rhai::OptimizationLevel; diff --git a/examples/rhai_runner.rs b/examples/rhai_runner.rs index c2edf508..166e1c3e 100644 --- a/examples/rhai_runner.rs +++ b/examples/rhai_runner.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult, Position}; +use rhai::{Engine, EvalAltResult}; #[cfg(not(feature = "no_optimize"))] use rhai::OptimizationLevel; From f5fff828e89d811c24b85121afd8bbeba35c6b13 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 18 Apr 2020 00:14:33 +0800 Subject: [PATCH 22/34] Box errors to reduce return type footprint. --- examples/repl.rs | 2 +- src/api.rs | 30 ++++-- src/engine.rs | 244 ++++++++++++++++++++++++--------------------- src/error.rs | 4 +- src/fn_register.rs | 8 +- src/optimize.rs | 2 +- src/parser.rs | 56 +++++------ src/result.rs | 12 ++- 8 files changed, 198 insertions(+), 160 deletions(-) diff --git a/examples/repl.rs b/examples/repl.rs index 3813d75d..4c807814 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -130,7 +130,7 @@ fn main() { match engine .compile_with_scope(&scope, &script) - .map_err(EvalAltResult::ErrorParsing) + .map_err(|err| err.into()) .and_then(|r| { ast_u = r.clone(); diff --git a/src/api.rs b/src/api.rs index 90a918c7..f410b815 100644 --- a/src/api.rs +++ b/src/api.rs @@ -396,7 +396,7 @@ impl Engine { ) -> Result { let scripts = [script]; let stream = lex(&scripts); - parse(&mut stream.peekable(), self, scope, optimization_level) + parse(&mut stream.peekable(), self, scope, optimization_level).map_err(|err| *err) } /// Read the contents of a file into a string. @@ -592,7 +592,9 @@ impl Engine { ) -> Result { let scripts = [script]; let stream = lex(&scripts); + parse_global_expr(&mut stream.peekable(), self, scope, self.optimization_level) + .map_err(|err| *err) } /// Evaluate a script file. @@ -798,7 +800,10 @@ impl Engine { scope: &mut Scope, ast: &AST, ) -> Result { - let result = self.eval_ast_with_scope_raw(scope, ast)?; + let result = self + .eval_ast_with_scope_raw(scope, ast) + .map_err(|err| *err)?; + let return_type = self.map_type_name(result.type_name()); return result.try_cast::().ok_or_else(|| { @@ -810,13 +815,13 @@ impl Engine { &self, scope: &mut Scope, ast: &AST, - ) -> Result { + ) -> Result> { ast.0 .iter() .try_fold(Dynamic::from_unit(), |_, stmt| { self.eval_stmt(scope, Some(ast.1.as_ref()), stmt, 0) }) - .or_else(|err| match err { + .or_else(|err| match *err { EvalAltResult::Return(out, _) => Ok(out), _ => Err(err), }) @@ -875,11 +880,13 @@ impl Engine { .try_fold(Dynamic::from_unit(), |_, stmt| { self.eval_stmt(scope, Some(ast.1.as_ref()), stmt, 0) }) - .map(|_| ()) - .or_else(|err| match err { - EvalAltResult::Return(_, _) => Ok(()), - _ => Err(err), - }) + .map_or_else( + |err| match *err { + EvalAltResult::Return(_, _) => Ok(()), + err => Err(err), + }, + |_| Ok(()), + ) } /// Call a script function defined in an `AST` with multiple arguments. @@ -930,7 +937,10 @@ impl Engine { let fn_lib = Some(ast.1.as_ref()); let pos = Position::none(); - let result = self.call_fn_raw(Some(scope), fn_lib, name, &mut args, None, pos, 0)?; + let result = self + .call_fn_raw(Some(scope), fn_lib, name, &mut args, None, pos, 0) + .map_err(|err| *err)?; + let return_type = self.map_type_name(result.type_name()); return result diff --git a/src/engine.rs b/src/engine.rs index e91489a4..6c8f1b3e 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -35,9 +35,10 @@ pub type Map = HashMap; pub type FnCallArgs<'a> = [&'a mut Dynamic]; #[cfg(feature = "sync")] -pub type FnAny = dyn Fn(&mut FnCallArgs, Position) -> Result + Send + Sync; +pub type FnAny = + dyn Fn(&mut FnCallArgs, Position) -> Result> + Send + Sync; #[cfg(not(feature = "sync"))] -pub type FnAny = dyn Fn(&mut FnCallArgs, Position) -> Result; +pub type FnAny = dyn Fn(&mut FnCallArgs, Position) -> Result>; #[cfg(feature = "sync")] type IteratorFn = dyn Fn(&Dynamic) -> Box> + Send + Sync; @@ -244,7 +245,7 @@ pub struct Engine { /// Maximum levels of call-stack to prevent infinite recursion. /// - /// Defaults to 32 for debug builds and 256 for non-debug builds. + /// Defaults to 28 for debug builds and 256 for non-debug builds. pub(crate) max_call_stack_depth: usize, } @@ -398,10 +399,10 @@ impl Engine { def_val: Option<&Dynamic>, pos: Position, level: usize, - ) -> Result { + ) -> Result> { // Check for stack overflow if level > self.max_call_stack_depth { - return Err(EvalAltResult::ErrorStackOverflow(pos)); + return Err(Box::new(EvalAltResult::ErrorStackOverflow(pos))); } #[cfg(feature = "no_function")] @@ -427,10 +428,10 @@ impl Engine { // Evaluate the function at one higher level of call depth let result = self .eval_stmt(scope, fn_lib, &fn_def.body, level + 1) - .or_else(|err| match err { + .or_else(|err| match *err { // Convert return statement to return value EvalAltResult::Return(x, _) => Ok(x), - err => Err(err.set_position(pos)), + err => Err(Box::new(err.set_position(pos))), }); scope.rewind(scope_len); @@ -453,19 +454,23 @@ impl Engine { // Evaluate the function at one higher level of call depth return self .eval_stmt(&mut scope, fn_lib, &fn_def.body, level + 1) - .or_else(|err| match err { + .or_else(|err| match *err { // Convert return statement to return value EvalAltResult::Return(x, _) => Ok(x), - err => Err(err.set_position(pos)), + err => Err(Box::new(err.set_position(pos))), }); } } } // Argument must be a string - fn cast_to_string(r: &Dynamic, pos: Position) -> Result<&str, EvalAltResult> { - r.as_str() - .map_err(|type_name| EvalAltResult::ErrorMismatchOutputType(type_name.into(), pos)) + fn cast_to_string(r: &Dynamic, pos: Position) -> Result<&str, Box> { + r.as_str().map_err(|type_name| { + Box::new(EvalAltResult::ErrorMismatchOutputType( + type_name.into(), + pos, + )) + }) } // Search built-in's and external functions @@ -499,10 +504,10 @@ impl Engine { .unwrap_or_else(|| Dynamic::from_unit())), // Getter function not found - _ => Err(EvalAltResult::ErrorDotExpr( + _ => Err(Box::new(EvalAltResult::ErrorDotExpr( format!("- property '{}' unknown or write-only", prop), pos, - )), + ))), }; } @@ -517,10 +522,10 @@ impl Engine { } // Setter function not found - _ => Err(EvalAltResult::ErrorDotExpr( + _ => Err(Box::new(EvalAltResult::ErrorDotExpr( format!("- property '{}' unknown or read-only", prop), pos, - )), + ))), }; } @@ -536,10 +541,10 @@ impl Engine { .map(|name| self.map_type_name(name)) .collect(); - Err(EvalAltResult::ErrorFunctionNotFound( + Err(Box::new(EvalAltResult::ErrorFunctionNotFound( format!("{} ({})", fn_name, types_list.join(", ")), pos, - )) + ))) } /// Chain-evaluate a dot setter. @@ -550,7 +555,7 @@ impl Engine { target: Target, dot_rhs: &Expr, level: usize, - ) -> Result { + ) -> Result> { match dot_rhs { // xxx.fn_name(arg_expr_list) Expr::FunctionCall(fn_name, arg_expr_list, def_val, pos) => { @@ -589,10 +594,10 @@ impl Engine { } // Syntax error _ => { - return Err(EvalAltResult::ErrorDotExpr( + return Err(Box::new(EvalAltResult::ErrorDotExpr( "".to_string(), dot_rhs.position(), - )) + ))) } }; @@ -625,10 +630,10 @@ impl Engine { } // Syntax error _ => { - return Err(EvalAltResult::ErrorDotExpr( + return Err(Box::new(EvalAltResult::ErrorDotExpr( "".to_string(), dot_rhs.position(), - )) + ))) } }; @@ -638,17 +643,17 @@ impl Engine { }) } // Syntax error - _ => Err(EvalAltResult::ErrorDotExpr( + _ => Err(Box::new(EvalAltResult::ErrorDotExpr( "".to_string(), dot_lhs.position(), - )), + ))), }, // Syntax error - _ => Err(EvalAltResult::ErrorDotExpr( + _ => Err(Box::new(EvalAltResult::ErrorDotExpr( "".to_string(), dot_rhs.position(), - )), + ))), } } @@ -660,7 +665,7 @@ impl Engine { dot_lhs: &Expr, dot_rhs: &Expr, level: usize, - ) -> Result { + ) -> Result> { match dot_lhs { // id.??? Expr::Variable(id, pos) => { @@ -685,10 +690,10 @@ impl Engine { if let Some(src) = src { match src.typ { ScopeEntryType::Constant => { - return Err(EvalAltResult::ErrorAssignmentToConstant( + return Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( src.name.to_string(), idx_lhs.position(), - )); + ))); } ScopeEntryType::Normal => { @@ -718,10 +723,10 @@ impl Engine { scope: &'a Scope, id: &str, begin: Position, - ) -> Result<(ScopeSource<'a>, Dynamic), EvalAltResult> { + ) -> Result<(ScopeSource<'a>, Dynamic), Box> { scope .get(id) - .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin)) + .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(id.into(), begin))) } /// Get the value at the indexed position of a base type @@ -734,7 +739,7 @@ impl Engine { op_pos: Position, level: usize, only_index: bool, - ) -> Result<(Dynamic, IndexValue), EvalAltResult> { + ) -> Result<(Dynamic, IndexValue), Box> { let idx_pos = idx_expr.position(); let type_name = self.map_type_name(val.type_name()); @@ -758,9 +763,15 @@ impl Engine { IndexValue::from_num(index), ) }) - .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), index, idx_pos)) + .ok_or_else(|| { + Box::new(EvalAltResult::ErrorArrayBounds(arr.len(), index, idx_pos)) + }) } else { - Err(EvalAltResult::ErrorArrayBounds(arr.len(), index, idx_pos)) + Err(Box::new(EvalAltResult::ErrorArrayBounds( + arr.len(), + index, + idx_pos, + ))) } } @@ -792,27 +803,27 @@ impl Engine { .as_int() .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; + let num_chars = s.chars().count(); + if index >= 0 { s.chars() .nth(index as usize) .map(|ch| (Dynamic::from_char(ch), IndexValue::from_num(index))) .ok_or_else(|| { - EvalAltResult::ErrorStringBounds(s.chars().count(), index, idx_pos) + Box::new(EvalAltResult::ErrorStringBounds(num_chars, index, idx_pos)) }) } else { - Err(EvalAltResult::ErrorStringBounds( - s.chars().count(), - index, - idx_pos, - )) + Err(Box::new(EvalAltResult::ErrorStringBounds( + num_chars, index, idx_pos, + ))) } } // Error - cannot be indexed - _ => Err(EvalAltResult::ErrorIndexingType( + _ => Err(Box::new(EvalAltResult::ErrorIndexingType( type_name.to_string(), op_pos, - )), + ))), } } @@ -825,7 +836,7 @@ impl Engine { idx_expr: &Expr, op_pos: Position, level: usize, - ) -> Result<(Option>, IndexValue, Dynamic), EvalAltResult> { + ) -> Result<(Option>, IndexValue, Dynamic), Box> { match lhs { // id[idx_expr] Expr::Variable(id, _) => { @@ -873,7 +884,7 @@ impl Engine { src: ScopeSource, idx: IndexValue, new_val: (Dynamic, Position), - ) -> Result { + ) -> Result> { let target = scope.get_mut(src); match target.get_mut() { @@ -908,7 +919,7 @@ impl Engine { idx: IndexValue, new_val: Dynamic, pos: Position, - ) -> Result { + ) -> Result> { match target.get_mut() { Union::Array(arr) => { arr[idx.as_num()] = new_val; @@ -940,7 +951,7 @@ impl Engine { dot_rhs: &Expr, new_val: (&mut Dynamic, Position), level: usize, - ) -> Result { + ) -> Result> { match dot_rhs { // xxx.id Expr::Property(id, pos) => { @@ -970,10 +981,10 @@ impl Engine { } // All others - syntax error for setters chain - _ => Err(EvalAltResult::ErrorDotExpr( + _ => Err(Box::new(EvalAltResult::ErrorDotExpr( "for assignment".to_string(), *op_pos, - )), + ))), }, // xxx.lhs.{...} @@ -1022,24 +1033,24 @@ impl Engine { } // All others - syntax error for setters chain - _ => Err(EvalAltResult::ErrorDotExpr( + _ => Err(Box::new(EvalAltResult::ErrorDotExpr( "for assignment".to_string(), *op_pos, - )), + ))), }, // All others - syntax error for setters chain - _ => Err(EvalAltResult::ErrorDotExpr( + _ => Err(Box::new(EvalAltResult::ErrorDotExpr( "for assignment".to_string(), lhs.position(), - )), + ))), }, // Syntax error - _ => Err(EvalAltResult::ErrorDotExpr( + _ => Err(Box::new(EvalAltResult::ErrorDotExpr( "for assignment".to_string(), dot_rhs.position(), - )), + ))), } } @@ -1053,16 +1064,15 @@ impl Engine { new_val: (&mut Dynamic, Position), op_pos: Position, level: usize, - ) -> Result { + ) -> Result> { match dot_lhs { // id.??? Expr::Variable(id, pos) => { let (src, mut target) = Self::search_scope(scope, id, *pos)?; match src.typ { - ScopeEntryType::Constant => Err(EvalAltResult::ErrorAssignmentToConstant( - id.to_string(), - op_pos, + ScopeEntryType::Constant => Err(Box::new( + EvalAltResult::ErrorAssignmentToConstant(id.to_string(), op_pos), )), _ => { // Avoid referencing scope which is used below as mut @@ -1093,10 +1103,10 @@ impl Engine { if let Some(src) = src { match src.typ { ScopeEntryType::Constant => { - return Err(EvalAltResult::ErrorAssignmentToConstant( + return Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( src.name.to_string(), lhs.position(), - )); + ))); } ScopeEntryType::Normal => { @@ -1114,10 +1124,10 @@ impl Engine { } // Syntax error - _ => Err(EvalAltResult::ErrorDotExpr( + _ => Err(Box::new(EvalAltResult::ErrorDotExpr( "for assignment".to_string(), dot_lhs.position(), - )), + ))), } } @@ -1129,7 +1139,7 @@ impl Engine { lhs: &Expr, rhs: &Expr, level: usize, - ) -> Result { + ) -> Result> { let mut lhs_value = self.eval_expr(scope, fn_lib, lhs, level)?; let rhs_value = self.eval_expr(scope, fn_lib, rhs, level)?; @@ -1161,7 +1171,7 @@ impl Engine { Dynamic(Union::Char(c)) => { Ok(Dynamic::from_bool(rhs_value.contains_key(&c.to_string()))) } - _ => Err(EvalAltResult::ErrorInExpr(lhs.position())), + _ => Err(Box::new(EvalAltResult::ErrorInExpr(lhs.position()))), } } Dynamic(Union::Str(rhs_value)) => { @@ -1169,10 +1179,10 @@ impl Engine { match lhs_value { Dynamic(Union::Str(s)) => Ok(Dynamic::from_bool(rhs_value.contains(&s))), Dynamic(Union::Char(c)) => Ok(Dynamic::from_bool(rhs_value.contains(c))), - _ => Err(EvalAltResult::ErrorInExpr(lhs.position())), + _ => Err(Box::new(EvalAltResult::ErrorInExpr(lhs.position()))), } } - _ => Err(EvalAltResult::ErrorInExpr(rhs.position())), + _ => Err(Box::new(EvalAltResult::ErrorInExpr(rhs.position()))), } } @@ -1183,7 +1193,7 @@ impl Engine { fn_lib: Option<&FunctionsLib>, expr: &Expr, level: usize, - ) -> Result { + ) -> Result> { match expr { Expr::IntegerConstant(i, _) => Ok(Dynamic::from_int(*i)), #[cfg(not(feature = "no_float"))] @@ -1204,10 +1214,10 @@ impl Engine { // name = rhs Expr::Variable(name, pos) => match scope.get(name) { None => { - return Err(EvalAltResult::ErrorVariableNotFound( + return Err(Box::new(EvalAltResult::ErrorVariableNotFound( name.to_string(), *pos, - )) + ))) } Some(( @@ -1231,10 +1241,10 @@ impl Engine { .. }, _, - )) => Err(EvalAltResult::ErrorAssignmentToConstant( + )) => Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( name.to_string(), *op_pos, - )), + ))), }, // idx_lhs[idx_expr] = rhs @@ -1244,15 +1254,15 @@ impl Engine { self.eval_index_expr(scope, fn_lib, idx_lhs, idx_expr, *op_pos, level)?; match src.map(|x| x.typ) { - None => Err(EvalAltResult::ErrorAssignmentToUnknownLHS( + None => Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS( idx_lhs.position(), - )), + ))), Some(ScopeEntryType::Constant) => { - Err(EvalAltResult::ErrorAssignmentToConstant( + Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( src.unwrap().name.to_string(), idx_lhs.position(), - )) + ))) } Some(ScopeEntryType::Normal) => Ok(Self::update_indexed_var_in_scope( @@ -1272,13 +1282,17 @@ impl Engine { } // Error assignment to constant - expr if expr.is_constant() => Err(EvalAltResult::ErrorAssignmentToConstant( - expr.get_constant_str(), - lhs.position(), - )), + expr if expr.is_constant() => { + Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( + expr.get_constant_str(), + lhs.position(), + ))) + } // Syntax error - _ => Err(EvalAltResult::ErrorAssignmentToUnknownLHS(lhs.position())), + _ => Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS( + lhs.position(), + ))), } } @@ -1362,13 +1376,13 @@ impl Engine { script, OptimizationLevel::None, ) - .map_err(EvalAltResult::ErrorParsing)?; + .map_err(|err| EvalAltResult::ErrorParsing(Box::new(err)))?; // If new functions are defined within the eval string, it is an error if ast.1.len() > 0 { - return Err(EvalAltResult::ErrorParsing( + return Err(Box::new(EvalAltResult::ErrorParsing( ParseErrorType::WrongFnDefinition.into_err(pos), - )); + ))); } if let Some(lib) = fn_lib { @@ -1384,7 +1398,7 @@ impl Engine { // Evaluate the AST self.eval_ast_with_scope_raw(scope, &ast) - .map_err(|err| err.set_position(pos)) + .map_err(|err| Box::new(err.set_position(pos))) } // Normal function call @@ -1452,7 +1466,7 @@ impl Engine { fn_lib: Option<&FunctionsLib>, stmt: &Stmt, level: usize, - ) -> Result { + ) -> Result> { match stmt { // No-op Stmt::Noop(_) => Ok(Dynamic::from_unit()), @@ -1486,7 +1500,7 @@ impl Engine { Stmt::IfThenElse(guard, if_body, else_body) => self .eval_expr(scope, fn_lib, guard, level)? .as_bool() - .map_err(|_| EvalAltResult::ErrorLogicGuard(guard.position())) + .map_err(|_| Box::new(EvalAltResult::ErrorLogicGuard(guard.position()))) .and_then(|guard_val| { if guard_val { self.eval_stmt(scope, fn_lib, if_body, level) @@ -1500,26 +1514,32 @@ impl Engine { // While loop Stmt::While(guard, body) => loop { match self.eval_expr(scope, fn_lib, guard, level)?.as_bool() { - Ok(guard_val) if guard_val => { - match self.eval_stmt(scope, fn_lib, body, level) { - Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), - Err(EvalAltResult::ErrorLoopBreak(true, _)) => { + Ok(true) => match self.eval_stmt(scope, fn_lib, body, level) { + Ok(_) => (), + Err(err) => match *err { + EvalAltResult::ErrorLoopBreak(false, _) => (), + EvalAltResult::ErrorLoopBreak(true, _) => { return Ok(Dynamic::from_unit()) } - Err(x) => return Err(x), - } + _ => return Err(err), + }, + }, + Ok(false) => return Ok(Dynamic::from_unit()), + Err(_) => { + return Err(Box::new(EvalAltResult::ErrorLogicGuard(guard.position()))) } - Ok(_) => return Ok(Dynamic::from_unit()), - Err(_) => return Err(EvalAltResult::ErrorLogicGuard(guard.position())), } }, // Loop statement Stmt::Loop(body) => loop { match self.eval_stmt(scope, fn_lib, body, level) { - Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), - Err(EvalAltResult::ErrorLoopBreak(true, _)) => return Ok(Dynamic::from_unit()), - Err(x) => return Err(x), + Ok(_) => (), + Err(err) => match *err { + EvalAltResult::ErrorLoopBreak(false, _) => (), + EvalAltResult::ErrorLoopBreak(true, _) => return Ok(Dynamic::from_unit()), + _ => return Err(err), + }, } }, @@ -1543,48 +1563,50 @@ impl Engine { *scope.get_mut(entry) = a; match self.eval_stmt(scope, fn_lib, body, level) { - Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), - Err(EvalAltResult::ErrorLoopBreak(true, _)) => break, - Err(x) => return Err(x), + Ok(_) => (), + Err(err) => match *err { + EvalAltResult::ErrorLoopBreak(false, _) => (), + EvalAltResult::ErrorLoopBreak(true, _) => break, + _ => return Err(err), + }, } } scope.rewind(scope.len() - 1); Ok(Dynamic::from_unit()) } else { - Err(EvalAltResult::ErrorFor(expr.position())) + Err(Box::new(EvalAltResult::ErrorFor(expr.position()))) } } // Continue statement - Stmt::Continue(pos) => Err(EvalAltResult::ErrorLoopBreak(false, *pos)), + Stmt::Continue(pos) => Err(Box::new(EvalAltResult::ErrorLoopBreak(false, *pos))), // Break statement - Stmt::Break(pos) => Err(EvalAltResult::ErrorLoopBreak(true, *pos)), + Stmt::Break(pos) => Err(Box::new(EvalAltResult::ErrorLoopBreak(true, *pos))), // Empty return Stmt::ReturnWithVal(None, ReturnType::Return, pos) => { - Err(EvalAltResult::Return(Dynamic::from_unit(), *pos)) + Err(Box::new(EvalAltResult::Return(Dynamic::from_unit(), *pos))) } // Return value - Stmt::ReturnWithVal(Some(a), ReturnType::Return, pos) => Err(EvalAltResult::Return( - self.eval_expr(scope, fn_lib, a, level)?, - *pos, + Stmt::ReturnWithVal(Some(a), ReturnType::Return, pos) => Err(Box::new( + EvalAltResult::Return(self.eval_expr(scope, fn_lib, a, level)?, *pos), )), // Empty throw Stmt::ReturnWithVal(None, ReturnType::Exception, pos) => { - Err(EvalAltResult::ErrorRuntime("".into(), *pos)) + Err(Box::new(EvalAltResult::ErrorRuntime("".into(), *pos))) } // Throw value Stmt::ReturnWithVal(Some(a), ReturnType::Exception, pos) => { let val = self.eval_expr(scope, fn_lib, a, level)?; - Err(EvalAltResult::ErrorRuntime( + Err(Box::new(EvalAltResult::ErrorRuntime( val.take_string().unwrap_or_else(|_| "".to_string()), *pos, - )) + ))) } // Let statement diff --git a/src/error.rs b/src/error.rs index b3e6be13..4de71bbf 100644 --- a/src/error.rs +++ b/src/error.rs @@ -107,8 +107,8 @@ pub enum ParseErrorType { impl ParseErrorType { /// Make a `ParseError` using the current type and position. - pub(crate) fn into_err(self, pos: Position) -> ParseError { - ParseError(self, pos) + pub(crate) fn into_err(self, pos: Position) -> Box { + Box::new(ParseError(self, pos)) } } diff --git a/src/fn_register.rs b/src/fn_register.rs index 08bf9109..5941095e 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -156,7 +156,7 @@ macro_rules! def_register { const NUM_ARGS: usize = count_args!($($par)*); if args.len() != NUM_ARGS { - return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)); + return Err(Box::new(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos))); } #[allow(unused_variables, unused_mut)] @@ -194,7 +194,7 @@ macro_rules! def_register { const NUM_ARGS: usize = count_args!($($par)*); if args.len() != NUM_ARGS { - return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)); + return Err(Box::new(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos))); } #[allow(unused_variables, unused_mut)] @@ -231,7 +231,7 @@ macro_rules! def_register { const NUM_ARGS: usize = count_args!($($par)*); if args.len() != NUM_ARGS { - return Err(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos)); + return Err(Box::new(EvalAltResult::ErrorFunctionArgsMismatch(fn_name.clone(), NUM_ARGS, args.len(), pos))); } #[allow(unused_variables, unused_mut)] @@ -244,7 +244,7 @@ macro_rules! def_register { // Call the user-supplied function using ($clone) to // potentially clone the value, otherwise pass the reference. f($(($clone)($par)),*).map(|r| r.into_dynamic()) - .map_err(|err| err.set_position(pos)) + .map_err(|err| Box::new(err.set_position(pos))) }; self.register_fn_raw(name, vec![$(TypeId::of::<$par>()),*], Box::new(func)); } diff --git a/src/optimize.rs b/src/optimize.rs index 1e8161e7..d12b161a 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -114,7 +114,7 @@ fn call_fn( fn_name: &str, args: &mut FnCallArgs, pos: Position, -) -> Result, EvalAltResult> { +) -> Result, Box> { // Search built-in's and external functions functions .and_then(|f| f.get(&calc_fn_spec(fn_name, args.iter().map(|a| a.type_id())))) diff --git a/src/parser.rs b/src/parser.rs index d50a5a60..978de38d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -522,7 +522,7 @@ fn eat_token(input: &mut Peekable, token: Token) -> Position { } /// Match a particular token, consuming it if matched. -fn match_token(input: &mut Peekable, token: Token) -> Result { +fn match_token(input: &mut Peekable, token: Token) -> Result> { let (t, _) = input.peek().unwrap(); if *t == token { eat_token(input, token); @@ -537,7 +537,7 @@ fn parse_paren_expr<'a>( input: &mut Peekable>, begin: Position, allow_stmt_expr: bool, -) -> Result { +) -> Result> { if match_token(input, Token::RightParen)? { return Ok(Expr::Unit(begin)); } @@ -562,7 +562,7 @@ fn parse_call_expr<'a, S: Into> + Display>( input: &mut Peekable>, begin: Position, allow_stmt_expr: bool, -) -> Result { +) -> Result> { let mut args_expr_list = Vec::new(); match input.peek().unwrap() { @@ -618,7 +618,7 @@ fn parse_index_expr<'a>( input: &mut Peekable>, pos: Position, allow_stmt_expr: bool, -) -> Result { +) -> Result> { let idx_expr = parse_expr(input, allow_stmt_expr)?; // Check type of indexing - must be integer or string @@ -742,7 +742,7 @@ fn parse_array_literal<'a>( input: &mut Peekable>, begin: Position, allow_stmt_expr: bool, -) -> Result { +) -> Result> { let mut arr = Vec::new(); if !match_token(input, Token::RightBracket)? { @@ -780,7 +780,7 @@ fn parse_map_literal<'a>( input: &mut Peekable>, begin: Position, allow_stmt_expr: bool, -) -> Result { +) -> Result> { let mut map = Vec::new(); if !match_token(input, Token::RightBrace)? { @@ -857,7 +857,7 @@ fn parse_map_literal<'a>( fn parse_primary<'a>( input: &mut Peekable>, allow_stmt_expr: bool, -) -> Result { +) -> Result> { let (token, pos) = match input.peek().unwrap() { // { - block statement as expression (Token::LeftBrace, pos) if allow_stmt_expr => { @@ -921,7 +921,7 @@ fn parse_primary<'a>( fn parse_unary<'a>( input: &mut Peekable>, allow_stmt_expr: bool, -) -> Result { +) -> Result> { match input.peek().unwrap() { // If statement is allowed to act as expressions (Token::If, pos) => { @@ -986,9 +986,9 @@ fn parse_unary<'a>( } /// Parse an assignment. -fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result { +fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result> { // Is the LHS in a valid format for an assignment target? - fn valid_assignment_chain(expr: &Expr, is_top: bool) -> Option { + fn valid_assignment_chain(expr: &Expr, is_top: bool) -> Option> { match expr { // var Expr::Variable(_, _) => { @@ -1056,7 +1056,7 @@ fn parse_op_assignment>>( lhs: Expr, rhs: Expr, pos: Position, -) -> Result { +) -> Result> { let lhs_copy = lhs.clone(); // lhs op= rhs -> lhs = op(lhs, rhs) @@ -1068,7 +1068,7 @@ fn parse_op_assignment>>( } /// Parse an 'in' expression. -fn parse_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result { +fn parse_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result> { match (&lhs, &rhs) { (_, Expr::IntegerConstant(_, pos)) | (_, Expr::FloatConstant(_, pos)) @@ -1202,7 +1202,7 @@ fn parse_binary_op<'a>( parent_precedence: u8, lhs: Expr, allow_stmt_expr: bool, -) -> Result { +) -> Result> { let mut current_lhs = lhs; loop { @@ -1253,7 +1253,7 @@ fn parse_binary_op<'a>( #[cfg(not(feature = "no_object"))] Token::Period => { - fn check_property(expr: Expr) -> Result { + fn check_property(expr: Expr) -> Result> { match expr { // xxx.lhs.rhs Expr::Dot(lhs, rhs, pos) => Ok(Expr::Dot( @@ -1353,7 +1353,7 @@ fn parse_binary_op<'a>( fn parse_expr<'a>( input: &mut Peekable>, allow_stmt_expr: bool, -) -> Result { +) -> Result> { // Parse a real expression let lhs = parse_unary(input, allow_stmt_expr)?; parse_binary_op(input, 1, lhs, allow_stmt_expr) @@ -1363,7 +1363,7 @@ fn parse_expr<'a>( fn ensure_not_statement_expr<'a>( input: &mut Peekable>, type_name: &str, -) -> Result<(), ParseError> { +) -> Result<(), Box> { match input.peek().unwrap() { // Disallow statement expressions (Token::LeftBrace, pos) | (Token::EOF, pos) => { @@ -1379,7 +1379,7 @@ fn parse_if<'a>( input: &mut Peekable>, breakable: bool, allow_stmt_expr: bool, -) -> Result { +) -> Result> { // if ... eat_token(input, Token::If); @@ -1412,7 +1412,7 @@ fn parse_if<'a>( fn parse_while<'a>( input: &mut Peekable>, allow_stmt_expr: bool, -) -> Result { +) -> Result> { // while ... eat_token(input, Token::While); @@ -1428,7 +1428,7 @@ fn parse_while<'a>( fn parse_loop<'a>( input: &mut Peekable>, allow_stmt_expr: bool, -) -> Result { +) -> Result> { // loop ... eat_token(input, Token::Loop); @@ -1442,7 +1442,7 @@ fn parse_loop<'a>( fn parse_for<'a>( input: &mut Peekable>, allow_stmt_expr: bool, -) -> Result { +) -> Result> { // for ... eat_token(input, Token::For); @@ -1482,7 +1482,7 @@ fn parse_let<'a>( input: &mut Peekable>, var_type: ScopeEntryType, allow_stmt_expr: bool, -) -> Result { +) -> Result> { // let/const... (specified in `var_type`) input.next(); @@ -1521,7 +1521,7 @@ fn parse_block<'a>( input: &mut Peekable>, breakable: bool, allow_stmt_expr: bool, -) -> Result { +) -> Result> { // Must start with { let pos = match input.next().unwrap() { (Token::LeftBrace, pos) => pos, @@ -1575,7 +1575,7 @@ fn parse_block<'a>( fn parse_expr_stmt<'a>( input: &mut Peekable>, allow_stmt_expr: bool, -) -> Result { +) -> Result> { Ok(Stmt::Expr(Box::new(parse_expr(input, allow_stmt_expr)?))) } @@ -1584,7 +1584,7 @@ fn parse_stmt<'a>( input: &mut Peekable>, breakable: bool, allow_stmt_expr: bool, -) -> Result { +) -> Result> { let (token, pos) = match input.peek().unwrap() { (Token::EOF, pos) => return Ok(Stmt::Noop(*pos)), x => x, @@ -1649,7 +1649,7 @@ fn parse_stmt<'a>( fn parse_fn<'a>( input: &mut Peekable>, allow_stmt_expr: bool, -) -> Result { +) -> Result> { let pos = input.next().expect("should be fn").1; let name = match input.next().unwrap() { @@ -1721,7 +1721,7 @@ pub fn parse_global_expr<'a>( engine: &Engine, scope: &Scope, optimization_level: OptimizationLevel, -) -> Result { +) -> Result> { let expr = parse_expr(input, false)?; match input.peek().unwrap() { @@ -1747,7 +1747,7 @@ pub fn parse_global_expr<'a>( /// Parse the global level statements. fn parse_global_level<'a>( input: &mut Peekable>, -) -> Result<(Vec, HashMap), ParseError> { +) -> Result<(Vec, HashMap), Box> { let mut statements = Vec::::new(); let mut functions = HashMap::::new(); @@ -1800,7 +1800,7 @@ pub fn parse<'a>( engine: &Engine, scope: &Scope, optimization_level: OptimizationLevel, -) -> Result { +) -> Result> { let (statements, functions) = parse_global_level(input)?; let fn_lib = functions.into_iter().map(|(_, v)| v).collect(); diff --git a/src/result.rs b/src/result.rs index 9d443f82..21fe7ef8 100644 --- a/src/result.rs +++ b/src/result.rs @@ -22,7 +22,7 @@ use crate::stdlib::path::PathBuf; #[derive(Debug)] pub enum EvalAltResult { /// Syntax error. - ErrorParsing(ParseError), + ErrorParsing(Box), /// Error reading from a script file. Wrapped value is the path of the script file. /// @@ -230,6 +230,11 @@ impl fmt::Display for EvalAltResult { impl From for EvalAltResult { fn from(err: ParseError) -> Self { + Self::ErrorParsing(Box::new(err)) + } +} +impl From> for EvalAltResult { + fn from(err: Box) -> Self { Self::ErrorParsing(err) } } @@ -280,8 +285,9 @@ impl EvalAltResult { #[cfg(not(feature = "no_std"))] Self::ErrorReadingScriptFile(_, _) => (), - Self::ErrorParsing(ParseError(_, pos)) - | Self::ErrorFunctionNotFound(_, pos) + Self::ErrorParsing(err) => err.1 = new_position, + + Self::ErrorFunctionNotFound(_, pos) | Self::ErrorFunctionArgsMismatch(_, _, _, pos) | Self::ErrorBooleanArgMismatch(_, pos) | Self::ErrorCharMismatch(pos) From a0bc49c867564f97710fe1ea0836329f9f95a37b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 18 Apr 2020 11:10:03 +0800 Subject: [PATCH 23/34] Reduce size of Dynamic by boxing large types. --- src/any.rs | 47 +++++++++++++++++++++++++++++------------------ src/engine.rs | 10 +++++++--- src/parser.rs | 16 +++++++++------- 3 files changed, 45 insertions(+), 28 deletions(-) diff --git a/src/any.rs b/src/any.rs index a02604f1..49d27483 100644 --- a/src/any.rs +++ b/src/any.rs @@ -135,13 +135,13 @@ pub struct Dynamic(pub(crate) Union); pub enum Union { Unit(()), Bool(bool), - Str(String), + Str(Box), Char(char), Int(INT), #[cfg(not(feature = "no_float"))] Float(FLOAT), - Array(Array), - Map(Box), // Box it to reduce size + Array(Box), + Map(Box), Variant(Box), } @@ -324,15 +324,22 @@ impl Dynamic { cast_box::<_, Dynamic>(var) .map(|x| x.0) .or_else(|var| { - cast_box::<_, String>(var).map(Union::Str).or_else(|var| { - cast_box::<_, Array>(var).map(Union::Array).or_else(|var| { - cast_box::<_, Map>(var) - .map(|v| Union::Map(Box::new(v))) - .or_else(|var| -> Result { - Ok(Union::Variant(var as Box)) + cast_box::<_, String>(var) + .map(Box::new) + .map(Union::Str) + .or_else(|var| { + cast_box::<_, Array>(var) + .map(Box::new) + .map(Union::Array) + .or_else(|var| { + cast_box::<_, Map>(var) + .map(Box::new) + .map(Union::Map) + .or_else(|var| -> Result { + Ok(Union::Variant(var as Box)) + }) }) }) - }) }) .unwrap(), ) @@ -360,12 +367,16 @@ impl Dynamic { match &self.0 { Union::Unit(value) => (value as &dyn Variant).downcast_ref::().cloned(), Union::Bool(value) => (value as &dyn Variant).downcast_ref::().cloned(), - Union::Str(value) => (value as &dyn Variant).downcast_ref::().cloned(), + Union::Str(value) => (value.as_ref() as &dyn Variant) + .downcast_ref::() + .cloned(), Union::Char(value) => (value as &dyn Variant).downcast_ref::().cloned(), Union::Int(value) => (value as &dyn Variant).downcast_ref::().cloned(), #[cfg(not(feature = "no_float"))] Union::Float(value) => (value as &dyn Variant).downcast_ref::().cloned(), - Union::Array(value) => (value as &dyn Variant).downcast_ref::().cloned(), + Union::Array(value) => (value.as_ref() as &dyn Variant) + .downcast_ref::() + .cloned(), Union::Map(value) => (value.as_ref() as &dyn Variant) .downcast_ref::() .cloned(), @@ -404,12 +415,12 @@ impl Dynamic { match &self.0 { Union::Unit(value) => (value as &dyn Variant).downcast_ref::(), Union::Bool(value) => (value as &dyn Variant).downcast_ref::(), - Union::Str(value) => (value as &dyn Variant).downcast_ref::(), + Union::Str(value) => (value.as_ref() as &dyn Variant).downcast_ref::(), Union::Char(value) => (value as &dyn Variant).downcast_ref::(), Union::Int(value) => (value as &dyn Variant).downcast_ref::(), #[cfg(not(feature = "no_float"))] Union::Float(value) => (value as &dyn Variant).downcast_ref::(), - Union::Array(value) => (value as &dyn Variant).downcast_ref::(), + Union::Array(value) => (value.as_ref() as &dyn Variant).downcast_ref::(), Union::Map(value) => (value.as_ref() as &dyn Variant).downcast_ref::(), Union::Variant(value) => value.as_ref().downcast_ref::(), } @@ -426,12 +437,12 @@ impl Dynamic { match &mut self.0 { Union::Unit(value) => (value as &mut dyn Variant).downcast_mut::(), Union::Bool(value) => (value as &mut dyn Variant).downcast_mut::(), - Union::Str(value) => (value as &mut dyn Variant).downcast_mut::(), + Union::Str(value) => (value.as_mut() as &mut dyn Variant).downcast_mut::(), Union::Char(value) => (value as &mut dyn Variant).downcast_mut::(), Union::Int(value) => (value as &mut dyn Variant).downcast_mut::(), #[cfg(not(feature = "no_float"))] Union::Float(value) => (value as &mut dyn Variant).downcast_mut::(), - Union::Array(value) => (value as &mut dyn Variant).downcast_mut::(), + Union::Array(value) => (value.as_mut() as &mut dyn Variant).downcast_mut::(), Union::Map(value) => (value.as_mut() as &mut dyn Variant).downcast_mut::(), Union::Variant(value) => value.as_mut().downcast_mut::(), } @@ -477,7 +488,7 @@ impl Dynamic { /// Returns the name of the actual type if the cast fails. pub(crate) fn take_string(self) -> Result { match self.0 { - Union::Str(s) => Ok(s), + Union::Str(s) => Ok(*s), _ => Err(self.type_name()), } } @@ -499,7 +510,7 @@ impl Dynamic { Self(Union::Char(value)) } pub(crate) fn from_string(value: String) -> Self { - Self(Union::Str(value)) + Self(Union::Str(Box::new(value))) } } diff --git a/src/engine.rs b/src/engine.rs index 6c8f1b3e..6d7a165e 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1167,7 +1167,9 @@ impl Engine { Dynamic(Union::Map(rhs_value)) => { // Only allows String or char match lhs_value { - Dynamic(Union::Str(s)) => Ok(Dynamic::from_bool(rhs_value.contains_key(&s))), + Dynamic(Union::Str(s)) => { + Ok(Dynamic::from_bool(rhs_value.contains_key(s.as_ref()))) + } Dynamic(Union::Char(c)) => { Ok(Dynamic::from_bool(rhs_value.contains_key(&c.to_string()))) } @@ -1177,7 +1179,9 @@ impl Engine { Dynamic(Union::Str(rhs_value)) => { // Only allows String or char match lhs_value { - Dynamic(Union::Str(s)) => Ok(Dynamic::from_bool(rhs_value.contains(&s))), + Dynamic(Union::Str(s)) => { + Ok(Dynamic::from_bool(rhs_value.contains(s.as_ref()))) + } Dynamic(Union::Char(c)) => Ok(Dynamic::from_bool(rhs_value.contains(c))), _ => Err(Box::new(EvalAltResult::ErrorInExpr(lhs.position()))), } @@ -1314,7 +1318,7 @@ impl Engine { .map(|val| arr.push(val)) })?; - Ok(Dynamic(Union::Array(arr))) + Ok(Dynamic(Union::Array(Box::new(arr)))) } #[cfg(not(feature = "no_object"))] diff --git a/src/parser.rs b/src/parser.rs index 978de38d..802f8679 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -330,12 +330,14 @@ impl Expr { Self::False(_) => Dynamic::from_bool(false), Self::Unit(_) => Dynamic::from_unit(), - Self::Array(items, _) if items.iter().all(Self::is_constant) => Dynamic(Union::Array( - items - .iter() - .map(Self::get_constant_value) - .collect::>(), - )), + Self::Array(items, _) if items.iter().all(Self::is_constant) => { + Dynamic(Union::Array(Box::new( + items + .iter() + .map(Self::get_constant_value) + .collect::>(), + ))) + } Self::Map(items, _) if items.iter().all(|(_, v, _)| v.is_constant()) => { Dynamic(Union::Map(Box::new( @@ -1818,7 +1820,7 @@ pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option { Union::Unit(_) => Some(Expr::Unit(pos)), Union::Int(value) => Some(Expr::IntegerConstant(value, pos)), Union::Char(value) => Some(Expr::CharConstant(value, pos)), - Union::Str(value) => Some(Expr::StringConstant(value.into(), pos)), + Union::Str(value) => Some(Expr::StringConstant((*value).into(), pos)), Union::Bool(true) => Some(Expr::True(pos)), Union::Bool(false) => Some(Expr::False(pos)), #[cfg(not(feature = "no_index"))] From 1ebdf6dcfc15b9c8a62f379ca65e3af9e1793eb4 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 18 Apr 2020 12:36:10 +0800 Subject: [PATCH 24/34] Add one more level of Boxing for Dynamic::Variant to reduce size. --- src/any.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/any.rs b/src/any.rs index 49d27483..cb60384e 100644 --- a/src/any.rs +++ b/src/any.rs @@ -142,7 +142,7 @@ pub enum Union { Float(FLOAT), Array(Box), Map(Box), - Variant(Box), + Variant(Box>), } impl Dynamic { @@ -172,7 +172,7 @@ impl Dynamic { Union::Float(_) => TypeId::of::(), Union::Array(_) => TypeId::of::(), Union::Map(_) => TypeId::of::(), - Union::Variant(value) => (**value).type_id(), + Union::Variant(value) => (***value).type_id(), } } @@ -191,7 +191,7 @@ impl Dynamic { #[cfg(not(feature = "no_std"))] Union::Variant(value) if value.is::() => "timestamp", - Union::Variant(value) => (**value).type_name(), + Union::Variant(value) => (***value).type_name(), } } } @@ -242,7 +242,7 @@ impl Clone for Dynamic { Union::Float(value) => Self(Union::Float(value.clone())), Union::Array(value) => Self(Union::Array(value.clone())), Union::Map(value) => Self(Union::Map(value.clone())), - Union::Variant(value) => (**value).clone_into_dynamic(), + Union::Variant(value) => (***value).clone_into_dynamic(), } } } @@ -336,7 +336,7 @@ impl Dynamic { .map(Box::new) .map(Union::Map) .or_else(|var| -> Result { - Ok(Union::Variant(var as Box)) + Ok(Union::Variant(Box::new(var as Box))) }) }) }) @@ -380,7 +380,7 @@ impl Dynamic { Union::Map(value) => (value.as_ref() as &dyn Variant) .downcast_ref::() .cloned(), - Union::Variant(value) => value.as_ref().downcast_ref::().cloned(), + Union::Variant(value) => value.as_ref().as_ref().downcast_ref::().cloned(), } } @@ -422,7 +422,7 @@ impl Dynamic { Union::Float(value) => (value as &dyn Variant).downcast_ref::(), Union::Array(value) => (value.as_ref() as &dyn Variant).downcast_ref::(), Union::Map(value) => (value.as_ref() as &dyn Variant).downcast_ref::(), - Union::Variant(value) => value.as_ref().downcast_ref::(), + Union::Variant(value) => value.as_ref().as_ref().downcast_ref::(), } } @@ -444,7 +444,7 @@ impl Dynamic { Union::Float(value) => (value as &mut dyn Variant).downcast_mut::(), Union::Array(value) => (value.as_mut() as &mut dyn Variant).downcast_mut::(), Union::Map(value) => (value.as_mut() as &mut dyn Variant).downcast_mut::(), - Union::Variant(value) => value.as_mut().downcast_mut::(), + Union::Variant(value) => value.as_mut().as_mut().downcast_mut::(), } } From 5b911c6578ff8b38e165974c4d2eb823309c60b8 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 18 Apr 2020 12:52:52 +0800 Subject: [PATCH 25/34] Add benchmarking workflow. --- .github/workflows/benchmark.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/benchmark.yml diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 00000000..fdfcddc0 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,29 @@ +ame: Benchmark +on: + push: + branches: + - master + +jobs: + benchmark: + name: Run Rust benchmark + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: rustup toolchain update nightly && rustup default nightly + - name: Run benchmark + run: cargo +nightly bench | tee output.txt + - name: Store benchmark result + uses: rhysd/github-action-benchmark@v1 + with: + name: Rust Benchmark + tool: 'cargo' + output-file-path: output.txt + # Use personal access token instead of GITHUB_TOKEN due to https://github.community/t5/GitHub-Actions/Github-action-not-triggering-gh-pages-upon-push/td-p/26869/highlight/false + github-token: ${{ secrets.RHAI }} + auto-push: true + # Show alert with commit comment on detecting possible performance regression + alert-threshold: '200%' + comment-on-alert: true + fail-on-alert: true + alert-comment-cc-users: '@schungx' From e45040d9a644dbd7316b4a1fbc78a791b7f8e606 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 18 Apr 2020 13:15:09 +0800 Subject: [PATCH 26/34] Fix typo. --- .github/workflows/benchmark.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index fdfcddc0..df310705 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -1,4 +1,4 @@ -ame: Benchmark +name: Benchmark on: push: branches: From 5ee01d645c6d149059d3570487c25e5d949635eb Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 18 Apr 2020 13:28:03 +0800 Subject: [PATCH 27/34] Set theme jekyll-theme-slate --- _config.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 _config.yml diff --git a/_config.yml b/_config.yml new file mode 100644 index 00000000..c7418817 --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-slate \ No newline at end of file From 16da483f486f1d7f9eb1832e3aecf5b908dd7695 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 18 Apr 2020 14:30:16 +0800 Subject: [PATCH 28/34] Set theme jekyll-theme-slate From 81e1c3b331dd7c69523727db70b7412af530c22e Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 18 Apr 2020 15:30:43 +0800 Subject: [PATCH 29/34] Add primes benchmark. --- benches/primes.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 benches/primes.rs diff --git a/benches/primes.rs b/benches/primes.rs new file mode 100644 index 00000000..4144d2ad --- /dev/null +++ b/benches/primes.rs @@ -0,0 +1,44 @@ +#![feature(test)] + +///! Test evaluating expressions +extern crate test; + +use rhai::{Engine, OptimizationLevel}; +use test::Bencher; + +// This script uses the Sieve of Eratosthenes to calculate prime numbers. + +const SCRIPT: &str = r#" +let now = timestamp(); + +const MAX_NUMBER_TO_CHECK = 1_000; // 168 primes <= 1000 + +let prime_mask = []; +prime_mask.pad(MAX_NUMBER_TO_CHECK, true); + +prime_mask[0] = prime_mask[1] = false; + +let total_primes_found = 0; + +for p in range(2, MAX_NUMBER_TO_CHECK) { + if prime_mask[p] { + total_primes_found += 1; + let i = 2 * p; + + while i < MAX_NUMBER_TO_CHECK { + prime_mask[i] = false; + i += p; + } + } +} +"#; + +#[bench] +fn bench_eval_primes(bench: &mut Bencher) { + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(SCRIPT).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} From 53f7edf306221a543b386c2fbed372d0e7c24fe4 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 19 Apr 2020 13:22:37 +0800 Subject: [PATCH 30/34] Add doc on performance and min builds. --- README.md | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b5e84d7d..79162acc 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,39 @@ Excluding unneeded functionalities can result in smaller, faster builds as well [`no_std`]: #optional-features [`sync`]: #optional-features +### Performance builds + +Some features are for performance. For example, using `only_i32` or `only_i64` disables all other integer types (such as `u16`). +If only a single integer type is needed in scripts - most of the time this is the case - it is best to avoid registering +lots of functions related to other integer types that will never be used. As a result, performance will improve. + +If only 32-bit integers are needed - again, most of the time this is the case - using `only_i32` disables also `i64`. +On 64-bit targets this may not gain much, but on some 32-bit targets this improves performance due to 64-bit arithmetic +requiring more CPU cycles to complete. + +Also, turning on `no_float`, and `only_i32` makes the key [`Dynamic`] data type only 8 bytes small on 32-bit targets +while normally it can be up to 16 bytes (e.g. on x86/x64 CPU's) in order to hold an `i64` or `f64`. +Making [`Dynamic`] small helps performance due to more caching efficiency. + +### Minimal builds + +In order to compile a _minimal_build - i.e. a build optimized for size - perhaps for embedded targets, it is essential that +the correct linker flags are used in `cargo.toml`: + +```toml +[profile.release] +opt-level = "z" # optimize for size +``` + +Opt out of as many features as possible, if they are not needed, to reduce code size because, remember, by default +all code is compiled in as what a script requires cannot be predicted. If a language feature is not needed, +omitting them via special features is a prudent strategy to optimize the build for size. + +Start by using [`Engine::new_raw`](#raw-engine) to create a _raw_ engine which does not register the standard library of utility +functions. Secondly, omitting arrays (`no_index`) yields the most code-size savings, followed by floating-point support +(`no_float`), checked arithmetic (`unchecked`) and finally object maps and custom types (`no_object`). Disable script-defined +functions (`no_function`) only when the feature is not needed because code size savings is minimal. + Related ------- @@ -380,7 +413,7 @@ The default integer type is `i64`. If other integer types are not needed, it is smaller build with the [`only_i64`] feature. If only 32-bit integers are needed, enabling the [`only_i32`] feature will remove support for all integer types other than `i32`, including `i64`. -This is useful on some 32-bit systems where using 64-bit integers incurs a performance penalty. +This is useful on some 32-bit targets where using 64-bit integers incur a performance penalty. If no floating-point is needed or supported, use the [`no_float`] feature to remove it. From d3d62c7dd9d4271f7e30dd506bb298edee13a6b4 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 19 Apr 2020 13:40:11 +0800 Subject: [PATCH 31/34] Anticipate number of functions to be registered. --- src/api.rs | 14 +--- src/engine.rs | 202 ++++++++++++++++++++++++------------------------ src/optimize.rs | 6 +- 3 files changed, 104 insertions(+), 118 deletions(-) diff --git a/src/api.rs b/src/api.rs index f410b815..4bc12510 100644 --- a/src/api.rs +++ b/src/api.rs @@ -62,12 +62,7 @@ impl Box> + 'static> IteratorCal impl Engine { /// Register a custom function. pub(crate) fn register_fn_raw(&mut self, fn_name: &str, args: Vec, f: Box) { - if self.functions.is_none() { - self.functions = Some(HashMap::new()); - } self.functions - .as_mut() - .unwrap() .insert(calc_fn_spec(fn_name, args.into_iter()), f); } @@ -171,14 +166,7 @@ impl Engine { /// Register an iterator adapter for a type with the `Engine`. /// This is an advanced feature. pub fn register_iterator(&mut self, f: F) { - if self.type_iterators.is_none() { - self.type_iterators = Some(HashMap::new()); - } - - self.type_iterators - .as_mut() - .unwrap() - .insert(TypeId::of::(), Box::new(f)); + self.type_iterators.insert(TypeId::of::(), Box::new(f)); } /// Register a getter function for a member of a registered type with the `Engine`. diff --git a/src/engine.rs b/src/engine.rs index 6d7a165e..06e30342 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -59,6 +59,13 @@ pub const FUNC_TO_STRING: &str = "to_string"; pub const FUNC_GETTER: &str = "get$"; pub const FUNC_SETTER: &str = "set$"; +#[cfg(not(feature = "only_i32"))] +#[cfg(not(feature = "only_i64"))] +const FUNCTIONS_COUNT: usize = 512; + +#[cfg(any(feature = "only_i32", feature = "only_i64"))] +const FUNCTIONS_COUNT: usize = 256; + #[derive(Debug, Eq, PartialEq, Hash, Clone)] enum IndexValue { Num(usize), @@ -169,11 +176,7 @@ impl FunctionsLib { self.clone() } else { let mut functions = self.clone(); - - other.iter().for_each(|(hash, fn_def)| { - functions.insert(*hash, fn_def.clone()); - }); - + functions.extend(other.iter().map(|(hash, fn_def)| (*hash, fn_def.clone()))); functions } } @@ -219,10 +222,10 @@ impl DerefMut for FunctionsLib { /// Currently, `Engine` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`. pub struct Engine { /// A hashmap containing all compiled functions known to the engine. - pub(crate) functions: Option>>, + pub(crate) functions: HashMap>, /// A hashmap containing all iterators known to the engine. - pub(crate) type_iterators: Option>>, + pub(crate) type_iterators: HashMap>, /// A hashmap mapping type names to pretty-print names. pub(crate) type_names: Option>, @@ -253,8 +256,8 @@ impl Default for Engine { fn default() -> Self { // Create the new scripting Engine let mut engine = Engine { - functions: None, - type_iterators: None, + functions: HashMap::with_capacity(FUNCTIONS_COUNT), + type_iterators: HashMap::new(), type_names: None, // default print/debug implementations @@ -350,8 +353,8 @@ impl Engine { /// Create a new `Engine` with minimal configurations without the standard library etc. pub fn new_raw() -> Self { let mut engine = Engine { - functions: None, - type_iterators: None, + functions: HashMap::with_capacity(FUNCTIONS_COUNT / 2), + type_iterators: HashMap::new(), type_names: None, on_print: None, on_debug: None, @@ -476,7 +479,7 @@ impl Engine { // Search built-in's and external functions let fn_spec = calc_fn_spec(fn_name, args.iter().map(|a| a.type_id())); - if let Some(func) = self.functions.as_ref().and_then(|f| f.get(&fn_spec)) { + if let Some(func) = self.functions.get(&fn_spec) { // Run external function let result = func(args, pos)?; @@ -548,7 +551,7 @@ impl Engine { } /// Chain-evaluate a dot setter. - fn get_dot_val_helper( + fn dot_get_helper( &self, scope: &mut Scope, fn_lib: Option<&FunctionsLib>, @@ -563,13 +566,10 @@ impl Engine { .iter() .map(|arg_expr| self.eval_expr(scope, fn_lib, arg_expr, level)) .collect::, _>>()?; - - let this_ptr = target.get_mut(scope); - - let mut args: Vec<_> = once(this_ptr).chain(values.iter_mut()).collect(); - + let mut args: Vec<_> = once(target.get_mut(scope)) + .chain(values.iter_mut()) + .collect(); let def_val = def_val.as_ref(); - self.call_fn_raw(None, fn_lib, fn_name, &mut args, def_val, *pos, 0) } @@ -590,7 +590,7 @@ impl Engine { // xxx.???[???][idx_expr] Expr::Index(_, _, _) => { // Chain the indexing - self.get_dot_val_helper(scope, fn_lib, target, idx_lhs, level)? + self.dot_get_helper(scope, fn_lib, target, idx_lhs, level)? } // Syntax error _ => { @@ -601,7 +601,7 @@ impl Engine { } }; - self.get_indexed_value(scope, fn_lib, &lhs_value, idx_expr, *op_pos, level, false) + self.get_indexed_val(scope, fn_lib, &lhs_value, idx_expr, *op_pos, level, false) .map(|(val, _)| val) } @@ -612,7 +612,7 @@ impl Engine { let mut args = [target.get_mut(scope)]; self.call_fn_raw(None, fn_lib, &make_getter(id), &mut args, None, *pos, 0) .and_then(|mut val| { - self.get_dot_val_helper(scope, fn_lib, (&mut val).into(), rhs, level) + self.dot_get_helper(scope, fn_lib, (&mut val).into(), rhs, level) }) } // xxx.idx_lhs[idx_expr].rhs @@ -626,7 +626,7 @@ impl Engine { } // xxx.???[???][idx_expr].rhs Expr::Index(_, _, _) => { - self.get_dot_val_helper(scope, fn_lib, target, idx_lhs, level)? + self.dot_get_helper(scope, fn_lib, target, idx_lhs, level)? } // Syntax error _ => { @@ -637,9 +637,9 @@ impl Engine { } }; - self.get_indexed_value(scope, fn_lib, &val, idx_expr, *op_pos, level, false) + self.get_indexed_val(scope, fn_lib, &val, idx_expr, *op_pos, level, false) .and_then(|(mut val, _)| { - self.get_dot_val_helper(scope, fn_lib, (&mut val).into(), rhs, level) + self.dot_get_helper(scope, fn_lib, (&mut val).into(), rhs, level) }) } // Syntax error @@ -658,7 +658,7 @@ impl Engine { } /// Evaluate a dot chain getter - fn get_dot_val( + fn dot_get( &self, scope: &mut Scope, fn_lib: Option<&FunctionsLib>, @@ -676,15 +676,14 @@ impl Engine { // This is a variable property access (potential function call). // Use a direct index into `scope` to directly mutate the variable value. - self.get_dot_val_helper(scope, fn_lib, entry.into(), dot_rhs, level) + self.dot_get_helper(scope, fn_lib, entry.into(), dot_rhs, level) } // idx_lhs[idx_expr].??? Expr::Index(idx_lhs, idx_expr, op_pos) => { let (src, index, mut val) = self.eval_index_expr(scope, fn_lib, idx_lhs, idx_expr, *op_pos, level)?; - let value = - self.get_dot_val_helper(scope, fn_lib, (&mut val).into(), dot_rhs, level); + let value = self.dot_get_helper(scope, fn_lib, (&mut val).into(), dot_rhs, level); // In case the expression mutated `target`, we need to update it back into the scope because it is cloned. if let Some(src) = src { @@ -697,12 +696,8 @@ impl Engine { } ScopeEntryType::Normal => { - Self::update_indexed_var_in_scope( - scope, - src, - index, - (val, dot_rhs.position()), - )?; + let pos = dot_rhs.position(); + Self::update_indexed_scope_var(scope, src, index, val, pos)?; } } } @@ -713,7 +708,7 @@ impl Engine { // {expr}.??? expr => { let mut val = self.eval_expr(scope, fn_lib, expr, level)?; - self.get_dot_val_helper(scope, fn_lib, (&mut val).into(), dot_rhs, level) + self.dot_get_helper(scope, fn_lib, (&mut val).into(), dot_rhs, level) } } } @@ -730,7 +725,7 @@ impl Engine { } /// Get the value at the indexed position of a base type - fn get_indexed_value( + fn get_indexed_val( &self, scope: &mut Scope, fn_lib: Option<&FunctionsLib>, @@ -843,7 +838,7 @@ impl Engine { let (ScopeSource { typ, index, .. }, val) = Self::search_scope(scope, &id, lhs.position())?; let (val, idx) = - self.get_indexed_value(scope, fn_lib, &val, idx_expr, op_pos, level, false)?; + self.get_indexed_val(scope, fn_lib, &val, idx_expr, op_pos, level, false)?; Ok(( Some(ScopeSource { @@ -859,7 +854,7 @@ impl Engine { // (expr)[idx_expr] expr => { let val = self.eval_expr(scope, fn_lib, expr, level)?; - self.get_indexed_value(scope, fn_lib, &val, idx_expr, op_pos, level, false) + self.get_indexed_val(scope, fn_lib, &val, idx_expr, op_pos, level, false) .map(|(val, index)| (None, index, val)) } } @@ -879,29 +874,28 @@ impl Engine { } /// Update the value at an index position in a variable inside the scope - fn update_indexed_var_in_scope( + fn update_indexed_scope_var( scope: &mut Scope, src: ScopeSource, idx: IndexValue, - new_val: (Dynamic, Position), + new_val: Dynamic, + pos: Position, ) -> Result> { let target = scope.get_mut(src); match target.get_mut() { // array_id[idx] = val Union::Array(arr) => { - arr[idx.as_num()] = new_val.0; + arr[idx.as_num()] = new_val; } // map_id[idx] = val Union::Map(map) => { - map.insert(idx.as_str(), new_val.0); + map.insert(idx.as_str(), new_val); } // string_id[idx] = val Union::Str(s) => { - let pos = new_val.1; // Value must be a character let ch = new_val - .0 .as_char() .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; Self::str_replace_char(s, idx.as_num(), ch); @@ -914,7 +908,7 @@ impl Engine { } /// Update the value at an index position - fn update_indexed_value( + fn update_indexed_val( mut target: Dynamic, idx: IndexValue, new_val: Dynamic, @@ -932,7 +926,6 @@ impl Engine { let ch = new_val .as_char() .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; - Self::str_replace_char(s, idx.as_num(), ch); } // All other variable types should be an error @@ -943,19 +936,20 @@ impl Engine { } /// Chain-evaluate a dot setter - fn set_dot_val_helper( + fn dot_set_helper( &self, scope: &mut Scope, fn_lib: Option<&FunctionsLib>, this_ptr: &mut Dynamic, dot_rhs: &Expr, - new_val: (&mut Dynamic, Position), + new_val: &mut Dynamic, + val_pos: Position, level: usize, ) -> Result> { match dot_rhs { // xxx.id Expr::Property(id, pos) => { - let mut args = [this_ptr, new_val.0]; + let mut args = [this_ptr, new_val]; self.call_fn_raw(None, fn_lib, &make_setter(id), &mut args, None, *pos, 0) } @@ -967,11 +961,11 @@ impl Engine { let fn_name = make_getter(id); self.call_fn_raw(None, fn_lib, &fn_name, &mut [this_ptr], None, *pos, 0) .and_then(|val| { - let (_, index) = self.get_indexed_value( + let (_, index) = self.get_indexed_val( scope, fn_lib, &val, idx_expr, *op_pos, level, true, )?; - Self::update_indexed_value(val, index, new_val.0.clone(), new_val.1) + Self::update_indexed_val(val, index, new_val.clone(), val_pos) }) .and_then(|mut val| { let fn_name = make_setter(id); @@ -994,8 +988,10 @@ impl Engine { let fn_name = make_getter(id); self.call_fn_raw(None, fn_lib, &fn_name, &mut [this_ptr], None, *pos, 0) .and_then(|mut val| { - self.set_dot_val_helper(scope, fn_lib, &mut val, rhs, new_val, level) - .map(|_| val) // Discard Ok return value + self.dot_set_helper( + scope, fn_lib, &mut val, rhs, new_val, val_pos, level, + ) + .map(|_| val) // Discard Ok return value }) .and_then(|mut val| { let fn_name = make_setter(id); @@ -1012,18 +1008,16 @@ impl Engine { let fn_name = make_getter(id); self.call_fn_raw(None, fn_lib, &fn_name, &mut [this_ptr], None, *pos, 0) .and_then(|v| { - let (mut value, index) = self.get_indexed_value( + let (mut value, index) = self.get_indexed_val( scope, fn_lib, &v, idx_expr, *op_pos, level, false, )?; - let val_pos = new_val.1; - let this_ptr = &mut value; - self.set_dot_val_helper( - scope, fn_lib, this_ptr, rhs, new_val, level, + self.dot_set_helper( + scope, fn_lib, &mut value, rhs, new_val, val_pos, level, )?; // In case the expression mutated `target`, we need to update it back into the scope because it is cloned. - Self::update_indexed_value(v, index, value, val_pos) + Self::update_indexed_val(v, index, value, val_pos) }) .and_then(|mut v| { let fn_name = make_setter(id); @@ -1055,13 +1049,14 @@ impl Engine { } // Evaluate a dot chain setter - fn set_dot_val( + fn dot_set( &self, scope: &mut Scope, fn_lib: Option<&FunctionsLib>, dot_lhs: &Expr, dot_rhs: &Expr, - new_val: (&mut Dynamic, Position), + new_val: &mut Dynamic, + val_pos: Position, op_pos: Position, level: usize, ) -> Result> { @@ -1077,9 +1072,15 @@ impl Engine { _ => { // Avoid referencing scope which is used below as mut let entry = ScopeSource { name: id, ..src }; - let this_ptr = &mut target; - let value = self - .set_dot_val_helper(scope, fn_lib, this_ptr, dot_rhs, new_val, level); + let value = self.dot_set_helper( + scope, + fn_lib, + &mut target, + dot_rhs, + new_val, + val_pos, + level, + ); // In case the expression mutated `target`, we need to update it back into the scope because it is cloned. *scope.get_mut(entry) = target; @@ -1094,10 +1095,9 @@ impl Engine { Expr::Index(lhs, idx_expr, op_pos) => { let (src, index, mut target) = self.eval_index_expr(scope, fn_lib, lhs, idx_expr, *op_pos, level)?; - let val_pos = new_val.1; let this_ptr = &mut target; let value = - self.set_dot_val_helper(scope, fn_lib, this_ptr, dot_rhs, new_val, level); + self.dot_set_helper(scope, fn_lib, this_ptr, dot_rhs, new_val, val_pos, level); // In case the expression mutated `target`, we need to update it back into the scope because it is cloned. if let Some(src) = src { @@ -1108,14 +1108,8 @@ impl Engine { lhs.position(), ))); } - ScopeEntryType::Normal => { - Self::update_indexed_var_in_scope( - scope, - src, - index, - (target, val_pos), - )?; + Self::update_indexed_scope_var(scope, src, index, target, val_pos)?; } } } @@ -1257,32 +1251,36 @@ impl Engine { let (src, index, _) = self.eval_index_expr(scope, fn_lib, idx_lhs, idx_expr, *op_pos, level)?; - match src.map(|x| x.typ) { - None => Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS( - idx_lhs.position(), - ))), - - Some(ScopeEntryType::Constant) => { - Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( - src.unwrap().name.to_string(), - idx_lhs.position(), - ))) + if let Some(src) = src { + match src.typ { + ScopeEntryType::Constant => { + Err(Box::new(EvalAltResult::ErrorAssignmentToConstant( + src.name.to_string(), + idx_lhs.position(), + ))) + } + ScopeEntryType::Normal => { + let pos = rhs.position(); + Ok(Self::update_indexed_scope_var( + scope, src, index, rhs_val, pos, + )?) + } } - - Some(ScopeEntryType::Normal) => Ok(Self::update_indexed_var_in_scope( - scope, - src.unwrap(), - index, - (rhs_val, rhs.position()), - )?), + } else { + Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS( + idx_lhs.position(), + ))) } } // dot_lhs.dot_rhs = rhs #[cfg(not(feature = "no_object"))] Expr::Dot(dot_lhs, dot_rhs, _) => { - let new_val = (&mut rhs_val, rhs.position()); - self.set_dot_val(scope, fn_lib, dot_lhs, dot_rhs, new_val, *op_pos, level) + let new_val = &mut rhs_val; + let val_pos = rhs.position(); + self.dot_set( + scope, fn_lib, dot_lhs, dot_rhs, new_val, val_pos, *op_pos, level, + ) } // Error assignment to constant @@ -1307,7 +1305,7 @@ impl Engine { .map(|(_, _, x)| x), #[cfg(not(feature = "no_object"))] - Expr::Dot(lhs, rhs, _) => self.get_dot_val(scope, fn_lib, lhs, rhs, level), + Expr::Dot(lhs, rhs, _) => self.dot_get(scope, fn_lib, lhs, rhs, level), #[cfg(not(feature = "no_index"))] Expr::Array(contents, _) => { @@ -1325,9 +1323,9 @@ impl Engine { Expr::Map(contents, _) => { let mut map = Map::new(); - contents.into_iter().try_for_each(|item| { - self.eval_expr(scope, fn_lib, &item.1, level).map(|val| { - map.insert(item.0.clone(), val); + contents.into_iter().try_for_each(|(key, expr, _)| { + self.eval_expr(scope, fn_lib, &expr, level).map(|val| { + map.insert(key.clone(), val); }) })?; @@ -1341,10 +1339,10 @@ impl Engine { fn_lib: Option<&FunctionsLib>, name: &str, ) -> bool { - engine.functions.as_ref().map_or(false, |lib| { - let fn_spec = calc_fn_spec(name, [TypeId::of::()].iter().cloned()); - lib.contains_key(&fn_spec) - }) || fn_lib.map_or(false, |lib| lib.has_function(name, 1)) + engine + .functions + .contains_key(&calc_fn_spec(name, once(TypeId::of::()))) + || fn_lib.map_or(false, |lib| lib.has_function(name, 1)) } match fn_name.as_ref() { @@ -1552,7 +1550,7 @@ impl Engine { let arr = self.eval_expr(scope, fn_lib, expr, level)?; let tid = arr.type_id(); - if let Some(iter_fn) = self.type_iterators.as_ref().and_then(|t| t.get(&tid)) { + if let Some(iter_fn) = self.type_iterators.get(&tid) { // Add the loop variable - variable name is copied // TODO - avoid copying variable name scope.push(name.clone(), ()); diff --git a/src/optimize.rs b/src/optimize.rs index d12b161a..39a5b995 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -110,14 +110,14 @@ impl<'a> State<'a> { /// Call a registered function fn call_fn( - functions: Option<&HashMap>>, + functions: &HashMap>, fn_name: &str, args: &mut FnCallArgs, pos: Position, ) -> Result, Box> { // Search built-in's and external functions functions - .and_then(|f| f.get(&calc_fn_spec(fn_name, args.iter().map(|a| a.type_id())))) + .get(&calc_fn_spec(fn_name, args.iter().map(|a| a.type_id()))) .map(|func| func(args, pos)) .transpose() } @@ -576,7 +576,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { "" }; - call_fn(state.engine.functions.as_ref(), &id, &mut call_args, pos).ok() + call_fn(&state.engine.functions, &id, &mut call_args, pos).ok() .and_then(|result| result.or_else(|| { if !arg_for_type_of.is_empty() { From dd09dbf79e580f50bcdc1d6ac6b4637232212f6f Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 19 Apr 2020 18:32:53 +0800 Subject: [PATCH 32/34] Add section on operator overloading. --- README.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 79162acc..868cfe43 100644 --- a/README.md +++ b/README.md @@ -14,13 +14,14 @@ to add scripting to any application. Rhai's current features set: * `no-std` support -* Easy integration with Rust native functions and data types, including getter/setter methods +* Easy integration with Rust native functions and types, including getter/setter/methods * Easily call a script-defined function from Rust * Freely pass variables/constants into a script via an external [`Scope`] * Fairly efficient (1 million iterations in 0.75 sec on my 5 year old laptop) * Low compile-time overhead (~0.6 sec debug/~3 sec release for script runner app) * Easy-to-use language similar to JS+Rust -* Support for overloaded functions +* Support for function overloading +* Support for operator overloading * Compiled script is optimized for repeat evaluations * Support for minimal builds by excluding unneeded language features * Very few additional dependencies (right now only [`num-traits`](https://crates.io/crates/num-traits/) @@ -598,7 +599,7 @@ Generic functions ----------------- Rust generic functions can be used in Rhai, but separate instances for each concrete type must be registered separately. -Essentially this is a form of function overloading as Rhai does not support generics. +This is essentially function overloading (Rhai does not natively support generics). ```rust use std::fmt::Display; @@ -672,6 +673,50 @@ fn to_int(num) { print(to_int(123)); // what happens? ``` +Operator overloading +-------------------- + +In Rhai, a lot of functionalities are actually implemented as functions, including basic operations such as arithmetic calculations. +For example, in the expression "`a + b`", the `+` operator is _not_ built-in, but calls a function named "`+`" instead! + +```rust +let x = a + b; +let x = +(a, b); // <- the above is equivalent to this function call +``` + +Similarly, comparison operators including `==`, `!=` etc. are all implemented as functions, with the stark exception of `&&` and `||`. +Because they [_short-circuit_](#boolean-operators), `&&` and `||` are handled specially and _not_ via a function; as a result, +overriding them has no effect at all. + +Operator functions cannot be defined as a script function (because operators syntax are not valid function names). +However, operator functions _can_ be registered to the [`Engine`] via `register_fn`, `register_result_fn` etc. +When a custom operator function is registered with the same name as an operator, it _overloads_ (or overrides) the built-in version. + +```rust +use rhai::{Engine, EvalAltResult, RegisterFn}; + +let mut engine = Engine::new(); + +fn strange_add(a: i64, b: i64) -> i64 { (a + b) * 42 } + +engine.register_fn("+", strange_add); // overload '+' operator for two integers! + +let result: i64 = engine.eval("1 + 0"); // the overloading version is used + +println!("result: {}", result); // prints 42 + +let result: f64 = engine.eval("1.0 + 0.0"); // '+' operator for two floats not overloaded + +println!("result: {}", result); // prints 1.0 +``` + +Use operator overloading for custom types (described below) only. Be very careful when overloading built-in operators because +script writers expect standard operators to behave in a consistent and predictable manner, and will be annoyed if a calculation +for '+' turns into a subtraction, for example. + +Operator overloading also impacts script optimization when using [`OptimizationLevel::Full`]. +See the [relevant section](#script-optimization) for more details. + Custom types and methods ----------------------- @@ -707,7 +752,7 @@ fn main() -> Result<(), EvalAltResult> let result = engine.eval::("let x = new_ts(); x.update(); x")?; - println!("result: {}", result.field); // prints 42 + println!("result: {}", result.field); // prints 42 Ok(()) } From b23fd6e20a8769ce533b27ac259992ced27e781a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 19 Apr 2020 18:33:02 +0800 Subject: [PATCH 33/34] Reshuffle code sections. --- src/engine.rs | 238 +++++++++++++++++++++++--------------------------- 1 file changed, 111 insertions(+), 127 deletions(-) diff --git a/src/engine.rs b/src/engine.rs index 06e30342..ad995cda 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -344,6 +344,98 @@ pub(crate) fn calc_fn_def(fn_name: &str, params: usize) -> u64 { s.finish() } +/// Print/debug to stdout +fn default_print(s: &str) { + #[cfg(not(feature = "no_std"))] + println!("{}", s); +} + +/// Search for a variable within the scope, returning its value and index inside the Scope +fn search_scope<'a>( + scope: &'a Scope, + id: &str, + begin: Position, +) -> Result<(ScopeSource<'a>, Dynamic), Box> { + scope + .get(id) + .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(id.into(), begin))) +} + +/// Replace a character at an index position in a mutable string +fn str_replace_char(s: &mut String, idx: usize, new_ch: char) { + let mut chars: Vec = s.chars().collect(); + let ch = *chars.get(idx).expect("string index out of bounds"); + + // See if changed - if so, update the String + if ch != new_ch { + chars[idx] = new_ch; + s.clear(); + chars.iter().for_each(|&ch| s.push(ch)); + } +} + +/// Update the value at an index position +fn update_indexed_val( + mut target: Dynamic, + idx: IndexValue, + new_val: Dynamic, + pos: Position, +) -> Result> { + match target.get_mut() { + Union::Array(arr) => { + arr[idx.as_num()] = new_val; + } + Union::Map(map) => { + map.insert(idx.as_str(), new_val); + } + Union::Str(s) => { + // Value must be a character + let ch = new_val + .as_char() + .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; + str_replace_char(s, idx.as_num(), ch); + } + // All other variable types should be an error + _ => panic!("invalid type for indexing: {}", target.type_name()), + } + + Ok(target) +} + +/// Update the value at an index position in a variable inside the scope +fn update_indexed_scope_var( + scope: &mut Scope, + src: ScopeSource, + idx: IndexValue, + new_val: Dynamic, + pos: Position, +) -> Result> { + let target = scope.get_mut(src); + + match target.get_mut() { + // array_id[idx] = val + Union::Array(arr) => { + arr[idx.as_num()] = new_val; + } + // map_id[idx] = val + Union::Map(map) => { + map.insert(idx.as_str(), new_val); + } + // string_id[idx] = val + Union::Str(s) => { + // Value must be a character + let ch = new_val + .as_char() + .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; + str_replace_char(s, idx.as_num(), ch); + } + // All other variable types should be an error + _ => panic!("invalid type for indexing: {}", target.type_name()), + } + + Ok(Dynamic::from_unit()) +} + impl Engine { /// Create a new `Engine` pub fn new() -> Self { @@ -669,7 +761,7 @@ impl Engine { match dot_lhs { // id.??? Expr::Variable(id, pos) => { - let (entry, _) = Self::search_scope(scope, id, *pos)?; + let (entry, _) = search_scope(scope, id, *pos)?; // Avoid referencing scope which is used below as mut let entry = ScopeSource { name: id, ..entry }; @@ -696,8 +788,7 @@ impl Engine { } ScopeEntryType::Normal => { - let pos = dot_rhs.position(); - Self::update_indexed_scope_var(scope, src, index, val, pos)?; + update_indexed_scope_var(scope, src, index, val, dot_rhs.position())?; } } } @@ -713,17 +804,6 @@ impl Engine { } } - /// Search for a variable within the scope, returning its value and index inside the Scope - fn search_scope<'a>( - scope: &'a Scope, - id: &str, - begin: Position, - ) -> Result<(ScopeSource<'a>, Dynamic), Box> { - scope - .get(id) - .ok_or_else(|| Box::new(EvalAltResult::ErrorVariableNotFound(id.into(), begin))) - } - /// Get the value at the indexed position of a base type fn get_indexed_val( &self, @@ -746,6 +826,8 @@ impl Engine { .as_int() .map_err(|_| EvalAltResult::ErrorNumericIndexExpr(idx_expr.position()))?; + let arr_len = arr.len(); + if index >= 0 { arr.get(index as usize) .map(|v| { @@ -759,13 +841,11 @@ impl Engine { ) }) .ok_or_else(|| { - Box::new(EvalAltResult::ErrorArrayBounds(arr.len(), index, idx_pos)) + Box::new(EvalAltResult::ErrorArrayBounds(arr_len, index, idx_pos)) }) } else { Err(Box::new(EvalAltResult::ErrorArrayBounds( - arr.len(), - index, - idx_pos, + arr_len, index, idx_pos, ))) } } @@ -834,21 +914,13 @@ impl Engine { ) -> Result<(Option>, IndexValue, Dynamic), Box> { match lhs { // id[idx_expr] - Expr::Variable(id, _) => { + Expr::Variable(name, _) => { let (ScopeSource { typ, index, .. }, val) = - Self::search_scope(scope, &id, lhs.position())?; + search_scope(scope, &name, lhs.position())?; let (val, idx) = self.get_indexed_val(scope, fn_lib, &val, idx_expr, op_pos, level, false)?; - Ok(( - Some(ScopeSource { - name: &id, - typ, - index, - }), - idx, - val, - )) + Ok((Some(ScopeSource { name, typ, index }), idx, val)) } // (expr)[idx_expr] @@ -860,81 +932,6 @@ impl Engine { } } - /// Replace a character at an index position in a mutable string - fn str_replace_char(s: &mut String, idx: usize, new_ch: char) { - let mut chars: Vec = s.chars().collect(); - let ch = *chars.get(idx).expect("string index out of bounds"); - - // See if changed - if so, update the String - if ch != new_ch { - chars[idx] = new_ch; - s.clear(); - chars.iter().for_each(|&ch| s.push(ch)); - } - } - - /// Update the value at an index position in a variable inside the scope - fn update_indexed_scope_var( - scope: &mut Scope, - src: ScopeSource, - idx: IndexValue, - new_val: Dynamic, - pos: Position, - ) -> Result> { - let target = scope.get_mut(src); - - match target.get_mut() { - // array_id[idx] = val - Union::Array(arr) => { - arr[idx.as_num()] = new_val; - } - // map_id[idx] = val - Union::Map(map) => { - map.insert(idx.as_str(), new_val); - } - // string_id[idx] = val - Union::Str(s) => { - // Value must be a character - let ch = new_val - .as_char() - .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; - Self::str_replace_char(s, idx.as_num(), ch); - } - // All other variable types should be an error - _ => panic!("invalid type for indexing: {}", target.type_name()), - } - - Ok(Dynamic::from_unit()) - } - - /// Update the value at an index position - fn update_indexed_val( - mut target: Dynamic, - idx: IndexValue, - new_val: Dynamic, - pos: Position, - ) -> Result> { - match target.get_mut() { - Union::Array(arr) => { - arr[idx.as_num()] = new_val; - } - Union::Map(map) => { - map.insert(idx.as_str(), new_val); - } - Union::Str(s) => { - // Value must be a character - let ch = new_val - .as_char() - .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; - Self::str_replace_char(s, idx.as_num(), ch); - } - // All other variable types should be an error - _ => panic!("invalid type for indexing: {}", target.type_name()), - } - - Ok(target) - } - /// Chain-evaluate a dot setter fn dot_set_helper( &self, @@ -965,7 +962,7 @@ impl Engine { scope, fn_lib, &val, idx_expr, *op_pos, level, true, )?; - Self::update_indexed_val(val, index, new_val.clone(), val_pos) + update_indexed_val(val, index, new_val.clone(), val_pos) }) .and_then(|mut val| { let fn_name = make_setter(id); @@ -990,8 +987,8 @@ impl Engine { .and_then(|mut val| { self.dot_set_helper( scope, fn_lib, &mut val, rhs, new_val, val_pos, level, - ) - .map(|_| val) // Discard Ok return value + )?; + Ok(val) }) .and_then(|mut val| { let fn_name = make_setter(id); @@ -1017,7 +1014,7 @@ impl Engine { )?; // In case the expression mutated `target`, we need to update it back into the scope because it is cloned. - Self::update_indexed_val(v, index, value, val_pos) + update_indexed_val(v, index, value, val_pos) }) .and_then(|mut v| { let fn_name = make_setter(id); @@ -1063,7 +1060,7 @@ impl Engine { match dot_lhs { // id.??? Expr::Variable(id, pos) => { - let (src, mut target) = Self::search_scope(scope, id, *pos)?; + let (src, mut target) = search_scope(scope, id, *pos)?; match src.typ { ScopeEntryType::Constant => Err(Box::new( @@ -1072,14 +1069,9 @@ impl Engine { _ => { // Avoid referencing scope which is used below as mut let entry = ScopeSource { name: id, ..src }; + let this_ptr = &mut target; let value = self.dot_set_helper( - scope, - fn_lib, - &mut target, - dot_rhs, - new_val, - val_pos, - level, + scope, fn_lib, this_ptr, dot_rhs, new_val, val_pos, level, ); // In case the expression mutated `target`, we need to update it back into the scope because it is cloned. @@ -1109,7 +1101,7 @@ impl Engine { ))); } ScopeEntryType::Normal => { - Self::update_indexed_scope_var(scope, src, index, target, val_pos)?; + update_indexed_scope_var(scope, src, index, target, val_pos)?; } } } @@ -1198,7 +1190,7 @@ impl Engine { Expr::FloatConstant(f, _) => Ok(Dynamic::from_float(*f)), Expr::StringConstant(s, _) => Ok(Dynamic::from_string(s.to_string())), Expr::CharConstant(c, _) => Ok(Dynamic::from_char(*c)), - Expr::Variable(id, pos) => Self::search_scope(scope, id, *pos).map(|(_, val)| val), + Expr::Variable(id, pos) => search_scope(scope, id, *pos).map(|(_, val)| val), Expr::Property(_, _) => panic!("unexpected property."), // Statement block @@ -1261,9 +1253,7 @@ impl Engine { } ScopeEntryType::Normal => { let pos = rhs.position(); - Ok(Self::update_indexed_scope_var( - scope, src, index, rhs_val, pos, - )?) + Ok(update_indexed_scope_var(scope, src, index, rhs_val, pos)?) } } } else { @@ -1645,9 +1635,3 @@ impl Engine { .unwrap_or(name) } } - -/// Print/debug to stdout -fn default_print(s: &str) { - #[cfg(not(feature = "no_std"))] - println!("{}", s); -} From 9015499722fdc1a95513ac354f2bf104e491ec47 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 19 Apr 2020 18:33:09 +0800 Subject: [PATCH 34/34] Add more benchmarks. --- benches/engine.rs | 29 ++++++++++++++++++ benches/eval_array.rs | 63 ++++++++++++++++++++++++++++++++++++++ benches/eval_map.rs | 71 +++++++++++++++++++++++++++++++++++++++++++ benches/parsing.rs | 20 ++++++++++++ 4 files changed, 183 insertions(+) create mode 100644 benches/engine.rs create mode 100644 benches/eval_array.rs create mode 100644 benches/eval_map.rs diff --git a/benches/engine.rs b/benches/engine.rs new file mode 100644 index 00000000..e505129f --- /dev/null +++ b/benches/engine.rs @@ -0,0 +1,29 @@ +#![feature(test)] + +///! Test evaluating expressions +extern crate test; + +use rhai::{Array, Engine, Map, RegisterFn, INT}; +use test::Bencher; + +#[bench] +fn bench_engine_new(bench: &mut Bencher) { + bench.iter(|| Engine::new()); +} + +#[bench] +fn bench_engine_new_raw(bench: &mut Bencher) { + bench.iter(|| Engine::new_raw()); +} + +#[bench] +fn bench_engine_register_fn(bench: &mut Bencher) { + fn hello(a: INT, b: Array, c: Map) -> bool { + true + } + + bench.iter(|| { + let mut engine = Engine::new(); + engine.register_fn("hello", hello); + }); +} diff --git a/benches/eval_array.rs b/benches/eval_array.rs new file mode 100644 index 00000000..687f22f5 --- /dev/null +++ b/benches/eval_array.rs @@ -0,0 +1,63 @@ +#![feature(test)] + +///! Test evaluating expressions +extern crate test; + +use rhai::{Engine, OptimizationLevel}; +use test::Bencher; + +#[bench] +fn bench_eval_array_small_get(bench: &mut Bencher) { + let script = "let x = [1, 2, 3, 4, 5]; x[3]"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} + +#[bench] +fn bench_eval_array_small_set(bench: &mut Bencher) { + let script = "let x = [1, 2, 3, 4, 5]; x[3] = 42;"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} + +#[bench] +fn bench_eval_array_large_get(bench: &mut Bencher) { + let script = r#"let x = [ 1, 2.345, "hello", true, + [ 1, 2, 3, [ "hey", [ "deeply", "nested" ], "jude" ] ] + ]; + x[4][3][1][1] + "#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} + +#[bench] +fn bench_eval_array_large_set(bench: &mut Bencher) { + let script = r#"let x = [ 1, 2.345, "hello", true, + [ 1, 2, 3, [ "hey", [ "deeply", "nested" ], "jude" ] ] + ]; + x[4] = 42 + "#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} diff --git a/benches/eval_map.rs b/benches/eval_map.rs new file mode 100644 index 00000000..afe8d676 --- /dev/null +++ b/benches/eval_map.rs @@ -0,0 +1,71 @@ +#![feature(test)] + +///! Test evaluating expressions +extern crate test; + +use rhai::{Engine, OptimizationLevel}; +use test::Bencher; + +#[bench] +fn bench_eval_map_small_get(bench: &mut Bencher) { + let script = "let x = #{a:1}; x.a"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} + +#[bench] +fn bench_eval_map_small_set(bench: &mut Bencher) { + let script = "let x = #{a:1}; x.a = 42;"; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} + +#[bench] +fn bench_eval_map_large_get(bench: &mut Bencher) { + let script = r#"let x = #{ + a:1, + b:2.345, + c:"hello", + d: true, + e: #{ x: 42, "y$@#%": (), z: [ 1, 2, 3, #{}, #{ "hey": "jude" }]} + }; + x["e"].z[4].hey + "#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} + +#[bench] +fn bench_eval_map_large_set(bench: &mut Bencher) { + let script = r#"let x = #{ + a:1, + b:2.345, + c:"hello", + d: true, + e: #{ x: 42, "y$@#%": (), z: [ 1, 2, 3, #{}, #{ "hey": "jude" }]} + }; + x["e"].z[4].hey = 42; + "#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + let ast = engine.compile(script).unwrap(); + + bench.iter(|| engine.consume_ast(&ast).unwrap()); +} diff --git a/benches/parsing.rs b/benches/parsing.rs index 280b9f45..7acf47b3 100644 --- a/benches/parsing.rs +++ b/benches/parsing.rs @@ -42,6 +42,26 @@ fn bench_parse_full(bench: &mut Bencher) { bench.iter(|| engine.compile_expression(script).unwrap()); } +#[bench] +fn bench_parse_array(bench: &mut Bencher) { + let script = r#"[1, 234.789, "hello", false, [ 9, 8, 7] ]"#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + bench.iter(|| engine.compile_expression(script).unwrap()); +} + +#[bench] +fn bench_parse_map(bench: &mut Bencher) { + let script = r#"#{a: 1, b: 42, c: "hi", "dc%$& ": "strange", x: true, y: 123.456 }"#; + + let mut engine = Engine::new(); + engine.set_optimization_level(OptimizationLevel::None); + + bench.iter(|| engine.compile_expression(script).unwrap()); +} + #[bench] fn bench_parse_primes(bench: &mut Bencher) { let script = r#"