diff --git a/CHANGELOG.md b/CHANGELOG.md index 15545a79..6adfdff0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ Bug fixes Reserved Symbols ---------------- -* `?`, `??`, `?.` and `!.` are now reserved symbols. +* `?`, `??`, `?.`, `?[` and `!.` are now reserved symbols. Deprecated API's ---------------- @@ -29,7 +29,7 @@ Deprecated API's New features ------------ -* The _Elvis operator_ (`?.`) is now supported for property access and method calls. +* The _Elvis operators_ (`?.` and `?[`) are now supported for property access, method calls and indexing. * The _null-coalescing operator_ (`??`) is now supported to short-circuit `()` values. Enhancements @@ -41,6 +41,7 @@ Enhancements * Originally, the debugger's custom state uses the same state as `EvalState::tag()` (which is the same as `NativeCallContext::tag()`). It is now split into its own variable accessible under `Debugger::state()`. * Non-borrowed string keys can now be deserialized for object maps via `serde`. * `Scope::get` is added to get a reference to a variable's value. +* Variable resolvers can now return a _shared_ value which can be mutated. Version 1.7.0 diff --git a/Cargo.toml b/Cargo.toml index c96d19a8..0328b394 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [".", "codegen"] [package] name = "rhai" -version = "1.7.0" +version = "1.8.0" rust-version = "1.57" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index 3982aee6..4aacf153 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -1,7 +1,7 @@ //! Module implementing custom syntax for [`Engine`]. use crate::ast::Expr; -use crate::func::native::SendSync; +use crate::func::SendSync; use crate::parser::ParseResult; use crate::tokenizer::{is_valid_identifier, Token}; use crate::types::dynamic::Variant; diff --git a/src/api/optimize.rs b/src/api/optimize.rs index 7589c2fd..fca162f2 100644 --- a/src/api/optimize.rs +++ b/src/api/optimize.rs @@ -56,12 +56,7 @@ impl Engine { .shared_lib() .iter_fn() .filter(|f| f.func.is_script()) - .map(|f| { - f.func - .get_script_fn_def() - .expect("script-defined function") - .clone() - }) + .map(|f| f.func.get_script_fn_def().unwrap().clone()) .collect(); crate::optimizer::optimize_into_ast( diff --git a/src/api/register.rs b/src/api/register.rs index 8d69f7d6..3ee7eb39 100644 --- a/src/api/register.rs +++ b/src/api/register.rs @@ -123,9 +123,7 @@ impl Engine { let param_type_names: crate::StaticVec<_> = F::param_names() .iter() .map(|ty| format!("_: {}", self.format_type_name(ty))) - .chain(std::iter::once( - self.format_type_name(F::return_type_name()).into(), - )) + .chain(Some(self.format_type_name(F::return_type_name()).into())) .collect(); #[cfg(feature = "metadata")] @@ -993,7 +991,7 @@ impl Engine { if !name.contains(separator.as_ref()) { if !module.is_indexed() { // Index the module (making a clone copy if necessary) if it is not indexed - let mut module = crate::func::native::shared_take_or_clone(module); + let mut module = crate::func::shared_take_or_clone(module); module.build_index(); root.insert(name.into(), module.into()); } else { @@ -1011,7 +1009,7 @@ impl Engine { root.insert(sub_module.into(), m.into()); } else { let m = root.remove(sub_module).expect("contains sub-module"); - let mut m = crate::func::native::shared_take_or_clone(m); + let mut m = crate::func::shared_take_or_clone(m); register_static_module_raw(m.sub_modules_mut(), remainder, module); m.build_index(); root.insert(sub_module.into(), m.into()); diff --git a/src/api/type_names.rs b/src/api/type_names.rs index ea76ffa4..de6961c1 100644 --- a/src/api/type_names.rs +++ b/src/api/type_names.rs @@ -86,7 +86,7 @@ fn map_std_type_name(name: &str, shorthands: bool) -> &str { } #[cfg(not(feature = "no_float"))] - if name == type_name::() { + if name == type_name::>() { return if shorthands { "range" } else { @@ -94,7 +94,7 @@ fn map_std_type_name(name: &str, shorthands: bool) -> &str { }; } #[cfg(feature = "decimal")] - if name == type_name::() { + if name == type_name::>() { return if shorthands { "range" } else { diff --git a/src/ast/ast.rs b/src/ast/ast.rs index f7e17414..67237b95 100644 --- a/src/ast/ast.rs +++ b/src/ast/ast.rs @@ -615,8 +615,7 @@ impl AST { #[cfg(not(feature = "no_function"))] if !other.lib.is_empty() { - crate::func::native::shared_make_mut(&mut self.lib) - .merge_filtered(&other.lib, &_filter); + crate::func::shared_make_mut(&mut self.lib).merge_filtered(&other.lib, &_filter); } #[cfg(not(feature = "no_module"))] @@ -629,10 +628,8 @@ impl AST { self.set_resolver(other.resolver.unwrap()); } (_, _) => { - let resolver = - crate::func::native::shared_make_mut(self.resolver.as_mut().unwrap()); - let other_resolver = - crate::func::native::shared_take_or_clone(other.resolver.unwrap()); + let resolver = crate::func::shared_make_mut(self.resolver.as_mut().unwrap()); + let other_resolver = crate::func::shared_take_or_clone(other.resolver.unwrap()); for (k, v) in other_resolver { resolver.insert(k, crate::func::shared_take_or_clone(v)); } @@ -673,7 +670,7 @@ impl AST { filter: impl Fn(FnNamespace, FnAccess, &str, usize) -> bool, ) -> &mut Self { if !self.lib.is_empty() { - crate::func::native::shared_make_mut(&mut self.lib).retain_script_functions(filter); + crate::func::shared_make_mut(&mut self.lib).retain_script_functions(filter); } self } diff --git a/src/ast/expr.rs b/src/ast/expr.rs index 42cc91d2..a802e688 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -411,6 +411,7 @@ pub enum Expr { /// /// ### Flags /// + /// [`NEGATED`][ASTFlags::NEGATED] = `?[` ... `]` (`[` ... `]` if unset) /// [`BREAK`][ASTFlags::BREAK] = terminate the chain (recurse into the chain if unset) Index(Box, ASTFlags, Position), /// lhs `&&` rhs @@ -827,7 +828,7 @@ impl Expr { #[cfg(not(feature = "no_object"))] Token::Period | Token::Elvis => return true, #[cfg(not(feature = "no_index"))] - Token::LeftBracket => return true, + Token::LeftBracket | Token::QuestionBracket => return true, _ => (), } diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index af62ebe5..810486a9 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -60,6 +60,11 @@ impl Engine { match chain_type { #[cfg(not(feature = "no_index"))] ChainType::Indexing => { + // Check for existence with the null conditional operator + if _parent_options.contains(ASTFlags::NEGATED) && target.is::<()>() { + return Ok((Dynamic::UNIT, false)); + } + let pos = rhs.start_position(); match rhs { diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 0ae9c2fb..b5171d8e 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -146,7 +146,7 @@ impl Engine { let target_is_shared = false; if target_is_shared { - lock_guard = target.write_lock::().expect("`Dynamic`"); + lock_guard = target.write_lock::().unwrap(); lhs_ptr_inner = &mut *lock_guard; } else { lhs_ptr_inner = &mut *target; @@ -181,7 +181,20 @@ impl Engine { } } else { // Normal assignment - *target.as_mut() = new_val; + + #[cfg(not(feature = "no_closure"))] + if target.is_shared() { + // Handle case where target is a `Dynamic` shared value + // (returned by a variable resolver, for example) + *target.write_lock::().unwrap() = new_val; + } else { + *target.as_mut() = new_val; + } + + #[cfg(feature = "no_closure")] + { + *target.as_mut() = new_val; + } } target.propagate_changed_value(op_info.pos) @@ -250,7 +263,15 @@ impl Engine { let var_name = lhs.get_variable_name(false).expect("`Expr::Variable`"); - if !lhs_ptr.is_ref() { + #[cfg(not(feature = "no_closure"))] + // Also handle case where target is a `Dynamic` shared value + // (returned by a variable resolver, for example) + let is_temp_result = !lhs_ptr.is_ref() && !lhs_ptr.is_shared(); + #[cfg(feature = "no_closure")] + let is_temp_result = !lhs_ptr.is_ref(); + + // Cannot assign to temp result from expression + if is_temp_result { return Err( ERR::ErrorAssignmentToConstant(var_name.to_string(), pos).into() ); @@ -950,7 +971,7 @@ impl Engine { if !export.is_empty() { if !module.is_indexed() { // Index the module (making a clone copy if necessary) if it is not indexed - let mut m = crate::func::native::shared_take_or_clone(module); + let mut m = crate::func::shared_take_or_clone(module); m.build_index(); global.push_import(export.name.clone(), m); } else { diff --git a/src/func/args.rs b/src/func/args.rs index 4d14280c..c80dfbbb 100644 --- a/src/func/args.rs +++ b/src/func/args.rs @@ -28,9 +28,9 @@ pub trait FuncArgs { /// /// impl FuncArgs for Options { /// fn parse>(self, args: &mut ARGS) { - /// args.extend(std::iter::once(self.foo.into())); - /// args.extend(std::iter::once(self.bar.into())); - /// args.extend(std::iter::once(self.baz.into())); + /// args.extend(Some(self.foo.into())); + /// args.extend(Some(self.bar.into())); + /// args.extend(Some(self.baz.into())); /// } /// } /// diff --git a/src/func/call.rs b/src/func/call.rs index 62f92c0b..6a0b05aa 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -165,7 +165,7 @@ impl Engine { ) } - /// Resolve a function call. + /// Resolve a normal (non-qualified) function call. /// /// Search order: /// 1) AST - script functions in the AST @@ -201,11 +201,8 @@ impl Engine { .entry(hash) .or_insert_with(|| { let num_args = args.as_ref().map_or(0, |a| a.len()); - let max_bitmask = if !allow_dynamic { - 0 - } else { - 1usize << usize::min(num_args, MAX_DYNAMIC_PARAMETERS) - }; + let mut max_bitmask = 0; // One above maximum bitmask based on number of parameters. + // Set later when a specific matching function is not found. let mut bitmask = 1usize; // Bitmask of which parameter to replace with `Dynamic` loop { @@ -247,62 +244,85 @@ impl Engine { }) }); - match func { - // Specific version found - Some(f) => return Some(f), + // Specific version found + if let Some(f) = func { + return Some(f); + } - // Stop when all permutations are exhausted - None if bitmask >= max_bitmask => { - if num_args != 2 { - return None; - } + // Check `Dynamic` parameters for functions with parameters + if allow_dynamic && max_bitmask == 0 && num_args > 0 { + let is_dynamic = lib.iter().any(|&m| m.contains_dynamic_fn(hash_script)) + || self + .global_modules + .iter() + .any(|m| m.contains_dynamic_fn(hash_script)); - return args.and_then(|args| { - if !is_op_assignment { - get_builtin_binary_op_fn(fn_name, &args[0], &args[1]).map(|f| { - FnResolutionCacheEntry { - func: CallableFunction::from_method( - Box::new(f) as Box - ), - source: None, - } - }) - } else { - let (first_arg, rest_args) = args.split_first().unwrap(); + #[cfg(not(feature = "no_module"))] + let is_dynamic = is_dynamic + || _global + .iter_imports_raw() + .any(|(_, m)| m.contains_dynamic_fn(hash_script)) + || self + .global_sub_modules + .values() + .any(|m| m.contains_dynamic_fn(hash_script)); - get_builtin_op_assignment_fn(fn_name, *first_arg, rest_args[0]) - .map(|f| FnResolutionCacheEntry { - func: CallableFunction::from_method( - Box::new(f) as Box - ), - source: None, - }) - } - }); - } - - // Try all permutations with `Dynamic` wildcards - None => { - let hash_params = calc_fn_params_hash( - args.as_ref() - .expect("no permutations") - .iter() - .enumerate() - .map(|(i, a)| { - let mask = 1usize << (num_args - i - 1); - if bitmask & mask != 0 { - // Replace with `Dynamic` - TypeId::of::() - } else { - a.type_id() - } - }), - ); - hash = combine_hashes(hash_script, hash_params); - - bitmask += 1; + // Set maximum bitmask when there are dynamic versions of the function + if is_dynamic { + max_bitmask = 1usize << usize::min(num_args, MAX_DYNAMIC_PARAMETERS); } } + + // Stop when all permutations are exhausted + if bitmask >= max_bitmask { + if num_args != 2 { + return None; + } + + return args.and_then(|args| { + if !is_op_assignment { + get_builtin_binary_op_fn(fn_name, &args[0], &args[1]).map(|f| { + FnResolutionCacheEntry { + func: CallableFunction::from_method( + Box::new(f) as Box + ), + source: None, + } + }) + } else { + let (first_arg, rest_args) = args.split_first().unwrap(); + + get_builtin_op_assignment_fn(fn_name, *first_arg, rest_args[0]).map( + |f| FnResolutionCacheEntry { + func: CallableFunction::from_method( + Box::new(f) as Box + ), + source: None, + }, + ) + } + }); + } + + // Try all permutations with `Dynamic` wildcards + let hash_params = calc_fn_params_hash( + args.as_ref() + .expect("no permutations") + .iter() + .enumerate() + .map(|(i, a)| { + let mask = 1usize << (num_args - i - 1); + if bitmask & mask != 0 { + // Replace with `Dynamic` + TypeId::of::() + } else { + a.type_id() + } + }), + ); + hash = combine_hashes(hash_script, hash_params); + + bitmask += 1; } }); @@ -1202,7 +1222,7 @@ impl Engine { let (mut target, _pos) = self.search_namespace(scope, global, lib, this_ptr, first_expr, level)?; - if target.as_ref().is_read_only() { + if target.is_read_only() { target = target.into_owned(); } diff --git a/src/func/hashing.rs b/src/func/hashing.rs index 96bdba56..380da5db 100644 --- a/src/func/hashing.rs +++ b/src/func/hashing.rs @@ -5,7 +5,6 @@ use std::prelude::v1::*; use std::{ any::TypeId, hash::{BuildHasher, Hash, Hasher}, - iter::empty, }; /// Dummy hash value to map zeros to. This value can be anything. @@ -87,12 +86,16 @@ pub fn get_hasher() -> ahash::AHasher { /// The first module name is skipped. Hashing starts from the _second_ module in the chain. #[inline] #[must_use] -pub fn calc_qualified_var_hash<'a>(modules: impl Iterator, var_name: &str) -> u64 { +pub fn calc_qualified_var_hash<'a>( + modules: impl IntoIterator, + var_name: &str, +) -> u64 { let s = &mut get_hasher(); // We always skip the first module let mut len = 0; modules + .into_iter() .inspect(|_| len += 1) .skip(1) .for_each(|m| m.hash(s)); @@ -121,7 +124,7 @@ pub fn calc_qualified_var_hash<'a>(modules: impl Iterator, var_n #[inline] #[must_use] pub fn calc_qualified_fn_hash<'a>( - modules: impl Iterator, + modules: impl IntoIterator, fn_name: &str, num: usize, ) -> u64 { @@ -130,6 +133,7 @@ pub fn calc_qualified_fn_hash<'a>( // We always skip the first module let mut len = 0; modules + .into_iter() .inspect(|_| len += 1) .skip(1) .for_each(|m| m.hash(s)); @@ -154,7 +158,7 @@ pub fn calc_qualified_fn_hash<'a>( #[inline(always)] #[must_use] pub fn calc_fn_hash(fn_name: &str, num: usize) -> u64 { - calc_qualified_fn_hash(empty(), fn_name, num) + calc_qualified_fn_hash(None, fn_name, num) } /// Calculate a non-zero [`u64`] hash key from a list of parameter types. @@ -166,10 +170,13 @@ pub fn calc_fn_hash(fn_name: &str, num: usize) -> u64 { /// If the hash happens to be zero, it is mapped to `DEFAULT_HASH`. #[inline] #[must_use] -pub fn calc_fn_params_hash(params: impl Iterator) -> u64 { +pub fn calc_fn_params_hash(params: impl IntoIterator) -> u64 { let s = &mut get_hasher(); let mut len = 0; - params.inspect(|_| len += 1).for_each(|t| t.hash(s)); + params + .into_iter() + .inspect(|_| len += 1) + .for_each(|t| t.hash(s)); len.hash(s); match s.finish() { diff --git a/src/func/mod.rs b/src/func/mod.rs index f4779861..50d444f1 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -22,8 +22,8 @@ pub use hashing::{ combine_hashes, get_hasher, }; pub use native::{ - locked_write, shared_make_mut, shared_take, shared_take_or_clone, shared_try_take, FnAny, - FnPlugin, IteratorFn, Locked, NativeCallContext, SendSync, Shared, + locked_read, locked_write, shared_get_mut, shared_make_mut, shared_take, shared_take_or_clone, + shared_try_take, FnAny, FnPlugin, IteratorFn, Locked, NativeCallContext, SendSync, Shared, }; pub use plugin::PluginFunction; pub use register::RegisterNativeFunction; diff --git a/src/lib.rs b/src/lib.rs index 129b1f84..a63cac6a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,7 @@ //! //! ## Contents of `my_script.rhai` //! -//! ```ignore +//! ```rhai //! /// Brute force factorial function //! fn factorial(x) { //! if x == 1 { return 1; } diff --git a/src/module/mod.rs b/src/module/mod.rs index c3b68758..9a5e8366 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -7,7 +7,7 @@ use crate::func::{ }; use crate::types::{dynamic::Variant, CustomTypesCollection}; use crate::{ - calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes, Dynamic, Identifier, + calc_fn_hash, calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes, Dynamic, Identifier, ImmutableString, NativeCallContext, RhaiResultOf, Shared, StaticVec, }; #[cfg(feature = "no_std")] @@ -17,7 +17,6 @@ use std::{ cmp::Ordering, collections::{BTreeMap, BTreeSet}, fmt, - iter::{empty, once}, ops::{Add, AddAssign}, }; @@ -214,7 +213,7 @@ impl FuncInfo { /// The first module name is skipped. Hashing starts from the _second_ module in the chain. #[inline] pub fn calc_native_fn_hash<'a>( - modules: impl Iterator, + modules: impl IntoIterator, fn_name: &str, params: &[TypeId], ) -> u64 { @@ -242,11 +241,13 @@ pub struct Module { variables: BTreeMap, /// Flattened collection of all [`Module`] variables, including those in sub-modules. all_variables: BTreeMap, - /// External Rust functions. + /// Functions (both native Rust and scripted). functions: BTreeMap>, - /// Flattened collection of all external Rust functions, native or scripted. + /// Flattened collection of all functions, native Rust and scripted. /// including those in sub-modules. all_functions: BTreeMap, + /// Native Rust functions (in scripted hash format) that contain [`Dynamic`] parameters. + dynamic_functions: BTreeSet, /// Iterator functions, keyed by the type producing the iterator. type_iterators: BTreeMap>, /// Flattened collection of iterator functions, including those in sub-modules. @@ -349,6 +350,7 @@ impl Module { all_variables: BTreeMap::new(), functions: BTreeMap::new(), all_functions: BTreeMap::new(), + dynamic_functions: BTreeSet::new(), type_iterators: BTreeMap::new(), all_type_iterators: BTreeMap::new(), indexed: true, @@ -418,6 +420,25 @@ impl Module { self } + /// Clear the [`Module`]. + #[inline(always)] + pub fn clear(&mut self) { + self.id.clear(); + self.internal = false; + self.standard = false; + self.custom_types.clear(); + self.modules.clear(); + self.variables.clear(); + self.all_variables.clear(); + self.functions.clear(); + self.all_functions.clear(); + self.dynamic_functions.clear(); + self.type_iterators.clear(); + self.all_type_iterators.clear(); + self.indexed = false; + self.contains_indexed_global_functions = false; + } + /// Map a custom type to a friendly display name. /// /// # Example @@ -626,7 +647,7 @@ impl Module { let value = Dynamic::from(value); if self.indexed { - let hash_var = crate::calc_qualified_var_hash(once(""), &ident); + let hash_var = crate::calc_qualified_var_hash(Some(""), &ident); self.all_variables.insert(hash_var, value.clone()); } self.variables.insert(ident, value); @@ -965,6 +986,10 @@ impl Module { .collect(); param_types.shrink_to_fit(); + let is_dynamic = param_types + .iter() + .any(|&type_id| type_id == TypeId::of::()); + #[cfg(feature = "metadata")] let (param_names, return_type_name) = { let mut names = _arg_names @@ -981,7 +1006,12 @@ impl Module { (names, return_type) }; - let hash_fn = calc_native_fn_hash(empty::<&str>(), name.as_ref(), ¶m_types); + let hash_fn = calc_native_fn_hash(None, name.as_ref(), ¶m_types); + + if is_dynamic { + self.dynamic_functions + .insert(calc_fn_hash(name.as_ref(), param_types.len())); + } self.functions.insert( hash_fn, @@ -1445,19 +1475,30 @@ impl Module { ) } - /// Get a Rust function. + /// Look up a Rust function by hash. /// /// The [`u64`] hash is returned by the [`set_native_fn`][Module::set_native_fn] call. #[inline] #[must_use] - pub(crate) fn get_fn(&self, hash_fn: u64) -> Option<&CallableFunction> { + pub(crate) fn get_fn(&self, hash_native: u64) -> Option<&CallableFunction> { if !self.functions.is_empty() { - self.functions.get(&hash_fn).map(|f| &f.func) + self.functions.get(&hash_native).map(|f| &f.func) } else { None } } + /// Does the particular function with [`Dynamic`] parameter(s) exist in the [`Module`]? + #[inline(always)] + #[must_use] + pub(crate) fn contains_dynamic_fn(&self, hash_script: u64) -> bool { + if !self.dynamic_functions.is_empty() { + self.dynamic_functions.contains(&hash_script) + } else { + false + } + } + /// Does the particular namespace-qualified function exist in the [`Module`]? /// /// The [`u64`] hash is calculated by [`build_index`][Module::build_index]. @@ -1492,6 +1533,8 @@ impl Module { self.modules.extend(other.modules.into_iter()); self.variables.extend(other.variables.into_iter()); self.functions.extend(other.functions.into_iter()); + self.dynamic_functions + .extend(other.dynamic_functions.into_iter()); self.type_iterators.extend(other.type_iterators.into_iter()); self.all_functions.clear(); self.all_variables.clear(); @@ -1511,6 +1554,8 @@ impl Module { } self.variables.extend(other.variables.into_iter()); self.functions.extend(other.functions.into_iter()); + self.dynamic_functions + .extend(other.dynamic_functions.into_iter()); self.type_iterators.extend(other.type_iterators.into_iter()); self.all_functions.clear(); self.all_variables.clear(); @@ -1537,6 +1582,8 @@ impl Module { for (&k, v) in &other.functions { self.functions.entry(k).or_insert_with(|| v.clone()); } + self.dynamic_functions + .extend(other.dynamic_functions.iter().cloned()); for (&k, v) in &other.type_iterators { self.type_iterators.entry(k).or_insert_with(|| v.clone()); } @@ -1571,6 +1618,7 @@ impl Module { self.variables .extend(other.variables.iter().map(|(k, v)| (k.clone(), v.clone()))); + self.functions.extend( other .functions @@ -1586,6 +1634,9 @@ impl Module { }) .map(|(&k, v)| (k, v.clone())), ); + // This may introduce entries that are superfluous because the function has been filtered away. + self.dynamic_functions + .extend(other.dynamic_functions.iter().cloned()); self.type_iterators .extend(other.type_iterators.iter().map(|(&k, v)| (k, v.clone()))); @@ -1621,6 +1672,7 @@ impl Module { .collect(); self.all_functions.clear(); + self.dynamic_functions.clear(); self.all_variables.clear(); self.all_type_iterators.clear(); self.indexed = false; diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs index 5b6f232a..054168b6 100644 --- a/src/module/resolvers/file.rs +++ b/src/module/resolvers/file.rs @@ -2,7 +2,7 @@ #![cfg(not(target_family = "wasm"))] use crate::eval::GlobalRuntimeState; -use crate::func::native::{locked_read, locked_write}; +use crate::func::{locked_read, locked_write}; use crate::{ Engine, Identifier, Module, ModuleResolver, Position, RhaiResultOf, Scope, Shared, ERR, }; @@ -307,12 +307,7 @@ impl FileModuleResolver { let file_path = self.get_file_path(path, source_path); if self.is_cache_enabled() { - #[cfg(not(feature = "sync"))] - let c = self.cache.borrow(); - #[cfg(feature = "sync")] - let c = self.cache.read().unwrap(); - - if let Some(module) = c.get(&file_path) { + if let Some(module) = locked_read(&self.cache).get(&file_path) { return Ok(module.clone()); } } diff --git a/src/module/resolvers/mod.rs b/src/module/resolvers/mod.rs index 6f2899cb..a7f2d5c4 100644 --- a/src/module/resolvers/mod.rs +++ b/src/module/resolvers/mod.rs @@ -1,5 +1,5 @@ use crate::eval::GlobalRuntimeState; -use crate::func::native::SendSync; +use crate::func::SendSync; use crate::{Engine, Module, Position, RhaiResultOf, Shared, AST}; #[cfg(feature = "no_std")] use std::prelude::v1::*; diff --git a/src/optimizer.rs b/src/optimizer.rs index 1313495f..89fae709 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -578,7 +578,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // Then check ranges if value.is::() && !ranges.is_empty() { - let value = value.as_int().expect("`INT`"); + let value = value.as_int().unwrap(); // Only one range or all ranges without conditions if ranges.len() == 1 @@ -940,6 +940,12 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { #[cfg(not(feature = "no_object"))] Expr::Dot(x,..) => { optimize_expr(&mut x.lhs, state, false); optimize_expr(&mut x.rhs, state, _chaining); } + // ()?[rhs] + #[cfg(not(feature = "no_index"))] + Expr::Index(x, options, ..) if options.contains(ASTFlags::NEGATED) && matches!(x.lhs, Expr::Unit(..)) => { + state.set_dirty(); + *expr = mem::take(&mut x.lhs); + } // lhs[rhs] #[cfg(not(feature = "no_index"))] Expr::Index(x, ..) if !_chaining => match (&mut x.lhs, &mut x.rhs) { @@ -1337,7 +1343,7 @@ pub fn optimize_into_ast( let lib2 = &[&lib2]; for fn_def in functions { - let mut fn_def = crate::func::native::shared_take_or_clone(fn_def); + let mut fn_def = crate::func::shared_take_or_clone(fn_def); // Optimize the function body let body = mem::take(&mut *fn_def.body); diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 273f42bf..bda254e2 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -1397,11 +1397,11 @@ pub mod array_functions { /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// - /// let y = x.reduce(|r, v| v + if r == () { 0 } else { r }); + /// let y = x.reduce(|r, v| v + (r ?? 0)); /// /// print(y); // prints 15 /// - /// let y = x.reduce(|r, v, i| v + i + if r == () { 0 } else { r }); + /// let y = x.reduce(|r, v, i| v + i + (r ?? 0)); /// /// print(y); // prints 25 /// ``` @@ -1423,10 +1423,10 @@ pub mod array_functions { /// /// ```rhai /// fn process(r, x) { - /// x + if r == () { 0 } else { r } + /// x + (r ?? 0) /// } /// fn process_extra(r, x, i) { - /// x + i + if r == () { 0 } else { r } + /// x + i + (r ?? 0) /// } /// /// let x = [1, 2, 3, 4, 5]; @@ -1556,11 +1556,11 @@ pub mod array_functions { /// ```rhai /// let x = [1, 2, 3, 4, 5]; /// - /// let y = x.reduce_rev(|r, v| v + if r == () { 0 } else { r }); + /// let y = x.reduce_rev(|r, v| v + (r ?? 0)); /// /// print(y); // prints 15 /// - /// let y = x.reduce_rev(|r, v, i| v + i + if r == () { 0 } else { r }); + /// let y = x.reduce_rev(|r, v, i| v + i + (r ?? 0)); /// /// print(y); // prints 25 /// ``` @@ -1583,10 +1583,10 @@ pub mod array_functions { /// /// ```rhai /// fn process(r, x) { - /// x + if r == () { 0 } else { r } + /// x + (r ?? 0) /// } /// fn process_extra(r, x, i) { - /// x + i + if r == () { 0 } else { r } + /// x + i + (r ?? 0) /// } /// /// let x = [1, 2, 3, 4, 5]; diff --git a/src/packages/blob_basic.rs b/src/packages/blob_basic.rs index 92a5b684..6cd8ebff 100644 --- a/src/packages/blob_basic.rs +++ b/src/packages/blob_basic.rs @@ -953,7 +953,7 @@ mod parse_int_functions { /// * If number of bytes in `range` < number of bytes for `INT`, zeros are padded. /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes are ignored. /// - /// ```ignore + /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; @@ -974,7 +974,7 @@ mod parse_int_functions { /// * If number of bytes in `range` < number of bytes for `INT`, zeros are padded. /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes are ignored. /// - /// ```ignore + /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; @@ -1001,7 +1001,7 @@ mod parse_int_functions { /// * If number of bytes in range < number of bytes for `INT`, zeros are padded. /// * If number of bytes in range > number of bytes for `INT`, extra bytes are ignored. /// - /// ```ignore + /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; @@ -1019,7 +1019,7 @@ mod parse_int_functions { /// * If number of bytes in `range` < number of bytes for `INT`, zeros are padded. /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes are ignored. /// - /// ```ignore + /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; @@ -1040,7 +1040,7 @@ mod parse_int_functions { /// * If number of bytes in `range` < number of bytes for `INT`, zeros are padded. /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes are ignored. /// - /// ```ignore + /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; @@ -1067,7 +1067,7 @@ mod parse_int_functions { /// * If number of bytes in range < number of bytes for `INT`, zeros are padded. /// * If number of bytes in range > number of bytes for `INT`, extra bytes are ignored. /// - /// ```ignore + /// ```rhai /// let b = blob(); /// /// b += 1; b += 2; b += 3; b += 4; b += 5; @@ -1213,7 +1213,7 @@ mod write_int_functions { /// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written. /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified. /// - /// ```ignore + /// ```rhai /// let b = blob(8); /// /// b.write_le_int(1..3, 0x12345678); @@ -1232,7 +1232,7 @@ mod write_int_functions { /// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written. /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified. /// - /// ```ignore + /// ```rhai /// let b = blob(8); /// /// b.write_le_int(1..=3, 0x12345678); @@ -1257,7 +1257,7 @@ mod write_int_functions { /// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written. /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified. /// - /// ```ignore + /// ```rhai /// let b = blob(8); /// /// b.write_le_int(1, 3, 0x12345678); @@ -1274,7 +1274,7 @@ mod write_int_functions { /// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written. /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified. /// - /// ```ignore + /// ```rhai /// let b = blob(8, 0x42); /// /// b.write_be_int(1..3, 0x99); @@ -1293,7 +1293,7 @@ mod write_int_functions { /// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written. /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified. /// - /// ```ignore + /// ```rhai /// let b = blob(8, 0x42); /// /// b.write_be_int(1..=3, 0x99); @@ -1318,7 +1318,7 @@ mod write_int_functions { /// * If number of bytes in `range` < number of bytes for `INT`, extra bytes in `INT` are not written. /// * If number of bytes in `range` > number of bytes for `INT`, extra bytes in `range` are not modified. /// - /// ```ignore + /// ```rhai /// let b = blob(8, 0x42); /// /// b.write_be_int(1, 3, 0x99); @@ -1464,7 +1464,7 @@ mod write_string_functions { /// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written. /// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified. /// - /// ```ignore + /// ```rhai /// let b = blob(8); /// /// b.write_utf8(1..5, "朝には紅顔ありて夕べには白骨となる"); @@ -1482,7 +1482,7 @@ mod write_string_functions { /// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written. /// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified. /// - /// ```ignore + /// ```rhai /// let b = blob(8); /// /// b.write_utf8(1..=5, "朝には紅顔ありて夕べには白骨となる"); @@ -1506,7 +1506,7 @@ mod write_string_functions { /// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written. /// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified. /// - /// ```ignore + /// ```rhai /// let b = blob(8); /// /// b.write_utf8(1, 5, "朝には紅顔ありて夕べには白骨となる"); @@ -1525,7 +1525,7 @@ mod write_string_functions { /// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written. /// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified. /// - /// ```ignore + /// ```rhai /// let b = blob(8); /// /// b.write_ascii(1..5, "hello, world!"); @@ -1546,7 +1546,7 @@ mod write_string_functions { /// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written. /// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified. /// - /// ```ignore + /// ```rhai /// let b = blob(8); /// /// b.write_ascii(1..=5, "hello, world!"); @@ -1574,7 +1574,7 @@ mod write_string_functions { /// * If number of bytes in `range` < length of `string`, extra bytes in `string` are not written. /// * If number of bytes in `range` > length of `string`, extra bytes in `range` are not modified. /// - /// ```ignore + /// ```rhai /// let b = blob(8); /// /// b.write_ascii(1, 5, "hello, world!"); diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index 7e8b6af8..5627c834 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -1,34 +1,59 @@ use crate::eval::calc_index; use crate::plugin::*; -use crate::types::dynamic::Variant; use crate::{def_package, ExclusiveRange, InclusiveRange, RhaiResultOf, INT, INT_BITS}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ + any::type_name, + cmp::Ordering, + fmt::Debug, iter::{ExactSizeIterator, FusedIterator}, ops::{Range, RangeInclusive}, }; #[cfg(not(feature = "unchecked"))] -use num_traits::{CheckedAdd as Add, CheckedSub as Sub}; - -#[cfg(feature = "unchecked")] -use std::ops::{Add, Sub}; +#[inline(always)] +fn std_add(x: T, y: T) -> Option +where + T: Debug + Copy + PartialOrd + num_traits::CheckedAdd, +{ + x.checked_add(&y) +} +#[inline(always)] +fn regular_add(x: T, y: T) -> Option +where + T: Debug + Copy + PartialOrd + std::ops::Add, +{ + Some(x + y) +} // Range iterator with step -#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] -pub struct StepRange(T, T, T) -where - T: Variant + Copy + PartialOrd + Add + Sub; +#[derive(Clone, Copy, Hash, Eq, PartialEq)] +pub struct StepRange { + pub from: T, + pub to: T, + pub step: T, + pub add: fn(T, T) -> Option, + pub dir: i8, +} -impl StepRange -where - T: Variant + Copy + PartialOrd + Add + Sub, -{ - pub fn new(from: T, to: T, step: T) -> RhaiResultOf { - #[cfg(not(feature = "unchecked"))] - if let Some(r) = from.checked_add(&step) { - if r == from { +impl Debug for StepRange { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple(&format!("StepRange<{}>", type_name::())) + .field(&self.from) + .field(&self.to) + .field(&self.step) + .finish() + } +} + +impl StepRange { + pub fn new(from: T, to: T, step: T, add: fn(T, T) -> Option) -> RhaiResultOf { + let mut dir = 0; + + if let Some(n) = add(from, step) { + #[cfg(not(feature = "unchecked"))] + if n == from { return Err(crate::ERR::ErrorInFunctionCall( "range".to_string(), String::new(), @@ -41,77 +66,53 @@ where ) .into()); } + + match from.partial_cmp(&to).unwrap_or(Ordering::Equal) { + Ordering::Less if n > from => dir = 1, + Ordering::Greater if n < from => dir = -1, + _ => (), + } } - Ok(Self(from, to, step)) + Ok(Self { + from, + to, + step, + add, + dir, + }) } } -impl Iterator for StepRange -where - T: Variant + Copy + PartialOrd + Add + Sub, -{ +impl Iterator for StepRange { type Item = T; fn next(&mut self) -> Option { - if self.0 == self.1 { - None - } else if self.0 < self.1 { - #[cfg(not(feature = "unchecked"))] - let diff1 = self.1.checked_sub(&self.0)?; - #[cfg(feature = "unchecked")] - let diff1 = self.1 - self.0; + if self.dir == 0 { + return None; + } - let v = self.0; + let v = self.from; - #[cfg(not(feature = "unchecked"))] - let n = self.0.checked_add(&self.2)?; - #[cfg(feature = "unchecked")] - let n = self.0 + self.2; + self.from = (self.add)(self.from, self.step)?; - #[cfg(not(feature = "unchecked"))] - let diff2 = self.1.checked_sub(&n)?; - #[cfg(feature = "unchecked")] - let diff2 = self.1 - n; - - if diff2 >= diff1 { - None - } else { - self.0 = if n >= self.1 { self.1 } else { n }; - Some(v) + if self.dir > 0 { + if self.from >= self.to { + self.dir = 0; + } + } else if self.dir < 0 { + if self.from <= self.to { + self.dir = 0; } } else { - #[cfg(not(feature = "unchecked"))] - let diff1 = self.0.checked_sub(&self.1)?; - #[cfg(feature = "unchecked")] - let diff1 = self.0 - self.1; - - let v = self.0; - - #[cfg(not(feature = "unchecked"))] - let n = self.0.checked_add(&self.2)?; - #[cfg(feature = "unchecked")] - let n = self.0 + self.2; - - #[cfg(not(feature = "unchecked"))] - let diff2 = n.checked_sub(&self.1)?; - #[cfg(feature = "unchecked")] - let diff2 = n - self.1; - - if diff2 >= diff1 { - None - } else { - self.0 = if n <= self.1 { self.1 } else { n }; - Some(v) - } + unreachable!(); } + + Some(v) } } -impl FusedIterator for StepRange where - T: Variant + Copy + PartialOrd + Add + Sub -{ -} +impl FusedIterator for StepRange {} // Bit-field iterator with step #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] @@ -237,134 +238,6 @@ impl ExactSizeIterator for CharsStream { } } -#[cfg(not(feature = "no_float"))] -pub mod float { - use super::*; - use crate::FLOAT; - - #[derive(Debug, Clone, Copy, PartialEq)] - pub struct StepFloatRange(FLOAT, FLOAT, FLOAT); - - impl StepFloatRange { - pub fn new(from: FLOAT, to: FLOAT, step: FLOAT) -> RhaiResultOf { - #[cfg(not(feature = "unchecked"))] - if step == 0.0 { - return Err(crate::ERR::ErrorInFunctionCall( - "range".to_string(), - "".to_string(), - crate::ERR::ErrorArithmetic( - "step value cannot be zero".to_string(), - Position::NONE, - ) - .into(), - Position::NONE, - ) - .into()); - } - - Ok(Self(from, to, step)) - } - } - - impl Iterator for StepFloatRange { - type Item = FLOAT; - - fn next(&mut self) -> Option { - if self.0 == self.1 { - None - } else if self.0 < self.1 { - #[cfg(not(feature = "unchecked"))] - if self.2 < 0.0 { - return None; - } - - let v = self.0; - let n = self.0 + self.2; - - self.0 = if n >= self.1 { self.1 } else { n }; - Some(v) - } else { - #[cfg(not(feature = "unchecked"))] - if self.2 > 0.0 { - return None; - } - - let v = self.0; - let n = self.0 + self.2; - - self.0 = if n <= self.1 { self.1 } else { n }; - Some(v) - } - } - } - - impl FusedIterator for StepFloatRange {} -} - -#[cfg(feature = "decimal")] -pub mod decimal { - use super::*; - use rust_decimal::Decimal; - - #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] - pub struct StepDecimalRange(Decimal, Decimal, Decimal); - - impl StepDecimalRange { - pub fn new(from: Decimal, to: Decimal, step: Decimal) -> RhaiResultOf { - #[cfg(not(feature = "unchecked"))] - if step.is_zero() { - return Err(crate::ERR::ErrorInFunctionCall( - "range".to_string(), - "".to_string(), - crate::ERR::ErrorArithmetic( - "step value cannot be zero".to_string(), - Position::NONE, - ) - .into(), - Position::NONE, - ) - .into()); - } - - Ok(Self(from, to, step)) - } - } - - impl Iterator for StepDecimalRange { - type Item = Decimal; - - fn next(&mut self) -> Option { - if self.0 == self.1 { - None - } else if self.0 < self.1 { - #[cfg(not(feature = "unchecked"))] - if self.2.is_sign_negative() { - return None; - } - - let v = self.0; - let n = self.0 + self.2; - - self.0 = if n >= self.1 { self.1 } else { n }; - Some(v) - } else { - #[cfg(not(feature = "unchecked"))] - if self.2.is_sign_positive() { - return None; - } - - let v = self.0; - let n = self.0 + self.2; - - self.0 = if n <= self.1 { self.1 } else { n }; - Some(v) - } - } - } - - impl FusedIterator for StepDecimalRange {} -} - macro_rules! reg_range { ($lib:ident | $x:expr => $( $y:ty ),*) => { $( @@ -377,11 +250,13 @@ macro_rules! reg_range { concat!("to: ", stringify!($y)), concat!("Iterator"), ], [ - "/// Return an iterator over the range of `from..to`.", + "/// Return an iterator over the exclusive range of `from..to`.", + "/// The value `to` is never included.", "///", "/// # Example", "///", "/// ```rhai", + "/// // prints all values from 8 to 17", "/// for n in range(8, 18) {", "/// print(n);", "/// }", @@ -392,9 +267,15 @@ macro_rules! reg_range { )* }; ($lib:ident | step $x:expr => $( $y:ty ),*) => { + #[cfg(not(feature = "unchecked"))] + reg_range!($lib | step(std_add) $x => $( $y ),*); + #[cfg(feature = "unchecked")] + reg_range!($lib | step(regular_add) $x => $( $y ),*); + }; + ($lib:ident | step ( $add:ident ) $x:expr => $( $y:ty ),*) => { $( $lib.set_iterator::>(); - let _hash = $lib.set_native_fn($x, |from: $y, to: $y, step: $y| StepRange::new(from, to, step)); + let _hash = $lib.set_native_fn($x, |from: $y, to: $y, step: $y| StepRange::new(from, to, step, $add)); #[cfg(feature = "metadata")] $lib.update_fn_metadata_with_comments(_hash, [ @@ -403,17 +284,22 @@ macro_rules! reg_range { concat!("step: ", stringify!($y)), concat!("Iterator") ], [ - "/// Return an iterator over the range of `from..to`, each iterator increasing by `step`.", + "/// Return an iterator over the exclusive range of `from..to`, each iteration increasing by `step`.", + "/// The value `to` is never included.", "///", "/// If `from` > `to` and `step` < 0, the iteration goes backwards.", "///", + "/// If `from` > `to` and `step` > 0 or `from` < `to` and `step` < 0, an empty iterator is returned.", + "///", "/// # Example", "///", "/// ```rhai", + "/// // prints all values from 8 to 17 in steps of 3", "/// for n in range(8, 18, 3) {", "/// print(n);", "/// }", "///", + "/// // prints all values down from 18 to 9 in steps of -3", "/// for n in range(18, 8, -3) {", "/// print(n);", "/// }", @@ -436,7 +322,6 @@ def_package! { reg_range!(lib | "range" => i8, u8, i16, u16, i32, u32, i64, u64); #[cfg(not(target_family = "wasm"))] - reg_range!(lib | "range" => i128, u128); } @@ -448,63 +333,22 @@ def_package! { reg_range!(lib | step "range" => i8, u8, i16, u16, i32, u32, i64, u64); #[cfg(not(target_family = "wasm"))] - reg_range!(lib | step "range" => i128, u128); } #[cfg(not(feature = "no_float"))] - { - lib.set_iterator::(); - - let _hash = lib.set_native_fn("range", float::StepFloatRange::new); - #[cfg(feature = "metadata")] - lib.update_fn_metadata_with_comments( - _hash, - ["from: FLOAT", "to: FLOAT", "step: FLOAT", "Iterator"], - [ - "/// Return an iterator over the range of `from..to`, each iterator increasing by `step`.", - "///", - "/// If `from` > `to` and `step` < 0, the iteration goes backwards.", - "///", - "/// # Example", - "///", - "/// ```rhai", - "/// for n in range(8.0, 18.0, 3.0) {", - "/// print(n);", - "/// }", - "///", - "/// for n in range(18.0, 8.0, -3.0) {", - "/// print(n);", - "/// }", - "/// ```" - ] - ); - } + reg_range!(lib | step(regular_add) "range" => crate::FLOAT); #[cfg(feature = "decimal")] - { - lib.set_iterator::(); - - let _hash = lib.set_native_fn("range", decimal::StepDecimalRange::new); - #[cfg(feature = "metadata")] - lib.update_fn_metadata_with_comments( - _hash, - ["from: Decimal", "to: Decimal", "step: Decimal", "Iterator"], - [ - "/// Return an iterator over the range of `from..to`, each iterator increasing by `step`.", - "///", - "/// If `from` > `to` and `step` < 0, the iteration goes backwards.", - ] - ); - } + reg_range!(lib | step "range" => rust_decimal::Decimal); // Register string iterator lib.set_iterator::(); #[cfg(feature = "metadata")] let (range_type, range_inclusive_type) = ( - format!("range: Range<{}>", std::any::type_name::()), - format!("range: RangeInclusive<{}>", std::any::type_name::()), + format!("range: Range<{}>", type_name::()), + format!("range: RangeInclusive<{}>", type_name::()), ); let _hash = lib.set_native_fn("chars", |string, range: ExclusiveRange| { diff --git a/src/parser.rs b/src/parser.rs index 4a1f1c16..d19c3d2d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -641,6 +641,7 @@ impl Engine { state: &mut ParseState, lib: &mut FnLib, lhs: Expr, + options: ASTFlags, check_index_type: bool, settings: ParseSettings, ) -> ParseResult { @@ -756,29 +757,35 @@ impl Engine { // Any more indexing following? match input.peek().expect(NEVER_ENDS) { // If another indexing level, right-bind it - (Token::LeftBracket, ..) => { + (Token::LeftBracket, ..) | (Token::QuestionBracket, ..) => { + let (token, pos) = input.next().expect(NEVER_ENDS); let prev_pos = settings.pos; - settings.pos = eat_token(input, Token::LeftBracket); + settings.pos = pos; // Recursively parse the indexing chain, right-binding each let idx_expr = self.parse_index_chain( input, state, lib, idx_expr, + match token { + Token::LeftBracket => ASTFlags::NONE, + Token::QuestionBracket => ASTFlags::NEGATED, + _ => unreachable!(), + }, false, settings.level_up(), )?; // Indexing binds to right Ok(Expr::Index( BinaryExpr { lhs, rhs: idx_expr }.into(), - ASTFlags::NONE, + options, prev_pos, )) } // Otherwise terminate the indexing chain _ => Ok(Expr::Index( BinaryExpr { lhs, rhs: idx_expr }.into(), - ASTFlags::BREAK, + options | ASTFlags::BREAK, settings.pos, )), } @@ -1634,8 +1641,13 @@ impl Engine { } // Indexing #[cfg(not(feature = "no_index"))] - (expr, Token::LeftBracket) => { - self.parse_index_chain(input, state, lib, expr, true, settings.level_up())? + (expr, token @ Token::LeftBracket) | (expr, token @ Token::QuestionBracket) => { + let opt = match token { + Token::LeftBracket => ASTFlags::NONE, + Token::QuestionBracket => ASTFlags::NEGATED, + _ => unreachable!(), + }; + self.parse_index_chain(input, state, lib, expr, opt, true, settings.level_up())? } // Property access #[cfg(not(feature = "no_object"))] diff --git a/src/serde/metadata.rs b/src/serde/metadata.rs index 652f5122..8cd30eaa 100644 --- a/src/serde/metadata.rs +++ b/src/serde/metadata.rs @@ -7,7 +7,7 @@ use crate::{calc_fn_hash, Engine, AST}; use serde::{Deserialize, Serialize}; #[cfg(feature = "no_std")] use std::prelude::v1::*; -use std::{borrow::Cow, cmp::Ordering, collections::BTreeMap, iter::empty}; +use std::{borrow::Cow, cmp::Ordering, collections::BTreeMap}; #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -106,7 +106,7 @@ impl<'a> From<&'a FuncInfo> for FnMetadata<'a> { } else { ( FnType::Native, - calc_native_fn_hash(empty::<&str>(), &info.metadata.name, &info.param_types), + calc_native_fn_hash(None, &info.metadata.name, &info.param_types), ) }; diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 1dd89768..59a63bb5 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -421,9 +421,17 @@ pub enum Token { /// `.` Period, /// `?.` + /// + /// Reserved under the `no_object` feature. + #[cfg(not(feature = "no_object"))] Elvis, /// `??` DoubleQuestion, + /// `?[` + /// + /// Reserved under the `no_object` feature. + #[cfg(not(feature = "no_index"))] + QuestionBracket, /// `..` ExclusiveRange, /// `..=` @@ -580,8 +588,11 @@ impl Token { Underscore => "_", Comma => ",", Period => ".", + #[cfg(not(feature = "no_object"))] Elvis => "?.", DoubleQuestion => "??", + #[cfg(not(feature = "no_index"))] + QuestionBracket => "?[", ExclusiveRange => "..", InclusiveRange => "..=", MapStart => "#{", @@ -777,8 +788,11 @@ impl Token { "_" => Underscore, "," => Comma, "." => Period, + #[cfg(not(feature = "no_object"))] "?." => Elvis, "??" => DoubleQuestion, + #[cfg(not(feature = "no_index"))] + "?[" => QuestionBracket, ".." => ExclusiveRange, "..=" => InclusiveRange, "#{" => MapStart, @@ -892,6 +906,7 @@ impl Token { //Period | //Elvis | //DoubleQuestion | + //QuestionBracket | ExclusiveRange | // .. - is unary InclusiveRange | // ..= - is unary LeftBrace | // { -expr } - is unary @@ -999,12 +1014,18 @@ impl Token { match self { LeftBrace | RightBrace | LeftParen | RightParen | LeftBracket | RightBracket | Plus | UnaryPlus | Minus | UnaryMinus | Multiply | Divide | Modulo | PowerOf | LeftShift - | RightShift | SemiColon | Colon | DoubleColon | Comma | Period | Elvis - | DoubleQuestion | ExclusiveRange | InclusiveRange | MapStart | Equals | LessThan - | GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo | EqualsTo | NotEqualsTo - | Bang | Pipe | Or | XOr | Ampersand | And | PlusAssign | MinusAssign - | MultiplyAssign | DivideAssign | LeftShiftAssign | RightShiftAssign | AndAssign - | OrAssign | XOrAssign | ModuloAssign | PowerOfAssign => true, + | RightShift | SemiColon | Colon | DoubleColon | Comma | Period | DoubleQuestion + | ExclusiveRange | InclusiveRange | MapStart | Equals | LessThan | GreaterThan + | LessThanEqualsTo | GreaterThanEqualsTo | EqualsTo | NotEqualsTo | Bang | Pipe + | Or | XOr | Ampersand | And | PlusAssign | MinusAssign | MultiplyAssign + | DivideAssign | LeftShiftAssign | RightShiftAssign | AndAssign | OrAssign + | XOrAssign | ModuloAssign | PowerOfAssign => true, + + #[cfg(not(feature = "no_object"))] + Elvis => true, + + #[cfg(not(feature = "no_index"))] + QuestionBracket => true, _ => false, } @@ -1499,7 +1520,7 @@ fn get_next_token_inner( } #[cfg(any(not(feature = "no_float"), feature = "decimal"))] '.' => { - stream.get_next().expect("`.`"); + stream.get_next().unwrap(); // Check if followed by digits or something that cannot start a property name match stream.peek_next().unwrap_or('\0') { @@ -1546,7 +1567,7 @@ fn get_next_token_inner( '+' | '-' => { result.push(next_char); pos.advance(); - result.push(stream.get_next().expect("`+` or `-`")); + result.push(stream.get_next().unwrap()); pos.advance(); } // Not a floating-point number @@ -2047,12 +2068,28 @@ fn get_next_token_inner( ('?', '.') => { eat_next(stream, pos); - return Some((Token::Elvis, start_pos)); + return Some(( + #[cfg(not(feature = "no_object"))] + Token::Elvis, + #[cfg(feature = "no_object")] + Token::Reserved("?.".into()), + start_pos, + )); } ('?', '?') => { eat_next(stream, pos); return Some((Token::DoubleQuestion, start_pos)); } + ('?', '[') => { + eat_next(stream, pos); + return Some(( + #[cfg(not(feature = "no_index"))] + Token::QuestionBracket, + #[cfg(feature = "no_index")] + Token::Reserved("?[".into()), + start_pos, + )); + } ('?', ..) => return Some((Token::Reserved("?".into()), start_pos)), (ch, ..) if ch.is_whitespace() => (), diff --git a/src/types/custom_types.rs b/src/types/custom_types.rs index 34759d1f..4331d545 100644 --- a/src/types/custom_types.rs +++ b/src/types/custom_types.rs @@ -25,6 +25,11 @@ impl CustomTypesCollection { pub fn new() -> Self { Self(BTreeMap::new()) } + /// Clear the [`CustomTypesCollection`]. + #[inline(always)] + pub fn clear(&mut self) { + self.0.clear(); + } /// Register a custom type. #[inline(always)] pub fn add(&mut self, type_name: impl Into, name: impl Into) { diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index 8c71de89..1336551b 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -1,7 +1,7 @@ //! Helper module which defines the [`Dynamic`] data type and the //! [`Any`] trait to to allow custom type handling. -use crate::func::native::SendSync; +use crate::func::{locked_read, SendSync}; use crate::{reify, ExclusiveRange, FnPtr, ImmutableString, InclusiveRange, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -26,7 +26,7 @@ pub use instant::Instant; const CHECKED: &str = "data type was checked"; mod private { - use crate::func::native::SendSync; + use crate::func::SendSync; use std::any::Any; /// A sealed trait that prevents other crates from implementing [`Variant`]. @@ -384,12 +384,7 @@ impl Dynamic { Union::Variant(ref v, ..) => (***v).type_id(), #[cfg(not(feature = "no_closure"))] - #[cfg(not(feature = "sync"))] - Union::Shared(ref cell, ..) => (*cell.borrow()).type_id(), - - #[cfg(not(feature = "no_closure"))] - #[cfg(feature = "sync")] - Union::Shared(ref cell, ..) => (*cell.read().unwrap()).type_id(), + Union::Shared(ref cell, ..) => (*locked_read(cell)).type_id(), } } /// Get the name of the type of the value held by this [`Dynamic`]. @@ -455,66 +450,17 @@ impl Hash for Dynamic { #[cfg(feature = "decimal")] Union::Decimal(ref d, ..) => d.hash(state), #[cfg(not(feature = "no_index"))] - Union::Array(ref a, ..) => a.as_ref().hash(state), + Union::Array(ref a, ..) => a.hash(state), #[cfg(not(feature = "no_index"))] - Union::Blob(ref a, ..) => a.as_ref().hash(state), + Union::Blob(ref a, ..) => a.hash(state), #[cfg(not(feature = "no_object"))] - Union::Map(ref m, ..) => m.as_ref().hash(state), + Union::Map(ref m, ..) => m.hash(state), Union::FnPtr(ref f, ..) => f.hash(state), #[cfg(not(feature = "no_closure"))] - #[cfg(not(feature = "sync"))] - Union::Shared(ref cell, ..) => (*cell.borrow()).hash(state), + Union::Shared(ref cell, ..) => (*locked_read(cell)).hash(state), - #[cfg(not(feature = "no_closure"))] - #[cfg(feature = "sync")] - Union::Shared(ref cell, ..) => (*cell.read().unwrap()).hash(state), - - Union::Variant(ref _v, ..) => { - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { - let value_any = (***_v).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); - } - - #[cfg(not(target_family = "wasm"))] - 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") - } + Union::Variant(..) => unimplemented!("{} cannot be hashed", self.type_name()), #[cfg(not(feature = "no_std"))] Union::TimeStamp(..) => unimplemented!("{} cannot be hashed", self.type_name()), @@ -550,49 +496,47 @@ impl fmt::Display for Dynamic { #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] - if _type_id == TypeId::of::() { - return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); - } else if _type_id == TypeId::of::() { - return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); - } else if _type_id == TypeId::of::() { - return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); - } else if _type_id == TypeId::of::() { - return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); - } else if _type_id == TypeId::of::() { - return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); - } else if _type_id == TypeId::of::() { - return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); - } else if _type_id == TypeId::of::() { - return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); - } else if _type_id == TypeId::of::() { - return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); + if let Some(value) = _value_any.downcast_ref::() { + return fmt::Display::fmt(value, f); + } else if let Some(value) = _value_any.downcast_ref::() { + return fmt::Display::fmt(value, f); + } else if let Some(value) = _value_any.downcast_ref::() { + return fmt::Display::fmt(value, f); + } else if let Some(value) = _value_any.downcast_ref::() { + return fmt::Display::fmt(value, f); + } else if let Some(value) = _value_any.downcast_ref::() { + return fmt::Display::fmt(value, f); + } else if let Some(value) = _value_any.downcast_ref::() { + return fmt::Display::fmt(value, f); + } else if let Some(value) = _value_any.downcast_ref::() { + return fmt::Display::fmt(value, f); + } else if let Some(value) = _value_any.downcast_ref::() { + return fmt::Display::fmt(value, f); } #[cfg(not(feature = "no_float"))] #[cfg(not(feature = "f32_float"))] - if _type_id == TypeId::of::() { - return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); + if let Some(value) = _value_any.downcast_ref::() { + return fmt::Display::fmt(value, f); } #[cfg(not(feature = "no_float"))] #[cfg(feature = "f32_float")] - if _type_id == TypeId::of::() { - return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); + if let Some(value) = _value_any.downcast_ref::() { + return fmt::Display::fmt(value, f); } #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] #[cfg(not(target_family = "wasm"))] - if _type_id == TypeId::of::() { - return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); - } else if _type_id == TypeId::of::() { - return fmt::Display::fmt(_value_any.downcast_ref::().expect(CHECKED), f); + if let Some(value) = _value_any.downcast_ref::() { + return fmt::Display::fmt(value, f); + } else if let Some(value) = _value_any.downcast_ref::() { + return fmt::Display::fmt(value, f); } - if _type_id == TypeId::of::() { - let range = _value_any.downcast_ref::().expect(CHECKED); + if let Some(range) = _value_any.downcast_ref::() { return write!(f, "{}..{}", range.start, range.end); - } else if _type_id == TypeId::of::() { - let range = _value_any.downcast_ref::().expect(CHECKED); + } else if let Some(range) = _value_any.downcast_ref::() { return write!(f, "{}..={}", range.start(), range.end()); } @@ -655,49 +599,47 @@ impl fmt::Debug for Dynamic { #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] - if _type_id == TypeId::of::() { - return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); - } else if _type_id == TypeId::of::() { - return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); - } else if _type_id == TypeId::of::() { - return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); - } else if _type_id == TypeId::of::() { - return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); - } else if _type_id == TypeId::of::() { - return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); - } else if _type_id == TypeId::of::() { - return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); - } else if _type_id == TypeId::of::() { - return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); - } else if _type_id == TypeId::of::() { - return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); + if let Some(value) = _value_any.downcast_ref::() { + return fmt::Debug::fmt(value, f); + } else if let Some(value) = _value_any.downcast_ref::() { + return fmt::Debug::fmt(value, f); + } else if let Some(value) = _value_any.downcast_ref::() { + return fmt::Debug::fmt(value, f); + } else if let Some(value) = _value_any.downcast_ref::() { + return fmt::Debug::fmt(value, f); + } else if let Some(value) = _value_any.downcast_ref::() { + return fmt::Debug::fmt(value, f); + } else if let Some(value) = _value_any.downcast_ref::() { + return fmt::Debug::fmt(value, f); + } else if let Some(value) = _value_any.downcast_ref::() { + return fmt::Debug::fmt(value, f); + } else if let Some(value) = _value_any.downcast_ref::() { + return fmt::Debug::fmt(value, f); } #[cfg(not(feature = "no_float"))] #[cfg(not(feature = "f32_float"))] - if _type_id == TypeId::of::() { - return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); + if let Some(value) = _value_any.downcast_ref::() { + return fmt::Debug::fmt(value, f); } #[cfg(not(feature = "no_float"))] #[cfg(feature = "f32_float")] - if _type_id == TypeId::of::() { - return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); + if let Some(value) = _value_any.downcast_ref::() { + return fmt::Debug::fmt(value, f); } #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] #[cfg(not(target_family = "wasm"))] - if _type_id == TypeId::of::() { - return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); - } else if _type_id == TypeId::of::() { - return fmt::Debug::fmt(_value_any.downcast_ref::().expect(CHECKED), f); + if let Some(value) = _value_any.downcast_ref::() { + return fmt::Debug::fmt(value, f); + } else if let Some(value) = _value_any.downcast_ref::() { + return fmt::Debug::fmt(value, f); } - if _type_id == TypeId::of::() { - let range = _value_any.downcast_ref::().expect(CHECKED); + if let Some(range) = _value_any.downcast_ref::() { return write!(f, "{}..{}", range.start, range.end); - } else if _type_id == TypeId::of::() { - let range = _value_any.downcast_ref::().expect(CHECKED); + } else if let Some(range) = _value_any.downcast_ref::() { return write!(f, "{}..={}", range.start(), range.end()); } @@ -1061,28 +1003,27 @@ impl Dynamic { /// /// Constant [`Dynamic`] values are read-only. /// - /// If a [`&mut Dynamic`][Dynamic] to such a constant is passed to a Rust function, the function - /// can use this information to return an error of - /// [`ErrorAssignmentToConstant`][crate::EvalAltResult::ErrorAssignmentToConstant] if its value - /// is going to be modified. + /// # Usage /// - /// This safe-guards constant values from being modified from within Rust functions. + /// If a [`&mut Dynamic`][Dynamic] to such a constant is passed to a Rust function, the function + /// can use this information to return the error + /// [`ErrorAssignmentToConstant`][crate::EvalAltResult::ErrorAssignmentToConstant] if its value + /// will be modified. + /// + /// This safe-guards constant values from being modified within Rust functions. + /// + /// # Shared Values + /// + /// If a [`Dynamic`] holds a _shared_ value, then it is read-only only if the shared value + /// itself is read-only. #[must_use] pub fn is_read_only(&self) -> bool { #[cfg(not(feature = "no_closure"))] match self.0 { - Union::Shared(.., ReadOnly) => return true, - - #[cfg(not(feature = "sync"))] + // Shared values do not consider the current access mode + //Union::Shared(.., ReadOnly) => return true, Union::Shared(ref cell, ..) => { - return match cell.borrow().access_mode() { - ReadWrite => false, - ReadOnly => true, - } - } - #[cfg(feature = "sync")] - Union::Shared(ref cell, ..) => { - return match cell.read().unwrap().access_mode() { + return match locked_read(cell).access_mode() { ReadWrite => false, ReadOnly => true, } @@ -1114,12 +1055,7 @@ impl Dynamic { Union::Map(..) => true, #[cfg(not(feature = "no_closure"))] - #[cfg(not(feature = "sync"))] - Union::Shared(ref cell, ..) => cell.borrow().is_hashable(), - - #[cfg(not(feature = "no_closure"))] - #[cfg(feature = "sync")] - Union::Shared(ref cell, ..) => cell.read().unwrap().is_hashable(), + Union::Shared(ref cell, ..) => locked_read(cell).is_hashable(), _ => false, } @@ -1370,11 +1306,7 @@ impl Dynamic { pub fn flatten_clone(&self) -> Self { match self.0 { #[cfg(not(feature = "no_closure"))] - #[cfg(not(feature = "sync"))] - Union::Shared(ref cell, ..) => cell.borrow().clone(), - #[cfg(not(feature = "no_closure"))] - #[cfg(feature = "sync")] - Union::Shared(ref cell, ..) => cell.read().unwrap().clone(), + Union::Shared(ref cell, ..) => locked_read(cell).clone(), _ => self.clone(), } } @@ -1389,11 +1321,8 @@ impl Dynamic { pub fn flatten(self) -> Self { match self.0 { #[cfg(not(feature = "no_closure"))] - Union::Shared(cell, ..) => crate::func::native::shared_try_take(cell).map_or_else( - #[cfg(not(feature = "sync"))] - |cell| cell.borrow().clone(), - #[cfg(feature = "sync")] - |cell| cell.read().unwrap().clone(), + Union::Shared(cell, ..) => crate::func::shared_try_take(cell).map_or_else( + |ref cell| locked_read(cell).clone(), #[cfg(not(feature = "sync"))] |value| value.into_inner(), #[cfg(feature = "sync")] @@ -1414,11 +1343,8 @@ impl Dynamic { #[cfg(not(feature = "no_closure"))] Union::Shared(ref mut cell, ..) => { let cell = mem::take(cell); - *self = crate::func::native::shared_try_take(cell).map_or_else( - #[cfg(not(feature = "sync"))] - |cell| cell.borrow().clone(), - #[cfg(feature = "sync")] - |cell| cell.read().unwrap().clone(), + *self = crate::func::shared_try_take(cell).map_or_else( + |ref cell| locked_read(cell).clone(), #[cfg(not(feature = "sync"))] |value| value.into_inner(), #[cfg(feature = "sync")] @@ -1470,10 +1396,7 @@ impl Dynamic { match self.0 { #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => { - #[cfg(not(feature = "sync"))] - let value = cell.borrow(); - #[cfg(feature = "sync")] - let value = cell.read().unwrap(); + let value = locked_read(cell); if (*value).type_id() != TypeId::of::() && TypeId::of::() != TypeId::of::() @@ -1505,7 +1428,7 @@ impl Dynamic { match self.0 { #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => { - let guard = crate::func::native::locked_write(cell); + let guard = crate::func::locked_write(cell); if (*guard).type_id() != TypeId::of::() && TypeId::of::() != TypeId::of::() @@ -1820,11 +1743,8 @@ impl Dynamic { match self.0 { Union::Str(s, ..) => Ok(s), #[cfg(not(feature = "no_closure"))] - Union::Shared(cell, ..) => { - #[cfg(not(feature = "sync"))] - let value = cell.borrow(); - #[cfg(feature = "sync")] - let value = cell.read().unwrap(); + Union::Shared(ref cell, ..) => { + let value = locked_read(cell); match value.0 { Union::Str(ref s, ..) => Ok(s.clone()), @@ -1842,11 +1762,8 @@ impl Dynamic { match self.0 { Union::Array(a, ..) => Ok(*a), #[cfg(not(feature = "no_closure"))] - Union::Shared(cell, ..) => { - #[cfg(not(feature = "sync"))] - let value = cell.borrow(); - #[cfg(feature = "sync")] - let value = cell.read().unwrap(); + Union::Shared(ref cell, ..) => { + let value = locked_read(cell); match value.0 { Union::Array(ref a, ..) => Ok(a.as_ref().clone()), @@ -1880,11 +1797,8 @@ impl Dynamic { .collect(), Union::Blob(..) if TypeId::of::() == TypeId::of::() => Ok(self.cast::>()), #[cfg(not(feature = "no_closure"))] - Union::Shared(cell, ..) => { - #[cfg(not(feature = "sync"))] - let value = cell.borrow(); - #[cfg(feature = "sync")] - let value = cell.read().unwrap(); + Union::Shared(ref cell, ..) => { + let value = locked_read(cell); match value.0 { Union::Array(ref a, ..) => { @@ -1921,11 +1835,8 @@ impl Dynamic { match self.0 { Union::Blob(a, ..) => Ok(*a), #[cfg(not(feature = "no_closure"))] - Union::Shared(cell, ..) => { - #[cfg(not(feature = "sync"))] - let value = cell.borrow(); - #[cfg(feature = "sync")] - let value = cell.read().unwrap(); + Union::Shared(ref cell, ..) => { + let value = locked_read(cell); match value.0 { Union::Blob(ref a, ..) => Ok(a.as_ref().clone()), diff --git a/src/types/immutable_string.rs b/src/types/immutable_string.rs index e959b83c..85cd4127 100644 --- a/src/types/immutable_string.rs +++ b/src/types/immutable_string.rs @@ -1,6 +1,6 @@ //! The `ImmutableString` type. -use crate::func::native::{shared_get_mut, shared_make_mut, shared_take}; +use crate::func::{shared_get_mut, shared_make_mut, shared_take}; use crate::{Shared, SmartString}; #[cfg(feature = "no_std")] use std::prelude::v1::*; diff --git a/tests/arrays.rs b/tests/arrays.rs index fb6e4a4d..aaea491a 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -405,3 +405,14 @@ fn test_arrays_map_reduce() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_arrays_elvis() -> Result<(), Box> { + let engine = Engine::new(); + + assert_eq!(engine.eval::<()>("let x = (); x?[2]")?, ()); + + engine.run("let x = (); x?[2] = 42")?; + + Ok(()) +} diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 0efeb14c..fa333a62 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -1,6 +1,6 @@ #![cfg(not(feature = "no_function"))] use rhai::{Dynamic, Engine, EvalAltResult, FnPtr, Func, FuncArgs, Scope, AST, INT}; -use std::{any::TypeId, iter::once}; +use std::any::TypeId; #[test] fn test_call_fn() -> Result<(), Box> { @@ -107,9 +107,9 @@ struct Options { impl FuncArgs for Options { fn parse>(self, container: &mut C) { - container.extend(once(self.foo.into())); - container.extend(once(self.bar.into())); - container.extend(once(self.baz.into())); + container.extend(Some(self.foo.into())); + container.extend(Some(self.bar.into())); + container.extend(Some(self.baz.into())); } } diff --git a/tests/var_scope.rs b/tests/var_scope.rs index 014ec42b..fdf256d3 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult, ParseErrorType, Position, Scope, INT}; +use rhai::{Dynamic, Engine, EvalAltResult, ParseErrorType, Position, Scope, INT}; #[test] fn test_var_scope() -> Result<(), Box> { @@ -162,9 +162,16 @@ fn test_var_resolver() -> Result<(), Box> { scope.push("chameleon", 123 as INT); scope.push("DO_NOT_USE", 999 as INT); - engine.on_var(|name, _, context| { + #[cfg(not(feature = "no_closure"))] + let mut base = Dynamic::ONE.into_shared(); + #[cfg(not(feature = "no_closure"))] + let shared = base.clone(); + + engine.on_var(move |name, _, context| { match name { "MYSTIC_NUMBER" => Ok(Some((42 as INT).into())), + #[cfg(not(feature = "no_closure"))] + "HELLO" => Ok(Some(shared.clone())), // Override a variable - make it not found even if it exists! "DO_NOT_USE" => { Err(EvalAltResult::ErrorVariableNotFound(name.to_string(), Position::NONE).into()) @@ -186,6 +193,19 @@ fn test_var_resolver() -> Result<(), Box> { engine.eval_with_scope::(&mut scope, "MYSTIC_NUMBER")?, 42 ); + + #[cfg(not(feature = "no_closure"))] + { + assert_eq!(engine.eval::("HELLO")?, 1); + *base.write_lock::().unwrap() = 42; + assert_eq!(engine.eval::("HELLO")?, 42); + engine.run("HELLO = 123")?; + assert_eq!(base.as_int().unwrap(), 123); + assert_eq!(engine.eval::("HELLO = HELLO + 1; HELLO")?, 124); + assert_eq!(engine.eval::("HELLO = HELLO * 2; HELLO")?, 248); + assert_eq!(base.as_int().unwrap(), 248); + } + assert_eq!(engine.eval_with_scope::(&mut scope, "chameleon")?, 1); assert!( matches!(*engine.eval_with_scope::(&mut scope, "DO_NOT_USE").expect_err("should error"),