diff --git a/.gitignore b/.gitignore index 6eed6fee..9b226ad0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,6 @@ target/ Cargo.lock .vscode/ .cargo/ +benches/results before* after* diff --git a/CHANGELOG.md b/CHANGELOG.md index 07ee0936..a5abea2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,42 @@ Rhai Release Notes ================== -This version introduces functions with `Dynamic` parameters acting as wildcards. +Version 0.19.14 +=============== + +Bug fixes +--------- + +* Errors in native Rust functions now contain the correct function call positions. +* Fixed error types in `EvalAltResult::ErrorMismatchDataType` which were swapped. + +Breaking changes +---------------- + +* Zero step in the `range` function now raises an error instead of creating an infinite stream. +* Error variable captured by `catch` is now an _object map_ containing error fields. +* `EvalAltResult::clear_position` is renamed `EvalAltResult::take_position` and returns the position taken. +* `private` functions in an `AST` can now be called with `call_fn` etc. +* `NativeCallContext::call_fn_dynamic_raw` no longer has the `pub_only` parameter. +* `Module::update_fn_metadata` input parameter is changed. +* Function keywords (e.g. `type_of`, `eval`, `Fn`) can no longer be overloaded. It is more trouble than worth. To disable these keywords, use `Engine::disable_symbol`. +* `is_def_var` and `is_def_fn` are now reserved keywords. +* `Engine::id` field is removed. + +Enhancements +------------ + +* Function calls are more optimized and should now run faster. +* `range` function now supports negative step and decreasing streams (i.e. to < from). +* More information is provided to the error variable captured by the `catch` statement in an _object map_. +* Previously, `private` functions in an `AST` cannot be called with `call_fn` etc. This is inconvenient when trying to call a function inside a script which also serves as a loadable module exporting part (but not all) of the functions. Now, all functions (`private` or not) can be called in an `AST`. The `private` keyword is relegated to preventing a function from being exported. + Version 0.19.13 =============== +This version introduces functions with `Dynamic` parameters acting as wildcards. + Bug fixes --------- @@ -694,7 +725,7 @@ Breaking changes ---------------- * `Engine::compile_XXX` functions now return `ParseError` instead of `Box`. -* The `RegisterDynamicFn` trait is merged into the `RegisterResultFn` trait which now always returns `Result>`. +* The `RegisterDynamicFn` trait is merged into the `RegisterResultFn` trait which now always returns `RhaiResult`. * Default maximum limit on levels of nested function calls is fine-tuned and set to a different value. * Some operator functions are now built in (see _Speed enhancements_ below), so they are available even under `Engine::new_raw`. * Strings are now immutable. The type `rhai::ImmutableString` is used instead of `std::string::String`. This is to avoid excessive cloning of strings. All native-Rust functions taking string parameters should switch to `rhai::ImmutableString` (which is either `Rc` or `Arc` depending on whether the `sync` feature is used). diff --git a/Cargo.toml b/Cargo.toml index 57743468..a334d795 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,11 +6,11 @@ members = [ [package] name = "rhai" -version = "0.19.13" +version = "0.19.14" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] description = "Embedded scripting for Rust" -homepage = "https://rhai.rs/book" +homepage = "https://rhai.rs" repository = "https://github.com/rhaiscript" readme = "README.md" license = "MIT OR Apache-2.0" diff --git a/README.md b/README.md index 628108e8..1bfd1806 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Rhai - Embedded Scripting for Rust [![chat](https://img.shields.io/discord/767611025456889857.svg?logo=discord)](https://discord.gg/HquqbYFcZ9) [![Reddit](https://img.shields.io/reddit/subreddit-subscribers/Rhai?logo=reddit)](https://www.reddit.com/r/Rhai) -![Rhai logo](https://rhai.rs/book/images/logo/rhai-banner-transparent-colour.svg) +[![Rhai logo](https://rhai.rs/book/images/logo/rhai-banner-transparent-colour.svg)](https://rhai.rs) Rhai is an embedded scripting language and evaluation engine for Rust that gives a safe and easy way to add scripting to any application. @@ -66,6 +66,12 @@ For those who actually want their own language * Extend the language with [custom syntax](https://rhai.rs/book/engine/custom-syntax.html). +Project Site +------------ + +[`rhai.rs`](https://rhai.rs) + + Documentation ------------- diff --git a/src/ast.rs b/src/ast.rs index b43e1c8c..050ea197 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -38,25 +38,6 @@ pub enum FnAccess { Private, } -impl FnAccess { - /// Is this access mode [private][FnAccess::Private]? - #[inline(always)] - pub fn is_private(self) -> bool { - match self { - Self::Private => true, - Self::Public => false, - } - } - /// Is this access mode [public][FnAccess::Public]? - #[inline(always)] - pub fn is_public(self) -> bool { - match self { - Self::Private => false, - Self::Public => true, - } - } -} - /// _(INTERNALS)_ A type containing information on a scripted function. /// Exported under the `internals` feature only. /// @@ -91,10 +72,9 @@ impl fmt::Display for ScriptFnDef { write!( f, "{}{}({})", - if self.access.is_private() { - "private " - } else { - "" + match self.access { + FnAccess::Public => "", + FnAccess::Private => "private", }, self.name, self.params @@ -134,10 +114,9 @@ impl fmt::Display for ScriptFnMetadata<'_> { write!( f, "{}{}({})", - if self.access.is_private() { - "private " - } else { - "" + match self.access { + FnAccess::Public => "", + FnAccess::Private => "private", }, self.name, self.params.iter().cloned().collect::>().join(", ") diff --git a/src/bin/rhai-repl.rs b/src/bin/rhai-repl.rs index 0f911027..c5c91a18 100644 --- a/src/bin/rhai-repl.rs +++ b/src/bin/rhai-repl.rs @@ -13,8 +13,7 @@ use std::{ /// Pretty-print error. fn print_error(input: &str, mut err: EvalAltResult) { let lines: Vec<_> = input.trim().split('\n').collect(); - let pos = err.position(); - err.clear_position(); + let pos = err.take_position(); let line_no = if lines.len() > 1 { if pos.is_none() { diff --git a/src/bin/rhai-run.rs b/src/bin/rhai-run.rs index 24e60452..2ffe8423 100644 --- a/src/bin/rhai-run.rs +++ b/src/bin/rhai-run.rs @@ -23,8 +23,7 @@ fn eprint_error(input: &str, mut err: EvalAltResult) { let lines: Vec<_> = input.split('\n').collect(); // Print error - let pos = err.position(); - err.clear_position(); + let pos = err.take_position(); if pos.is_none() { // No position diff --git a/src/dynamic.rs b/src/dynamic.rs index de5a4bb8..93d2c845 100644 --- a/src/dynamic.rs +++ b/src/dynamic.rs @@ -1447,9 +1447,9 @@ impl Dynamic { /// Exported under the `decimal` feature only. #[cfg(feature = "decimal")] #[inline(always)] - pub fn as_decimal(self) -> Result { - match self.0 { - Union::Decimal(n, _) => Ok(*n), + pub fn as_decimal(&self) -> Result { + match &self.0 { + Union::Decimal(n, _) => Ok(**n), #[cfg(not(feature = "no_closure"))] Union::Shared(_, _) => self.read_lock().map(|v| *v).ok_or_else(|| self.type_name()), _ => Err(self.type_name()), @@ -1503,7 +1503,6 @@ impl Dynamic { pub fn take_immutable_string(self) -> Result { match self.0 { Union::Str(s, _) => Ok(s), - Union::FnPtr(f, _) => Ok(f.take_data().0), #[cfg(not(feature = "no_closure"))] Union::Shared(cell, _) => { #[cfg(not(feature = "sync"))] @@ -1513,7 +1512,6 @@ impl Dynamic { match &data.0 { Union::Str(s, _) => Ok(s.clone()), - Union::FnPtr(f, _) => Ok(f.get_fn_name().clone()), _ => Err((*data).type_name()), } } diff --git a/src/engine.rs b/src/engine.rs index 5d1957d8..df58d80d 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -17,7 +17,7 @@ use crate::stdlib::{ collections::{HashMap, HashSet}, fmt, format, hash::{Hash, Hasher}, - iter::{empty, FromIterator}, + iter::empty, num::{NonZeroU64, NonZeroU8, NonZeroUsize}, ops::DerefMut, string::{String, ToString}, @@ -25,8 +25,8 @@ use crate::stdlib::{ use crate::syntax::CustomSyntax; use crate::utils::{get_hasher, StraightHasherBuilder}; use crate::{ - calc_native_fn_hash, Dynamic, EvalAltResult, FnPtr, ImmutableString, Module, Position, Scope, - Shared, StaticVec, + calc_native_fn_hash, Dynamic, EvalAltResult, FnPtr, ImmutableString, Module, Position, + RhaiResult, Scope, Shared, StaticVec, }; #[cfg(not(feature = "no_index"))] @@ -107,6 +107,11 @@ impl Imports { pub(crate) fn iter_raw(&self) -> impl Iterator)> { self.0.iter().rev().map(|(n, m)| (n, m)) } + /// Get an iterator to this stack of imported [modules][Module] in forward order. + #[inline(always)] + pub(crate) fn scan_raw(&self) -> impl Iterator)> { + self.0.iter().map(|(n, m)| (n, m)) + } /// Get a consuming iterator to this stack of imported [modules][Module] in reverse order. #[inline(always)] pub fn into_iter(self) -> impl Iterator)> { @@ -151,24 +156,6 @@ impl Imports { } } -impl<'a, T: IntoIterator)>> From for Imports { - #[inline(always)] - fn from(value: T) -> Self { - Self( - value - .into_iter() - .map(|(k, v)| (k.clone(), v.clone())) - .collect(), - ) - } -} -impl FromIterator<(ImmutableString, Shared)> for Imports { - #[inline(always)] - fn from_iter)>>(iter: T) -> Self { - Self(iter.into_iter().collect()) - } -} - #[cfg(not(feature = "unchecked"))] #[cfg(debug_assertions)] #[cfg(not(feature = "no_function"))] @@ -205,6 +192,8 @@ pub const KEYWORD_FN_PTR_CURRY: &str = "curry"; #[cfg(not(feature = "no_closure"))] pub const KEYWORD_IS_SHARED: &str = "is_shared"; pub const KEYWORD_IS_DEF_VAR: &str = "is_def_var"; +#[cfg(not(feature = "no_function"))] +pub const KEYWORD_IS_DEF_FN: &str = "is_def_fn"; pub const KEYWORD_THIS: &str = "this"; #[cfg(not(feature = "no_object"))] pub const FN_GET: &str = "get$"; @@ -422,8 +411,8 @@ impl<'a> Target<'a> { // Replace the character at the specified index position let new_ch = new_val.as_char().map_err(|err| { Box::new(EvalAltResult::ErrorMismatchDataType( - err.to_string(), "char".to_string(), + err.to_string(), pos, )) })?; @@ -536,18 +525,6 @@ impl State { pub fn is_global(&self) -> bool { self.scope_level == 0 } - /// Get the current functions resolution cache. - pub fn fn_resolution_cache( - &self, - ) -> Option< - &HashMap< - NonZeroU64, - Option<(CallableFunction, Option)>, - StraightHasherBuilder, - >, - > { - self.fn_resolution_caches.last() - } /// Get a mutable reference to the current functions resolution cache. pub fn fn_resolution_cache_mut( &mut self, @@ -628,17 +605,17 @@ pub struct Limits { /// Context of a script evaluation process. #[derive(Debug)] -pub struct EvalContext<'e, 'x, 'px, 'a, 's, 'm, 't, 'pt> { - pub(crate) engine: &'e Engine, +pub struct EvalContext<'a, 'x, 'px, 'm, 's, 't, 'pt> { + pub(crate) engine: &'a Engine, pub(crate) scope: &'x mut Scope<'px>, - pub(crate) mods: &'a mut Imports, + pub(crate) mods: &'m mut Imports, pub(crate) state: &'s mut State, - pub(crate) lib: &'m [&'m Module], + pub(crate) lib: &'a [&'a Module], pub(crate) this_ptr: &'t mut Option<&'pt mut Dynamic>, pub(crate) level: usize, } -impl<'e, 'x, 'px, 'a, 's, 'm, 't, 'pt> EvalContext<'e, 'x, 'px, 'a, 's, 'm, 't, 'pt> { +impl<'x, 'px> EvalContext<'_, 'x, 'px, '_, '_, '_, '_> { /// The current [`Engine`]. #[inline(always)] pub fn engine(&self) -> &Engine { @@ -721,9 +698,6 @@ impl<'e, 'x, 'px, 'a, 's, 'm, 't, 'pt> EvalContext<'e, 'x, 'px, 'a, 's, 'm, 't, /// # } /// ``` pub struct Engine { - /// A unique ID identifying this scripting [`Engine`]. - pub id: String, - /// A module containing all functions directly loaded into the Engine. pub(crate) global_namespace: Module, /// A collection of all modules loaded into the global namespace of the Engine. @@ -768,11 +742,7 @@ pub struct Engine { impl fmt::Debug for Engine { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if !self.id.is_empty() { - write!(f, "Engine({})", self.id) - } else { - f.write_str("Engine") - } + f.write_str("Engine") } } @@ -824,40 +794,12 @@ fn default_debug(_s: &str, _source: Option<&str>, _pos: Position) { } } -/// Search for a module within an imports stack. -/// [`Position`] in [`EvalAltResult`] is [`None`][Position::None] and must be set afterwards. -pub fn search_imports( - mods: &Imports, - state: &mut State, - namespace: &NamespaceRef, -) -> Result, Box> { - let Ident { name: root, pos } = &namespace[0]; - - // Qualified - check if the root module is directly indexed - let index = if state.always_search { - None - } else { - namespace.index() - }; - - Ok(if let Some(index) = index { - let offset = mods.len() - index.get(); - mods.get(offset).expect("invalid index in Imports") - } else { - mods.find(root) - .map(|n| mods.get(n).expect("invalid index in Imports")) - .ok_or_else(|| EvalAltResult::ErrorModuleNotFound(root.to_string(), *pos))? - }) -} - impl Engine { /// Create a new [`Engine`] #[inline] pub fn new() -> Self { // Create the new scripting Engine let mut engine = Self { - id: Default::default(), - global_namespace: Default::default(), global_modules: Default::default(), global_sub_modules: Default::default(), @@ -923,8 +865,6 @@ impl Engine { #[inline] pub fn new_raw() -> Self { Self { - id: Default::default(), - global_namespace: Default::default(), global_modules: Default::default(), global_sub_modules: Default::default(), @@ -970,6 +910,32 @@ impl Engine { } } + /// Search for a module within an imports stack. + pub(crate) fn search_imports( + &self, + mods: &Imports, + state: &mut State, + namespace: &NamespaceRef, + ) -> Option> { + let root = &namespace[0].name; + + // Qualified - check if the root module is directly indexed + let index = if state.always_search { + None + } else { + namespace.index() + }; + + if let Some(index) = index { + let offset = mods.len() - index.get(); + Some(mods.get(offset).expect("invalid index in Imports")) + } else { + mods.find(root) + .map(|n| mods.get(n).expect("invalid index in Imports")) + .or_else(|| self.global_sub_modules.get(root).cloned()) + } + } + /// Search for a variable within the scope or within imports, /// depending on whether the variable name is namespace-qualified. pub(crate) fn search_namespace<'s>( @@ -985,7 +951,12 @@ impl Engine { Expr::Variable(v) => match v.as_ref() { // Qualified variable (_, Some((hash_var, modules)), Ident { name, pos }) => { - let module = search_imports(mods, state, modules)?; + let module = self.search_imports(mods, state, modules).ok_or_else(|| { + EvalAltResult::ErrorModuleNotFound( + modules[0].name.to_string(), + modules[0].pos, + ) + })?; let target = module.get_qualified_var(*hash_var).map_err(|mut err| { match *err { EvalAltResult::ErrorVariableNotFound(ref mut err_name, _) => { @@ -1077,7 +1048,7 @@ impl Engine { } /// Chain-evaluate a dot/index chain. - /// [`Position`] in [`EvalAltResult`] is [`None`][Position::None] and must be set afterwards. + /// [`Position`] in [`EvalAltResult`] is [`NONE`][Position::NONE] and must be set afterwards. #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] fn eval_dot_index_chain_helper( &self, @@ -1164,8 +1135,8 @@ impl Engine { let args = &mut [target_val, &mut idx_val2, &mut (new_val.0).0]; self.exec_fn_call( - mods, state, lib, FN_IDX_SET, None, args, is_ref, true, false, - val_pos, None, level, + mods, state, lib, FN_IDX_SET, None, args, is_ref, true, val_pos, + None, level, ) .map_err(|err| match *err { EvalAltResult::ErrorFunctionNotFound(fn_sig, _) @@ -1205,7 +1176,7 @@ impl Engine { } = x.as_ref(); let args = idx_val.as_fn_call_args(); self.make_method_call( - mods, state, lib, name, *hash, target, args, false, *pos, level, + mods, state, lib, name, *hash, target, args, *pos, level, ) } // xxx.fn_name(...) = ??? @@ -1245,8 +1216,8 @@ impl Engine { let mut new_val = new_val; let mut args = [target_val, &mut (new_val.as_mut().unwrap().0).0]; self.exec_fn_call( - mods, state, lib, setter, None, &mut args, is_ref, true, false, *pos, - None, level, + mods, state, lib, setter, None, &mut args, is_ref, true, *pos, None, + level, ) .map(|(v, _)| (v, true)) } @@ -1255,8 +1226,8 @@ impl Engine { let (getter, _, Ident { pos, .. }) = x.as_ref(); let mut args = [target_val]; self.exec_fn_call( - mods, state, lib, getter, None, &mut args, is_ref, true, false, *pos, - None, level, + mods, state, lib, getter, None, &mut args, is_ref, true, *pos, None, + level, ) .map(|(v, _)| (v, false)) } @@ -1280,7 +1251,7 @@ impl Engine { } = x.as_ref(); let args = idx_val.as_fn_call_args(); let (val, _) = self.make_method_call( - mods, state, lib, name, *hash, target, args, false, *pos, level, + mods, state, lib, name, *hash, target, args, *pos, level, )?; val.into() } @@ -1307,8 +1278,8 @@ impl Engine { let arg_values = &mut [target_val, &mut Default::default()]; let args = &mut arg_values[..1]; let (mut val, updated) = self.exec_fn_call( - mods, state, lib, getter, None, args, is_ref, true, false, - *pos, None, level, + mods, state, lib, getter, None, args, is_ref, true, *pos, None, + level, )?; let val = &mut val; @@ -1334,7 +1305,7 @@ impl Engine { arg_values[1] = val; self.exec_fn_call( mods, state, lib, setter, None, arg_values, is_ref, true, - false, *pos, None, level, + *pos, None, level, ) .or_else( |err| match *err { @@ -1359,7 +1330,7 @@ impl Engine { } = f.as_ref(); let args = idx_val.as_fn_call_args(); let (mut val, _) = self.make_method_call( - mods, state, lib, name, *hash, target, args, false, *pos, level, + mods, state, lib, name, *hash, target, args, *pos, level, )?; let val = &mut val; let target = &mut val.into(); @@ -1399,7 +1370,7 @@ impl Engine { expr: &Expr, level: usize, new_val: Option<((Dynamic, Position), (&str, Position))>, - ) -> Result> { + ) -> RhaiResult { let (crate::ast::BinaryExpr { lhs, rhs }, chain_type, op_pos) = match expr { Expr::Index(x, pos) => (x.as_ref(), ChainType::Index, *pos), Expr::Dot(x, pos) => (x.as_ref(), ChainType::Dot, *pos), @@ -1629,8 +1600,8 @@ impl Engine { let mut idx = idx; let args = &mut [target, &mut idx]; self.exec_fn_call( - _mods, state, _lib, FN_IDX_GET, None, args, _is_ref, true, false, idx_pos, - None, _level, + _mods, state, _lib, FN_IDX_GET, None, args, _is_ref, true, idx_pos, None, + _level, ) .map(|(v, _)| v.into()) .map_err(|err| match *err { @@ -1663,7 +1634,7 @@ impl Engine { lhs: &Expr, rhs: &Expr, level: usize, - ) -> Result> { + ) -> RhaiResult { self.inc_operations(state, rhs.position())?; let lhs_value = self.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?; @@ -1721,7 +1692,7 @@ impl Engine { this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, level: usize, - ) -> Result> { + ) -> RhaiResult { self.inc_operations(state, expr.position())?; let result = match expr { @@ -1742,9 +1713,16 @@ impl Engine { .map(|(val, _)| val.take_or_clone()), // Statement block - Expr::Stmt(x, _) => { - self.eval_stmt_block(scope, mods, state, lib, this_ptr, x.as_ref(), true, level) - } + Expr::Stmt(x, _) => self.eval_stmt_block( + scope, + mods, + state, + lib, + this_ptr, + x.as_ref().as_ref(), + true, + level, + ), // lhs[idx_expr] #[cfg(not(feature = "no_index"))] @@ -1791,8 +1769,7 @@ impl Engine { .. } = x.as_ref(); self.make_function_call( - scope, mods, state, lib, this_ptr, name, args, *hash, false, *pos, *cap_scope, - level, + scope, mods, state, lib, this_ptr, name, args, *hash, *pos, *cap_scope, level, ) } @@ -1874,47 +1851,59 @@ impl Engine { } /// Evaluate a statements block. - pub(crate) fn eval_stmt_block<'a>( + pub(crate) fn eval_stmt_block( &self, scope: &mut Scope, mods: &mut Imports, state: &mut State, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, - statements: impl IntoIterator, - restore: bool, + statements: &[Stmt], + restore_prev_state: bool, level: usize, - ) -> Result> { - let mut _has_imports = false; + ) -> RhaiResult { + let mut _restore_fn_resolution_cache = false; let prev_always_search = state.always_search; let prev_scope_len = scope.len(); let prev_mods_len = mods.len(); - if restore { + if restore_prev_state { state.scope_level += 1; } - let result = statements.into_iter().try_fold(Dynamic::UNIT, |_, stmt| { + let result = statements.iter().try_fold(Dynamic::UNIT, |_, stmt| { + let _mods_len = mods.len(); + + let r = self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level)?; + #[cfg(not(feature = "no_module"))] - match stmt { - Stmt::Import(_, _, _) => { - // When imports list is modified, clear the functions lookup cache - if _has_imports { + if matches!(stmt, Stmt::Import(_, _, _)) { + // Get the extra modules - see if any functions are marked global. + // Without global functions, the extra modules never affect function resolution. + if mods + .scan_raw() + .skip(_mods_len) + .any(|(_, m)| m.has_namespace(crate::FnNamespace::Global, true)) + { + if _restore_fn_resolution_cache { + // When new module is imported with global functions and there is already + // a new cache, clear it - notice that this is expensive as all function + // resolutions must start again state.clear_fn_resolution_cache(); - } else if restore { + } else if restore_prev_state { + // When new module is imported with global functions, push a new cache state.push_fn_resolution_cache(); - _has_imports = true; + _restore_fn_resolution_cache = true; } } - _ => (), } - self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level) + Ok(r) }); - if restore { + if restore_prev_state { scope.rewind(prev_scope_len); - if _has_imports { + if _restore_fn_resolution_cache { // If imports list is modified, pop the functions lookup cache state.pop_fn_resolution_cache(); } @@ -2000,7 +1989,7 @@ impl Engine { this_ptr: &mut Option<&mut Dynamic>, stmt: &Stmt, level: usize, - ) -> Result> { + ) -> RhaiResult { self.inc_operations(state, stmt.position())?; let result = match stmt { @@ -2175,6 +2164,13 @@ impl Engine { let iter_obj = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; let iter_type = iter_obj.type_id(); + // lib should only contain scripts, so technically they cannot have iterators + + // Search order: + // 1) Global namespace - functions registered via Engine::register_XXX + // 2) Global modules - packages + // 3) Imported modules - functions marked with global namespace + // 4) Global sub-modules - functions marked with global namespace let func = self .global_namespace .get_iter(iter_type) @@ -2183,7 +2179,12 @@ impl Engine { .iter() .find_map(|m| m.get_iter(iter_type)) }) - .or_else(|| mods.get_iter(iter_type)); + .or_else(|| mods.get_iter(iter_type)) + .or_else(|| { + self.global_sub_modules + .values() + .find_map(|m| m.get_qualified_iter(iter_type)) + }); if let Some(func) = func { // Add the loop variable @@ -2245,19 +2246,55 @@ impl Engine { Err(err) if err.is_pseudo_error() => Err(err), Err(err) if !err.is_catchable() => Err(err), Err(mut err) => { - let value = match *err { + let err_value = match *err { EvalAltResult::ErrorRuntime(ref x, _) => x.clone(), + + #[cfg(feature = "no_object")] _ => { - err.set_position(Position::NONE); + err.take_position(); err.to_string().into() } + #[cfg(not(feature = "no_object"))] + _ => { + use crate::INT; + + let mut err_map: Map = Default::default(); + let err_pos = err.take_position(); + + err_map.insert("message".into(), err.to_string().into()); + + if let Some(ref source) = state.source { + err_map.insert("source".into(), source.clone().into()); + } + + if err_pos.is_none() { + // No position info + } else { + err_map.insert( + "line".into(), + (err_pos.line().unwrap() as INT).into(), + ); + err_map.insert( + "position".into(), + if err_pos.is_beginning_of_line() { + 0 + } else { + err_pos.position().unwrap() as INT + } + .into(), + ); + } + + err.dump_fields(&mut err_map); + err_map.into() + } }; let orig_scope_len = scope.len(); state.scope_level += 1; if let Some(Ident { name, .. }) = err_var { - scope.push(unsafe_cast_var_name_to_lifetime(&name), value); + scope.push(unsafe_cast_var_name_to_lifetime(&name), err_value); } let result = @@ -2427,21 +2464,13 @@ impl Engine { /// [`Position`] in [`EvalAltResult`] may be None and should be set afterwards. #[cfg(feature = "unchecked")] #[inline(always)] - fn check_data_size( - &self, - result: Result>, - _pos: Position, - ) -> Result> { + fn check_data_size(&self, result: RhaiResult, _pos: Position) -> RhaiResult { result } /// Check a result to ensure that the data size is within allowable limit. #[cfg(not(feature = "unchecked"))] - fn check_data_size( - &self, - result: Result>, - pos: Position, - ) -> Result> { + fn check_data_size(&self, result: RhaiResult, pos: Position) -> RhaiResult { // Simply return all errors if result.is_err() { return result; @@ -2589,8 +2618,8 @@ impl Engine { #[inline(always)] pub(crate) fn make_type_mismatch_err(&self, typ: &str, pos: Position) -> Box { EvalAltResult::ErrorMismatchDataType( - typ.into(), self.map_type_name(type_name::()).into(), + typ.into(), pos, ) .into() diff --git a/src/engine_api.rs b/src/engine_api.rs index e98ace88..ee804636 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -13,7 +13,7 @@ use crate::stdlib::{ }; use crate::{ scope::Scope, Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, Module, NativeCallContext, - ParseError, Position, Shared, AST, + ParseError, Position, RhaiResult, Shared, AST, }; #[cfg(not(feature = "no_index"))] @@ -33,9 +33,9 @@ impl Engine { /// Arguments are simply passed in as a mutable array of [`&mut Dynamic`][Dynamic], /// The arguments are guaranteed to be of the correct types matching the [`TypeId`][std::any::TypeId]'s. /// - /// To access a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::()` + /// To access a primary argument value (i.e. cloning is cheap), use: `args[n].as_xxx().unwrap()` /// - /// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. + /// To access an argument value and avoid cloning, use `std::mem::take(args[n]).cast::()`. /// Notice that this will _consume_ the argument, replacing it with `()`. /// /// To access the first mutable parameter, use `args.get_mut(0).unwrap()` @@ -71,6 +71,7 @@ impl Engine { /// /// impl TestStruct { /// fn new() -> Self { Self { field: 1 } } + /// /// fn update(&mut self, offset: i64) { self.field += offset; } /// } /// @@ -170,6 +171,7 @@ impl Engine { /// /// impl TestStruct { /// fn new() -> Self { Self { field: 1 } } + /// /// // Even a getter must start with `&mut self` and not `&self`. /// fn get_field(&mut self) -> i64 { self.field } /// } @@ -216,6 +218,7 @@ impl Engine { /// /// impl TestStruct { /// fn new() -> Self { Self { field: 1 } } + /// /// // Even a getter must start with `&mut self` and not `&self`. /// fn get_field(&mut self) -> Result> { /// Ok(self.field.into()) @@ -241,7 +244,7 @@ impl Engine { pub fn register_get_result( &mut self, name: &str, - get_fn: impl Fn(&mut T) -> Result> + SendSync + 'static, + get_fn: impl Fn(&mut T) -> RhaiResult + SendSync + 'static, ) -> &mut Self { use crate::{engine::make_getter, RegisterResultFn}; self.register_result_fn(&make_getter(name), get_fn) @@ -258,6 +261,7 @@ impl Engine { /// /// impl TestStruct { /// fn new() -> Self { Self { field: 1 } } + /// /// fn set_field(&mut self, new_val: i64) { self.field = new_val; } /// } /// @@ -305,6 +309,7 @@ impl Engine { /// /// impl TestStruct { /// fn new() -> Self { Self { field: 1 } } + /// /// fn set_field(&mut self, new_val: i64) -> Result<(), Box> { /// self.field = new_val; /// Ok(()) @@ -356,8 +361,10 @@ impl Engine { /// /// impl TestStruct { /// fn new() -> Self { Self { field: 1 } } + /// /// // Even a getter must start with `&mut self` and not `&self`. /// fn get_field(&mut self) -> i64 { self.field } + /// /// fn set_field(&mut self, new_val: i64) { self.field = new_val; } /// } /// @@ -407,6 +414,7 @@ impl Engine { /// /// impl TestStruct { /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } + /// /// // Even a getter must start with `&mut self` and not `&self`. /// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] } /// } @@ -473,6 +481,7 @@ impl Engine { /// /// impl TestStruct { /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } + /// /// // Even a getter must start with `&mut self` and not `&self`. /// fn get_field(&mut self, index: i64) -> Result> { /// Ok(self.fields[index as usize].into()) @@ -499,7 +508,7 @@ impl Engine { #[inline(always)] pub fn register_indexer_get_result( &mut self, - get_fn: impl Fn(&mut T, X) -> Result> + SendSync + 'static, + get_fn: impl Fn(&mut T, X) -> RhaiResult + SendSync + 'static, ) -> &mut Self { if TypeId::of::() == TypeId::of::() { panic!("Cannot register indexer for arrays."); @@ -535,6 +544,7 @@ impl Engine { /// /// impl TestStruct { /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } + /// /// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; } /// } /// @@ -601,6 +611,7 @@ impl Engine { /// /// impl TestStruct { /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } + /// /// fn set_field(&mut self, index: i64, value: i64) -> Result<(), Box> { /// self.fields[index as usize] = value; /// Ok(()) @@ -672,8 +683,10 @@ impl Engine { /// /// impl TestStruct { /// fn new() -> Self { Self { fields: vec![1, 2, 3, 4, 5] } } + /// /// // Even a getter must start with `&mut self` and not `&self`. /// fn get_field(&mut self, index: i64) -> i64 { self.fields[index as usize] } + /// /// fn set_field(&mut self, index: i64, value: i64) { self.fields[index as usize] = value; } /// } /// @@ -764,29 +777,25 @@ impl Engine { /// # } /// ``` #[cfg(not(feature = "no_module"))] - pub fn register_static_module( - &mut self, - name: impl AsRef, - module: Shared, - ) -> &mut Self { + pub fn register_static_module(&mut self, name: &str, module: Shared) -> &mut Self { fn register_static_module_raw( root: &mut crate::stdlib::collections::HashMap>, - name: impl AsRef, + name: &str, module: Shared, ) { let separator = crate::token::Token::DoubleColon.syntax(); - if !name.as_ref().contains(separator.as_ref()) { + 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::fn_native::shared_take_or_clone(module); module.build_index(); - root.insert(name.as_ref().trim().into(), module.into()); + root.insert(name.trim().into(), module.into()); } else { - root.insert(name.as_ref().trim().into(), module); + root.insert(name.trim().into(), module); } } else { - let mut iter = name.as_ref().splitn(2, separator.as_ref()); + let mut iter = name.splitn(2, separator.as_ref()); let sub_module = iter.next().unwrap().trim(); let remainder = iter.next().unwrap().trim(); @@ -817,11 +826,7 @@ impl Engine { #[cfg(not(feature = "no_module"))] #[inline(always)] #[deprecated = "use `register_static_module` instead"] - pub fn register_module( - &mut self, - name: impl AsRef, - module: impl Into>, - ) -> &mut Self { + pub fn register_module(&mut self, name: &str, module: impl Into>) -> &mut Self { self.register_static_module(name, module.into()) } /// Compile a string into an [`AST`], which can be used later for evaluation. @@ -1023,7 +1028,7 @@ impl Engine { scripts: &[&str], optimization_level: OptimizationLevel, ) -> Result { - let stream = self.lex(scripts); + let stream = self.lex_raw(scripts, None); self.parse(&mut stream.peekable(), scope, optimization_level) } /// Read the contents of a file into a string. @@ -1185,9 +1190,9 @@ impl Engine { .into()); }; - let stream = self.lex_with_map( + let stream = self.lex_raw( &scripts, - if has_null { + Some(if has_null { |token| match token { // If `null` is present, make sure `null` is treated as a variable Token::Reserved(s) if s == "null" => Token::Identifier(s), @@ -1195,7 +1200,7 @@ impl Engine { } } else { |t| t - }, + }), ); let ast = @@ -1278,7 +1283,7 @@ impl Engine { script: &str, ) -> Result { let scripts = [script]; - let stream = self.lex(&scripts); + let stream = self.lex_raw(&scripts, None); let mut peekable = stream.peekable(); self.parse_global_expr(&mut peekable, scope, self.optimization_level) @@ -1439,7 +1444,7 @@ impl Engine { script: &str, ) -> Result> { let scripts = [script]; - let stream = self.lex(&scripts); + let stream = self.lex_raw(&scripts, None); // No need to optimize a lone expression let ast = self.parse_global_expr(&mut stream.peekable(), scope, OptimizationLevel::None)?; @@ -1503,7 +1508,7 @@ impl Engine { scope: &mut Scope, ast: &AST, ) -> Result> { - let mods = &mut (&self.global_sub_modules).into(); + let mods = &mut Default::default(); let result = self.eval_ast_with_scope_raw(scope, mods, ast, 0)?; @@ -1526,7 +1531,7 @@ impl Engine { mods: &mut Imports, ast: &'a AST, level: usize, - ) -> Result> { + ) -> RhaiResult { let mut state: State = Default::default(); state.source = ast.clone_source(); #[cfg(not(feature = "no_module"))] @@ -1580,7 +1585,7 @@ impl Engine { script: &str, ) -> Result<(), Box> { let scripts = [script]; - let stream = self.lex(&scripts); + let stream = self.lex_raw(&scripts, None); let ast = self.parse(&mut stream.peekable(), scope, self.optimization_level)?; self.consume_ast_with_scope(scope, &ast) } @@ -1598,7 +1603,7 @@ impl Engine { scope: &mut Scope, ast: &AST, ) -> Result<(), Box> { - let mods = &mut (&self.global_sub_modules).into(); + let mods = &mut Default::default(); let mut state: State = Default::default(); state.source = ast.clone_source(); #[cfg(not(feature = "no_module"))] @@ -1741,7 +1746,7 @@ impl Engine { name: &str, mut this_ptr: Option<&mut Dynamic>, mut arg_values: impl AsMut<[Dynamic]>, - ) -> Result> { + ) -> RhaiResult { let mut args: crate::StaticVec<_> = arg_values.as_mut().iter_mut().collect(); self.call_fn_dynamic_raw(scope, ast, eval_ast, name, &mut this_ptr, args.as_mut()) @@ -1764,9 +1769,9 @@ impl Engine { name: &str, this_ptr: &mut Option<&mut Dynamic>, args: &mut FnCallArgs, - ) -> Result> { + ) -> RhaiResult { let state = &mut Default::default(); - let mods = &mut (&self.global_sub_modules).into(); + let mods = &mut Default::default(); let lib = &[ast.lib()]; if eval_ast { @@ -1775,7 +1780,7 @@ impl Engine { let fn_def = ast .lib() - .get_script_fn(name, args.len(), true) + .get_script_fn(name, args.len(), false) .ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::NONE))?; // Check for data race. diff --git a/src/engine_settings.rs b/src/engine_settings.rs index 84b57184..166f41f0 100644 --- a/src/engine_settings.rs +++ b/src/engine_settings.rs @@ -203,7 +203,7 @@ impl Engine { /// # Examples /// /// The following will raise an error during parsing because the `if` keyword is disabled - /// and is recognized as a variable name! + /// and is recognized as a reserved symbol! /// /// ```rust,should_panic /// # fn main() -> Result<(), rhai::ParseError> { @@ -214,7 +214,7 @@ impl Engine { /// engine.disable_symbol("if"); // disable the 'if' keyword /// /// engine.compile("let x = if true { 42 } else { 0 };")?; - /// // ^ 'if' is rejected as a reserved keyword + /// // ^ 'if' is rejected as a reserved symbol /// # Ok(()) /// # } /// ``` diff --git a/src/fn_builtin.rs b/src/fn_builtin.rs new file mode 100644 index 00000000..4a9561de --- /dev/null +++ b/src/fn_builtin.rs @@ -0,0 +1,601 @@ +//! Built-in implementations for common operators. + +use crate::fn_native::{FnCallArgs, NativeCallContext}; +use crate::stdlib::{any::TypeId, format, string::ToString}; +use crate::{Dynamic, ImmutableString, RhaiResult, INT}; + +#[cfg(not(feature = "no_float"))] +use crate::FLOAT; + +#[cfg(feature = "decimal")] +use rust_decimal::Decimal; + +#[cfg(feature = "no_std")] +#[cfg(not(feature = "no_float"))] +use num_traits::float::Float; + +/// Is the type a numeric type? +fn is_numeric(type_id: TypeId) -> bool { + let result = type_id == TypeId::of::() + || type_id == TypeId::of::() + || type_id == TypeId::of::() + || type_id == TypeId::of::() + || type_id == TypeId::of::() + || type_id == TypeId::of::() + || type_id == TypeId::of::() + || type_id == TypeId::of::(); + + #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] + let result = result || type_id == TypeId::of::() || type_id == TypeId::of::(); + + #[cfg(not(feature = "no_float"))] + let result = result || type_id == TypeId::of::() || type_id == TypeId::of::(); + + #[cfg(feature = "decimal")] + let result = result || type_id == TypeId::of::(); + + result +} + +/// Build in common binary operator implementations to avoid the cost of calling a registered function. +pub fn get_builtin_binary_op_fn( + op: &str, + x: &Dynamic, + y: &Dynamic, +) -> Option RhaiResult> { + let type1 = x.type_id(); + let type2 = y.type_id(); + + // One of the operands is a custom type, so it is never built-in + if x.is_variant() || y.is_variant() { + if is_numeric(type1) && is_numeric(type2) { + // Disallow comparisons between different numeric types + return None; + } + + // If the types are not the same, default to not compare + if type1 != type2 { + return match op { + "!=" => Some(|_, _| Ok(Dynamic::TRUE)), + "==" | ">" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)), + _ => None, + }; + } + + // Disallow comparisons between the same type + return None; + } + + let types_pair = (type1, type2); + + macro_rules! impl_op { + ($func:ident ( $op:tt )) => { + return Some(|_, args| { + let (x, y) = $func(args); + Ok((x $op y).into()) + }) + }; + ($xx:ident $op:tt $yy:ident -> $base:ty) => { + return Some(|_, args| { + let x = args[0].$xx().unwrap() as $base; + let y = args[1].$yy().unwrap() as $base; + Ok((x $op y).into()) + }) + }; + ($xx:ident . $func:ident ( $yy:ident as $yyy:ty) -> $base:ty) => { + return Some(|_, args| { + let x = args[0].$xx().unwrap() as $base; + let y = args[1].$yy().unwrap() as $base; + Ok(x.$func(y as $yyy).into()) + }) + }; + ($func:ident ( $xx:ident, $yy:ident ) -> $base:ty) => { + return Some(|_, args| { + let x = args[0].$xx().unwrap() as $base; + let y = args[1].$yy().unwrap() as $base; + $func(x, y) + }) + }; + ($xx:ident $op:tt $yy:ident -> from $base:ty) => { + return Some(|_, args| { + let x = <$base>::from(args[0].$xx().unwrap()); + let y = <$base>::from(args[1].$yy().unwrap()); + Ok((x $op y).into()) + }) + }; + ($xx:ident . $func:ident ( $yy:ident ) -> from $base:ty) => { + return Some(|_, args| { + let x = <$base>::from(args[0].$xx().unwrap()); + let y = <$base>::from(args[1].$yy().unwrap()); + Ok(x.$func(y).into()) + }) + }; + ($func:ident ( $xx:ident, $yy:ident ) -> from $base:ty) => { + return Some(|_, args| { + let x = <$base>::from(args[0].$xx().unwrap()); + let y = <$base>::from(args[1].$yy().unwrap()); + $func(x, y) + }) + }; + (& $x:ident $op:tt & $y:ident) => { + return Some(|_, args| { + let x = &*args[0].read_lock::<$x>().unwrap(); + let y = &*args[1].read_lock::<$y>().unwrap(); + Ok((x $op y).into()) + }) + } + } + + macro_rules! impl_float { + ($x:ty, $xx:ident, $y:ty, $yy:ident) => { + #[cfg(not(feature = "no_float"))] + if types_pair == (TypeId::of::<$x>(), TypeId::of::<$y>()) { + match op { + "+" => impl_op!($xx + $yy -> FLOAT), + "-" => impl_op!($xx - $yy -> FLOAT), + "*" => impl_op!($xx * $yy -> FLOAT), + "/" => impl_op!($xx / $yy -> FLOAT), + "%" => impl_op!($xx % $yy -> FLOAT), + "**" => impl_op!($xx.powf($yy as FLOAT) -> FLOAT), + "==" => impl_op!($xx == $yy -> FLOAT), + "!=" => impl_op!($xx != $yy -> FLOAT), + ">" => impl_op!($xx > $yy -> FLOAT), + ">=" => impl_op!($xx >= $yy -> FLOAT), + "<" => impl_op!($xx < $yy -> FLOAT), + "<=" => impl_op!($xx <= $yy -> FLOAT), + _ => return None, + } + } + }; + } + + impl_float!(FLOAT, as_float, FLOAT, as_float); + impl_float!(FLOAT, as_float, INT, as_int); + impl_float!(INT, as_int, FLOAT, as_float); + + macro_rules! impl_decimal { + ($x:ty, $xx:ident, $y:ty, $yy:ident) => { + #[cfg(feature = "decimal")] + if types_pair == (TypeId::of::<$x>(), TypeId::of::<$y>()) { + if cfg!(not(feature = "unchecked")) { + use crate::packages::arithmetic::decimal_functions::*; + + match op { + "+" => impl_op!(add($xx, $yy) -> from Decimal), + "-" => impl_op!(subtract($xx, $yy) -> from Decimal), + "*" => impl_op!(multiply($xx, $yy) -> from Decimal), + "/" => impl_op!(divide($xx, $yy) -> from Decimal), + "%" => impl_op!(modulo($xx, $yy) -> from Decimal), + _ => () + } + } else { + match op { + "+" => impl_op!($xx + $yy -> from Decimal), + "-" => impl_op!($xx - $yy -> from Decimal), + "*" => impl_op!($xx * $yy -> from Decimal), + "/" => impl_op!($xx / $yy -> from Decimal), + "%" => impl_op!($xx % $yy -> from Decimal), + _ => () + } + } + + match op { + "==" => impl_op!($xx == $yy -> from Decimal), + "!=" => impl_op!($xx != $yy -> from Decimal), + ">" => impl_op!($xx > $yy -> from Decimal), + ">=" => impl_op!($xx >= $yy -> from Decimal), + "<" => impl_op!($xx < $yy -> from Decimal), + "<=" => impl_op!($xx <= $yy -> from Decimal), + _ => return None + } + } + }; + } + + impl_decimal!(Decimal, as_decimal, Decimal, as_decimal); + impl_decimal!(Decimal, as_decimal, INT, as_int); + impl_decimal!(INT, as_int, Decimal, as_decimal); + + // char op string + if types_pair == (TypeId::of::(), TypeId::of::()) { + #[inline(always)] + fn get_s1s2(args: &FnCallArgs) -> ([char; 2], [char; 2]) { + let x = args[0].as_char().unwrap(); + let y = &*args[1].read_lock::().unwrap(); + let s1 = [x, '\0']; + let mut y = y.chars(); + let s2 = [y.next().unwrap_or('\0'), y.next().unwrap_or('\0')]; + (s1, s2) + } + + match op { + "+" => { + return Some(|_, args| { + let x = args[0].as_char().unwrap(); + let y = &*args[1].read_lock::().unwrap(); + Ok(format!("{}{}", x, y).into()) + }) + } + "==" => impl_op!(get_s1s2(==)), + "!=" => impl_op!(get_s1s2(!=)), + ">" => impl_op!(get_s1s2(>)), + ">=" => impl_op!(get_s1s2(>=)), + "<" => impl_op!(get_s1s2(<)), + "<=" => impl_op!(get_s1s2(<=)), + _ => return None, + } + } + // string op char + if types_pair == (TypeId::of::(), TypeId::of::()) { + #[inline(always)] + fn get_s1s2(args: &FnCallArgs) -> ([char; 2], [char; 2]) { + let x = &*args[0].read_lock::().unwrap(); + let y = args[1].as_char().unwrap(); + let mut x = x.chars(); + let s1 = [x.next().unwrap_or('\0'), x.next().unwrap_or('\0')]; + let s2 = [y, '\0']; + (s1, s2) + } + + match op { + "+" => { + return Some(|_, args| { + let x = &*args[0].read_lock::().unwrap(); + let y = args[1].as_char().unwrap(); + Ok((x + y).into()) + }) + } + "-" => { + return Some(|_, args| { + let x = &*args[0].read_lock::().unwrap(); + let y = args[1].as_char().unwrap(); + Ok((x - y).into()) + }) + } + "==" => impl_op!(get_s1s2(==)), + "!=" => impl_op!(get_s1s2(!=)), + ">" => impl_op!(get_s1s2(>)), + ">=" => impl_op!(get_s1s2(>=)), + "<" => impl_op!(get_s1s2(<)), + "<=" => impl_op!(get_s1s2(<=)), + _ => return None, + } + } + + // Default comparison operators for different types + if type2 != type1 { + return match op { + "!=" => Some(|_, _| Ok(Dynamic::TRUE)), + "==" | ">" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)), + _ => None, + }; + } + + // Beyond here, type1 == type2 + + if type1 == TypeId::of::() { + if cfg!(not(feature = "unchecked")) { + use crate::packages::arithmetic::arith_basic::INT::functions::*; + + match op { + "+" => impl_op!(add(as_int, as_int) -> INT), + "-" => impl_op!(subtract(as_int, as_int) -> INT), + "*" => impl_op!(multiply(as_int, as_int) -> INT), + "/" => impl_op!(divide(as_int, as_int) -> INT), + "%" => impl_op!(modulo(as_int, as_int) -> INT), + "**" => impl_op!(power(as_int, as_int) -> INT), + ">>" => impl_op!(shift_right(as_int, as_int) -> INT), + "<<" => impl_op!(shift_left(as_int, as_int) -> INT), + _ => (), + } + } else { + match op { + "+" => impl_op!(as_int + as_int -> INT), + "-" => impl_op!(as_int - as_int -> INT), + "*" => impl_op!(as_int * as_int -> INT), + "/" => impl_op!(as_int / as_int -> INT), + "%" => impl_op!(as_int % as_int -> INT), + "**" => impl_op!(as_int.pow(as_int as u32) -> INT), + ">>" => impl_op!(as_int >> as_int -> INT), + "<<" => impl_op!(as_int << as_int -> INT), + _ => (), + } + } + + match op { + "==" => impl_op!(as_int == as_int -> INT), + "!=" => impl_op!(as_int != as_int -> INT), + ">" => impl_op!(as_int > as_int -> INT), + ">=" => impl_op!(as_int >= as_int -> INT), + "<" => impl_op!(as_int < as_int -> INT), + "<=" => impl_op!(as_int <= as_int -> INT), + "&" => impl_op!(as_int & as_int -> INT), + "|" => impl_op!(as_int | as_int -> INT), + "^" => impl_op!(as_int ^ as_int -> INT), + _ => return None, + } + } + + if type1 == TypeId::of::() { + match op { + "==" => impl_op!(as_bool == as_bool -> bool), + "!=" => impl_op!(as_bool != as_bool -> bool), + ">" => impl_op!(as_bool > as_bool -> bool), + ">=" => impl_op!(as_bool >= as_bool -> bool), + "<" => impl_op!(as_bool < as_bool -> bool), + "<=" => impl_op!(as_bool <= as_bool -> bool), + "&" => impl_op!(as_bool & as_bool -> bool), + "|" => impl_op!(as_bool | as_bool -> bool), + "^" => impl_op!(as_bool ^ as_bool -> bool), + _ => return None, + } + } + + if type1 == TypeId::of::() { + match op { + "+" => impl_op!(&ImmutableString + &ImmutableString), + "-" => impl_op!(&ImmutableString - &ImmutableString), + "==" => impl_op!(&ImmutableString == &ImmutableString), + "!=" => impl_op!(&ImmutableString != &ImmutableString), + ">" => impl_op!(&ImmutableString > &ImmutableString), + ">=" => impl_op!(&ImmutableString >= &ImmutableString), + "<" => impl_op!(&ImmutableString < &ImmutableString), + "<=" => impl_op!(&ImmutableString <= &ImmutableString), + _ => return None, + } + } + + if type1 == TypeId::of::() { + match op { + "==" => impl_op!(as_char == as_char -> char), + "!=" => impl_op!(as_char != as_char -> char), + ">" => impl_op!(as_char > as_char -> char), + ">=" => impl_op!(as_char >= as_char -> char), + "<" => impl_op!(as_char < as_char -> char), + "<=" => impl_op!(as_char <= as_char -> char), + _ => return None, + } + } + + if type1 == TypeId::of::<()>() { + match op { + "==" => return Some(|_, _| Ok(Dynamic::TRUE)), + "!=" | ">" | ">=" | "<" | "<=" => return Some(|_, _| Ok(Dynamic::FALSE)), + _ => return None, + } + } + + None +} + +/// Build in common operator assignment implementations to avoid the cost of calling a registered function. +pub fn get_builtin_op_assignment_fn( + op: &str, + x: &Dynamic, + y: &Dynamic, +) -> Option RhaiResult> { + let type1 = x.type_id(); + let type2 = y.type_id(); + + let types_pair = (type1, type2); + + macro_rules! impl_op { + ($x:ident = x $op:tt $yy:ident) => { + return Some(|_, args| { + let x = args[0].$yy().unwrap(); + let y = args[1].$yy().unwrap() as $x; + Ok((*args[0].write_lock::<$x>().unwrap() = x $op y).into()) + }) + }; + ($x:ident $op:tt $yy:ident) => { + return Some(|_, args| { + let y = args[1].$yy().unwrap() as $x; + Ok((*args[0].write_lock::<$x>().unwrap() $op y).into()) + }) + }; + ($x:ident $op:tt $yy:ident as $yyy:ty) => { + return Some(|_, args| { + let y = args[1].$yy().unwrap() as $yyy; + Ok((*args[0].write_lock::<$x>().unwrap() $op y).into()) + }) + }; + ($xx:ident . $func:ident ( $yy:ident as $yyy:ty ) -> $x:ty) => { + return Some(|_, args| { + let x = args[0].$xx().unwrap(); + let y = args[1].$yy().unwrap() as $x; + Ok((*args[0].write_lock::<$x>().unwrap() = x.$func(y as $yyy)).into()) + }) + }; + ($func:ident ( $xx:ident, $yy:ident ) -> $x:ty) => { + return Some(|_, args| { + let x = args[0].$xx().unwrap(); + let y = args[1].$yy().unwrap() as $x; + Ok((*args[0].write_lock().unwrap() = $func(x, y)?).into()) + }) + }; + (from $x:ident $op:tt $yy:ident) => { + return Some(|_, args| { + let y = <$x>::from(args[1].$yy().unwrap()); + Ok((*args[0].write_lock::<$x>().unwrap() $op y).into()) + }) + }; + ($xx:ident . $func:ident ( $yy:ident ) -> from $x:ty) => { + return Some(|_, args| { + let x = args[0].$xx().unwrap(); + let y = <$x>::from(args[1].$yy().unwrap()); + Ok((*args[0].write_lock::<$x>().unwrap() = x.$func(y)).into()) + }) + }; + ($func:ident ( $xx:ident, $yy:ident ) -> from $x:ty) => { + return Some(|_, args| { + let x = args[0].$xx().unwrap(); + let y = <$x>::from(args[1].$yy().unwrap()); + Ok((*args[0].write_lock().unwrap() = $func(x, y)?).into()) + }) + }; + } + + macro_rules! impl_float { + ($x:ident, $xx:ident, $y:ty, $yy:ident) => { + #[cfg(not(feature = "no_float"))] + if types_pair == (TypeId::of::<$x>(), TypeId::of::<$y>()) { + match op { + "+=" => impl_op!($x += $yy), + "-=" => impl_op!($x -= $yy), + "*=" => impl_op!($x *= $yy), + "/=" => impl_op!($x /= $yy), + "%=" => impl_op!($x %= $yy), + "**=" => impl_op!($xx.powf($yy as $x) -> $x), + _ => return None, + } + } + } + } + + impl_float!(FLOAT, as_float, FLOAT, as_float); + impl_float!(FLOAT, as_float, INT, as_int); + + macro_rules! impl_decimal { + ($x:ident, $xx:ident, $y:ty, $yy:ident) => { + #[cfg(feature = "decimal")] + if types_pair == (TypeId::of::<$x>(), TypeId::of::<$y>()) { + if cfg!(not(feature = "unchecked")) { + use crate::packages::arithmetic::decimal_functions::*; + + match op { + "+=" => impl_op!(add($xx, $yy) -> from $x), + "-=" => impl_op!(subtract($xx, $yy) -> from $x), + "*=" => impl_op!(multiply($xx, $yy) -> from $x), + "/=" => impl_op!(divide($xx, $yy) -> from $x), + "%=" => impl_op!(modulo($xx, $yy) -> from $x), + _ => return None, + } + } else { + match op { + "+=" => impl_op!(from $x += $yy), + "-=" => impl_op!(from $x -= $yy), + "*=" => impl_op!(from $x *= $yy), + "/=" => impl_op!(from $x /= $yy), + "%=" => impl_op!(from $x %= $yy), + _ => return None, + } + } + } + }; + } + + impl_decimal!(Decimal, as_decimal, Decimal, as_decimal); + impl_decimal!(Decimal, as_decimal, INT, as_int); + + // string op= char + if types_pair == (TypeId::of::(), TypeId::of::()) { + match op { + "+=" => impl_op!(ImmutableString += as_char as char), + "-=" => impl_op!(ImmutableString -= as_char as char), + _ => return None, + } + } + // char op= string + if types_pair == (TypeId::of::(), TypeId::of::()) { + match op { + "+=" => { + return Some(|_, args| { + let mut ch = args[0].as_char().unwrap().to_string(); + ch.push_str(args[1].read_lock::().unwrap().as_str()); + + let mut x = args[0].write_lock::().unwrap(); + Ok((*x = ch.into()).into()) + }) + } + _ => return None, + } + } + + // No built-in op-assignments for different types. + if type2 != type1 { + return None; + } + + // Beyond here, type1 == type2 + if type1 == TypeId::of::() { + if cfg!(not(feature = "unchecked")) { + use crate::packages::arithmetic::arith_basic::INT::functions::*; + + match op { + "+=" => impl_op!(add(as_int, as_int) -> INT), + "-=" => impl_op!(subtract(as_int, as_int) -> INT), + "*=" => impl_op!(multiply(as_int, as_int) -> INT), + "/=" => impl_op!(divide(as_int, as_int) -> INT), + "%=" => impl_op!(modulo(as_int, as_int) -> INT), + "**=" => impl_op!(power(as_int, as_int) -> INT), + ">>=" => impl_op!(shift_right(as_int, as_int) -> INT), + "<<=" => impl_op!(shift_left(as_int, as_int) -> INT), + _ => (), + } + } else { + match op { + "+=" => impl_op!(INT += as_int), + "-=" => impl_op!(INT -= as_int), + "*=" => impl_op!(INT *= as_int), + "/=" => impl_op!(INT /= as_int), + "%=" => impl_op!(INT %= as_int), + "**=" => impl_op!(as_int.pow(as_int as u32) -> INT), + ">>=" => impl_op!(INT >>= as_int), + "<<=" => impl_op!(INT <<= as_int), + _ => (), + } + } + + match op { + "&=" => impl_op!(INT &= as_int), + "|=" => impl_op!(INT |= as_int), + "^=" => impl_op!(INT ^= as_int), + _ => (), + } + } + + if type1 == TypeId::of::() { + match op { + "&=" => impl_op!(bool = x && as_bool), + "|=" => impl_op!(bool = x || as_bool), + _ => return None, + } + } + + if type1 == TypeId::of::() { + match op { + "+=" => { + return Some(|_, args| { + let y = args[1].as_char().unwrap(); + let mut x = args[0].write_lock::().unwrap(); + Ok((*x = format!("{}{}", *x, y).into()).into()) + }) + } + _ => return None, + } + } + + if type1 == TypeId::of::() { + match op { + "+=" => { + return Some(|_, args| { + let (first, second) = args.split_first_mut().unwrap(); + let mut x = first.write_lock::().unwrap(); + let y = &*second[0].read_lock::().unwrap(); + Ok((*x += y).into()) + }) + } + "-=" => { + return Some(|_, args| { + let (first, second) = args.split_first_mut().unwrap(); + let mut x = first.write_lock::().unwrap(); + let y = &*second[0].read_lock::().unwrap(); + Ok((*x -= y).into()) + }) + } + _ => return None, + } + } + + None +} diff --git a/src/fn_call.rs b/src/fn_call.rs index 26956964..a9e4c009 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -1,12 +1,12 @@ //! Implement function-calling mechanism for [`Engine`]. -use crate::ast::{Expr, Stmt}; use crate::engine::{ - search_imports, Imports, State, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, - KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF, + Imports, State, 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, }; -use crate::fn_native::FnCallArgs; +use crate::fn_builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn}; +use crate::fn_native::{FnAny, FnCallArgs}; use crate::module::NamespaceRef; use crate::optimize::OptimizationLevel; use crate::stdlib::{ @@ -17,28 +17,23 @@ use crate::stdlib::{ iter::{empty, once}, mem, num::NonZeroU64, - string::ToString, + string::{String, ToString}, vec::Vec, }; use crate::utils::combine_hashes; +use crate::{ + ast::{Expr, Stmt}, + fn_native::CallableFunction, + RhaiResult, +}; use crate::{ calc_native_fn_hash, calc_script_fn_hash, Dynamic, Engine, EvalAltResult, FnPtr, ImmutableString, Module, ParseErrorType, Position, Scope, StaticVec, INT, }; -#[cfg(not(feature = "no_float"))] -use crate::FLOAT; - #[cfg(not(feature = "no_object"))] use crate::Map; -#[cfg(feature = "decimal")] -use rust_decimal::Decimal; - -#[cfg(feature = "no_std")] -#[cfg(not(feature = "no_float"))] -use num_traits::float::Float; - /// Extract the property name from a getter function name. #[cfg(not(feature = "no_object"))] #[inline(always)] @@ -81,12 +76,7 @@ impl<'a> ArgBackup<'a> { /// /// If `restore_first_arg` is called before the end of the scope, the shorter lifetime will not leak. #[inline(always)] - fn change_first_arg_to_copy(&mut self, normalize: bool, args: &mut FnCallArgs<'a>) { - // Only do it for method calls with arguments. - if !normalize || args.is_empty() { - return; - } - + fn change_first_arg_to_copy(&mut self, args: &mut FnCallArgs<'a>) { // Clone the original value. self.value_copy = args[0].clone(); @@ -111,7 +101,7 @@ impl<'a> ArgBackup<'a> { /// If `change_first_arg_to_copy` has been called, this function **MUST** be called _BEFORE_ exiting /// the current scope. Otherwise it is undefined behavior as the shorter lifetime will leak. #[inline(always)] - fn restore_first_arg(&mut self, args: &mut FnCallArgs<'a>) { + fn restore_first_arg(mut self, args: &mut FnCallArgs<'a>) { if let Some(this_pointer) = self.orig_mut.take() { args[0] = this_pointer; } @@ -156,6 +146,156 @@ pub fn ensure_no_data_race( } impl Engine { + /// Generate the signature for a function call. + fn gen_call_signature( + &self, + namespace: Option<&NamespaceRef>, + fn_name: &str, + args: &[&mut Dynamic], + ) -> String { + format!( + "{}{} ({})", + namespace.map_or(String::new(), |ns| ns.to_string()), + fn_name, + args.iter() + .map(|a| if a.is::() { + "&str | ImmutableString | String" + } else { + self.map_type_name((*a).type_name()) + }) + .collect::>() + .join(", ") + ) + } + + /// Resolve a function call. + /// + /// Search order: + /// 1) AST - script functions in the AST + /// 2) Global namespace - functions registered via Engine::register_XXX + /// 3) Global modules - packages + /// 4) Imported modules - functions marked with global namespace + /// 5) Global sub-modules - functions marked with global namespace + #[inline] + fn resolve_function<'s>( + &self, + mods: &Imports, + state: &'s mut State, + lib: &[&Module], + fn_name: &str, + mut hash: NonZeroU64, + args: &mut FnCallArgs, + allow_dynamic: bool, + is_op_assignment: bool, + ) -> &'s Option<(CallableFunction, Option)> { + fn find_function( + engine: &Engine, + hash: NonZeroU64, + mods: &Imports, + lib: &[&Module], + ) -> Option<(CallableFunction, Option)> { + lib.iter() + .find_map(|m| { + m.get_fn(hash, false) + .map(|f| (f.clone(), m.id_raw().cloned())) + }) + .or_else(|| { + engine + .global_namespace + .get_fn(hash, false) + .cloned() + .map(|f| (f, None)) + }) + .or_else(|| { + engine.global_modules.iter().find_map(|m| { + m.get_fn(hash, false) + .map(|f| (f.clone(), m.id_raw().cloned())) + }) + }) + .or_else(|| { + mods.get_fn(hash) + .map(|(f, source)| (f.clone(), source.cloned())) + }) + .or_else(|| { + engine.global_sub_modules.values().find_map(|m| { + m.get_qualified_fn(hash) + .map(|f| (f.clone(), m.id_raw().cloned())) + }) + }) + } + + &*state + .fn_resolution_cache_mut() + .entry(hash) + .or_insert_with(|| { + let num_args = args.len(); + let max_bitmask = if !allow_dynamic { + 0 + } else { + 1usize << args.len().min(MAX_DYNAMIC_PARAMETERS) + }; + let mut bitmask = 1usize; // Bitmask of which parameter to replace with `Dynamic` + + loop { + match find_function(self, hash, mods, lib) { + // Specific version found + Some(f) => return Some(f), + + // Stop when all permutations are exhausted + None if bitmask >= max_bitmask => { + return if num_args != 2 { + None + } else if !is_op_assignment { + if let Some(f) = + get_builtin_binary_op_fn(fn_name, &args[0], &args[1]) + { + Some(( + CallableFunction::from_method(Box::new(f) as Box), + None, + )) + } else { + None + } + } else { + let (first, second) = args.split_first().unwrap(); + + if let Some(f) = + get_builtin_op_assignment_fn(fn_name, *first, second[0]) + { + Some(( + CallableFunction::from_method(Box::new(f) as Box), + None, + )) + } else { + None + } + } + } + + // Try all permutations with `Dynamic` wildcards + None => { + hash = calc_native_fn_hash( + empty(), + fn_name, + args.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() + } + }), + ) + .unwrap(); + + bitmask += 1; + } + } + } + }) + } + /// Call a native Rust function registered with the [`Engine`]. /// /// # WARNING @@ -180,67 +320,26 @@ impl Engine { let source = state.source.clone(); // Check if function access already in the cache - let func = &*state - .fn_resolution_cache_mut() - .entry(hash_fn) - .or_insert_with(|| { - let num_args = args.len(); - let max_bitmask = 1usize << args.len().min(MAX_DYNAMIC_PARAMETERS); - let mut hash = hash_fn; - let mut bitmask = 1usize; // Bitmask of which parameter to replace with `Dynamic` - - loop { - //lib.get_fn(hash, false).or_else(|| - match self - .global_namespace - .get_fn(hash, false) - .cloned() - .map(|f| (f, None)) - .or_else(|| { - self.global_modules.iter().find_map(|m| { - m.get_fn(hash, false) - .map(|f| (f.clone(), m.id_raw().cloned())) - }) - }) - .or_else(|| { - mods.get_fn(hash) - .map(|(f, source)| (f.clone(), source.cloned())) - }) { - // Specific version found - Some(f) => return Some(f), - - // Stop when all permutations are exhausted - _ if bitmask >= max_bitmask => return None, - - // Try all permutations with `Dynamic` wildcards - _ => { - hash = calc_native_fn_hash( - empty(), - fn_name, - args.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() - } - }), - ) - .unwrap(); - - bitmask += 1; - } - } - } - }); + let func = self.resolve_function( + mods, + state, + lib, + fn_name, + hash_fn, + args, + true, + is_op_assignment, + ); if let Some((func, src)) = func { assert!(func.is_native()); // Calling pure function but the first argument is a reference? - let mut backup: ArgBackup = Default::default(); - backup.change_first_arg_to_copy(is_ref && func.is_pure(), args); + let mut backup: Option = None; + if is_ref && func.is_pure() && !args.is_empty() { + backup = Some(Default::default()); + backup.as_mut().unwrap().change_first_arg_to_copy(args); + } // Run external function let source = src.as_ref().or_else(|| source.as_ref()).map(|s| s.as_str()); @@ -252,9 +351,11 @@ impl Engine { }; // Restore the original reference - backup.restore_first_arg(args); + if let Some(backup) = backup { + backup.restore_first_arg(args); + } - let result = result?; + let result = result.map_err(|err| err.fill_position(pos))?; // See if the function match print/debug (which requires special processing) return Ok(match fn_name { @@ -283,27 +384,6 @@ impl Engine { }); } - // See if it is built in. - if args.len() == 2 && !args[0].is_variant() && !args[1].is_variant() { - // Op-assignment? - if is_op_assignment { - if !is_ref { - unreachable!("op-assignments must have ref argument"); - } - let (first, second) = args.split_first_mut().unwrap(); - - match run_builtin_op_assignment(fn_name, first, second[0])? { - Some(_) => return Ok((Dynamic::UNIT, false)), - None => (), - } - } else { - match run_builtin_binary_op(fn_name, args[0], args[1])? { - Some(v) => return Ok((v, false)), - None => (), - } - } - } - // Getter function not found? #[cfg(not(feature = "no_object"))] if let Some(prop) = extract_prop_from_getter(fn_name) { @@ -363,18 +443,7 @@ impl Engine { // Raise error EvalAltResult::ErrorFunctionNotFound( - format!( - "{} ({})", - fn_name, - args.iter() - .map(|name| if name.is::() { - "&str | ImmutableString | String" - } else { - self.map_type_name((*name).type_name()) - }) - .collect::>() - .join(", ") - ), + self.gen_call_signature(None, fn_name, args.as_ref()), pos, ) .into() @@ -399,7 +468,7 @@ impl Engine { args: &mut FnCallArgs, pos: Position, level: usize, - ) -> Result> { + ) -> RhaiResult { #[inline(always)] fn make_error( name: crate::stdlib::string::String, @@ -407,7 +476,7 @@ impl Engine { state: &State, err: Box, pos: Position, - ) -> Result> { + ) -> RhaiResult { Err(Box::new(EvalAltResult::ErrorInFunctionCall( name, fn_def @@ -514,17 +583,20 @@ impl Engine { pub(crate) fn has_override_by_name_and_arguments( &self, mods: Option<&Imports>, - state: Option<&mut State>, + state: &mut State, lib: &[&Module], fn_name: &str, - arg_types: impl AsRef<[TypeId]>, - pub_only: bool, + arg_types: &[TypeId], ) -> bool { let arg_types = arg_types.as_ref(); - let hash_fn = calc_native_fn_hash(empty(), fn_name, arg_types.iter().cloned()); - let hash_script = calc_script_fn_hash(empty(), fn_name, arg_types.len()); - self.has_override(mods, state, lib, hash_fn, hash_script, pub_only) + self.has_override( + mods, + state, + lib, + calc_native_fn_hash(empty(), fn_name, arg_types.iter().cloned()), + calc_script_fn_hash(empty(), fn_name, arg_types.len()), + ) } // Has a system function an override? @@ -532,54 +604,45 @@ impl Engine { pub(crate) fn has_override( &self, mods: Option<&Imports>, - mut state: Option<&mut State>, + state: &mut State, lib: &[&Module], hash_fn: Option, hash_script: Option, - pub_only: bool, ) -> bool { - // Check if it is already in the cache - if let Some(state) = state.as_mut() { - if let Some(hash) = hash_script { - match state.fn_resolution_cache().map_or(None, |c| c.get(&hash)) { - Some(v) => return v.is_some(), - None => (), - } - } - if let Some(hash) = hash_fn { - match state.fn_resolution_cache().map_or(None, |c| c.get(&hash)) { - Some(v) => return v.is_some(), - None => (), - } - } + let cache = state.fn_resolution_cache_mut(); + + if hash_script.map_or(false, |hash| cache.contains_key(&hash)) + || hash_fn.map_or(false, |hash| cache.contains_key(&hash)) + { + return true; } // First check script-defined functions - let r = hash_script.map_or(false, |hash| lib.iter().any(|&m| m.contains_fn(hash, pub_only))) - //|| hash_fn.map_or(false, |hash| lib.iter().any(|&m| m.contains_fn(hash, pub_only))) + if hash_script.map_or(false, |hash| lib.iter().any(|&m| m.contains_fn(hash, false))) + //|| hash_fn.map_or(false, |hash| lib.iter().any(|&m| m.contains_fn(hash, false))) // Then check registered functions - //|| hash_script.map_or(false, |hash| self.global_namespace.contains_fn(hash, pub_only)) + || hash_script.map_or(false, |hash| self.global_namespace.contains_fn(hash, false)) || hash_fn.map_or(false, |hash| self.global_namespace.contains_fn(hash, false)) // Then check packages || hash_script.map_or(false, |hash| self.global_modules.iter().any(|m| m.contains_fn(hash, false))) || hash_fn.map_or(false, |hash| self.global_modules.iter().any(|m| m.contains_fn(hash, false))) // Then check imported modules || hash_script.map_or(false, |hash| mods.map_or(false, |m| m.contains_fn(hash))) - || hash_fn.map_or(false, |hash| mods.map_or(false, |m| m.contains_fn(hash))); - - // If there is no override, put that information into the cache - if !r { - if let Some(state) = state.as_mut() { - if let Some(hash) = hash_script { - state.fn_resolution_cache_mut().insert(hash, None); - } - if let Some(hash) = hash_fn { - state.fn_resolution_cache_mut().insert(hash, None); - } + || hash_fn.map_or(false, |hash| mods.map_or(false, |m| m.contains_fn(hash))) + // Then check sub-modules + || hash_script.map_or(false, |hash| self.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash))) + || hash_fn.map_or(false, |hash| self.global_sub_modules.values().any(|m| m.contains_qualified_fn(hash))) + { + true + } else { + if let Some(hash_fn) = hash_fn { + cache.insert(hash_fn, None); } + if let Some(hash_script) = hash_script { + cache.insert(hash_script, None); + } + false } - - r } /// Perform an actual function call, native Rust or scripted, taking care of special functions. @@ -599,7 +662,6 @@ impl Engine { args: &mut FnCallArgs, is_ref: bool, _is_method: bool, - pub_only: bool, pos: Position, _capture_scope: Option, _level: usize, @@ -609,176 +671,176 @@ impl Engine { ensure_no_data_race(fn_name, args, is_ref)?; } - let hash_fn = - calc_native_fn_hash(empty(), fn_name, args.iter().map(|a| a.type_id())).unwrap(); - + // These may be redirected from method style calls. match fn_name { - // type_of - KEYWORD_TYPE_OF - if args.len() == 1 - && !self.has_override( - Some(mods), - Some(state), - lib, - Some(hash_fn), - hash_script, - pub_only, - ) => - { - Ok(( + // Handle type_of() + KEYWORD_TYPE_OF if args.len() == 1 => { + return Ok(( self.map_type_name(args[0].type_name()).to_string().into(), false, - )) + )); } - // Fn/eval - reaching this point it must be a method-style call, mostly like redirected - // by a function pointer so it isn't caught at parse time. - KEYWORD_FN_PTR | KEYWORD_EVAL - if args.len() == 1 - && !self.has_override( - Some(mods), - Some(state), - lib, - Some(hash_fn), - hash_script, - pub_only, - ) => + // Handle is_def_fn() + #[cfg(not(feature = "no_function"))] + crate::engine::KEYWORD_IS_DEF_FN + if args.len() == 2 && args[0].is::() && args[1].is::() => { - EvalAltResult::ErrorRuntime( + let fn_name = args[0].as_str().unwrap(); + let num_params = args[1].as_int().unwrap(); + + return Ok(( + if num_params < 0 { + Dynamic::FALSE + } else { + let hash_script = + calc_script_fn_hash(empty(), fn_name, num_params as usize); + self.has_override(Some(mods), state, lib, None, hash_script) + .into() + }, + false, + )); + } + + // Handle is_shared() + #[cfg(not(feature = "no_closure"))] + crate::engine::KEYWORD_IS_SHARED if args.len() == 1 => { + return Err(Box::new(EvalAltResult::ErrorRuntime( format!( - "'{}' should not be called in method style. Try {}(...);", + "'{}' should not be called this way. Try {}(...);", fn_name, fn_name ) .into(), pos, - ) - .into() + ))) } - #[cfg(not(feature = "no_function"))] - _ if hash_script.is_some() => { - let hash_script = hash_script.unwrap(); - - // Check if script function access already in the cache - let (func, source) = state - .fn_resolution_cache_mut() - .entry(hash_script) - .or_insert_with(|| { - lib.iter() - .find_map(|&m| { - m.get_fn(hash_script, pub_only) - .map(|f| (f.clone(), m.id_raw().cloned())) - }) - //.or_else(|| self.global_namespace.get_fn(hash_script, pub_only)) - .or_else(|| { - self.global_modules.iter().find_map(|m| { - m.get_fn(hash_script, false) - .map(|f| (f.clone(), m.id_raw().cloned())) - }) - }) - // .or_else(|| mods.iter().find_map(|(_, m)| m.get_qualified_fn(hash_script).map(|f| (f.clone(), m.id_raw().cloned())))) - }) - .as_ref() - .map(|(f, s)| (Some(f.clone()), s.clone())) - .unwrap_or((None, None)); - - if let Some(func) = func { - // Script function call - assert!(func.is_script()); - - let func = func.get_fn_def(); - - let scope: &mut Scope = &mut Default::default(); - - // Move captured variables into scope - #[cfg(not(feature = "no_closure"))] - if let Some(captured) = _capture_scope { - if !func.externals.is_empty() { - captured - .into_iter() - .filter(|(name, _, _)| func.externals.iter().any(|ex| ex == name)) - .for_each(|(name, value, _)| { - // Consume the scope values. - scope.push_dynamic(name, value); - }); - } - } - - let result = if _is_method { - // Method call of script function - map first argument to `this` - let (first, rest) = args.split_first_mut().unwrap(); - - let orig_source = mem::take(&mut state.source); - state.source = source; - - let level = _level + 1; - - let result = self.call_script_fn( - scope, - mods, - state, - lib, - &mut Some(*first), - func, - rest, - pos, - level, - ); - - // Restore the original source - state.source = orig_source; - - result? - } else { - // Normal call of script function - // The first argument is a reference? - let mut backup: ArgBackup = Default::default(); - backup.change_first_arg_to_copy(is_ref, args); - - let orig_source = mem::take(&mut state.source); - state.source = source; - - let level = _level + 1; - - let result = self.call_script_fn( - scope, mods, state, lib, &mut None, func, args, pos, level, - ); - - // Restore the original source - state.source = orig_source; - - // Restore the original reference - backup.restore_first_arg(args); - - result? - }; - - Ok((result, false)) - } else { - // Native function call - self.call_native_fn( - mods, state, lib, fn_name, hash_fn, args, is_ref, false, pos, + KEYWORD_FN_PTR | KEYWORD_EVAL | KEYWORD_IS_DEF_VAR if args.len() == 1 => { + return Err(Box::new(EvalAltResult::ErrorRuntime( + format!( + "'{}' should not be called this way. Try {}(...);", + fn_name, fn_name ) + .into(), + pos, + ))) + } + + KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY if !args.is_empty() => { + return Err(Box::new(EvalAltResult::ErrorRuntime( + format!( + "'{}' should not be called this way. Try {}(...);", + fn_name, fn_name + ) + .into(), + pos, + ))) + } + + _ => (), + } + + #[cfg(not(feature = "no_function"))] + if let Some((func, source)) = hash_script.and_then(|hash| { + self.resolve_function(mods, state, lib, fn_name, hash, args, false, false) + .as_ref() + .map(|(f, s)| (f.clone(), s.clone())) + }) { + // Script function call + assert!(func.is_script()); + + let func = func.get_fn_def(); + + let scope: &mut Scope = &mut Default::default(); + + // Move captured variables into scope + #[cfg(not(feature = "no_closure"))] + if let Some(captured) = _capture_scope { + if !func.externals.is_empty() { + captured + .into_iter() + .filter(|(name, _, _)| func.externals.iter().any(|ex| ex == name)) + .for_each(|(name, value, _)| { + // Consume the scope values. + scope.push_dynamic(name, value); + }); } } - // Native function call - _ => self.call_native_fn(mods, state, lib, fn_name, hash_fn, args, is_ref, false, pos), + let result = if _is_method { + // Method call of script function - map first argument to `this` + let (first, rest) = args.split_first_mut().unwrap(); + + let orig_source = mem::take(&mut state.source); + state.source = source; + + let level = _level + 1; + + let result = self.call_script_fn( + scope, + mods, + state, + lib, + &mut Some(*first), + func, + rest, + pos, + level, + ); + + // Restore the original source + state.source = orig_source; + + result? + } else { + // Normal call of script function + // The first argument is a reference? + let mut backup: Option = None; + if is_ref && !args.is_empty() { + backup = Some(Default::default()); + backup.as_mut().unwrap().change_first_arg_to_copy(args); + } + + let orig_source = mem::take(&mut state.source); + state.source = source; + + let level = _level + 1; + + let result = + self.call_script_fn(scope, mods, state, lib, &mut None, func, args, pos, level); + + // Restore the original source + state.source = orig_source; + + // Restore the original reference + if let Some(backup) = backup { + backup.restore_first_arg(args); + } + + result? + }; + + return Ok((result, false)); } + + // Native function call + let hash_fn = + calc_native_fn_hash(empty(), fn_name, args.iter().map(|a| a.type_id())).unwrap(); + self.call_native_fn(mods, state, lib, fn_name, hash_fn, args, is_ref, false, pos) } /// Evaluate a list of statements with no `this` pointer. /// This is commonly used to evaluate a list of statements in an [`AST`] or a script function body. #[inline] - pub(crate) fn eval_global_statements<'a>( + pub(crate) fn eval_global_statements( &self, scope: &mut Scope, mods: &mut Imports, state: &mut State, - statements: impl IntoIterator, + statements: &[Stmt], lib: &[&Module], level: usize, - ) -> Result> { + ) -> RhaiResult { self.eval_stmt_block(scope, mods, state, lib, &mut None, statements, false, level) .or_else(|err| match *err { EvalAltResult::Return(out, _) => Ok(out), @@ -799,7 +861,7 @@ impl Engine { script: &str, pos: Position, level: usize, - ) -> Result> { + ) -> RhaiResult { self.inc_operations(state, pos)?; let script = script.trim(); @@ -844,7 +906,6 @@ impl Engine { hash_script: Option, target: &mut crate::engine::Target, mut call_args: StaticVec, - pub_only: bool, pos: Position, level: usize, ) -> Result<(Dynamic, bool), Box> { @@ -854,114 +915,116 @@ impl Engine { let obj = target.as_mut(); let mut fn_name = fn_name; - let (result, updated) = if fn_name == KEYWORD_FN_PTR_CALL && obj.is::() { - // FnPtr call - let fn_ptr = obj.read_lock::().unwrap(); - // Redirect function name - let fn_name = fn_ptr.fn_name(); - let args_len = call_args.len() + fn_ptr.curry().len(); - // Recalculate hash - let hash = hash_script.and_then(|_| calc_script_fn_hash(empty(), fn_name, args_len)); - // Arguments are passed as-is, adding the curried arguments - let mut curry = fn_ptr.curry().iter().cloned().collect::>(); - let mut arg_values = curry - .iter_mut() - .chain(call_args.iter_mut()) - .collect::>(); - let args = arg_values.as_mut(); + let (result, updated) = match fn_name { + KEYWORD_FN_PTR_CALL if obj.is::() => { + // FnPtr call + let fn_ptr = obj.read_lock::().unwrap(); + // Redirect function name + let fn_name = fn_ptr.fn_name(); + let args_len = call_args.len() + fn_ptr.curry().len(); + // Recalculate hash + let hash = + hash_script.and_then(|_| calc_script_fn_hash(empty(), fn_name, args_len)); + // Arguments are passed as-is, adding the curried arguments + let mut curry = fn_ptr.curry().iter().cloned().collect::>(); + let mut arg_values = curry + .iter_mut() + .chain(call_args.iter_mut()) + .collect::>(); + let args = arg_values.as_mut(); - // Map it to name(args) in function-call style - self.exec_fn_call( - mods, state, lib, fn_name, hash, args, false, false, pub_only, pos, None, level, - ) - } else if fn_name == KEYWORD_FN_PTR_CALL - && call_args.len() > 0 - && call_args[0].is::() - { - // FnPtr call on object - let fn_ptr = call_args.remove(0).cast::(); - // Redirect function name - let fn_name = fn_ptr.fn_name(); - let args_len = call_args.len() + fn_ptr.curry().len(); - // Recalculate hash - let hash = hash_script.and_then(|_| calc_script_fn_hash(empty(), fn_name, args_len)); - // Replace the first argument with the object pointer, adding the curried arguments - let mut curry = fn_ptr.curry().iter().cloned().collect::>(); - let mut arg_values = once(obj) - .chain(curry.iter_mut()) - .chain(call_args.iter_mut()) - .collect::>(); - let args = arg_values.as_mut(); - - // Map it to name(args) in function-call style - self.exec_fn_call( - mods, state, lib, fn_name, hash, args, is_ref, true, pub_only, pos, None, level, - ) - } else if fn_name == KEYWORD_FN_PTR_CURRY && obj.is::() { - // Curry call - let fn_ptr = obj.read_lock::().unwrap(); - Ok(( - FnPtr::new_unchecked( - fn_ptr.get_fn_name().clone(), - fn_ptr - .curry() - .iter() - .cloned() - .chain(call_args.into_iter()) - .collect(), + // Map it to name(args) in function-call style + self.exec_fn_call( + mods, state, lib, fn_name, hash, args, false, false, pos, None, level, ) - .into(), - false, - )) - } else if { - #[cfg(not(feature = "no_closure"))] - { - fn_name == crate::engine::KEYWORD_IS_SHARED && call_args.is_empty() } - #[cfg(feature = "no_closure")] - false - } { - // is_shared call - Ok((target.is_shared().into(), false)) - } else { - let _redirected; - let mut hash = hash_script; + KEYWORD_FN_PTR_CALL if call_args.len() > 0 && call_args[0].is::() => { + // FnPtr call on object + let fn_ptr = call_args.remove(0).cast::(); + // Redirect function name + let fn_name = fn_ptr.fn_name(); + let args_len = call_args.len() + fn_ptr.curry().len(); + // Recalculate hash + let hash = + hash_script.and_then(|_| calc_script_fn_hash(empty(), fn_name, args_len)); + // Replace the first argument with the object pointer, adding the curried arguments + let mut curry = fn_ptr.curry().iter().cloned().collect::>(); + let mut arg_values = once(obj) + .chain(curry.iter_mut()) + .chain(call_args.iter_mut()) + .collect::>(); + let args = arg_values.as_mut(); - // Check if it is a map method call in OOP style - #[cfg(not(feature = "no_object"))] - if let Some(map) = obj.read_lock::() { - if let Some(val) = map.get(fn_name) { - if let Some(fn_ptr) = val.read_lock::() { - // Remap the function name - _redirected = fn_ptr.get_fn_name().clone(); - fn_name = &_redirected; - // Add curried arguments + // Map it to name(args) in function-call style + self.exec_fn_call( + mods, state, lib, fn_name, hash, args, is_ref, true, pos, None, level, + ) + } + KEYWORD_FN_PTR_CURRY if obj.is::() => { + // Curry call + let fn_ptr = obj.read_lock::().unwrap(); + Ok(( + FnPtr::new_unchecked( + fn_ptr.get_fn_name().clone(), fn_ptr .curry() .iter() .cloned() - .enumerate() - .for_each(|(i, v)| call_args.insert(i, v)); - // Recalculate the hash based on the new function name and new arguments - hash = hash_script - .and_then(|_| calc_script_fn_hash(empty(), fn_name, call_args.len())); - } - } - }; - - if hash_script.is_none() { - hash = None; + .chain(call_args.into_iter()) + .collect(), + ) + .into(), + false, + )) } - // Attached object pointer in front of the arguments - let mut arg_values = once(obj) - .chain(call_args.iter_mut()) - .collect::>(); - let args = arg_values.as_mut(); + // Handle is_shared() + #[cfg(not(feature = "no_closure"))] + crate::engine::KEYWORD_IS_SHARED if call_args.is_empty() => { + return Ok((target.is_shared().into(), false)); + } - self.exec_fn_call( - mods, state, lib, fn_name, hash, args, is_ref, true, pub_only, pos, None, level, - ) + _ => { + let _redirected; + let mut hash = hash_script; + + // Check if it is a map method call in OOP style + #[cfg(not(feature = "no_object"))] + if let Some(map) = obj.read_lock::() { + if let Some(val) = map.get(fn_name) { + if let Some(fn_ptr) = val.read_lock::() { + // Remap the function name + _redirected = fn_ptr.get_fn_name().clone(); + fn_name = &_redirected; + // Add curried arguments + fn_ptr + .curry() + .iter() + .cloned() + .enumerate() + .for_each(|(i, v)| call_args.insert(i, v)); + // Recalculate the hash based on the new function name and new arguments + hash = hash_script.and_then(|_| { + calc_script_fn_hash(empty(), fn_name, call_args.len()) + }); + } + } + }; + + if hash_script.is_none() { + hash = None; + } + + // Attached object pointer in front of the arguments + let mut arg_values = once(obj) + .chain(call_args.iter_mut()) + .collect::>(); + let args = arg_values.as_mut(); + + self.exec_fn_call( + mods, state, lib, fn_name, hash, args, is_ref, true, pos, None, level, + ) + } }?; // Propagate the changed value back to the source if necessary @@ -981,13 +1044,12 @@ impl Engine { lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, fn_name: &str, - args_expr: impl AsRef<[Expr]>, + args_expr: &[Expr], mut hash_script: Option, - pub_only: bool, pos: Position, capture_scope: bool, level: usize, - ) -> Result> { + ) -> RhaiResult { let args_expr = args_expr.as_ref(); // Handle call() - Redirect function call @@ -996,39 +1058,36 @@ impl Engine { let mut curry = StaticVec::new(); let mut name = fn_name; - if name == KEYWORD_FN_PTR_CALL - && args_expr.len() >= 1 - && !self.has_override(Some(mods), Some(state), lib, None, hash_script, pub_only) - { - let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; + match name { + // Handle call() + KEYWORD_FN_PTR_CALL if args_expr.len() >= 1 => { + let fn_ptr = + self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; - if !fn_ptr.is::() { - return Err(self.make_type_mismatch_err::( - self.map_type_name(fn_ptr.type_name()), - args_expr[0].position(), - )); + if !fn_ptr.is::() { + return Err(self.make_type_mismatch_err::( + self.map_type_name(fn_ptr.type_name()), + args_expr[0].position(), + )); + } + + let fn_ptr = fn_ptr.cast::(); + curry.extend(fn_ptr.curry().iter().cloned()); + + // Redirect function name + redirected = fn_ptr.take_data().0; + name = &redirected; + + // Skip the first argument + args_expr = &args_expr.as_ref()[1..]; + + // Recalculate hash + let args_len = args_expr.len() + curry.len(); + hash_script = calc_script_fn_hash(empty(), name, args_len); } - let fn_ptr = fn_ptr.cast::(); - curry.extend(fn_ptr.curry().iter().cloned()); - - // Redirect function name - redirected = fn_ptr.take_data().0; - name = &redirected; - - // Skip the first argument - args_expr = &args_expr.as_ref()[1..]; - - // Recalculate hash - let args_len = args_expr.len() + curry.len(); - hash_script = calc_script_fn_hash(empty(), name, args_len); - } - - // Handle Fn() - if name == KEYWORD_FN_PTR && args_expr.len() == 1 { - let hash_fn = calc_native_fn_hash(empty(), name, once(TypeId::of::())); - - if !self.has_override(Some(mods), Some(state), lib, hash_fn, hash_script, pub_only) { + // Handle Fn() + KEYWORD_FN_PTR if args_expr.len() == 1 => { // Fn - only in function call style return self .eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)? @@ -1040,47 +1099,67 @@ impl Engine { .map(Into::::into) .map_err(|err| err.fill_position(args_expr[0].position())); } - } - // Handle curry() - if name == KEYWORD_FN_PTR_CURRY && args_expr.len() > 1 { - let fn_ptr = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; + // Handle curry() + KEYWORD_FN_PTR_CURRY if args_expr.len() > 1 => { + let fn_ptr = + self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; - if !fn_ptr.is::() { - return Err(self.make_type_mismatch_err::( - self.map_type_name(fn_ptr.type_name()), - args_expr[0].position(), - )); + if !fn_ptr.is::() { + return Err(self.make_type_mismatch_err::( + self.map_type_name(fn_ptr.type_name()), + args_expr[0].position(), + )); + } + + let (name, mut fn_curry) = fn_ptr.cast::().take_data(); + + // Append the new curried arguments to the existing list. + + args_expr.iter().skip(1).try_for_each( + |expr| -> Result<(), Box> { + fn_curry + .push(self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?); + Ok(()) + }, + )?; + + return Ok(FnPtr::new_unchecked(name, fn_curry).into()); } - let (name, mut fn_curry) = fn_ptr.cast::().take_data(); + // Handle is_shared() + #[cfg(not(feature = "no_closure"))] + crate::engine::KEYWORD_IS_SHARED if args_expr.len() == 1 => { + let value = + self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; + return Ok(value.is_shared().into()); + } - // Append the new curried arguments to the existing list. - - args_expr - .iter() - .skip(1) - .try_for_each(|expr| -> Result<(), Box> { - fn_curry.push(self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?); - Ok(()) + // Handle is_def_fn() + #[cfg(not(feature = "no_function"))] + crate::engine::KEYWORD_IS_DEF_FN if args_expr.len() == 2 => { + let fn_name = + self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; + let fn_name = fn_name.as_str().map_err(|err| { + self.make_type_mismatch_err::(err, args_expr[0].position()) + })?; + let num_params = + self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[1], level)?; + let num_params = num_params.as_int().map_err(|err| { + self.make_type_mismatch_err::(err, args_expr[0].position()) })?; - return Ok(FnPtr::new_unchecked(name, fn_curry).into()); - } + return Ok(if num_params < 0 { + Dynamic::FALSE + } else { + let hash_script = calc_script_fn_hash(empty(), fn_name, num_params as usize); + self.has_override(Some(mods), state, lib, None, hash_script) + .into() + }); + } - // Handle is_shared() - #[cfg(not(feature = "no_closure"))] - if name == crate::engine::KEYWORD_IS_SHARED && args_expr.len() == 1 { - let value = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; - - return Ok(value.is_shared().into()); - } - - // Handle is_def_var() - if name == KEYWORD_IS_DEF_VAR && args_expr.len() == 1 { - let hash_fn = calc_native_fn_hash(empty(), name, once(TypeId::of::())); - - if !self.has_override(Some(mods), Some(state), lib, hash_fn, hash_script, pub_only) { + // Handle is_def_var() + KEYWORD_IS_DEF_VAR if args_expr.len() == 1 => { let var_name = self.eval_expr(scope, mods, state, lib, this_ptr, &args_expr[0], level)?; let var_name = var_name.as_str().map_err(|err| { @@ -1088,15 +1167,10 @@ impl Engine { })?; return Ok(scope.contains(var_name).into()); } - } - // Handle eval() - if name == KEYWORD_EVAL && args_expr.len() == 1 { - let hash_fn = calc_native_fn_hash(empty(), name, once(TypeId::of::())); - - let script_expr = &args_expr[0]; - - if !self.has_override(Some(mods), Some(state), lib, hash_fn, hash_script, pub_only) { + // Handle eval() + KEYWORD_EVAL if args_expr.len() == 1 => { + let script_expr = &args_expr[0]; let script_pos = script_expr.position(); // eval - only in function call style @@ -1135,6 +1209,8 @@ impl Engine { )) }); } + + _ => (), } // Normal function call - except for Fn, curry, call and eval (handled above) @@ -1203,7 +1279,6 @@ impl Engine { args, is_ref, false, - pub_only, pos, capture, level, @@ -1221,14 +1296,14 @@ impl Engine { this_ptr: &mut Option<&mut Dynamic>, namespace: Option<&NamespaceRef>, fn_name: &str, - args_expr: impl AsRef<[Expr]>, + args_expr: &[Expr], hash_script: NonZeroU64, pos: Position, level: usize, - ) -> Result> { + ) -> RhaiResult { let args_expr = args_expr.as_ref(); - let namespace = namespace.as_ref().unwrap(); + let namespace = namespace.unwrap(); let mut arg_values: StaticVec<_>; let mut first_arg_value = None; let mut args: StaticVec<_>; @@ -1282,7 +1357,9 @@ impl Engine { } } - let module = search_imports(mods, state, namespace)?; + let module = self.search_imports(mods, state, namespace).ok_or_else(|| { + EvalAltResult::ErrorModuleNotFound(namespace[0].name.to_string(), namespace[0].pos) + })?; // First search in script-defined functions (can override built-in) let func = match module.get_qualified_fn(hash_script) { @@ -1334,481 +1411,29 @@ impl Engine { result } - Some(f) if f.is_plugin_fn() => f.get_plugin_fn().clone().call( - (self, fn_name, module.id(), &*mods, lib).into(), - args.as_mut(), - ), + + Some(f) if f.is_plugin_fn() => f + .get_plugin_fn() + .clone() + .call( + (self, fn_name, module.id(), &*mods, lib).into(), + args.as_mut(), + ) + .map_err(|err| err.fill_position(pos)), + Some(f) if f.is_native() => f.get_native_fn()( (self, fn_name, module.id(), &*mods, lib).into(), args.as_mut(), - ), + ) + .map_err(|err| err.fill_position(pos)), + Some(f) => unreachable!("unknown function type: {:?}", f), + None => EvalAltResult::ErrorFunctionNotFound( - format!( - "{}{} ({})", - namespace, - fn_name, - args.iter() - .map(|a| if a.is::() { - "&str | ImmutableString | String" - } else { - self.map_type_name((*a).type_name()) - }) - .collect::>() - .join(", ") - ), + self.gen_call_signature(Some(namespace), fn_name, args.as_ref()), pos, ) .into(), } } } - -/// Build in common binary operator implementations to avoid the cost of calling a registered function. -pub fn run_builtin_binary_op( - op: &str, - x: &Dynamic, - y: &Dynamic, -) -> Result, Box> { - let type1 = x.type_id(); - let type2 = y.type_id(); - - if x.is_variant() || y.is_variant() { - // One of the operands is a custom type, so it is never built-in - return Ok(match op { - "!=" if type1 != type2 => Some(Dynamic::TRUE), - "==" | ">" | ">=" | "<" | "<=" if type1 != type2 => Some(Dynamic::FALSE), - _ => None, - }); - } - - let types_pair = (type1, type2); - - #[cfg(not(feature = "no_float"))] - if let Some((x, y)) = if types_pair == (TypeId::of::(), TypeId::of::()) { - // FLOAT op FLOAT - Some((x.clone().cast::(), y.clone().cast::())) - } else if types_pair == (TypeId::of::(), TypeId::of::()) { - // FLOAT op INT - Some((x.clone().cast::(), y.clone().cast::() as FLOAT)) - } else if types_pair == (TypeId::of::(), TypeId::of::()) { - // INT op FLOAT - Some((x.clone().cast::() as FLOAT, y.clone().cast::())) - } else { - None - } { - match op { - "+" => return Ok(Some((x + y).into())), - "-" => return Ok(Some((x - y).into())), - "*" => return Ok(Some((x * y).into())), - "/" => return Ok(Some((x / y).into())), - "%" => return Ok(Some((x % y).into())), - "**" => return Ok(Some(x.powf(y).into())), - "==" => return Ok(Some((x == y).into())), - "!=" => return Ok(Some((x != y).into())), - ">" => return Ok(Some((x > y).into())), - ">=" => return Ok(Some((x >= y).into())), - "<" => return Ok(Some((x < y).into())), - "<=" => return Ok(Some((x <= y).into())), - _ => return Ok(None), - } - } - - #[cfg(feature = "decimal")] - if let Some((x, y)) = if types_pair == (TypeId::of::(), TypeId::of::()) { - // Decimal op Decimal - Some(( - *x.read_lock::().unwrap(), - *y.read_lock::().unwrap(), - )) - } else if types_pair == (TypeId::of::(), TypeId::of::()) { - // Decimal op INT - Some(( - *x.read_lock::().unwrap(), - y.clone().cast::().into(), - )) - } else if types_pair == (TypeId::of::(), TypeId::of::()) { - // INT op Decimal - Some(( - x.clone().cast::().into(), - *y.read_lock::().unwrap(), - )) - } else { - None - } { - if cfg!(not(feature = "unchecked")) { - use crate::packages::arithmetic::decimal_functions::*; - - match op { - "+" => return add(x, y).map(Some), - "-" => return subtract(x, y).map(Some), - "*" => return multiply(x, y).map(Some), - "/" => return divide(x, y).map(Some), - "%" => return modulo(x, y).map(Some), - _ => (), - } - } else { - match op { - "+" => return Ok(Some((x + y).into())), - "-" => return Ok(Some((x - y).into())), - "*" => return Ok(Some((x * y).into())), - "/" => return Ok(Some((x / y).into())), - "%" => return Ok(Some((x % y).into())), - _ => (), - } - } - - match op { - "==" => return Ok(Some((x == y).into())), - "!=" => return Ok(Some((x != y).into())), - ">" => return Ok(Some((x > y).into())), - ">=" => return Ok(Some((x >= y).into())), - "<" => return Ok(Some((x < y).into())), - "<=" => return Ok(Some((x <= y).into())), - _ => return Ok(None), - } - } - - // char op string - if types_pair == (TypeId::of::(), TypeId::of::()) { - let x = x.clone().cast::(); - let y = &*y.read_lock::().unwrap(); - - match op { - "+" => return Ok(Some(format!("{}{}", x, y).into())), - "==" | "!=" | ">" | ">=" | "<" | "<=" => { - let s1 = [x, '\0']; - let mut y = y.chars(); - let s2 = [y.next().unwrap_or('\0'), y.next().unwrap_or('\0')]; - - match op { - "==" => return Ok(Some((s1 == s2).into())), - "!=" => return Ok(Some((s1 != s2).into())), - ">" => return Ok(Some((s1 > s2).into())), - ">=" => return Ok(Some((s1 >= s2).into())), - "<" => return Ok(Some((s1 < s2).into())), - "<=" => return Ok(Some((s1 <= s2).into())), - _ => unreachable!(), - } - } - _ => return Ok(None), - } - } - // string op char - if types_pair == (TypeId::of::(), TypeId::of::()) { - let x = &*x.read_lock::().unwrap(); - let y = y.clone().cast::(); - - match op { - "+" => return Ok(Some((x + y).into())), - "-" => return Ok(Some((x - y).into())), - "==" | "!=" | ">" | ">=" | "<" | "<=" => { - let mut x = x.chars(); - let s1 = [x.next().unwrap_or('\0'), x.next().unwrap_or('\0')]; - let s2 = [y, '\0']; - - match op { - "==" => return Ok(Some((s1 == s2).into())), - "!=" => return Ok(Some((s1 != s2).into())), - ">" => return Ok(Some((s1 > s2).into())), - ">=" => return Ok(Some((s1 >= s2).into())), - "<" => return Ok(Some((s1 < s2).into())), - "<=" => return Ok(Some((s1 <= s2).into())), - _ => unreachable!(), - } - } - _ => return Ok(None), - } - } - - // Default comparison operators for different types - if type2 != type1 { - return Ok(match op { - "!=" => Some(Dynamic::TRUE), - "==" | ">" | ">=" | "<" | "<=" => Some(Dynamic::FALSE), - _ => None, - }); - } - - // Beyond here, type1 == type2 - - if type1 == TypeId::of::() { - let x = x.clone().cast::(); - let y = y.clone().cast::(); - - if cfg!(not(feature = "unchecked")) { - use crate::packages::arithmetic::arith_basic::INT::functions::*; - - match op { - "+" => return add(x, y).map(Some), - "-" => return subtract(x, y).map(Some), - "*" => return multiply(x, y).map(Some), - "/" => return divide(x, y).map(Some), - "%" => return modulo(x, y).map(Some), - "**" => return power(x, y).map(Some), - ">>" => return shift_right(x, y).map(Some), - "<<" => return shift_left(x, y).map(Some), - _ => (), - } - } else { - match op { - "+" => return Ok(Some((x + y).into())), - "-" => return Ok(Some((x - y).into())), - "*" => return Ok(Some((x * y).into())), - "/" => return Ok(Some((x / y).into())), - "%" => return Ok(Some((x % y).into())), - "**" => return Ok(Some(x.pow(y as u32).into())), - ">>" => return Ok(Some((x >> y).into())), - "<<" => return Ok(Some((x << y).into())), - _ => (), - } - } - - match op { - "==" => return Ok(Some((x == y).into())), - "!=" => return Ok(Some((x != y).into())), - ">" => return Ok(Some((x > y).into())), - ">=" => return Ok(Some((x >= y).into())), - "<" => return Ok(Some((x < y).into())), - "<=" => return Ok(Some((x <= y).into())), - "&" => return Ok(Some((x & y).into())), - "|" => return Ok(Some((x | y).into())), - "^" => return Ok(Some((x ^ y).into())), - _ => return Ok(None), - } - } - - if type1 == TypeId::of::() { - let x = x.clone().cast::(); - let y = y.clone().cast::(); - - match op { - "&" => return Ok(Some((x && y).into())), - "|" => return Ok(Some((x || y).into())), - "^" => return Ok(Some((x ^ y).into())), - "==" => return Ok(Some((x == y).into())), - "!=" => return Ok(Some((x != y).into())), - _ => return Ok(None), - } - } - - if type1 == TypeId::of::() { - let x = &*x.read_lock::().unwrap(); - let y = &*y.read_lock::().unwrap(); - - match op { - "+" => return Ok(Some((x + y).into())), - "-" => return Ok(Some((x - y).into())), - "==" => return Ok(Some((x == y).into())), - "!=" => return Ok(Some((x != y).into())), - ">" => return Ok(Some((x > y).into())), - ">=" => return Ok(Some((x >= y).into())), - "<" => return Ok(Some((x < y).into())), - "<=" => return Ok(Some((x <= y).into())), - _ => return Ok(None), - } - } - - if type1 == TypeId::of::() { - let x = x.clone().cast::(); - let y = y.clone().cast::(); - - match op { - "+" => return Ok(Some(format!("{}{}", x, y).into())), - "==" => return Ok(Some((x == y).into())), - "!=" => return Ok(Some((x != y).into())), - ">" => return Ok(Some((x > y).into())), - ">=" => return Ok(Some((x >= y).into())), - "<" => return Ok(Some((x < y).into())), - "<=" => return Ok(Some((x <= y).into())), - _ => return Ok(None), - } - } - - if type1 == TypeId::of::<()>() { - match op { - "==" => return Ok(Some(true.into())), - "!=" | ">" | ">=" | "<" | "<=" => return Ok(Some(false.into())), - _ => return Ok(None), - } - } - - Ok(None) -} - -/// Build in common operator assignment implementations to avoid the cost of calling a registered function. -pub fn run_builtin_op_assignment( - op: &str, - x: &mut Dynamic, - y: &Dynamic, -) -> Result, Box> { - let type1 = x.type_id(); - let type2 = y.type_id(); - - let types_pair = (type1, type2); - - #[cfg(not(feature = "no_float"))] - if let Some((mut x, y)) = if types_pair == (TypeId::of::(), TypeId::of::()) { - // FLOAT op= FLOAT - let y = y.clone().cast::(); - Some((x.write_lock::().unwrap(), y)) - } else if types_pair == (TypeId::of::(), TypeId::of::()) { - // FLOAT op= INT - let y = y.clone().cast::() as FLOAT; - Some((x.write_lock::().unwrap(), y)) - } else { - None - } { - match op { - "+=" => return Ok(Some(*x += y)), - "-=" => return Ok(Some(*x -= y)), - "*=" => return Ok(Some(*x *= y)), - "/=" => return Ok(Some(*x /= y)), - "%=" => return Ok(Some(*x %= y)), - "**=" => return Ok(Some(*x = x.powf(y))), - _ => return Ok(None), - } - } - - #[cfg(feature = "decimal")] - if let Some((mut x, y)) = if types_pair == (TypeId::of::(), TypeId::of::()) { - // Decimal op= Decimal - let y = *y.read_lock::().unwrap(); - Some((x.write_lock::().unwrap(), y)) - } else if types_pair == (TypeId::of::(), TypeId::of::()) { - // Decimal op= INT - let y = y.clone().cast::().into(); - Some((x.write_lock::().unwrap(), y)) - } else { - None - } { - if cfg!(not(feature = "unchecked")) { - use crate::packages::arithmetic::decimal_functions::*; - - match op { - "+=" => return Ok(Some(*x = add(*x, y)?.as_decimal().unwrap())), - "-=" => return Ok(Some(*x = subtract(*x, y)?.as_decimal().unwrap())), - "*=" => return Ok(Some(*x = multiply(*x, y)?.as_decimal().unwrap())), - "/=" => return Ok(Some(*x = divide(*x, y)?.as_decimal().unwrap())), - "%=" => return Ok(Some(*x = modulo(*x, y)?.as_decimal().unwrap())), - _ => (), - } - } else { - match op { - "+=" => return Ok(Some(*x += y)), - "-=" => return Ok(Some(*x -= y)), - "*=" => return Ok(Some(*x *= y)), - "/=" => return Ok(Some(*x /= y)), - "%=" => return Ok(Some(*x %= y)), - _ => (), - } - } - } - - // string op= char - if types_pair == (TypeId::of::(), TypeId::of::()) { - let y = y.clone().cast::(); - let mut x = x.write_lock::().unwrap(); - - match op { - "+=" => return Ok(Some(*x += y)), - "-=" => return Ok(Some(*x -= y)), - _ => return Ok(None), - } - } - // char op= string - if types_pair == (TypeId::of::(), TypeId::of::()) { - let y = y.read_lock::().unwrap(); - let mut ch = x.read_lock::().unwrap().to_string(); - let mut x = x.write_lock::().unwrap(); - - match op { - "+=" => { - ch.push_str(y.as_str()); - return Ok(Some(*x = ch.into())); - } - _ => return Ok(None), - } - } - - // No built-in op-assignments for different types. - if type2 != type1 { - return Ok(None); - } - - // Beyond here, type1 == type2 - - if type1 == TypeId::of::() { - let y = y.clone().cast::(); - let mut x = x.write_lock::().unwrap(); - - if cfg!(not(feature = "unchecked")) { - use crate::packages::arithmetic::arith_basic::INT::functions::*; - - match op { - "+=" => return Ok(Some(*x = add(*x, y)?.as_int().unwrap())), - "-=" => return Ok(Some(*x = subtract(*x, y)?.as_int().unwrap())), - "*=" => return Ok(Some(*x = multiply(*x, y)?.as_int().unwrap())), - "/=" => return Ok(Some(*x = divide(*x, y)?.as_int().unwrap())), - "%=" => return Ok(Some(*x = modulo(*x, y)?.as_int().unwrap())), - "**=" => return Ok(Some(*x = power(*x, y)?.as_int().unwrap())), - ">>=" => return Ok(Some(*x = shift_right(*x, y)?.as_int().unwrap())), - "<<=" => return Ok(Some(*x = shift_left(*x, y)?.as_int().unwrap())), - _ => (), - } - } else { - match op { - "+=" => return Ok(Some(*x += y)), - "-=" => return Ok(Some(*x -= y)), - "*=" => return Ok(Some(*x *= y)), - "/=" => return Ok(Some(*x /= y)), - "%=" => return Ok(Some(*x %= y)), - "**=" => return Ok(Some(*x = x.pow(y as u32))), - ">>=" => return Ok(Some(*x = *x >> y)), - "<<=" => return Ok(Some(*x = *x << y)), - _ => (), - } - } - - match op { - "&=" => return Ok(Some(*x &= y)), - "|=" => return Ok(Some(*x |= y)), - "^=" => return Ok(Some(*x ^= y)), - _ => return Ok(None), - } - } - - if type1 == TypeId::of::() { - let y = y.clone().cast::(); - let mut x = x.write_lock::().unwrap(); - - match op { - "&=" => return Ok(Some(*x = *x && y)), - "|=" => return Ok(Some(*x = *x || y)), - _ => return Ok(None), - } - } - - if type1 == TypeId::of::() { - let y = y.clone().cast::(); - let mut x = x.write_lock::().unwrap(); - - match op { - "+=" => return Ok(Some(*x = format!("{}{}", *x, y).into())), - _ => return Ok(None), - } - } - - if type1 == TypeId::of::() { - let y = &*y.read_lock::().unwrap(); - let mut x = x.write_lock::().unwrap(); - - match op { - "+=" => return Ok(Some(*x += y)), - "-=" => return Ok(Some(*x -= y)), - _ => return Ok(None), - } - } - - Ok(None) -} diff --git a/src/fn_native.rs b/src/fn_native.rs index 7f458ad2..11801050 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -15,7 +15,7 @@ use crate::stdlib::{ use crate::token::is_valid_identifier; use crate::{ calc_script_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ImmutableString, Module, - Position, + Position, RhaiResult, }; #[cfg(not(feature = "sync"))] @@ -55,20 +55,19 @@ pub type Locked = crate::stdlib::sync::RwLock; /// Context of a native Rust function call. #[derive(Debug, Copy, Clone)] -pub struct NativeCallContext<'e, 'n, 's, 'a, 'm> { - engine: &'e Engine, - fn_name: &'n str, - source: Option<&'s str>, - pub(crate) mods: Option<&'a Imports>, - pub(crate) lib: &'m [&'m Module], +pub struct NativeCallContext<'a> { + engine: &'a Engine, + fn_name: &'a str, + source: Option<&'a str>, + mods: Option<&'a Imports>, + lib: &'a [&'a Module], } -impl<'e, 'n, 's, 'a, 'm, M: AsRef<[&'m Module]> + ?Sized> - From<(&'e Engine, &'n str, Option<&'s str>, &'a Imports, &'m M)> - for NativeCallContext<'e, 'n, 's, 'a, 'm> +impl<'a, M: AsRef<[&'a Module]> + ?Sized> + From<(&'a Engine, &'a str, Option<&'a str>, &'a Imports, &'a M)> for NativeCallContext<'a> { #[inline(always)] - fn from(value: (&'e Engine, &'n str, Option<&'s str>, &'a Imports, &'m M)) -> Self { + fn from(value: (&'a Engine, &'a str, Option<&'a str>, &'a Imports, &'a M)) -> Self { Self { engine: value.0, fn_name: value.1, @@ -79,11 +78,11 @@ impl<'e, 'n, 's, 'a, 'm, M: AsRef<[&'m Module]> + ?Sized> } } -impl<'e, 'n, 'm, M: AsRef<[&'m Module]> + ?Sized> From<(&'e Engine, &'n str, &'m M)> - for NativeCallContext<'e, 'n, '_, '_, 'm> +impl<'a, M: AsRef<[&'a Module]> + ?Sized> From<(&'a Engine, &'a str, &'a M)> + for NativeCallContext<'a> { #[inline(always)] - fn from(value: (&'e Engine, &'n str, &'m M)) -> Self { + fn from(value: (&'a Engine, &'a str, &'a M)) -> Self { Self { engine: value.0, fn_name: value.1, @@ -94,16 +93,16 @@ impl<'e, 'n, 'm, M: AsRef<[&'m Module]> + ?Sized> From<(&'e Engine, &'n str, &'m } } -impl<'e, 'n, 's, 'a, 'm> NativeCallContext<'e, 'n, 's, 'a, 'm> { +impl<'a> NativeCallContext<'a> { /// Create a new [`NativeCallContext`]. #[inline(always)] - pub fn new(engine: &'e Engine, fn_name: &'n str, lib: &'m impl AsRef<[&'m Module]>) -> Self { + pub fn new(engine: &'a Engine, fn_name: &'a str, lib: &'a [&Module]) -> Self { Self { engine, fn_name, source: None, mods: None, - lib: lib.as_ref(), + lib, } } /// _(INTERNALS)_ Create a new [`NativeCallContext`]. @@ -112,18 +111,18 @@ impl<'e, 'n, 's, 'a, 'm> NativeCallContext<'e, 'n, 's, 'a, 'm> { #[cfg(not(feature = "no_module"))] #[inline(always)] pub fn new_with_all_fields( - engine: &'e Engine, - fn_name: &'n str, - source: &'s Option<&str>, - imports: &'a mut Imports, - lib: &'m impl AsRef<[&'m Module]>, + engine: &'a Engine, + fn_name: &'a str, + source: &'a Option<&str>, + imports: &'a Imports, + lib: &'a [&Module], ) -> Self { Self { engine, fn_name, source: source.clone(), mods: Some(imports), - lib: lib.as_ref(), + lib, } } /// The current [`Engine`]. @@ -147,6 +146,14 @@ impl<'e, 'n, 's, 'a, 'm> NativeCallContext<'e, 'n, 's, 'a, 'm> { pub fn iter_imports(&self) -> impl Iterator { self.mods.iter().flat_map(|&m| m.iter()) } + /// Get an iterator over the current set of modules imported via `import` statements. + #[cfg(not(feature = "no_module"))] + #[inline(always)] + pub(crate) fn iter_imports_raw( + &self, + ) -> impl Iterator)> { + self.mods.iter().flat_map(|&m| m.iter_raw()) + } /// _(INTERNALS)_ The current set of modules imported via `import` statements. /// Available under the `internals` feature only. #[cfg(feature = "internals")] @@ -184,9 +191,8 @@ impl<'e, 'n, 's, 'a, 'm> NativeCallContext<'e, 'n, 's, 'a, 'm> { &self, fn_name: &str, is_method: bool, - public_only: bool, args: &mut [&mut Dynamic], - ) -> Result> { + ) -> RhaiResult { self.engine() .exec_fn_call( &mut self.mods.cloned().unwrap_or_default(), @@ -197,7 +203,6 @@ impl<'e, 'n, 's, 'a, 'm> NativeCallContext<'e, 'n, 's, 'a, 'm> { args, is_method, is_method, - public_only, Position::NONE, None, 0, @@ -319,7 +324,7 @@ impl FnPtr { ctx: NativeCallContext, this_ptr: Option<&mut Dynamic>, mut arg_values: impl AsMut<[Dynamic]>, - ) -> Result> { + ) -> RhaiResult { let arg_values = arg_values.as_mut(); let mut args_data = self @@ -337,7 +342,7 @@ impl FnPtr { args.insert(0, obj); } - ctx.call_fn_dynamic_raw(self.fn_name(), is_method, true, args.as_mut()) + ctx.call_fn_dynamic_raw(self.fn_name(), is_method, args.as_mut()) } } @@ -383,11 +388,10 @@ impl TryFrom<&str> for FnPtr { /// A general function trail object. #[cfg(not(feature = "sync"))] -pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> Result>; +pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult; /// A general function trail object. #[cfg(feature = "sync")] -pub type FnAny = - dyn Fn(NativeCallContext, &mut FnCallArgs) -> Result> + Send + Sync; +pub type FnAny = dyn Fn(NativeCallContext, &mut FnCallArgs) -> RhaiResult + Send + Sync; /// A standard function that gets an iterator from a type. pub type IteratorFn = fn(Dynamic) -> Box>; diff --git a/src/fn_register.rs b/src/fn_register.rs index 4021fa57..bba1e46d 100644 --- a/src/fn_register.rs +++ b/src/fn_register.rs @@ -7,7 +7,7 @@ use crate::fn_native::{CallableFunction, FnAny, FnCallArgs, SendSync}; use crate::r#unsafe::unsafe_cast_box; use crate::stdlib::{any::TypeId, boxed::Box, mem, string::String}; use crate::{ - Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, ImmutableString, NativeCallContext, + Dynamic, Engine, FnAccess, FnNamespace, ImmutableString, NativeCallContext, RhaiResult, }; /// Trait to register custom functions with the [`Engine`]. @@ -42,7 +42,7 @@ pub trait RegisterFn { fn register_fn(&mut self, name: &str, f: FN) -> &mut Self; } -/// Trait to register fallible custom functions returning [`Result`]`<`[`Dynamic`]`, `[`Box`]`<`[`EvalAltResult`]`>>` with the [`Engine`]. +/// Trait to register fallible custom functions returning [`Result`]`<`[`Dynamic`]`, `[`Box`]`<`[`EvalAltResult`][crate::EvalAltResult]`>>` with the [`Engine`]. pub trait RegisterResultFn { /// Register a custom fallible function with the [`Engine`]. /// @@ -131,7 +131,7 @@ macro_rules! make_func { $($let)* $($par = ($convert)(_drain.next().unwrap()); )* - // Call the function with each parameter value + // Call the function with each argument value let r = $fn($($arg),*); // Map the result @@ -142,15 +142,13 @@ macro_rules! make_func { /// To Dynamic mapping function. #[inline(always)] -pub fn map_dynamic(data: impl Variant + Clone) -> Result> { +pub fn map_dynamic(data: impl Variant + Clone) -> RhaiResult { Ok(data.into_dynamic()) } /// To Dynamic mapping function. #[inline(always)] -pub fn map_result( - data: Result>, -) -> Result> { +pub fn map_result(data: RhaiResult) -> RhaiResult { data } @@ -197,7 +195,7 @@ macro_rules! def_register { impl< $($par: Variant + Clone,)* - FN: Fn($($param),*) -> Result> + SendSync + 'static, + FN: Fn($($param),*) -> RhaiResult + SendSync + 'static, > RegisterResultFn for Engine { #[inline] diff --git a/src/lib.rs b/src/lib.rs index db6e4687..72a8c881 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,6 +69,7 @@ mod engine; mod engine_api; mod engine_settings; mod fn_args; +mod fn_builtin; mod fn_call; mod fn_func; mod fn_native; @@ -87,6 +88,8 @@ mod token; mod r#unsafe; mod utils; +type RhaiResult = Result>; + /// The system integer type. It is defined as [`i64`]. /// /// If the `only_i32` feature is enabled, this will be [`i32`] instead. diff --git a/src/module/mod.rs b/src/module/mod.rs index 01a7ed25..695f8565 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -47,25 +47,6 @@ impl Default for FnNamespace { } } -impl FnNamespace { - /// Is this namespace [global][FnNamespace::Global]? - #[inline(always)] - pub fn is_global(self) -> bool { - match self { - Self::Global => true, - Self::Internal => false, - } - } - /// Is this namespace [internal][FnNamespace::Internal]? - #[inline(always)] - pub fn is_internal(self) -> bool { - match self { - Self::Global => false, - Self::Internal => true, - } - } -} - /// Data structure containing a single registered function. #[derive(Debug, Clone)] pub struct FuncInfo { @@ -372,12 +353,15 @@ impl Module { self.indexed } - /// Generate signatures for all the functions in the [`Module`]. + /// Generate signatures for all the non-private functions in the [`Module`]. #[inline(always)] pub fn gen_fn_signatures(&self) -> impl Iterator + '_ { self.functions .values() - .filter(|FuncInfo { access, .. }| !access.is_private()) + .filter(|FuncInfo { access, .. }| match access { + FnAccess::Public => true, + FnAccess::Private => false, + }) .map(FuncInfo::gen_signature) } @@ -619,7 +603,10 @@ impl Module { if public_only { self.functions .get(&hash_fn) - .map_or(false, |FuncInfo { access, .. }| access.is_public()) + .map_or(false, |FuncInfo { access, .. }| match access { + FnAccess::Public => true, + FnAccess::Private => false, + }) } else { self.functions.contains_key(&hash_fn) } @@ -640,13 +627,9 @@ impl Module { /// The _last entry_ in the list should be the _return type_ of the function. /// In other words, the number of entries should be one larger than the number of parameters. #[inline(always)] - pub fn update_fn_metadata<'a>( - &mut self, - hash_fn: NonZeroU64, - arg_names: impl AsRef<[&'a str]>, - ) -> &mut Self { + pub fn update_fn_metadata(&mut self, hash_fn: NonZeroU64, arg_names: &[&str]) -> &mut Self { if let Some(f) = self.functions.get_mut(&hash_fn) { - f.param_names = arg_names.as_ref().iter().map(|&n| n.into()).collect(); + f.param_names = arg_names.iter().map(|&n| n.into()).collect(); } self } @@ -739,6 +722,8 @@ impl Module { /// /// This function is very low level. /// + /// # Arguments + /// /// A list of [`TypeId`]'s is taken as the argument types. /// /// Arguments are simply passed in as a mutable array of [`&mut Dynamic`][Dynamic], @@ -747,12 +732,12 @@ impl Module { /// The function is assumed to be a _method_, meaning that the first argument should not be consumed. /// All other arguments can be consumed. /// - /// To access a primary parameter value (i.e. cloning is cheap), use: `args[n].clone().cast::()` + /// To access a primary argument value (i.e. cloning is cheap), use: `args[n].as_xxx().unwrap()` /// - /// To access a parameter value and avoid cloning, use `std::mem::take(args[n]).cast::()`. + /// To access an argument value and avoid cloning, use `std::mem::take(args[n]).cast::()`. /// Notice that this will _consume_ the argument, replacing it with `()`. /// - /// To access the first mutable parameter, use `args.get_mut(0).unwrap()` + /// To access the first mutable argument, use `args.get_mut(0).unwrap()` /// /// # Function Metadata /// @@ -1783,7 +1768,7 @@ impl Module { ast: &crate::AST, engine: &crate::Engine, ) -> Result> { - let mut mods: crate::engine::Imports = (&engine.global_sub_modules).into(); + let mut mods: crate::engine::Imports = Default::default(); let orig_mods_len = mods.len(); // Run the script @@ -1813,19 +1798,21 @@ impl Module { // Non-private functions defined become module functions #[cfg(not(feature = "no_function"))] - { - ast.lib() - .functions - .values() - .filter(|FuncInfo { access, func, .. }| !access.is_private() && func.is_script()) - .for_each(|FuncInfo { func, .. }| { - // Encapsulate AST environment - let mut func = func.get_fn_def().clone(); - func.lib = Some(ast.shared_lib()); - func.mods = func_mods.clone(); - module.set_script_fn(func); - }); - } + ast.lib() + .functions + .values() + .filter(|FuncInfo { access, .. }| match access { + FnAccess::Public => true, + FnAccess::Private => false, + }) + .filter(|FuncInfo { func, .. }| func.is_script()) + .for_each(|FuncInfo { func, .. }| { + // Encapsulate AST environment + let mut func = func.get_fn_def().clone(); + func.lib = Some(ast.shared_lib()); + func.mods = func_mods.clone(); + module.set_script_fn(func); + }); module.set_id(ast.clone_source()); module.build_index(); @@ -1833,6 +1820,31 @@ impl Module { Ok(module) } + /// Are there functions (or type iterators) marked for the specified namespace? + pub fn has_namespace(&self, namespace: FnNamespace, recursive: bool) -> bool { + // Type iterators are default global + if !self.type_iterators.is_empty() { + return true; + } + // Any function marked global? + if self.functions.values().any(|f| f.namespace == namespace) { + return true; + } + + // Scan sub-modules + if recursive { + if self + .modules + .values() + .any(|m| m.has_namespace(namespace, recursive)) + { + return true; + } + } + + false + } + /// Scan through all the sub-modules in the [`Module`] and build a hash index of all /// variables and functions as one flattened namespace. /// @@ -1866,55 +1878,55 @@ impl Module { }); // Index all Rust functions - module - .functions - .iter() - .filter(|(_, FuncInfo { access, .. })| access.is_public()) - .for_each( - |( - &hash, - FuncInfo { - name, - namespace, - params, - param_types, - func, - .. - }, - )| { - // Flatten all functions with global namespace - if namespace.is_global() { + module.functions.iter().for_each( + |( + &hash, + FuncInfo { + name, + namespace, + access, + params, + param_types, + func, + .. + }, + )| { + match namespace { + FnNamespace::Global => { + // Flatten all functions with global namespace functions.insert(hash, func.clone()); } + FnNamespace::Internal => (), + } + match access { + FnAccess::Public => (), + FnAccess::Private => return, // Do not index private functions + } - let hash_qualified_script = - crate::calc_script_fn_hash(qualifiers.iter().cloned(), name, *params) - .unwrap(); - - if !func.is_script() { - assert_eq!(*params, param_types.len()); - - // Namespace-qualified Rust functions are indexed in two steps: - // 1) Calculate a hash in a similar manner to script-defined functions, - // i.e. qualifiers + function name + number of arguments. - // 2) Calculate a second hash with no qualifiers, empty function name, - // and the actual list of argument [`TypeId`]'.s - let hash_fn_args = crate::calc_native_fn_hash( - empty(), - "", - param_types.iter().cloned(), - ) + let hash_qualified_script = + crate::calc_script_fn_hash(qualifiers.iter().cloned(), name, *params) .unwrap(); - // 3) The two hashes are combined. - let hash_qualified_fn = - combine_hashes(hash_qualified_script, hash_fn_args); - functions.insert(hash_qualified_fn, func.clone()); - } else if cfg!(not(feature = "no_function")) { - functions.insert(hash_qualified_script, func.clone()); - } - }, - ); + if !func.is_script() { + assert_eq!(*params, param_types.len()); + + // Namespace-qualified Rust functions are indexed in two steps: + // 1) Calculate a hash in a similar manner to script-defined functions, + // i.e. qualifiers + function name + number of arguments. + // 2) Calculate a second hash with no qualifiers, empty function name, + // and the actual list of argument [`TypeId`]'.s + let hash_fn_args = + crate::calc_native_fn_hash(empty(), "", param_types.iter().cloned()) + .unwrap(); + // 3) The two hashes are combined. + let hash_qualified_fn = combine_hashes(hash_qualified_script, hash_fn_args); + + functions.insert(hash_qualified_fn, func.clone()); + } else if cfg!(not(feature = "no_function")) { + functions.insert(hash_qualified_script, func.clone()); + } + }, + ); } if !self.indexed { diff --git a/src/optimize.rs b/src/optimize.rs index 64609e60..b484e643 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -2,8 +2,8 @@ use crate::ast::{Expr, ScriptFnDef, Stmt}; use crate::dynamic::AccessMode; -use crate::engine::{Imports, KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF}; -use crate::fn_call::run_builtin_binary_op; +use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF}; +use crate::fn_builtin::get_builtin_binary_op_fn; use crate::parser::map_dynamic_to_expr; use crate::stdlib::{ boxed::Box, @@ -62,8 +62,6 @@ struct State<'a> { propagate_constants: bool, /// An [`Engine`] instance for eager function evaluation. engine: &'a Engine, - /// Collection of sub-modules. - mods: Imports, /// [Module] containing script-defined functions. lib: &'a [&'a Module], /// Optimization level. @@ -79,7 +77,6 @@ impl<'a> State<'a> { variables: vec![], propagate_constants: true, engine, - mods: (&engine.global_sub_modules).into(), lib, optimization_level: level, } @@ -669,13 +666,17 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { && x.args.iter().all(Expr::is_constant) // all arguments are constants && !is_valid_identifier(x.name.chars()) // cannot be scripted => { - let arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap()).collect(); + let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap()).collect(); let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect(); // Search for overloaded operators (can override built-in). - if !state.engine.has_override_by_name_and_arguments(Some(&state.mods), None, state.lib, x.name.as_ref(), arg_types.as_ref(), false) { - if let Some(result) = run_builtin_binary_op(x.name.as_ref(), &arg_values[0], &arg_values[1]) - .ok().flatten() + if !state.engine.has_override_by_name_and_arguments(Some(&Default::default()), &mut Default::default(), state.lib, x.name.as_ref(), arg_types.as_ref()) { + if let Some(result) = get_builtin_binary_op_fn(x.name.as_ref(), &arg_values[0], &arg_values[1]) + .and_then(|f| { + let ctx = (state.engine, x.name.as_ref(), state.lib).into(); + let (first, second) = arg_values.split_first_mut().unwrap(); + (f)(ctx, &mut [ first, &mut second[0] ]).ok() + }) .and_then(|result| map_dynamic_to_expr(result, *pos)) { state.set_dirty(); diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 0eca9695..dc66d207 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -649,7 +649,7 @@ mod array_functions { for (a1, a2) in array.iter_mut().zip(array2.iter_mut()) { let equals = ctx - .call_fn_dynamic_raw(OP_EQUALS, true, false, &mut [a1, a2]) + .call_fn_dynamic_raw(OP_EQUALS, true, &mut [a1, a2]) .map(|v| v.as_bool().unwrap_or(false))?; if !equals { diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index 86185215..e9bd49ff 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -19,23 +19,10 @@ mod fn_ptr_functions { #[cfg(not(feature = "no_function"))] pub mod functions { - use crate::{calc_script_fn_hash, stdlib::iter::empty, INT}; - #[rhai_fn(name = "is_anonymous", get = "is_anonymous", pure)] pub fn is_anonymous(f: &mut FnPtr) -> bool { f.is_anonymous() } - - pub fn is_def_fn(ctx: NativeCallContext, fn_name: &str, num_params: INT) -> bool { - if num_params < 0 { - false - } else { - let hash_script = calc_script_fn_hash(empty(), fn_name, num_params as usize); - - ctx.engine() - .has_override(ctx.mods, None, ctx.lib, None, hash_script, true) - } - } } #[cfg(not(feature = "no_function"))] @@ -123,15 +110,13 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> Array { let mut list: Array = Default::default(); - ctx.lib - .iter() + ctx.iter_namespaces() .flat_map(|m| m.iter_script_fn()) .for_each(|(_, _, _, _, f)| list.push(make_metadata(&dict, None, f).into())); - if let Some(mods) = ctx.mods { - mods.iter_raw() - .for_each(|(ns, m)| scan_module(&mut list, &dict, ns.clone(), m.as_ref())); - } + #[cfg(not(feature = "no_module"))] + ctx.iter_imports_raw() + .for_each(|(ns, m)| scan_module(&mut list, &dict, ns.clone(), m.as_ref())); list } diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index 51eda3dd..3d927e9f 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -2,8 +2,9 @@ use crate::dynamic::Variant; use crate::stdlib::{ boxed::Box, ops::{Add, Range}, + string::ToString, }; -use crate::{def_package, EvalAltResult, INT}; +use crate::{def_package, EvalAltResult, Position, INT}; fn get_range(from: T, to: T) -> Result, Box> { Ok(from..to) @@ -16,6 +17,23 @@ where for<'a> &'a T: Add<&'a T, Output = T>, T: Variant + Clone + PartialOrd; +impl StepRange +where + for<'a> &'a T: Add<&'a T, Output = T>, + T: Variant + Clone + PartialOrd, +{ + pub fn new(from: T, to: T, step: T) -> Result> { + if &from + &step == from { + Err(Box::new(EvalAltResult::ErrorArithmetic( + "invalid step value".to_string(), + Position::NONE, + ))) + } else { + Ok(Self(from, to, step)) + } + } +} + impl Iterator for StepRange where for<'a> &'a T: Add<&'a T, Output = T>, @@ -24,12 +42,18 @@ where type Item = T; fn next(&mut self) -> Option { - if self.0 < self.1 { + if self.0 == self.1 { + None + } else if self.0 < self.1 { let v = self.0.clone(); - self.0 = self.0.add(&self.2); + let n = self.0.add(&self.2); + self.0 = if n >= self.1 { self.1.clone() } else { n }; Some(v) } else { - None + let v = self.0.clone(); + let n = self.0.add(&self.2); + self.0 = if n <= self.1 { self.1.clone() } else { n }; + Some(v) } } } @@ -39,7 +63,7 @@ where for<'a> &'a T: Add<&'a T, Output = T>, T: Variant + Clone + PartialOrd, { - Ok(StepRange::(from, to, step)) + StepRange::::new(from, to, step) } macro_rules! reg_range { @@ -47,7 +71,7 @@ macro_rules! reg_range { $( $lib.set_iterator::>(); let hash = $lib.set_fn_2($x, get_range::<$y>); - $lib.update_fn_metadata(hash, [ + $lib.update_fn_metadata(hash, &[ concat!("from: ", stringify!($y)), concat!("to: ", stringify!($y)), concat!("Iterator") @@ -61,7 +85,7 @@ macro_rules! reg_stepped_range { $( $lib.set_iterator::>(); let hash = $lib.set_fn_3($x, get_step_range::<$y>); - $lib.update_fn_metadata(hash, [ + $lib.update_fn_metadata(hash, &[ concat!("from: ", stringify!($y)), concat!("to: ", stringify!($y)), concat!("step: ", stringify!($y)), diff --git a/src/packages/logic.rs b/src/packages/logic.rs index fdd6e0a3..e6b6af54 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -1,7 +1,6 @@ #![allow(non_snake_case)] use crate::def_package; -use crate::fn_call::run_builtin_binary_op; use crate::plugin::*; #[cfg(feature = "decimal")] @@ -40,8 +39,6 @@ macro_rules! reg_functions { } def_package!(crate:LogicPackage:"Logical operators.", lib, { - combine_with_exported_module!(lib, "logic", logic_functions); - #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] { @@ -97,65 +94,6 @@ gen_cmp_functions!(float => f64); #[cfg(feature = "decimal")] gen_cmp_functions!(decimal => Decimal); -#[export_module] -mod logic_functions { - fn is_numeric(type_id: TypeId) -> bool { - let result = type_id == TypeId::of::() - || type_id == TypeId::of::() - || type_id == TypeId::of::() - || type_id == TypeId::of::() - || type_id == TypeId::of::() - || type_id == TypeId::of::() - || type_id == TypeId::of::() - || type_id == TypeId::of::(); - - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] - let result = result || type_id == TypeId::of::() || type_id == TypeId::of::(); - - #[cfg(not(feature = "no_float"))] - let result = result || type_id == TypeId::of::() || type_id == TypeId::of::(); - - #[cfg(feature = "decimal")] - let result = result || type_id == TypeId::of::(); - - result - } - - #[rhai_fn( - name = "==", - name = "!=", - name = ">", - name = ">=", - name = "<", - name = "<=", - return_raw - )] - pub fn cmp( - ctx: NativeCallContext, - x: Dynamic, - y: Dynamic, - ) -> Result> { - let type_x = x.type_id(); - let type_y = y.type_id(); - - if type_x != type_y && is_numeric(type_x) && is_numeric(type_y) { - // Disallow comparisons between different number types - } else if let Some(x) = run_builtin_binary_op(ctx.fn_name(), &x, &y)? { - return Ok(x); - } - - Err(Box::new(EvalAltResult::ErrorFunctionNotFound( - format!( - "{} ({}, {})", - ctx.fn_name(), - ctx.engine().map_type_name(x.type_name()), - ctx.engine().map_type_name(y.type_name()) - ), - Position::NONE, - ))) - } -} - #[cfg(not(feature = "no_float"))] #[export_module] mod f32_functions { diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index a9fbb79a..e1c0558b 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -61,7 +61,7 @@ mod map_functions { for (m1, v1) in map.iter_mut() { if let Some(v2) = map2.get_mut(m1) { let equals = ctx - .call_fn_dynamic_raw(OP_EQUALS, true, false, &mut [v1, v2]) + .call_fn_dynamic_raw(OP_EQUALS, true, &mut [v1, v2]) .map(|v| v.as_bool().unwrap_or(false))?; if !equals { diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index f02e2464..3861a0a2 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -21,7 +21,7 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[inline(always)] fn print_with_func(fn_name: &str, ctx: &NativeCallContext, value: &mut Dynamic) -> ImmutableString { - match ctx.call_fn_dynamic_raw(fn_name, true, false, &mut [value]) { + match ctx.call_fn_dynamic_raw(fn_name, true, &mut [value]) { Ok(result) if result.is::() => result.take_immutable_string().unwrap(), Ok(result) => ctx.engine().map_type_name(result.type_name()).into(), Err(_) => ctx.engine().map_type_name(value.type_name()).into(), diff --git a/src/plugin.rs b/src/plugin.rs index a46afdc4..8aa2d8e1 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -2,6 +2,7 @@ pub use crate::fn_native::{CallableFunction, FnCallArgs}; pub use crate::stdlib::{any::TypeId, boxed::Box, format, mem, string::ToString, vec as new_vec}; +use crate::RhaiResult; pub use crate::{ Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, ImmutableString, Module, NativeCallContext, Position, RegisterFn, RegisterResultFn, @@ -18,11 +19,7 @@ pub use rhai_codegen::{export_fn, register_exported_fn}; /// Use the `#[export_module]` and `#[export_fn]` procedural attributes instead. pub trait PluginFunction { /// Call the plugin function with the arguments provided. - fn call( - &self, - context: NativeCallContext, - args: &mut FnCallArgs, - ) -> Result>; + fn call(&self, context: NativeCallContext, args: &mut FnCallArgs) -> RhaiResult; /// Is this plugin function a method? fn is_method_call(&self) -> bool; diff --git a/src/result.rs b/src/result.rs index 59adae47..1e6ec07b 100644 --- a/src/result.rs +++ b/src/result.rs @@ -274,7 +274,7 @@ impl> From for Box { impl EvalAltResult { /// Is this a pseudo error? A pseudo error is one that does not occur naturally. /// - /// [`LoopBreak`][EvalAltResult::LoopBreak] or [`Return`][EvalAltResult::Return] are pseudo errors. + /// [`LoopBreak`][EvalAltResult::LoopBreak] and [`Return`][EvalAltResult::Return] are pseudo errors. pub fn is_pseudo_error(&self) -> bool { match self { Self::LoopBreak(_, _) | Self::Return(_, _) => true, @@ -344,6 +344,73 @@ impl EvalAltResult { } } /// Get the [position][Position] of this error. + #[cfg(not(feature = "no_object"))] + pub(crate) fn dump_fields(&self, map: &mut crate::Map) { + map.insert( + "error".into(), + crate::stdlib::format!("{:?}", self) + .split('(') + .next() + .unwrap() + .into(), + ); + + match self { + Self::LoopBreak(_, _) | Self::Return(_, _) => (), + + Self::ErrorSystem(_, _) + | Self::ErrorParsing(_, _) + | Self::ErrorUnboundThis(_) + | Self::ErrorFor(_) + | Self::ErrorInExpr(_) + | Self::ErrorArithmetic(_, _) + | Self::ErrorTooManyOperations(_) + | Self::ErrorTooManyModules(_) + | Self::ErrorStackOverflow(_) + | Self::ErrorRuntime(_, _) => (), + + Self::ErrorFunctionNotFound(f, _) => { + map.insert("function".into(), f.into()); + } + Self::ErrorInFunctionCall(f, s, _, _) => { + map.insert("function".into(), f.into()); + map.insert("source".into(), s.into()); + } + Self::ErrorInModule(m, _, _) => { + map.insert("module".into(), m.into()); + } + Self::ErrorMismatchDataType(r, a, _) | Self::ErrorMismatchOutputType(r, a, _) => { + map.insert("requested".into(), r.into()); + map.insert("actual".into(), a.into()); + } + Self::ErrorArrayBounds(n, i, _) | Self::ErrorStringBounds(n, i, _) => { + map.insert("length".into(), (*n as INT).into()); + map.insert("index".into(), (*i as INT).into()); + } + Self::ErrorIndexingType(t, _) => { + map.insert("type".into(), t.into()); + } + Self::ErrorVariableNotFound(v, _) + | Self::ErrorDataRace(v, _) + | Self::ErrorAssignmentToConstant(v, _) => { + map.insert("variable".into(), v.into()); + } + Self::ErrorModuleNotFound(m, _) => { + map.insert("module".into(), m.into()); + } + Self::ErrorDotExpr(p, _) => { + map.insert("property".into(), p.into()); + } + + Self::ErrorDataTooLarge(t, _) => { + map.insert("type".into(), t.into()); + } + Self::ErrorTerminated(t, _) => { + map.insert("token".into(), t.clone()); + } + }; + } + /// Get the [position][Position] of this error. pub fn position(&self) -> Position { match self { Self::ErrorSystem(_, _) => Position::NONE, @@ -376,10 +443,13 @@ impl EvalAltResult { | Self::Return(_, pos) => *pos, } } - /// Clear the [position][Position] information of this error. - pub fn clear_position(&mut self) -> &mut Self { + /// Remove the [position][Position] information from this error and return it. + /// + /// The [position][Position] of this error is set to [`NONE`][Position::NONE] afterwards. + pub fn take_position(&mut self) -> Position { + let pos = self.position(); self.set_position(Position::NONE); - self + pos } /// Override the [position][Position] of this error. pub fn set_position(&mut self, new_position: Position) { diff --git a/src/serde/ser.rs b/src/serde/ser.rs index a833c3b4..5f169431 100644 --- a/src/serde/ser.rs +++ b/src/serde/ser.rs @@ -1,7 +1,7 @@ //! Implement serialization support of [`Dynamic`][crate::Dynamic] for [`serde`]. use crate::stdlib::{boxed::Box, fmt, string::ToString}; -use crate::{Dynamic, EvalAltResult, Position}; +use crate::{Dynamic, EvalAltResult, Position, RhaiResult}; use serde::ser::{ Error, SerializeMap, SerializeSeq, SerializeStruct, SerializeTuple, SerializeTupleStruct, }; @@ -79,7 +79,7 @@ impl DynamicSerializer { /// # Ok(()) /// # } /// ``` -pub fn to_dynamic(value: T) -> Result> { +pub fn to_dynamic(value: T) -> RhaiResult { let mut s = DynamicSerializer::new(Default::default()); value.serialize(&mut s) } @@ -690,7 +690,7 @@ impl serde::ser::SerializeStructVariant for StructVariantSerializer { } #[cfg(not(feature = "no_object"))] -fn make_variant(variant: &'static str, value: Dynamic) -> Result> { +fn make_variant(variant: &'static str, value: Dynamic) -> RhaiResult { let mut map = Map::with_capacity(1); map.insert(variant.into(), value); Ok(map.into()) diff --git a/src/syntax.rs b/src/syntax.rs index 96498a2c..2e39554b 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -6,8 +6,7 @@ use crate::fn_native::SendSync; use crate::stdlib::{boxed::Box, format, string::ToString}; use crate::token::{is_valid_identifier, Token}; use crate::{ - Dynamic, Engine, EvalAltResult, ImmutableString, LexError, ParseError, Position, Shared, - StaticVec, + Engine, ImmutableString, LexError, ParseError, Position, RhaiResult, Shared, StaticVec, }; pub const MARKER_EXPR: &str = "$expr$"; @@ -16,12 +15,10 @@ pub const MARKER_IDENT: &str = "$ident$"; /// A general expression evaluation trait object. #[cfg(not(feature = "sync"))] -pub type FnCustomSyntaxEval = - dyn Fn(&mut EvalContext, &[Expression]) -> Result>; +pub type FnCustomSyntaxEval = dyn Fn(&mut EvalContext, &[Expression]) -> RhaiResult; /// A general expression evaluation trait object. #[cfg(feature = "sync")] -pub type FnCustomSyntaxEval = - dyn Fn(&mut EvalContext, &[Expression]) -> Result> + Send + Sync; +pub type FnCustomSyntaxEval = dyn Fn(&mut EvalContext, &[Expression]) -> RhaiResult + Send + Sync; /// A general expression parsing trait object. #[cfg(not(feature = "sync"))] @@ -61,17 +58,14 @@ impl Expression<'_> { } } -impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_> { +impl EvalContext<'_, '_, '_, '_, '_, '_, '_> { /// Evaluate an [expression tree][Expression]. /// /// # WARNING - Low Level API /// /// This function is very low level. It evaluates an expression from an [`AST`][crate::AST]. #[inline(always)] - pub fn eval_expression_tree( - &mut self, - expr: &Expression, - ) -> Result> { + pub fn eval_expression_tree(&mut self, expr: &Expression) -> RhaiResult { self.engine.eval_expr( self.scope, self.mods, @@ -111,11 +105,9 @@ impl Engine { /// current [`Scope`][crate::Scope] will be _popped_. Do not randomly remove variables. pub fn register_custom_syntax + Into>( &mut self, - keywords: impl AsRef<[S]>, + keywords: &[S], new_vars: isize, - func: impl Fn(&mut EvalContext, &[Expression]) -> Result> - + SendSync - + 'static, + func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static, ) -> Result<&mut Self, ParseError> { let keywords = keywords.as_ref(); @@ -234,9 +226,7 @@ impl Engine { + SendSync + 'static, new_vars: isize, - func: impl Fn(&mut EvalContext, &[Expression]) -> Result> - + SendSync - + 'static, + func: impl Fn(&mut EvalContext, &[Expression]) -> RhaiResult + SendSync + 'static, ) -> &mut Self { let syntax = CustomSyntax { parse: Box::new(parse), diff --git a/src/token.rs b/src/token.rs index ea25f37a..31c87f30 100644 --- a/src/token.rs +++ b/src/token.rs @@ -2,7 +2,7 @@ use crate::engine::{ KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, - KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, + KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_THIS, KEYWORD_TYPE_OF, }; use crate::stdlib::{ borrow::Cow, @@ -21,13 +21,16 @@ use crate::ast::FloatWrapper; #[cfg(feature = "decimal")] use rust_decimal::Decimal; +#[cfg(not(feature = "no_function"))] +use crate::engine::KEYWORD_IS_DEF_FN; + type LERR = LexError; /// Separator character for numbers. const NUM_SEP: char = '_'; /// A stream of tokens. -pub type TokenStream<'a, 't> = Peekable>; +pub type TokenStream<'a> = Peekable>; /// A location (line number + character position) in the input script. /// @@ -579,7 +582,12 @@ impl Token { | "async" | "await" | "yield" => Reserved(syntax.into()), KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR - | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS => Reserved(syntax.into()), + | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS | KEYWORD_IS_DEF_VAR => { + Reserved(syntax.into()) + } + + #[cfg(not(feature = "no_function"))] + KEYWORD_IS_DEF_FN => Reserved(syntax.into()), _ => return None, }) @@ -759,7 +767,6 @@ impl Token { #[cfg(not(feature = "no_function"))] pub(crate) fn into_function_name_for_override(self) -> Result { match self { - Self::Reserved(s) if can_override_keyword(&s) => Ok(s), Self::Custom(s) | Self::Identifier(s) if is_valid_identifier(s.chars()) => Ok(s), _ => Err(self), } @@ -1625,17 +1632,11 @@ fn get_identifier( pub fn is_keyword_function(name: &str) -> bool { match name { KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR - | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY => true, - _ => false, - } -} + | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR => true, + + #[cfg(not(feature = "no_function"))] + KEYWORD_IS_DEF_FN => true, -/// Can this keyword be overridden as a function? -#[cfg(not(feature = "no_function"))] -#[inline(always)] -pub fn can_override_keyword(name: &str) -> bool { - match name { - KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR => true, _ => false, } } @@ -1740,9 +1741,9 @@ impl InputStream for MultiInputsStream<'_> { } /// An iterator on a [`Token`] stream. -pub struct TokenIterator<'a, 'e> { +pub struct TokenIterator<'a> { /// Reference to the scripting `Engine`. - engine: &'e Engine, + engine: &'a Engine, /// Current state. state: TokenizeState, /// Current position. @@ -1753,7 +1754,7 @@ pub struct TokenIterator<'a, 'e> { map: Option Token>, } -impl<'a> Iterator for TokenIterator<'a, '_> { +impl<'a> Iterator for TokenIterator<'a> { type Item = (Token, Position); fn next(&mut self) -> Option { @@ -1836,30 +1837,31 @@ impl<'a> Iterator for TokenIterator<'a, '_> { } impl Engine { - /// Tokenize an input text stream. + /// _(INTERNALS)_ Tokenize an input text stream. + /// Exported under the `internals` feature only. + #[cfg(feature = "internals")] #[inline(always)] - pub fn lex<'a, 'e>( - &'e self, - input: impl IntoIterator, - ) -> TokenIterator<'a, 'e> { + pub fn lex<'a>(&'a self, input: impl IntoIterator) -> TokenIterator<'a> { self.lex_raw(input, None) } - /// Tokenize an input text stream with a mapping function. + /// _(INTERNALS)_ Tokenize an input text stream with a mapping function. + /// Exported under the `internals` feature only. + #[cfg(feature = "internals")] #[inline(always)] - pub fn lex_with_map<'a, 'e>( - &'e self, + pub fn lex_with_map<'a>( + &'a self, input: impl IntoIterator, map: fn(Token) -> Token, - ) -> TokenIterator<'a, 'e> { + ) -> TokenIterator<'a> { self.lex_raw(input, Some(map)) } /// Tokenize an input text stream with an optional mapping function. #[inline] - fn lex_raw<'a, 'e>( - &'e self, + pub(crate) fn lex_raw<'a>( + &'a self, input: impl IntoIterator, map: Option Token>, - ) -> TokenIterator<'a, 'e> { + ) -> TokenIterator<'a> { TokenIterator { engine: self, state: TokenizeState { diff --git a/tests/binary_ops.rs b/tests/binary_ops.rs index 7ede89fb..ca5c79db 100644 --- a/tests/binary_ops.rs +++ b/tests/binary_ops.rs @@ -4,19 +4,134 @@ use rhai::{Engine, EvalAltResult, INT}; fn test_binary_ops() -> Result<(), Box> { let engine = Engine::new(); + assert_eq!(engine.eval::("10 + 4")?, 14); + assert_eq!(engine.eval::("10 - 4")?, 6); + assert_eq!(engine.eval::("10 * 4")?, 40); + assert_eq!(engine.eval::("10 / 4")?, 2); assert_eq!(engine.eval::("10 % 4")?, 2); + assert_eq!(engine.eval::("10 ** 4")?, 10000); assert_eq!(engine.eval::("10 << 4")?, 160); assert_eq!(engine.eval::("10 >> 4")?, 0); assert_eq!(engine.eval::("10 & 4")?, 0); assert_eq!(engine.eval::("10 | 4")?, 14); assert_eq!(engine.eval::("10 ^ 4")?, 14); - assert_eq!(engine.eval::("42 == 42")?, true); - assert_eq!(engine.eval::("42 > 42")?, false); + assert!(engine.eval::("42 == 42")?); + assert!(!engine.eval::("42 != 42")?); + assert!(!engine.eval::("42 > 42")?); + assert!(engine.eval::("42 >= 42")?); + assert!(!engine.eval::("42 < 42")?); + assert!(engine.eval::("42 <= 42")?); + + assert_eq!(engine.eval::("let x = 10; x += 4; x")?, 14); + assert_eq!(engine.eval::("let x = 10; x -= 4; x")?, 6); + assert_eq!(engine.eval::("let x = 10; x *= 4; x")?, 40); + assert_eq!(engine.eval::("let x = 10; x /= 4; x")?, 2); + assert_eq!(engine.eval::("let x = 10; x %= 4; x")?, 2); + assert_eq!(engine.eval::("let x = 10; x **= 4; x")?, 10000); + assert_eq!(engine.eval::("let x = 10; x <<= 4; x")?, 160); + assert_eq!(engine.eval::("let x = 10; x >>= 4; x")?, 0); + assert_eq!(engine.eval::("let x = 10; x &= 4; x")?, 0); + assert_eq!(engine.eval::("let x = 10; x |= 4; x")?, 14); + assert_eq!(engine.eval::("let x = 10; x ^= 4; x")?, 14); + + #[cfg(not(feature = "no_float"))] + { + use rhai::FLOAT; + + assert_eq!(engine.eval::("10.0 + 4.0")?, 14.0); + assert_eq!(engine.eval::("10.0 - 4.0")?, 6.0); + assert_eq!(engine.eval::("10.0 * 4.0")?, 40.0); + assert_eq!(engine.eval::("10.0 / 4.0")?, 2.5); + assert_eq!(engine.eval::("10.0 % 4.0")?, 2.0); + assert_eq!(engine.eval::("10.0 ** 4.0")?, 10000.0); + + assert_eq!(engine.eval::("10.0 + 4")?, 14.0); + assert_eq!(engine.eval::("10.0 - 4")?, 6.0); + assert_eq!(engine.eval::("10.0 * 4")?, 40.0); + assert_eq!(engine.eval::("10.0 / 4")?, 2.5); + assert_eq!(engine.eval::("10.0 % 4")?, 2.0); + assert_eq!(engine.eval::("10.0 ** 4")?, 10000.0); + + assert_eq!(engine.eval::("10 + 4.0")?, 14.0); + assert_eq!(engine.eval::("10 - 4.0")?, 6.0); + assert_eq!(engine.eval::("10 * 4.0")?, 40.0); + assert_eq!(engine.eval::("10 / 4.0")?, 2.5); + assert_eq!(engine.eval::("10 % 4.0")?, 2.0); + assert_eq!(engine.eval::("10 ** 4.0")?, 10000.0); + + assert!(engine.eval::("42 == 42.0")?); + assert!(!engine.eval::("42 != 42.0")?); + assert!(!engine.eval::("42 > 42.0")?); + assert!(engine.eval::("42 >= 42.0")?); + assert!(!engine.eval::("42 < 42.0")?); + assert!(engine.eval::("42 <= 42.0")?); + + assert!(engine.eval::("42.0 == 42")?); + assert!(!engine.eval::("42.0 != 42")?); + assert!(!engine.eval::("42.0 > 42")?); + assert!(engine.eval::("42.0 >= 42")?); + assert!(!engine.eval::("42.0 < 42")?); + assert!(engine.eval::("42.0 <= 42")?); + + assert_eq!(engine.eval::("let x = 10.0; x += 4.0; x")?, 14.0); + assert_eq!(engine.eval::("let x = 10.0; x -= 4.0; x")?, 6.0); + assert_eq!(engine.eval::("let x = 10.0; x *= 4.0; x")?, 40.0); + assert_eq!(engine.eval::("let x = 10.0; x /= 4.0; x")?, 2.5); + assert_eq!(engine.eval::("let x = 10.0; x %= 4.0; x")?, 2.0); + assert_eq!(engine.eval::("let x = 10.0; x **= 4.0; x")?, 10000.0); + + assert_eq!(engine.eval::("let x = 10.0; x += 4; x")?, 14.0); + assert_eq!(engine.eval::("let x = 10.0; x -= 4; x")?, 6.0); + assert_eq!(engine.eval::("let x = 10.0; x *= 4; x")?, 40.0); + assert_eq!(engine.eval::("let x = 10.0; x /= 4; x")?, 2.5); + assert_eq!(engine.eval::("let x = 10.0; x %= 4; x")?, 2.0); + assert_eq!(engine.eval::("let x = 10.0; x **= 4; x")?, 10000.0); + } + + assert_eq!( + engine.eval::(r#""hello" + ", world""#)?, + "hello, world" + ); + assert_eq!(engine.eval::(r#""hello" + '!'"#)?, "hello!"); + assert_eq!(engine.eval::(r#""hello" - "el""#)?, "hlo"); + assert_eq!(engine.eval::(r#""hello" - 'l'"#)?, "heo"); + + assert!(!engine.eval::(r#""a" == "x""#)?); + assert!(engine.eval::(r#""a" != "x""#)?); + assert!(!engine.eval::(r#""a" > "x""#)?); + assert!(!engine.eval::(r#""a" >= "x""#)?); + assert!(engine.eval::(r#""a" < "x""#)?); + assert!(engine.eval::(r#""a" <= "x""#)?); + + assert!(engine.eval::(r#""x" == 'x'"#)?); + assert!(!engine.eval::(r#""x" != 'x'"#)?); + assert!(!engine.eval::(r#""x" > 'x'"#)?); + assert!(engine.eval::(r#""x" >= 'x'"#)?); + assert!(!engine.eval::(r#""x" < 'x'"#)?); + assert!(engine.eval::(r#""x" <= 'x'"#)?); + + assert!(engine.eval::(r#"'x' == "x""#)?); + assert!(!engine.eval::(r#"'x' != "x""#)?); + assert!(!engine.eval::(r#"'x' > "x""#)?); + assert!(engine.eval::(r#"'x' >= "x""#)?); + assert!(!engine.eval::(r#"'x' < "x""#)?); + assert!(engine.eval::(r#"'x' <= "x""#)?); // Incompatible types compare to false - assert_eq!(engine.eval::("true == 42")?, false); - assert_eq!(engine.eval::(r#""42" == 42"#)?, false); + assert!(!engine.eval::("true == 42")?); + assert!(engine.eval::("true != 42")?); + assert!(!engine.eval::("true > 42")?); + assert!(!engine.eval::("true >= 42")?); + assert!(!engine.eval::("true < 42")?); + assert!(!engine.eval::("true <= 42")?); + + assert!(!engine.eval::(r#""42" == 42"#)?); + assert!(engine.eval::(r#""42" != 42"#)?); + assert!(!engine.eval::(r#""42" > 42"#)?); + assert!(!engine.eval::(r#""42" >= 42"#)?); + assert!(!engine.eval::(r#""42" < 42"#)?); + assert!(!engine.eval::(r#""42" <= 42"#)?); Ok(()) } diff --git a/tests/call_fn.rs b/tests/call_fn.rs index 552ae48f..f7e4fa67 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -105,11 +105,8 @@ fn test_call_fn_private() -> Result<(), Box> { let ast = engine.compile("private fn add(x, n, ) { x + n }")?; - assert!(matches!( - *(engine.call_fn(&mut scope, &ast, "add", (40 as INT, 2 as INT)) as Result>) - .expect_err("should error"), - EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "add" - )); + let r: INT = engine.call_fn(&mut scope, &ast, "add", (40 as INT, 2 as INT))?; + assert_eq!(r, 42); Ok(()) } @@ -165,8 +162,8 @@ fn test_fn_ptr_raw() -> Result<(), Box> { 42 ); - assert!(matches!( - *engine.eval::( + assert_eq!( + engine.eval::( r#" private fn foo(x) { this += x; } @@ -174,9 +171,9 @@ fn test_fn_ptr_raw() -> Result<(), Box> { x.bar(Fn("foo"), 1); x "# - ).expect_err("should error"), - EvalAltResult::ErrorFunctionNotFound(x, _) if x.starts_with("foo (") - )); + )?, + 42 + ); assert_eq!( engine.eval::( @@ -210,16 +207,13 @@ fn test_anonymous_fn() -> Result<(), Box> { assert_eq!(calc_func(42, "hello".to_string(), 9)?, 423); - let calc_func = Func::<(INT, INT, INT), INT>::create_from_script( + let calc_func = Func::<(INT, String, INT), INT>::create_from_script( Engine::new(), - "private fn calc(x, y, z) { (x + y) * z }", + "private fn calc(x, y, z) { (x + len(y)) * z }", "calc", )?; - assert!(matches!( - *calc_func(42, 123, 9).expect_err("should error"), - EvalAltResult::ErrorFunctionNotFound(fn_name, _) if fn_name == "calc" - )); + assert_eq!(calc_func(42, "hello".to_string(), 9)?, 423); Ok(()) } diff --git a/tests/closures.rs b/tests/closures.rs index 3f9fcfa2..017200d6 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -102,6 +102,14 @@ fn test_closures() -> Result<(), Box> { "# )?); + assert!(engine.eval::( + r#" + let a = 41; + let foo = |x| { a += x }; + is_shared(a) + "# + )?); + engine.register_fn("plus_one", |x: INT| x + 1); assert_eq!( diff --git a/tests/constants.rs b/tests/constants.rs index 090abfaa..05ec5ea2 100644 --- a/tests/constants.rs +++ b/tests/constants.rs @@ -37,32 +37,6 @@ fn test_constant_scope() -> Result<(), Box> { Ok(()) } -#[test] -fn test_var_is_def() -> Result<(), Box> { - let engine = Engine::new(); - - assert!(engine.eval::( - r#" - let x = 42; - is_def_var("x") - "# - )?); - assert!(!engine.eval::( - r#" - let x = 42; - is_def_var("y") - "# - )?); - assert!(engine.eval::( - r#" - const x = 42; - is_def_var("x") - "# - )?); - - Ok(()) -} - #[cfg(not(feature = "no_object"))] #[test] fn test_constant_mut() -> Result<(), Box> { diff --git a/tests/eval.rs b/tests/eval.rs index c59ccfff..18b4145d 100644 --- a/tests/eval.rs +++ b/tests/eval.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult, LexError, ParseErrorType, RegisterFn, Scope, INT}; +use rhai::{Engine, EvalAltResult, LexError, ParseErrorType, Scope, INT}; #[test] fn test_eval() -> Result<(), Box> { @@ -80,32 +80,6 @@ fn test_eval_function() -> Result<(), Box> { Ok(()) } -#[test] -#[cfg(not(feature = "no_function"))] -fn test_eval_override() -> Result<(), Box> { - let engine = Engine::new(); - - assert_eq!( - engine.eval::( - r#" - fn eval(x) { x } // reflect the script back - - eval("40 + 2") - "# - )?, - "40 + 2" - ); - - let mut engine = Engine::new(); - - // Reflect the script back - engine.register_fn("eval", |script: &str| script.to_string()); - - assert_eq!(engine.eval::(r#"eval("40 + 2")"#)?, "40 + 2"); - - Ok(()) -} - #[test] fn test_eval_disabled() -> Result<(), Box> { let mut engine = Engine::new(); diff --git a/tests/modules.rs b/tests/modules.rs index aadf2a5d..c42111b6 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -24,7 +24,10 @@ fn test_module_sub_module() -> Result<(), Box> { sub_module2.set_var("answer", 41 as INT); let hash_inc = sub_module2.set_fn_1_mut("inc", FnNamespace::Internal, |x: &mut INT| Ok(*x + 1)); + assert!(!sub_module2.has_namespace(FnNamespace::Global, true)); + sub_module2.set_fn_1_mut("super_inc", FnNamespace::Global, |x: &mut INT| Ok(*x + 1)); + assert!(sub_module2.has_namespace(FnNamespace::Global, true)); #[cfg(not(feature = "no_object"))] sub_module2.set_getter_fn("doubled", |x: &mut INT| Ok(*x * 2)); @@ -33,6 +36,9 @@ fn test_module_sub_module() -> Result<(), Box> { module.set_sub_module("life", sub_module); module.set_var("MYSTIC_NUMBER", Dynamic::from(42 as INT)); + assert!(module.has_namespace(FnNamespace::Global, true)); + assert!(!module.has_namespace(FnNamespace::Global, false)); + assert!(module.contains_sub_module("life")); let m = module.get_sub_module("life").unwrap(); diff --git a/tests/string.rs b/tests/string.rs index a4f80c07..a5328fad 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -1,4 +1,4 @@ -use rhai::{Dynamic, Engine, EvalAltResult, ImmutableString, RegisterFn, Scope, INT}; +use rhai::{Engine, EvalAltResult, ImmutableString, RegisterFn, Scope, INT}; #[test] fn test_string() -> Result<(), Box> { diff --git a/tests/types.rs b/tests/types.rs index df72afdd..2ee51a68 100644 --- a/tests/types.rs +++ b/tests/types.rs @@ -46,6 +46,9 @@ fn test_type_of() -> Result<(), Box> { assert_eq!(engine.eval::(r#"type_of("hello")"#)?, "string"); + #[cfg(not(feature = "no_object"))] + assert_eq!(engine.eval::(r#""hello".type_of()"#)?, "string"); + #[cfg(not(feature = "only_i32"))] assert_eq!(engine.eval::("let x = 123; type_of(x)")?, "i64"); diff --git a/tests/var_scope.rs b/tests/var_scope.rs index 6c4c41aa..09cf8730 100644 --- a/tests/var_scope.rs +++ b/tests/var_scope.rs @@ -19,6 +19,32 @@ fn test_var_scope() -> Result<(), Box> { Ok(()) } +#[test] +fn test_var_is_def() -> Result<(), Box> { + let engine = Engine::new(); + + assert!(engine.eval::( + r#" + let x = 42; + is_def_var("x") + "# + )?); + assert!(!engine.eval::( + r#" + let x = 42; + is_def_var("y") + "# + )?); + assert!(engine.eval::( + r#" + const x = 42; + is_def_var("x") + "# + )?); + + Ok(()) +} + #[test] fn test_scope_eval() -> Result<(), Box> { let engine = Engine::new();