diff --git a/CHANGELOG.md b/CHANGELOG.md index 3680c038..9457c558 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ Bug fixes --------- * Fixed infinite loop in certain script optimizations. +* Building for `no-std` no longer requires patching `smartstring`. +* Parsing a lone `return` or `throw` without a semicolon at the end of a block no longer raises an error. Breaking changes ---------------- diff --git a/codegen/src/module.rs b/codegen/src/module.rs index df065797..f78dad1f 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -205,7 +205,6 @@ impl Parse for Module { } } -#[allow(dead_code)] impl Module { pub fn attrs(&self) -> &Vec { &self.mod_all.attrs @@ -300,22 +299,27 @@ impl Module { } } + #[allow(dead_code)] pub fn name(&self) -> &syn::Ident { &self.mod_all.ident } + #[allow(dead_code)] pub fn consts(&self) -> &[ExportedConst] { &self.consts } + #[allow(dead_code)] pub fn fns(&self) -> &[ExportedFn] { &self.fns } + #[allow(dead_code)] pub fn sub_modules(&self) -> &[Module] { &self.sub_modules } + #[allow(dead_code)] pub fn content(&self) -> Option<&Vec> { match self.mod_all { syn::ItemMod { diff --git a/no_std/no_std_test/Cargo.toml b/no_std/no_std_test/Cargo.toml index 94087a70..6a85b79d 100644 --- a/no_std/no_std_test/Cargo.toml +++ b/no_std/no_std_test/Cargo.toml @@ -36,7 +36,3 @@ inherits = "release" [profile.macos] inherits = "release" lto = "fat" - -[patch.crates-io] -# Patch smartstring wth a PR fix because it doesn't properly handle no-std builds. -smartstring = { git = "https://github.com/rhaiscript/smartstring" } diff --git a/no_std/no_std_test/README.md b/no_std/no_std_test/README.md index 0a12e48b..1c89b11f 100644 --- a/no_std/no_std_test/README.md +++ b/no_std/no_std_test/README.md @@ -12,7 +12,7 @@ To Compile The nightly compiler is required: ```bash -cargo +nightly build --release --profile unix -Z unstable-features +cargo +nightly build --profile unix -Z unstable-options ``` Available profiles are: `unix`, `windows` and `macos`. diff --git a/src/ast.rs b/src/ast.rs index 87af9821..3bd1d9b9 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1394,20 +1394,11 @@ pub struct CustomExpr { /// List of keywords. pub keywords: StaticVec, /// Is the current [`Scope`][crate::Scope] modified? - pub scope_changed: bool, + pub scope_may_be_changed: bool, /// List of tokens actually parsed. pub tokens: StaticVec, } -impl CustomExpr { - /// Convert this into a [`Expr::Custom`]. - #[inline(always)] - #[must_use] - pub fn into_custom_syntax_expr(self, pos: Position) -> Expr { - Expr::Custom(self.into(), pos) - } -} - /// _(INTERNALS)_ A binary expression. /// Exported under the `internals` feature only. /// diff --git a/src/custom_syntax.rs b/src/custom_syntax.rs index 36c97003..7e1aa614 100644 --- a/src/custom_syntax.rs +++ b/src/custom_syntax.rs @@ -156,33 +156,34 @@ pub struct CustomSyntax { /// Custom syntax implementation function. pub func: Shared, /// Any variables added/removed in the scope? - pub scope_changed: bool, + pub scope_may_be_changed: bool, } impl Engine { /// Register a custom syntax with the [`Engine`]. /// /// * `keywords` holds a slice of strings that define the custom syntax. - /// * `scope_changed` specifies variables have been added/removed by this custom syntax. + /// * `scope_may_be_changed` specifies variables _may_ be added/removed by this custom syntax. /// * `func` is the implementation function. /// - /// # Caveat - Do not change beyond block scope + /// ## Note on `scope_may_be_changed` /// - /// If `scope_changed` is `true`, then the current [`Scope`][crate::Scope] is assumed to be - /// modified by this custom syntax. + /// If `scope_may_be_changed` is `true`, then _size_ of the current [`Scope`][crate::Scope] + /// _may_ be modified by this custom syntax. /// - /// Adding new variables and/or removing variables are allowed. Simply modifying the values of - /// variables does NOT count, so `false` should be passed in this case. + /// Adding new variables and/or removing variables count. /// - /// However, only variables declared within the current _block scope_ should be touched, - /// since they all go away at the end of the block. + /// Simply modifying the values of existing variables does NOT count, as the _size_ of the + /// current [`Scope`][crate::Scope] is unchanged, so `false` should be passed. /// - /// Variables in parent blocks should be left untouched as they persist beyond the current block. + /// Replacing one variable with another (i.e. adding a new variable and removing one variable at + /// the same time so that the total _size_ of the [`Scope`][crate::Scope] is unchanged) also + /// does NOT count, so `false` should be passed. #[must_use] pub fn register_custom_syntax + Into>( &mut self, keywords: &[S], - scope_changed: bool, + scope_may_be_changed: bool, func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static, ) -> Result<&mut Self, ParseError> { let keywords = keywords.as_ref(); @@ -289,7 +290,7 @@ impl Engine { Ok(Some(segments[stream.len()].clone())) } }, - scope_changed, + scope_may_be_changed, func, ); @@ -301,7 +302,7 @@ impl Engine { /// /// This function is very low level. /// - /// * `scope_changed` specifies variables have been added/removed by this custom syntax. + /// * `scope_may_be_changed` specifies variables have been added/removed by this custom syntax. /// * `parse` is the parsing function. /// * `func` is the implementation function. /// @@ -313,7 +314,7 @@ impl Engine { parse: impl Fn(&[ImmutableString], &str) -> Result, ParseError> + SendSync + 'static, - scope_changed: bool, + scope_may_be_changed: bool, func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static, ) -> &mut Self { self.custom_syntax.insert( @@ -321,7 +322,7 @@ impl Engine { CustomSyntax { parse: Box::new(parse), func: (Box::new(func) as Box).into(), - scope_changed, + scope_may_be_changed, } .into(), ); diff --git a/src/dynamic.rs b/src/dynamic.rs index 81dde6f6..d4c0732e 100644 --- a/src/dynamic.rs +++ b/src/dynamic.rs @@ -508,6 +508,8 @@ impl Hash for Dynamic { Union::Int(ref i, _, _) => i.hash(state), #[cfg(not(feature = "no_float"))] Union::Float(ref f, _, _) => f.hash(state), + #[cfg(feature = "decimal")] + Union::Decimal(ref d, _, _) => d.hash(state), #[cfg(not(feature = "no_index"))] Union::Array(ref a, _, _) => a.as_ref().hash(state), #[cfg(not(feature = "no_object"))] @@ -522,49 +524,54 @@ impl Hash for Dynamic { #[cfg(feature = "sync")] Union::Shared(ref cell, _, _) => (*cell.read().unwrap()).hash(state), - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - Union::Variant(ref value, _, _) => { - let value_any = (***value).as_any(); - let type_id = value_any.type_id(); + Union::Variant(ref _value, _, _) => { + #[cfg(not(feature = "only_i32"))] + #[cfg(not(feature = "only_i64"))] + { + let value_any = (***_value).as_any(); + let type_id = value_any.type_id(); - if type_id == TypeId::of::() { - TypeId::of::().hash(state); - value_any.downcast_ref::().expect(CHECKED).hash(state); - } else if type_id == TypeId::of::() { - TypeId::of::().hash(state); - value_any.downcast_ref::().expect(CHECKED).hash(state); - } else if type_id == TypeId::of::() { - TypeId::of::().hash(state); - value_any.downcast_ref::().expect(CHECKED).hash(state); - } else if type_id == TypeId::of::() { - TypeId::of::().hash(state); - value_any.downcast_ref::().expect(CHECKED).hash(state); - } else if type_id == TypeId::of::() { - TypeId::of::().hash(state); - value_any.downcast_ref::().expect(CHECKED).hash(state); - } else if type_id == TypeId::of::() { - TypeId::of::().hash(state); - value_any.downcast_ref::().expect(CHECKED).hash(state); - } else if type_id == TypeId::of::() { - TypeId::of::().hash(state); - value_any.downcast_ref::().expect(CHECKED).hash(state); - } else if type_id == TypeId::of::() { - TypeId::of::().hash(state); - value_any.downcast_ref::().expect(CHECKED).hash(state); + if type_id == TypeId::of::() { + TypeId::of::().hash(state); + value_any.downcast_ref::().expect(CHECKED).hash(state); + } else if type_id == TypeId::of::() { + TypeId::of::().hash(state); + value_any.downcast_ref::().expect(CHECKED).hash(state); + } else if type_id == TypeId::of::() { + TypeId::of::().hash(state); + value_any.downcast_ref::().expect(CHECKED).hash(state); + } else if type_id == TypeId::of::() { + TypeId::of::().hash(state); + value_any.downcast_ref::().expect(CHECKED).hash(state); + } else if type_id == TypeId::of::() { + TypeId::of::().hash(state); + value_any.downcast_ref::().expect(CHECKED).hash(state); + } else if type_id == TypeId::of::() { + TypeId::of::().hash(state); + value_any.downcast_ref::().expect(CHECKED).hash(state); + } else if type_id == TypeId::of::() { + TypeId::of::().hash(state); + value_any.downcast_ref::().expect(CHECKED).hash(state); + } else if type_id == TypeId::of::() { + TypeId::of::().hash(state); + value_any.downcast_ref::().expect(CHECKED).hash(state); + } + + #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] + if type_id == TypeId::of::() { + TypeId::of::().hash(state); + value_any.downcast_ref::().expect(CHECKED).hash(state); + } else if type_id == TypeId::of::() { + TypeId::of::().hash(state); + value_any.downcast_ref::().expect(CHECKED).hash(state); + } } - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] - if type_id == TypeId::of::() { - TypeId::of::().hash(state); - value_any.downcast_ref::().expect(CHECKED).hash(state); - } else if type_id == TypeId::of::() { - TypeId::of::().hash(state); - value_any.downcast_ref::().expect(CHECKED).hash(state); - } + unimplemented!("a custom type cannot be hashed") } - _ => unimplemented!("{} cannot be hashed", self.type_name()), + #[cfg(not(feature = "no_std"))] + Union::TimeStamp(_, _, _) => unimplemented!("{} cannot be hashed", self.type_name()), } } } diff --git a/src/engine.rs b/src/engine.rs index fc6c1b44..91b20d8b 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -24,6 +24,7 @@ use std::{ collections::{BTreeMap, BTreeSet}, fmt, hash::{Hash, Hasher}, + iter::{FromIterator, Rev, Zip}, num::{NonZeroU8, NonZeroUsize}, ops::{Deref, DerefMut}, }; @@ -62,6 +63,12 @@ pub struct Imports { } impl Imports { + /// Create a new stack of imported [modules][Module]. + #[inline(always)] + #[must_use] + pub fn new() -> Self { + Default::default() + } /// Get the length of this stack of imported [modules][Module]. #[inline(always)] #[must_use] @@ -74,20 +81,20 @@ impl Imports { pub fn is_empty(&self) -> bool { self.keys.is_empty() } - /// Get the imported [modules][Module] at a particular index. + /// Get the imported [module][Module] at a particular index. #[inline(always)] #[must_use] pub fn get(&self, index: usize) -> Option> { self.modules.get(index).cloned() } - /// Get the imported [modules][Module] at a particular index. + /// Get the imported [module][Module] at a particular index. #[allow(dead_code)] #[inline(always)] #[must_use] pub(crate) fn get_mut(&mut self, index: usize) -> Option<&mut Shared> { self.modules.get_mut(index) } - /// Get the index of an imported [modules][Module] by name. + /// Get the index of an imported [module][Module] by name. #[inline(always)] #[must_use] pub fn find(&self, name: &str) -> Option { @@ -97,7 +104,7 @@ impl Imports { .rev() .find_map(|(i, key)| if key == name { Some(i) } else { None }) } - /// Push an imported [modules][Module] onto the stack. + /// Push an imported [module][Module] onto the stack. #[inline(always)] pub fn push(&mut self, name: impl Into, module: impl Into>) { self.keys.push(name.into()); @@ -134,15 +141,6 @@ impl Imports { pub(crate) fn scan_raw(&self) -> impl Iterator)> { self.keys.iter().zip(self.modules.iter()) } - /// Get a consuming iterator to this stack of imported [modules][Module] in reverse order. - #[inline(always)] - #[must_use] - pub fn into_iter(self) -> impl Iterator)> { - self.keys - .into_iter() - .rev() - .zip(self.modules.into_iter().rev()) - } /// Does the specified function hash key exist in this stack of imported [modules][Module]? #[allow(dead_code)] #[inline(always)] @@ -150,7 +148,7 @@ impl Imports { pub fn contains_fn(&self, hash: u64) -> bool { self.modules.iter().any(|m| m.contains_qualified_fn(hash)) } - /// Get specified function via its hash key. + /// Get the specified function via its hash key from this stack of imported [modules][Module]. #[inline(always)] #[must_use] pub fn get_fn(&self, hash: u64) -> Option<(&CallableFunction, Option<&Identifier>)> { @@ -167,7 +165,8 @@ impl Imports { pub fn contains_iter(&self, id: TypeId) -> bool { self.modules.iter().any(|m| m.contains_qualified_iter(id)) } - /// Get the specified [`TypeId`][std::any::TypeId] iterator. + /// Get the specified [`TypeId`][std::any::TypeId] iterator from this stack of imported + /// [modules][Module]. #[inline(always)] #[must_use] pub fn get_iter(&self, id: TypeId) -> Option { @@ -178,6 +177,37 @@ impl Imports { } } +impl IntoIterator for Imports { + type Item = (Identifier, Shared); + type IntoIter = + Zip>, Rev; 4]>>>; + + #[inline(always)] + fn into_iter(self) -> Self::IntoIter { + self.keys + .into_iter() + .rev() + .zip(self.modules.into_iter().rev()) + } +} + +impl, M: Into>> FromIterator<(K, M)> for Imports { + fn from_iter>(iter: T) -> Self { + let mut lib = Self::new(); + lib.extend(iter); + lib + } +} + +impl, M: Into>> Extend<(K, M)> for Imports { + fn extend>(&mut self, iter: T) { + iter.into_iter().for_each(|(k, m)| { + self.keys.push(k.into()); + self.modules.push(m.into()); + }) + } +} + impl fmt::Debug for Imports { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("Imports")?; @@ -348,7 +378,7 @@ impl<'a> Target<'a> { #[allow(dead_code)] #[inline(always)] #[must_use] - pub fn is_ref(&self) -> bool { + pub const fn is_ref(&self) -> bool { match self { Self::RefMut(_) => true, #[cfg(not(feature = "no_closure"))] @@ -363,7 +393,7 @@ impl<'a> Target<'a> { /// Is the `Target` a temp value? #[inline(always)] #[must_use] - pub fn is_temp_value(&self) -> bool { + pub const fn is_temp_value(&self) -> bool { match self { Self::RefMut(_) => false, #[cfg(not(feature = "no_closure"))] @@ -578,7 +608,12 @@ impl> From for Target<'_> { } } -/// An entry in a function resolution cache. +/// _(INTERNALS)_ An entry in a function resolution cache. +/// Exported under the `internals` feature only. +/// +/// # Volatile Data Structure +/// +/// This type is volatile and may change. #[derive(Debug, Clone)] pub struct FnResolutionCacheEntry { /// Function. @@ -587,7 +622,12 @@ pub struct FnResolutionCacheEntry { pub source: Option, } -/// A function resolution cache. +/// _(INTERNALS)_ A function resolution cache. +/// Exported under the `internals` feature only. +/// +/// # Volatile Data Structure +/// +/// This type is volatile and may change. pub type FnResolutionCache = BTreeMap>>; /// _(INTERNALS)_ A type that holds all the current states of the [`Engine`]. @@ -597,15 +637,15 @@ pub type FnResolutionCache = BTreeMap>>; /// /// This type is volatile and may change. #[derive(Debug, Clone, Default)] -pub struct State { +pub struct EvalState { /// Source of the current context. pub source: Option, - /// Normally, access to variables are parsed with a relative offset into the scope to avoid a lookup. - /// In some situation, e.g. after running an `eval` statement, subsequent offsets become mis-aligned. - /// When that happens, this flag is turned on to force a scope lookup by name. - pub always_search: bool, - /// Level of the current scope. The global (root) level is zero, a new block - /// (or function call) is one level higher, and so on. + /// Normally, access to variables are parsed with a relative offset into the [`Scope`] to avoid a lookup. + /// In some situation, e.g. after running an `eval` statement, subsequent offsets may become mis-aligned. + /// When that happens, this flag is turned on to force a [`Scope`] search by name. + pub always_search_scope: bool, + /// Level of the current scope. The global (root) level is zero, a new block (or function call) + /// is one level higher, and so on. pub scope_level: usize, /// Number of operations performed. pub operations: u64, @@ -614,15 +654,30 @@ pub struct State { /// Embedded module resolver. #[cfg(not(feature = "no_module"))] pub resolver: Option>, - /// Function resolution cache and free list. - fn_resolution_caches: StaticVec, + /// Stack of function resolution caches. + fn_resolution_caches: Vec, } -impl State { +impl EvalState { + /// Create a new [`EvalState`]. + #[inline(always)] + #[must_use] + pub const fn new() -> Self { + Self { + source: None, + always_search_scope: false, + scope_level: 0, + operations: 0, + modules: 0, + #[cfg(not(feature = "no_module"))] + resolver: None, + fn_resolution_caches: Vec::new(), + } + } /// Is the state currently at global (root) level? #[inline(always)] #[must_use] - pub fn is_global(&self) -> bool { + pub const fn is_global(&self) -> bool { self.scope_level == 0 } /// Get a mutable reference to the current function resolution cache. @@ -729,13 +784,13 @@ pub struct EvalContext<'a, 'x, 'px, 'm, 's, 'b, 't, 'pt> { pub(crate) engine: &'a Engine, pub(crate) scope: &'x mut Scope<'px>, pub(crate) mods: &'m mut Imports, - pub(crate) state: &'s mut State, + pub(crate) state: &'s mut EvalState, pub(crate) lib: &'b [&'b Module], pub(crate) this_ptr: &'t mut Option<&'pt mut Dynamic>, pub(crate) level: usize, } -impl<'x, 'px> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, '_> { +impl<'x, 'px, 'pt> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, 'pt> { /// The current [`Engine`]. #[inline(always)] #[must_use] @@ -796,6 +851,12 @@ impl<'x, 'px> EvalContext<'_, 'x, 'px, '_, '_, '_, '_, '_> { pub fn this_ptr(&self) -> Option<&Dynamic> { self.this_ptr.as_ref().map(|v| &**v) } + /// Mutable reference to the current bound `this` pointer, if any. + #[inline(always)] + #[must_use] + pub fn this_ptr_mut(&mut self) -> Option<&mut &'pt mut Dynamic> { + self.this_ptr.as_mut() + } /// The current nesting level of function calls. #[inline(always)] #[must_use] @@ -1002,13 +1063,13 @@ impl Engine { pub(crate) fn search_imports( &self, mods: &Imports, - state: &mut State, + state: &mut EvalState, namespace: &NamespaceRef, ) -> Option> { let root = &namespace[0].name; // Qualified - check if the root module is directly indexed - let index = if state.always_search { + let index = if state.always_search_scope { None } else { namespace.index() @@ -1037,7 +1098,7 @@ impl Engine { &self, scope: &'s mut Scope, mods: &mut Imports, - state: &mut State, + state: &mut EvalState, lib: &[&Module], this_ptr: &'s mut Option<&mut Dynamic>, expr: &Expr, @@ -1087,7 +1148,7 @@ impl Engine { &self, scope: &'s mut Scope, mods: &mut Imports, - state: &mut State, + state: &mut EvalState, lib: &[&Module], this_ptr: &'s mut Option<&mut Dynamic>, expr: &Expr, @@ -1103,7 +1164,7 @@ impl Engine { EvalAltResult::ErrorUnboundThis(*pos).into() } } - _ if state.always_search => (0, expr.position()), + _ if state.always_search_scope => (0, expr.position()), Expr::Variable(Some(i), pos, _) => (i.get() as usize, *pos), Expr::Variable(None, pos, v) => (v.0.map(NonZeroUsize::get).unwrap_or(0), *pos), _ => unreachable!("Expr::Variable expected, but gets {:?}", expr), @@ -1160,7 +1221,7 @@ impl Engine { fn eval_dot_index_chain_helper( &self, mods: &mut Imports, - state: &mut State, + state: &mut EvalState, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, target: &mut Target, @@ -1592,7 +1653,7 @@ impl Engine { &self, scope: &mut Scope, mods: &mut Imports, - state: &mut State, + state: &mut EvalState, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, @@ -1656,7 +1717,7 @@ impl Engine { &self, scope: &mut Scope, mods: &mut Imports, - state: &mut State, + state: &mut EvalState, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, @@ -1785,7 +1846,7 @@ impl Engine { fn get_indexed_mut<'t>( &self, mods: &mut Imports, - state: &mut State, + state: &mut EvalState, lib: &[&Module], target: &'t mut Dynamic, mut idx: Dynamic, @@ -1843,7 +1904,7 @@ impl Engine { #[cfg(not(feature = "no_object"))] Dynamic(Union::Map(map, _, _)) => { // val_map[idx] - let index = &*idx.read_lock::().ok_or_else(|| { + let index = idx.read_lock::().ok_or_else(|| { self.make_type_mismatch_err::(idx.type_name(), idx_pos) })?; @@ -1957,7 +2018,7 @@ impl Engine { &self, scope: &mut Scope, mods: &mut Imports, - state: &mut State, + state: &mut EvalState, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, @@ -2158,7 +2219,7 @@ impl Engine { &self, scope: &mut Scope, mods: &mut Imports, - state: &mut State, + state: &mut EvalState, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, statements: &[Stmt], @@ -2170,7 +2231,7 @@ impl Engine { } let mut _extra_fn_resolution_cache = false; - let prev_always_search = state.always_search; + let prev_always_search_scope = state.always_search_scope; let prev_scope_len = scope.len(); let prev_mods_len = mods.len(); @@ -2223,7 +2284,7 @@ impl Engine { // The impact of new local variables goes away at the end of a block // because any new variables introduced will go out of scope - state.always_search = prev_always_search; + state.always_search_scope = prev_always_search_scope; } result @@ -2235,7 +2296,7 @@ impl Engine { pub(crate) fn eval_op_assignment( &self, mods: &mut Imports, - state: &mut State, + state: &mut EvalState, lib: &[&Module], op_info: Option, op_pos: Position, @@ -2310,7 +2371,7 @@ impl Engine { &self, scope: &mut Scope, mods: &mut Imports, - state: &mut State, + state: &mut EvalState, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, stmt: &Stmt, @@ -3077,7 +3138,7 @@ impl Engine { #[must_use] pub(crate) fn inc_operations( &self, - state: &mut State, + state: &mut EvalState, pos: Position, ) -> Result<(), Box> { state.operations += 1; diff --git a/src/engine_api.rs b/src/engine_api.rs index 6a1b350b..ab896a2a 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -1,7 +1,7 @@ //! Module that defines the extern API of [`Engine`]. use crate::dynamic::Variant; -use crate::engine::{EvalContext, Imports, State}; +use crate::engine::{EvalContext, EvalState, Imports}; use crate::fn_call::FnCallArgs; use crate::fn_native::SendSync; use crate::fn_register::RegisterNativeFunction; @@ -1749,7 +1749,7 @@ impl Engine { ast: &'a AST, level: usize, ) -> RhaiResult { - let mut state: State = Default::default(); + let mut state = EvalState::new(); state.source = ast.source_raw().cloned(); #[cfg(not(feature = "no_module"))] { @@ -1831,7 +1831,7 @@ impl Engine { ast: &AST, ) -> Result<(), Box> { let mods = &mut Default::default(); - let mut state: State = Default::default(); + let mut state = EvalState::new(); state.source = ast.source_raw().cloned(); #[cfg(not(feature = "no_module"))] { @@ -2004,7 +2004,7 @@ impl Engine { this_ptr: &mut Option<&mut Dynamic>, args: &mut FnCallArgs, ) -> RhaiResult { - let state = &mut Default::default(); + let state = &mut EvalState::new(); let mods = &mut Default::default(); let lib = &[ast.lib()]; let statements = ast.statements(); diff --git a/src/error.rs b/src/error.rs index 6dd09b70..acdb4499 100644 --- a/src/error.rs +++ b/src/error.rs @@ -98,8 +98,10 @@ impl Error for EvalAltResult {} impl fmt::Display for EvalAltResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::ErrorSystem(s, err) if s.is_empty() => write!(f, "{}", err)?, - Self::ErrorSystem(s, err) => write!(f, "{}: {}", s, err)?, + Self::ErrorSystem(s, err) => match s.as_str() { + "" => write!(f, "{}", err), + s => write!(f, "{}: {}", s, err), + }?, Self::ErrorParsing(p, _) => write!(f, "Syntax error: {}", p)?, @@ -128,76 +130,74 @@ impl fmt::Display for EvalAltResult { Self::ErrorDataRace(s, _) => { write!(f, "Data race detected when accessing variable: {}", s)? } - Self::ErrorDotExpr(s, _) if !s.is_empty() => f.write_str(s)?, + Self::ErrorDotExpr(s, _) => match s.as_str() { + "" => f.write_str("Malformed dot expression"), + s => f.write_str(s), + }?, Self::ErrorIndexingType(s, _) => write!(f, "Indexer not registered for '{}'", s)?, Self::ErrorUnboundThis(_) => f.write_str("'this' is not bound")?, Self::ErrorFor(_) => f.write_str("For loop expects a type with an iterator defined")?, - Self::ErrorDotExpr(_, _) => f.write_str("Malformed dot expression")?, Self::ErrorTooManyOperations(_) => f.write_str("Too many operations")?, Self::ErrorTooManyModules(_) => f.write_str("Too many modules imported")?, Self::ErrorStackOverflow(_) => f.write_str("Stack overflow")?, Self::ErrorTerminated(_, _) => f.write_str("Script terminated")?, - Self::ErrorRuntime(d, _) if d.is::() => { - let s = &*d - .read_lock::() - .expect("never fails because the type was checked"); - - if s.is_empty() { - f.write_str("Runtime error")? - } else { - write!(f, "Runtime error: {}", s)? - } - } Self::ErrorRuntime(d, _) if d.is::<()>() => f.write_str("Runtime error")?, + Self::ErrorRuntime(d, _) + if d.read_lock::() + .map_or(false, |v| v.is_empty()) => + { + write!(f, "Runtime error")? + } Self::ErrorRuntime(d, _) => write!(f, "Runtime error: {}", d)?, Self::ErrorAssignmentToConstant(s, _) => write!(f, "Cannot modify constant {}", s)?, - Self::ErrorMismatchOutputType(s, r, _) => { - write!(f, "Output type is incorrect: {} (expecting {})", r, s)? - } - Self::ErrorMismatchDataType(s, r, _) if r.is_empty() => { - write!(f, "Data type is incorrect, expecting {}", s)? - } - Self::ErrorMismatchDataType(s, r, _) if s.is_empty() => { - write!(f, "Data type is incorrect: {}", r)? - } - Self::ErrorMismatchDataType(s, r, _) => { - write!(f, "Data type is incorrect: {} (expecting {})", r, s)? - } - Self::ErrorArithmetic(s, _) => f.write_str(s)?, + Self::ErrorMismatchOutputType(s, r, _) => match (r.as_str(), s.as_str()) { + ("", s) => write!(f, "Output type is incorrect, expecting {}", s), + (r, "") => write!(f, "Output type is incorrect: {}", r), + (r, s) => write!(f, "Output type is incorrect: {} (expecting {})", r, s), + }?, + Self::ErrorMismatchDataType(s, r, _) => match (r.as_str(), s.as_str()) { + ("", s) => write!(f, "Data type is incorrect, expecting {}", s), + (r, "") => write!(f, "Data type is incorrect: {}", r), + (r, s) => write!(f, "Data type is incorrect: {} (expecting {})", r, s), + }?, + Self::ErrorArithmetic(s, _) => match s.as_str() { + "" => f.write_str("Arithmetic error"), + s => f.write_str(s), + }?, - Self::LoopBreak(true, _) => f.write_str("Break statement not inside a loop")?, - Self::LoopBreak(false, _) => f.write_str("Continue statement not inside a loop")?, + Self::LoopBreak(true, _) => f.write_str("'break' not inside a loop")?, + Self::LoopBreak(false, _) => f.write_str("'continue' not inside a loop")?, - Self::Return(_, _) => f.write_str("NOT AN ERROR - Function returns value")?, + Self::Return(_, _) => f.write_str("NOT AN ERROR - function returns value")?, - Self::ErrorArrayBounds(0, index, _) => { - write!(f, "Array index {} out of bounds: array is empty", index)? - } - Self::ErrorArrayBounds(1, index, _) => write!( - f, - "Array index {} out of bounds: only 1 element in the array", - index - )?, - Self::ErrorArrayBounds(max, index, _) => write!( - f, - "Array index {} out of bounds: only {} elements in the array", - index, max - )?, - Self::ErrorStringBounds(0, index, _) => { - write!(f, "String index {} out of bounds: string is empty", index)? - } - Self::ErrorStringBounds(1, index, _) => write!( - f, - "String index {} out of bounds: only 1 character in the string", - index - )?, - Self::ErrorStringBounds(max, index, _) => write!( - f, - "String index {} out of bounds: only {} characters in the string", - index, max - )?, + Self::ErrorArrayBounds(max, index, _) => match max { + 0 => write!(f, "Array index {} out of bounds: array is empty", index), + 1 => write!( + f, + "Array index {} out of bounds: only 1 element in the array", + index + ), + _ => write!( + f, + "Array index {} out of bounds: only {} elements in the array", + index, max + ), + }?, + Self::ErrorStringBounds(max, index, _) => match max { + 0 => write!(f, "String index {} out of bounds: string is empty", index), + 1 => write!( + f, + "String index {} out of bounds: only 1 character in the string", + index + ), + _ => write!( + f, + "String index {} out of bounds: only {} characters in the string", + index, max + ), + }?, Self::ErrorBitFieldBounds(max, index, _) => write!( f, "Bit-field index {} out of bounds: only {} bits in the bit-field", diff --git a/src/error_parsing.rs b/src/error_parsing.rs index 5b797baa..d89ca226 100644 --- a/src/error_parsing.rs +++ b/src/error_parsing.rs @@ -125,6 +125,9 @@ pub enum ParseErrorType { VariableExpected, /// An identifier is a reserved keyword. Reserved(String), + /// An expression is of the wrong type. + /// Wrapped values are the type requested and type of the actual result. + MismatchedType(String, String), /// Missing an expression. Wrapped value is the expression type. ExprExpected(String), /// Defining a doc-comment in an appropriate place (e.g. not at global level). @@ -194,12 +197,22 @@ impl fmt::Display for ParseErrorType { Self::UnknownOperator(s) => write!(f, "Unknown operator: '{}'", s), - Self::MalformedCallExpr(s) if s.is_empty() => f.write_str("Invalid expression in function call arguments"), - Self::MalformedIndexExpr(s) if s.is_empty() => f.write_str("Invalid index in indexing expression"), - Self::MalformedInExpr(s) if s.is_empty() => f.write_str("Invalid 'in' expression"), - Self::MalformedCapture(s) if s.is_empty() => f.write_str("Invalid capturing"), - - Self::MalformedCallExpr(s) | Self::MalformedIndexExpr(s) | Self::MalformedInExpr(s) | Self::MalformedCapture(s) => f.write_str(s), + Self::MalformedCallExpr(s) => match s.as_str() { + "" => f.write_str("Invalid expression in function call arguments"), + s => f.write_str(s) + }, + Self::MalformedIndexExpr(s) => match s.as_str() { + "" => f.write_str("Invalid index in indexing expression"), + s => f.write_str(s) + }, + Self::MalformedInExpr(s) => match s.as_str() { + "" => f.write_str("Invalid 'in' expression"), + s => f.write_str(s) + }, + Self::MalformedCapture(s) => match s.as_str() { + "" => f.write_str("Invalid capturing"), + s => f.write_str(s) + }, Self::FnDuplicatedDefinition(s, n) => { write!(f, "Function '{}' with ", s)?; @@ -209,25 +222,30 @@ impl fmt::Display for ParseErrorType { _ => write!(f, "{} parameters already exists", n), } } - - Self::FnMissingBody(s) if s.is_empty() => f.write_str("Expecting body statement block for anonymous function"), - Self::FnMissingBody(s) => write!(f, "Expecting body statement block for function '{}'", s), - + Self::FnMissingBody(s) => match s.as_str() { + "" => f.write_str("Expecting body statement block for anonymous function"), + s => write!(f, "Expecting body statement block for function '{}'", s) + }, Self::FnMissingParams(s) => write!(f, "Expecting parameters for function '{}'", s), Self::FnDuplicatedParam(s, arg) => write!(f, "Duplicated parameter '{}' for function '{}'", arg, s), + Self::DuplicatedProperty(s) => write!(f, "Duplicated property '{}' for object map literal", s), Self::DuplicatedSwitchCase => f.write_str("Duplicated switch case"), Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name '{}'", s), + Self::MismatchedType(r, a) => write!(f, "Expecting {}, not {}", r, a), Self::ExprExpected(s) => write!(f, "Expecting {} expression", s), Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s), Self::MissingSymbol(s) => f.write_str(s), - Self::AssignmentToConstant(s) if s.is_empty() => f.write_str("Cannot assign to a constant value"), - Self::AssignmentToConstant(s) => write!(f, "Cannot assign to constant '{}'", s), - - Self::AssignmentToInvalidLHS(s) if s.is_empty() => f.write_str("Expression cannot be assigned to"), - Self::AssignmentToInvalidLHS(s) => f.write_str(s), + Self::AssignmentToConstant(s) => match s.as_str() { + "" => f.write_str("Cannot assign to a constant value"), + s => write!(f, "Cannot assign to constant '{}'", s) + }, + Self::AssignmentToInvalidLHS(s) => match s.as_str() { + "" => f.write_str("Expression cannot be assigned to"), + s => f.write_str(s) + }, Self::LiteralTooLarge(typ, max) => write!(f, "{} exceeds the maximum limit ({})", typ, max), Self::Reserved(s) => write!(f, "'{}' is a reserved keyword", s), diff --git a/src/fn_call.rs b/src/fn_call.rs index 36594362..45cc65e6 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -2,7 +2,7 @@ use crate::ast::FnCallHashes; use crate::engine::{ - FnResolutionCacheEntry, Imports, State, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, + EvalState, FnResolutionCacheEntry, Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF, MAX_DYNAMIC_PARAMETERS, }; @@ -162,7 +162,7 @@ impl Engine { fn resolve_fn<'s>( &self, mods: &Imports, - state: &'s mut State, + state: &'s mut EvalState, lib: &[&Module], fn_name: &str, hash_script: u64, @@ -266,7 +266,7 @@ impl Engine { pub(crate) fn call_native_fn( &self, mods: &Imports, - state: &mut State, + state: &mut EvalState, lib: &[&Module], name: &str, hash: u64, @@ -439,7 +439,7 @@ impl Engine { &self, scope: &mut Scope, mods: &mut Imports, - state: &mut State, + state: &mut EvalState, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, fn_def: &crate::ast::ScriptFnDef, @@ -451,7 +451,7 @@ impl Engine { fn make_error( name: String, fn_def: &crate::ast::ScriptFnDef, - state: &State, + state: &EvalState, err: Box, pos: Position, ) -> RhaiResult { @@ -569,7 +569,7 @@ impl Engine { pub(crate) fn has_script_fn( &self, mods: Option<&Imports>, - state: &mut State, + state: &mut EvalState, lib: &[&Module], hash_script: u64, ) -> bool { @@ -608,7 +608,7 @@ impl Engine { pub(crate) fn exec_fn_call( &self, mods: &mut Imports, - state: &mut State, + state: &mut EvalState, lib: &[&Module], fn_name: &str, hashes: FnCallHashes, @@ -644,7 +644,7 @@ impl Engine { crate::engine::KEYWORD_IS_DEF_FN if args.len() == 2 && args[0].is::() && args[1].is::() => { - let fn_name = &*args[0] + let fn_name = args[0] .read_lock::() .expect("never fails because `args[0]` is `FnPtr`"); let num_params = args[1] @@ -655,7 +655,7 @@ impl Engine { if num_params < 0 { Dynamic::FALSE } else { - let hash_script = calc_fn_hash(fn_name, num_params as usize); + let hash_script = calc_fn_hash(fn_name.as_str(), num_params as usize); self.has_script_fn(Some(mods), state, lib, hash_script) .into() }, @@ -788,7 +788,7 @@ impl Engine { &self, scope: &mut Scope, mods: &mut Imports, - state: &mut State, + state: &mut EvalState, statements: &[Stmt], lib: &[&Module], level: usize, @@ -809,7 +809,7 @@ impl Engine { &self, scope: &mut Scope, mods: &mut Imports, - state: &mut State, + state: &mut EvalState, lib: &[&Module], script: &str, _pos: Position, @@ -842,7 +842,7 @@ impl Engine { } // Evaluate the AST - let mut new_state: State = Default::default(); + let mut new_state = EvalState::new(); new_state.source = state.source.clone(); new_state.operations = state.operations; @@ -860,7 +860,7 @@ impl Engine { pub(crate) fn make_method_call( &self, mods: &mut Imports, - state: &mut State, + state: &mut EvalState, lib: &[&Module], fn_name: &str, mut hash: FnCallHashes, @@ -1024,7 +1024,7 @@ impl Engine { &self, scope: &mut Scope, mods: &mut Imports, - state: &mut State, + state: &mut EvalState, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, level: usize, @@ -1046,7 +1046,7 @@ impl Engine { &self, scope: &mut Scope, mods: &mut Imports, - state: &mut State, + state: &mut EvalState, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, fn_name: &str, @@ -1202,7 +1202,7 @@ impl Engine { // IMPORTANT! If the eval defines new variables in the current scope, // all variable offsets from this point on will be mis-aligned. if scope.len() != prev_len { - state.always_search = true; + state.always_search_scope = true; } return result.map_err(|err| { @@ -1298,7 +1298,7 @@ impl Engine { &self, scope: &mut Scope, mods: &mut Imports, - state: &mut State, + state: &mut EvalState, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, namespace: &NamespaceRef, diff --git a/src/fn_native.rs b/src/fn_native.rs index 4f91cec5..82670f89 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -399,7 +399,7 @@ impl CallableFunction { /// Is this an iterator function? #[inline(always)] #[must_use] - pub fn is_iter(&self) -> bool { + pub const fn is_iter(&self) -> bool { match self { Self::Iterator(_) => true, Self::Pure(_) | Self::Method(_) | Self::Plugin(_) => false, @@ -411,7 +411,7 @@ impl CallableFunction { /// Is this a Rhai-scripted function? #[inline(always)] #[must_use] - pub fn is_script(&self) -> bool { + pub const fn is_script(&self) -> bool { match self { #[cfg(not(feature = "no_function"))] Self::Script(_) => true, @@ -422,7 +422,7 @@ impl CallableFunction { /// Is this a plugin function? #[inline(always)] #[must_use] - pub fn is_plugin_fn(&self) -> bool { + pub const fn is_plugin_fn(&self) -> bool { match self { Self::Plugin(_) => true, Self::Pure(_) | Self::Method(_) | Self::Iterator(_) => false, @@ -434,7 +434,7 @@ impl CallableFunction { /// Is this a native Rust function? #[inline(always)] #[must_use] - pub fn is_native(&self) -> bool { + pub const fn is_native(&self) -> bool { match self { Self::Pure(_) | Self::Method(_) => true, Self::Plugin(_) => true, diff --git a/src/immutable_string.rs b/src/immutable_string.rs index 48c7e387..d6555698 100644 --- a/src/immutable_string.rs +++ b/src/immutable_string.rs @@ -522,6 +522,11 @@ impl PartialOrd for String { } impl ImmutableString { + /// Create a new [`ImmutableString`]. + #[inline(always)] + pub fn new() -> Self { + Self(SmartString::new().into()) + } /// Consume the [`ImmutableString`] and convert it into a [`String`]. /// If there are other references to the same string, a cloned copy is returned. #[inline(always)] diff --git a/src/lib.rs b/src/lib.rs index 6d1a9aae..6b13fac0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -233,10 +233,11 @@ pub use ast::{ #[cfg(feature = "internals")] #[deprecated = "this type is volatile and may change"] -pub use engine::{Imports, State as EvalState}; +pub use engine::{EvalState, FnResolutionCache, FnResolutionCacheEntry, Imports}; #[cfg(feature = "internals")] #[cfg(not(feature = "unchecked"))] +#[deprecated = "this type is volatile and may change"] pub use engine::Limits; #[cfg(feature = "internals")] diff --git a/src/module/mod.rs b/src/module/mod.rs index 19d674e5..1ff6e4a8 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1731,6 +1731,12 @@ impl From> for NamespaceRef { } impl NamespaceRef { + /// Create a new [`NamespaceRef`]. + #[inline(always)] + #[must_use] + pub fn new(&self) -> Self { + Default::default() + } /// Get the [`Scope`][crate::Scope] index offset. #[inline(always)] #[must_use] diff --git a/src/optimize.rs b/src/optimize.rs index 22a67ac8..17468495 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -43,7 +43,7 @@ impl Default for OptimizationLevel { /// Mutable state throughout an optimization pass. #[derive(Debug, Clone)] -struct State<'a> { +struct OptimizerState<'a> { /// Has the [`AST`] been changed during this pass? changed: bool, /// Collection of constants to use for eager function evaluations. @@ -58,7 +58,7 @@ struct State<'a> { optimization_level: OptimizationLevel, } -impl<'a> State<'a> { +impl<'a> OptimizerState<'a> { /// Create a new State. #[inline(always)] pub const fn new( @@ -121,7 +121,7 @@ impl<'a> State<'a> { } // Has a system function a Rust-native override? -fn has_native_fn(state: &State, hash_script: u64, arg_types: &[TypeId]) -> bool { +fn has_native_fn(state: &OptimizerState, hash_script: u64, arg_types: &[TypeId]) -> bool { let hash_params = calc_fn_params_hash(arg_types.iter().cloned()); let hash = combine_hashes(hash_script, hash_params); @@ -135,7 +135,7 @@ fn has_native_fn(state: &State, hash_script: u64, arg_types: &[TypeId]) -> bool /// Call a registered function fn call_fn_with_constant_arguments( - state: &State, + state: &OptimizerState, fn_name: &str, arg_values: &mut [Dynamic], ) -> Option { @@ -159,7 +159,7 @@ fn call_fn_with_constant_arguments( /// Optimize a block of [statements][Stmt]. fn optimize_stmt_block( mut statements: Vec, - state: &mut State, + state: &mut OptimizerState, preserve_result: bool, is_internal: bool, reduce_return: bool, @@ -368,7 +368,7 @@ fn optimize_stmt_block( } /// Optimize a [statement][Stmt]. -fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { +fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: bool) { match stmt { // var = var op expr => var op= expr Stmt::Assignment(x, _) @@ -682,7 +682,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { } /// Optimize an [expression][Expr]. -fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) { +fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { // These keywords are handled specially const DONT_EVAL_KEYWORDS: &[&str] = &[ KEYWORD_PRINT, // side effects @@ -1051,7 +1051,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State, _chaining: bool) { // Custom syntax Expr::Custom(x, _) => { - if x.scope_changed { + if x.scope_may_be_changed { state.propagate_constants = false; } x.keywords.iter_mut().for_each(|expr| optimize_expr(expr, state, false)); @@ -1077,7 +1077,7 @@ fn optimize_top_level( } // Set up the state - let mut state = State::new(engine, lib, optimization_level); + let mut state = OptimizerState::new(engine, lib, optimization_level); // Add constants and variables from the scope scope.iter().for_each(|(name, constant, value)| { @@ -1142,7 +1142,7 @@ pub fn optimize_into_ast( let mut fn_def = crate::fn_native::shared_take_or_clone(fn_def); // Optimize the function body - let state = &mut State::new(engine, lib2, level); + let state = &mut OptimizerState::new(engine, lib2, level); let body = mem::take(fn_def.body.statements_mut()).into_vec(); diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 348e602c..c361863f 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -173,6 +173,10 @@ mod number_formatting { pub fn int_to_octal(value: INT) -> ImmutableString { to_octal(value) } + #[rhai_fn(name = "to_binary")] + pub fn int_to_binary(value: INT) -> ImmutableString { + to_binary(value) + } #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] diff --git a/src/parse.rs b/src/parse.rs index 33380e50..9d00409f 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -137,7 +137,7 @@ impl<'e> ParseState<'e> { /// /// If the variable is not present in the scope adds it to the list of external variables /// - /// The return value is the offset to be deducted from `ParseState::stack::len`, + /// The return value is the offset to be deducted from `ParseState::stack::len()`, /// i.e. the top element of [`ParseState`]'s variables stack is offset 1. /// /// Return `None` when the variable name is not found in the `stack`. @@ -156,7 +156,7 @@ impl<'e> ParseState<'e> { barrier = true; false } else { - *n == name + n == name } }) .and_then(|(i, _)| NonZeroUsize::new(i + 1)); @@ -281,9 +281,73 @@ impl Expr { _ => self, } } + /// Raise an error if the expression can never yield a boolean value. + fn ensure_bool_expr(self) -> Result { + let type_name = match self { + Expr::Unit(_) => "()", + Expr::DynamicConstant(ref v, _) if !v.is::() => v.type_name(), + Expr::IntegerConstant(_, _) => "a number", + #[cfg(not(feature = "no_float"))] + Expr::FloatConstant(_, _) => "a floating-point number", + Expr::CharConstant(_, _) => "a character", + Expr::StringConstant(_, _) => "a string", + Expr::InterpolatedString(_, _) => "a string", + Expr::Array(_, _) => "an array", + Expr::Map(_, _) => "an object map", + _ => return Ok(self), + }; + + Err( + PERR::MismatchedType("a boolean expression".to_string(), type_name.to_string()) + .into_err(self.position()), + ) + } + /// Raise an error if the expression can never yield an iterable value. + fn ensure_iterable(self) -> Result { + let type_name = match self { + Expr::Unit(_) => "()", + Expr::BoolConstant(_, _) => "a boolean", + Expr::IntegerConstant(_, _) => "a number", + #[cfg(not(feature = "no_float"))] + Expr::FloatConstant(_, _) => "a floating-point number", + Expr::CharConstant(_, _) => "a character", + Expr::StringConstant(_, _) => "a string", + Expr::InterpolatedString(_, _) => "a string", + Expr::Map(_, _) => "an object map", + _ => return Ok(self), + }; + + Err( + PERR::MismatchedType("an iterable value".to_string(), type_name.to_string()) + .into_err(self.position()), + ) + } +} + +/// Make sure that the next expression is not a statement expression (i.e. wrapped in `{}`). +#[inline(always)] +fn ensure_not_statement_expr(input: &mut TokenStream, type_name: &str) -> Result<(), ParseError> { + match input.peek().expect(NEVER_ENDS) { + (Token::LeftBrace, pos) => Err(PERR::ExprExpected(type_name.to_string()).into_err(*pos)), + _ => Ok(()), + } +} + +/// Make sure that the next expression is not a mis-typed assignment (i.e. `a = b` instead of `a == b`). +#[inline(always)] +fn ensure_not_assignment(input: &mut TokenStream) -> Result<(), ParseError> { + match input.peek().expect(NEVER_ENDS) { + (Token::Equals, pos) => Err(LexError::ImproperSymbol( + "=".to_string(), + "Possibly a typo of '=='?".to_string(), + ) + .into_err(*pos)), + _ => Ok(()), + } } /// Consume a particular [token][Token], checking that it is the expected one. +#[inline] fn eat_token(input: &mut TokenStream, token: Token) -> Position { let (t, pos) = input.next().expect(NEVER_ENDS); @@ -299,6 +363,7 @@ fn eat_token(input: &mut TokenStream, token: Token) -> Position { } /// Match a particular [token][Token], consuming it if matched. +#[inline] fn match_token(input: &mut TokenStream, token: Token) -> (bool, Position) { let (t, pos) = input.peek().expect(NEVER_ENDS); if *t == token { @@ -1816,8 +1881,8 @@ fn parse_binary_op( .expect("never fails because `||` has two arguments"); Expr::Or( BinaryExpr { - lhs: current_lhs, - rhs, + lhs: current_lhs.ensure_bool_expr()?, + rhs: rhs.ensure_bool_expr()?, } .into(), pos, @@ -1832,8 +1897,8 @@ fn parse_binary_op( .expect("never fails because `&&` has two arguments"); Expr::And( BinaryExpr { - lhs: current_lhs, - rhs, + lhs: current_lhs.ensure_bool_expr()?, + rhs: rhs.ensure_bool_expr()?, } .into(), pos, @@ -1896,10 +1961,9 @@ fn parse_custom_syntax( let mut tokens: StaticVec<_> = Default::default(); // Adjust the variables stack - if syntax.scope_changed { - // Add an empty variable name to the stack. - // Empty variable names act as a barrier so earlier variables will not be matched. - // Variable searches stop at the first empty variable name. + if syntax.scope_may_be_changed { + // Add a barrier variable to the stack so earlier variables will not be matched. + // Variable searches stop at the first barrier. let empty = state.get_identifier(SCOPE_SEARCH_BARRIER_MARKER); state.stack.push((empty, AccessMode::ReadWrite)); } @@ -2017,12 +2081,15 @@ fn parse_custom_syntax( keywords.shrink_to_fit(); tokens.shrink_to_fit(); - Ok(CustomExpr { - keywords, - tokens, - scope_changed: syntax.scope_changed, - } - .into_custom_syntax_expr(pos)) + Ok(Expr::Custom( + CustomExpr { + keywords, + tokens, + scope_may_be_changed: syntax.scope_may_be_changed, + } + .into(), + pos, + )) } /// Parse an expression. @@ -2070,46 +2137,6 @@ fn parse_expr( ) } -/// Make sure that the expression is not a statement expression (i.e. wrapped in `{}`). -fn ensure_not_statement_expr(input: &mut TokenStream, type_name: &str) -> Result<(), ParseError> { - match input.peek().expect(NEVER_ENDS) { - // Disallow statement expressions - (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(()), - } -} - -/// Make sure that the expression is not a mis-typed assignment (i.e. `a = b` instead of `a == b`). -fn ensure_not_assignment(input: &mut TokenStream) -> Result<(), ParseError> { - match input.peek().expect(NEVER_ENDS) { - (Token::Equals, pos) => Err(LexError::ImproperSymbol( - "=".to_string(), - "Possibly a typo of '=='?".to_string(), - ) - .into_err(*pos)), - (token @ Token::PlusAssign, pos) - | (token @ Token::MinusAssign, pos) - | (token @ Token::MultiplyAssign, pos) - | (token @ Token::DivideAssign, pos) - | (token @ Token::LeftShiftAssign, pos) - | (token @ Token::RightShiftAssign, pos) - | (token @ Token::ModuloAssign, pos) - | (token @ Token::PowerOfAssign, pos) - | (token @ Token::AndAssign, pos) - | (token @ Token::OrAssign, pos) - | (token @ Token::XOrAssign, pos) => Err(LexError::ImproperSymbol( - token.syntax().to_string(), - "Expecting a boolean expression, not an assignment".to_string(), - ) - .into_err(*pos)), - - _ => Ok(()), - } -} - /// Parse an if statement. fn parse_if( input: &mut TokenStream, @@ -2125,7 +2152,7 @@ fn parse_if( // if guard { if_body } ensure_not_statement_expr(input, "a boolean")?; - let guard = parse_expr(input, state, lib, settings.level_up())?; + let guard = parse_expr(input, state, lib, settings.level_up())?.ensure_bool_expr()?; ensure_not_assignment(input)?; let if_body = parse_block(input, state, lib, settings.level_up())?; @@ -2163,16 +2190,16 @@ fn parse_while_loop( let (guard, token_pos) = match input.next().expect(NEVER_ENDS) { (Token::While, pos) => { ensure_not_statement_expr(input, "a boolean")?; - let expr = parse_expr(input, state, lib, settings.level_up())?; + let expr = parse_expr(input, state, lib, settings.level_up())?.ensure_bool_expr()?; + ensure_not_assignment(input)?; (expr, pos) } (Token::Loop, pos) => (Expr::Unit(Position::NONE), pos), _ => unreachable!(), }; settings.pos = token_pos; - - ensure_not_assignment(input)?; settings.is_breakable = true; + let body = parse_block(input, state, lib, settings.level_up())?; Ok(Stmt::While(guard, Box::new(body.into()), settings.pos)) @@ -2206,9 +2233,10 @@ fn parse_do( } }; - ensure_not_statement_expr(input, "a boolean")?; settings.is_breakable = false; - let guard = parse_expr(input, state, lib, settings.level_up())?; + + ensure_not_statement_expr(input, "a boolean")?; + let guard = parse_expr(input, state, lib, settings.level_up())?.ensure_bool_expr()?; ensure_not_assignment(input)?; Ok(Stmt::Do( @@ -2279,7 +2307,7 @@ fn parse_for( // for name in expr { body } ensure_not_statement_expr(input, "a boolean")?; - let expr = parse_expr(input, state, lib, settings.level_up())?; + let expr = parse_expr(input, state, lib, settings.level_up())?.ensure_iterable()?; let prev_stack_len = state.stack.len(); @@ -2758,6 +2786,10 @@ fn parse_stmt( match input.peek().expect(NEVER_ENDS) { // `return`/`throw` at (Token::EOF, _) => Ok(Stmt::Return(return_type, None, token_pos)), + // `return`/`throw` at end of block + (Token::RightBrace, _) if !settings.is_global => { + Ok(Stmt::Return(return_type, None, token_pos)) + } // `return;` or `throw;` (Token::SemiColon, _) => Ok(Stmt::Return(return_type, None, token_pos)), // `return` or `throw` with expression diff --git a/tests/bool_op.rs b/tests/bool_op.rs index 18dc4ea6..e6ef023a 100644 --- a/tests/bool_op.rs +++ b/tests/bool_op.rs @@ -25,9 +25,9 @@ fn test_bool_op3() -> Result<(), Box> { let engine = Engine::new(); assert!(engine.eval::("true && (false || 123)").is_err()); - assert_eq!(engine.eval::("true && (true || 123)")?, true); + assert_eq!(engine.eval::("true && (true || { throw })")?, true); assert!(engine.eval::("123 && (false || true)").is_err()); - assert_eq!(engine.eval::("false && (true || 123)")?, false); + assert_eq!(engine.eval::("false && (true || { throw })")?, false); Ok(()) } diff --git a/tests/native.rs b/tests/native.rs index 2112f6f7..349cebf8 100644 --- a/tests/native.rs +++ b/tests/native.rs @@ -30,6 +30,7 @@ fn test_native_context_fn_name() -> Result<(), Box> { let mut engine = Engine::new(); + #[allow(deprecated)] engine .register_raw_fn( "add_double", diff --git a/tests/switch.rs b/tests/switch.rs index 614d0876..b3bbc803 100644 --- a/tests/switch.rs +++ b/tests/switch.rs @@ -160,6 +160,7 @@ mod test_switch_enum { use super::*; use rhai::Array; #[derive(Debug, Clone)] + #[allow(dead_code)] enum MyEnum { Foo, Bar(INT),