diff --git a/RELEASES.md b/RELEASES.md index 2bc2a021..e85c6e56 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -9,9 +9,17 @@ Bug fixes * Bug in `Position::is_beginning_of_line` is fixed. +Breaking changes +---------------- + +* For plugin functions, constants passed to methods (i.e. `&mut` parameter) now raise an error unless the functions are marked with `#[rhai_fn(pure)]`. +* Visibility (i.e. `pub` or not) for generated _plugin_ modules now follow the visibility of the underlying module. +* Default stack-overflow and top-level expression nesting limits for release builds are lowered to 64 from 128. + New features ------------ +* Functions are now allowed to have `Dynamic` arguments. * `#[rhai_fn(pure)]` attribute to mark a plugin function with `&mut` parameter as _pure_ so constants can be passed to it. Without it, passing a constant value into the `&mut` parameter will now raise an error. * Comparisons between `FLOAT`/[`Decimal`](https://crates.io/crates/rust_decimal) and `INT` are now built in. @@ -22,6 +30,8 @@ Enhancements * Error position in `eval` statements is now wrapped in an `EvalAltResult::ErrorInFunctionCall`. * `Position` now implements `Add` and `AddAssign`. * `Scope` now implements `IntoIterator`. +* Strings now have the `split_rev` method and variations of `split` with maximum number of segments. +* Arrays now have the `split` method. Version 0.19.12 diff --git a/src/engine.rs b/src/engine.rs index ee54e8bd..39c67d5c 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -2,7 +2,6 @@ use crate::ast::{Expr, FnCallExpr, Ident, ReturnType, Stmt}; use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant}; -use crate::fn_call::run_builtin_op_assignment; use crate::fn_native::{ CallableFunction, IteratorFn, OnDebugCallback, OnPrintCallback, OnProgressCallback, OnVarCallback, @@ -18,7 +17,7 @@ use crate::stdlib::{ collections::{HashMap, HashSet}, fmt, format, hash::{Hash, Hasher}, - iter::{empty, once, FromIterator}, + iter::{empty, FromIterator}, num::{NonZeroU64, NonZeroU8, NonZeroUsize}, ops::DerefMut, string::{String, ToString}, @@ -627,7 +626,7 @@ pub struct Limits { /// Context of a script evaluation process. #[derive(Debug)] -pub struct EvalContext<'e, 'x, 'px: 'x, 'a, 's, 'm, 't, 'pt: 't> { +pub struct EvalContext<'e, 'x, 'px, 'a, 's, 'm, 't, 'pt> { pub(crate) engine: &'e Engine, pub(crate) scope: &'x mut Scope<'px>, pub(crate) mods: &'a mut Imports, @@ -2004,77 +2003,37 @@ impl Engine { } Ok(Dynamic::UNIT) } else { - // Op-assignment - in order of precedence: - // 1) Native registered overriding function - // 2) Built-in implementation - // 3) Map to `var = var op rhs` + let mut lock_guard; + let lhs_ptr_inner; - // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. - let arg_types = once(lhs_ptr.as_mut().type_id()).chain(once(rhs_val.type_id())); - let hash_fn = calc_native_fn_hash(empty(), op, arg_types).unwrap(); + if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { + lock_guard = lhs_ptr.as_mut().write_lock::().unwrap(); + lhs_ptr_inner = lock_guard.deref_mut(); + } else { + lhs_ptr_inner = lhs_ptr.as_mut(); + } - match self - .global_namespace - .get_fn(hash_fn, false) - .map(|f| (f, None)) - .or_else(|| { - self.global_modules - .iter() - .find_map(|m| m.get_fn(hash_fn, false).map(|f| (f, m.id_raw()))) - }) - .or_else(|| mods.get_fn(hash_fn)) - { - // op= function registered as method - Some((func, source)) if func.is_method() => { - let mut lock_guard; - let lhs_ptr_inner; + let args = &mut [lhs_ptr_inner, &mut rhs_val]; - if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { - lock_guard = lhs_ptr.as_mut().write_lock::().unwrap(); - lhs_ptr_inner = lock_guard.deref_mut(); - } else { - lhs_ptr_inner = lhs_ptr.as_mut(); - } - - let args = &mut [lhs_ptr_inner, &mut rhs_val]; - - // Overriding exact implementation - let source = - source.or_else(|| state.source.as_ref()).map(|s| s.as_str()); - if func.is_plugin_fn() { - func.get_plugin_fn() - .call((self, op.as_ref(), source, &*mods, lib).into(), args)?; - } else { - func.get_native_fn()( - (self, op.as_ref(), source, &*mods, lib).into(), - args, - )?; - } - } - // Built-in op-assignment function - _ if run_builtin_op_assignment(op, lhs_ptr.as_mut(), &rhs_val)? - .is_some() => {} - // Not built-in: expand to `var = var op rhs` - _ => { + match self.exec_fn_call( + mods, state, lib, op, None, args, true, false, false, *op_pos, None, None, + level, + ) { + Ok(_) => (), + Err(err) if matches!(err.as_ref(), EvalAltResult::ErrorFunctionNotFound(f, _) if f.starts_with(op.as_ref())) => + { + // Expand to `var = var op rhs` let op = &op[..op.len() - 1]; // extract operator without = - // Clone the LHS value - let args = &mut [&mut lhs_ptr.as_mut().clone(), &mut rhs_val]; - // Run function let (value, _) = self.exec_fn_call( mods, state, lib, op, None, args, false, false, false, *op_pos, None, None, level, )?; - let value = value.flatten(); - - if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() { - *lhs_ptr.as_mut().write_lock::().unwrap() = value; - } else { - *lhs_ptr.as_mut() = value; - } + *args[0] = value.flatten(); } + err => return err.map(|(v, _)| v), } Ok(Dynamic::UNIT) } diff --git a/src/fn_call.rs b/src/fn_call.rs index 2bede593..a63ba83a 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -185,26 +185,50 @@ impl Engine { .fn_resolution_cache_mut() .entry(hash_fn) .or_insert_with(|| { - // Search for the native function - // First search registered functions (can override packages) - // Then search packages - // Finally search modules + let num_args = args.len(); + let max_bitmask = 1usize << num_args; + let mut hash = hash_fn; + let mut bitmask = 1usize; - //lib.get_fn(hash_fn, pub_only) - self.global_namespace - .get_fn(hash_fn, pub_only) - .cloned() - .map(|f| (f, None)) - .or_else(|| { - self.global_modules.iter().find_map(|m| { - m.get_fn(hash_fn, false) - .map(|f| (f.clone(), m.id_raw().cloned())) + loop { + //lib.get_fn(hash, pub_only).or_else(|| + match self + .global_namespace + .get_fn(hash, pub_only) + .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_fn) - .map(|(f, source)| (f.clone(), source.cloned())) - }) + .or_else(|| { + mods.get_fn(hash) + .map(|(f, source)| (f.clone(), source.cloned())) + }) { + // Specific version found + Some(f) => return Some(f), + + // No more permutations with `Dynamic` wildcards + _ if bitmask >= max_bitmask => return None, + + // Try all permutations with `Dynamic` wildcards + _ => { + // Qualifiers (none) + function name + number of arguments + argument `TypeId`'s. + let arg_types = args.iter().enumerate().map(|(i, a)| { + let mask = 1usize << (num_args - i - 1); + if bitmask & mask != 0 { + TypeId::of::() + } else { + a.type_id() + } + }); + hash = calc_native_fn_hash(empty(), fn_name, arg_types).unwrap(); + bitmask += 1; + } + } + } }); if let Some((func, src)) = func { @@ -257,6 +281,15 @@ impl Engine { // See if it is built in. if args.len() == 2 { + if is_ref { + 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 => (), + } + } + match run_builtin_binary_op(fn_name, args[0], args[1])? { Some(v) => return Ok((v, false)), None => (), @@ -642,7 +675,7 @@ impl Engine { .map(|f| (f.clone(), m.id_raw().cloned())) }) }) - //.or_else(|| mods.iter().find_map(|(_, m)| m.get_qualified_fn(hash_script).map(|f| (f, m.id_raw().clone())))) + // .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())) diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index bb1258d1..d4a5394c 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -3,85 +3,10 @@ use crate::engine::{OP_EQUALS, TYPICAL_ARRAY_SIZE}; use crate::plugin::*; -use crate::stdlib::{any::TypeId, boxed::Box, cmp::max, cmp::Ordering, string::ToString}; -use crate::{ - def_package, Array, Dynamic, EvalAltResult, FnPtr, ImmutableString, NativeCallContext, - Position, INT, -}; - -#[cfg(not(feature = "no_object"))] -use crate::Map; - -pub type Unit = (); - -macro_rules! gen_array_functions { - ($root:ident => $($arg_type:ident),+ ) => { - pub mod $root { $( pub mod $arg_type { - use super::super::*; - - #[export_module] - pub mod functions { - #[rhai_fn(name = "push", name = "+=")] - pub fn push(array: &mut Array, item: $arg_type) { - array.push(Dynamic::from(item)); - } - - pub fn insert(array: &mut Array, position: INT, item: $arg_type) { - if position <= 0 { - array.insert(0, Dynamic::from(item)); - } else if (position as usize) >= array.len() { - push(array, item); - } else { - array.insert(position as usize, Dynamic::from(item)); - } - } - - #[rhai_fn(return_raw)] - pub fn pad(_ctx: NativeCallContext, array: &mut Array, len: INT, item: $arg_type) -> Result> { - // Check if array will be over max size limit - #[cfg(not(feature = "unchecked"))] - if _ctx.engine().max_array_size() > 0 && len > 0 && (len as usize) > _ctx.engine().max_array_size() { - return EvalAltResult::ErrorDataTooLarge( - "Size of array".to_string(), Position::NONE - ).into(); - } - - if len > 0 && len as usize > array.len() { - array.resize(len as usize, Dynamic::from(item)); - } - - Ok(Dynamic::UNIT) - } - } - })* } - } -} - -macro_rules! reg_functions { - ($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $( - combine_with_exported_module!($mod_name, "array_functions", $root::$arg_type::functions); - )* } -} +use crate::stdlib::{any::TypeId, boxed::Box, cmp::max, cmp::Ordering, mem, string::ToString}; +use crate::{def_package, Array, Dynamic, EvalAltResult, FnPtr, NativeCallContext, Position, INT}; def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { - reg_functions!(lib += basic; INT, bool, char, ImmutableString, FnPtr, Array, Unit); - - #[cfg(not(feature = "only_i32"))] - #[cfg(not(feature = "only_i64"))] - { - reg_functions!(lib += numbers; i8, u8, i16, u16, i32, i64, u32, u64); - - #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] - reg_functions!(lib += num_128; i128, u128); - } - - #[cfg(not(feature = "no_float"))] - reg_functions!(lib += float; f32, f64); - - #[cfg(not(feature = "no_object"))] - reg_functions!(lib += map; Map); - - // Merge in the module at the end to override `+=` for arrays combine_with_exported_module!(lib, "array", array_functions); // Register array iterator @@ -94,6 +19,10 @@ mod array_functions { pub fn len(array: &mut Array) -> INT { array.len() as INT } + #[rhai_fn(name = "push", name = "+=")] + pub fn push(array: &mut Array, item: Dynamic) { + array.push(item); + } #[rhai_fn(name = "append", name = "+=")] pub fn append(array: &mut Array, y: Array) { array.extend(y); @@ -103,6 +32,38 @@ mod array_functions { array.extend(y); array } + pub fn insert(array: &mut Array, position: INT, item: Dynamic) { + if position <= 0 { + array.insert(0, item); + } else if (position as usize) >= array.len() { + push(array, item); + } else { + array.insert(position as usize, item); + } + } + #[rhai_fn(return_raw)] + pub fn pad( + _ctx: NativeCallContext, + array: &mut Array, + len: INT, + item: Dynamic, + ) -> Result> { + // Check if array will be over max size limit + #[cfg(not(feature = "unchecked"))] + if _ctx.engine().max_array_size() > 0 + && len > 0 + && (len as usize) > _ctx.engine().max_array_size() + { + return EvalAltResult::ErrorDataTooLarge("Size of array".to_string(), Position::NONE) + .into(); + } + + if len > 0 && len as usize > array.len() { + array.resize(len as usize, item); + } + + Ok(Dynamic::UNIT) + } pub fn pop(array: &mut Array) -> Dynamic { array.pop().unwrap_or_else(|| ().into()) } @@ -191,6 +152,18 @@ mod array_functions { array[start..].iter().cloned().collect() } + #[rhai_fn(name = "split")] + pub fn split_at(array: &mut Array, start: INT) -> Array { + if start <= 0 { + mem::take(array) + } else if start as usize >= array.len() { + Default::default() + } else { + let mut result: Array = Default::default(); + result.extend(array.drain(start as usize..)); + result + } + } #[rhai_fn(return_raw)] pub fn map( ctx: NativeCallContext, @@ -697,20 +670,3 @@ mod array_functions { equals(ctx, array, array2).map(|r| (!r.as_bool().unwrap()).into()) } } - -gen_array_functions!(basic => INT, bool, char, ImmutableString, FnPtr, Array, Unit); - -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] -gen_array_functions!(numbers => i8, u8, i16, u16, i32, i64, u32, u64); - -#[cfg(not(feature = "only_i32"))] -#[cfg(not(feature = "only_i64"))] -#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] -gen_array_functions!(num_128 => i128, u128); - -#[cfg(not(feature = "no_float"))] -gen_array_functions!(float => f32, f64); - -#[cfg(not(feature = "no_object"))] -gen_array_functions!(map => Map); diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 65032da2..11357d88 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -360,7 +360,8 @@ mod string_functions { #[cfg(not(feature = "no_index"))] pub mod arrays { - use crate::Array; + use crate::stdlib::vec; + use crate::{Array, ImmutableString}; #[rhai_fn(name = "+")] pub fn append(string: &str, array: Array) -> String { @@ -370,13 +371,73 @@ mod string_functions { pub fn prepend(array: &mut Array, string: &str) -> String { format!("{:?}{}", array, string) } + #[rhai_fn(name = "split")] + pub fn chars(string: &str) -> Array { + string.chars().map(Into::::into).collect() + } + #[rhai_fn(name = "split")] + pub fn split_at(string: ImmutableString, start: INT) -> Array { + if start <= 0 { + vec!["".into(), string.into()] + } else { + let prefix: String = string.chars().take(start as usize).collect(); + let prefix_len = prefix.len(); + vec![prefix.into(), string[prefix_len..].into()] + } + } pub fn split(string: &str, delimiter: &str) -> Array { string.split(delimiter).map(Into::::into).collect() } #[rhai_fn(name = "split")] + pub fn splitn(string: &str, delimiter: &str, segments: INT) -> Array { + let pieces: usize = if segments < 1 { 1 } else { segments as usize }; + string + .splitn(pieces, delimiter) + .map(Into::::into) + .collect() + } + #[rhai_fn(name = "split")] pub fn split_char(string: &str, delimiter: char) -> Array { string.split(delimiter).map(Into::::into).collect() } + #[rhai_fn(name = "split")] + pub fn splitn_char(string: &str, delimiter: char, segments: INT) -> Array { + let pieces: usize = if segments < 1 { 1 } else { segments as usize }; + string + .splitn(pieces, delimiter) + .map(Into::::into) + .collect() + } + #[rhai_fn(name = "split_rev")] + pub fn rsplit(string: &str, delimiter: &str) -> Array { + string + .rsplit(delimiter) + .map(Into::::into) + .collect() + } + #[rhai_fn(name = "split_rev")] + pub fn rsplitn(string: &str, delimiter: &str, segments: INT) -> Array { + let pieces: usize = if segments < 1 { 1 } else { segments as usize }; + string + .rsplitn(pieces, delimiter) + .map(Into::::into) + .collect() + } + #[rhai_fn(name = "split_rev")] + pub fn rsplit_char(string: &str, delimiter: char) -> Array { + string + .rsplit(delimiter) + .map(Into::::into) + .collect() + } + #[rhai_fn(name = "split_rev")] + pub fn rsplitn_char(string: &str, delimiter: char, segments: INT) -> Array { + let pieces: usize = if segments < 1 { 1 } else { segments as usize }; + string + .rsplitn(pieces, delimiter) + .map(Into::::into) + .collect() + } } #[cfg(not(feature = "no_object"))]