Merge pull request #358 from schungx/master
Support for Dynamic arguments.
This commit is contained in:
commit
42a65ef3d0
10
RELEASES.md
10
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
|
||||
|
@ -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::<Dynamic>().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::<Dynamic>().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::<Dynamic>().unwrap() = value;
|
||||
} else {
|
||||
*lhs_ptr.as_mut() = value;
|
||||
}
|
||||
*args[0] = value.flatten();
|
||||
}
|
||||
err => return err.map(|(v, _)| v),
|
||||
}
|
||||
Ok(Dynamic::UNIT)
|
||||
}
|
||||
|
@ -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::<Dynamic>()
|
||||
} 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()))
|
||||
|
@ -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<Dynamic, Box<EvalAltResult>> {
|
||||
// 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<Dynamic, Box<EvalAltResult>> {
|
||||
// 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);
|
||||
|
@ -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::<Dynamic>::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::<Dynamic>::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::<Dynamic>::into)
|
||||
.collect()
|
||||
}
|
||||
#[rhai_fn(name = "split")]
|
||||
pub fn split_char(string: &str, delimiter: char) -> Array {
|
||||
string.split(delimiter).map(Into::<Dynamic>::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::<Dynamic>::into)
|
||||
.collect()
|
||||
}
|
||||
#[rhai_fn(name = "split_rev")]
|
||||
pub fn rsplit(string: &str, delimiter: &str) -> Array {
|
||||
string
|
||||
.rsplit(delimiter)
|
||||
.map(Into::<Dynamic>::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::<Dynamic>::into)
|
||||
.collect()
|
||||
}
|
||||
#[rhai_fn(name = "split_rev")]
|
||||
pub fn rsplit_char(string: &str, delimiter: char) -> Array {
|
||||
string
|
||||
.rsplit(delimiter)
|
||||
.map(Into::<Dynamic>::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::<Dynamic>::into)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
|
Loading…
Reference in New Issue
Block a user