Add support for Dynamic wildcard arguments.

This commit is contained in:
Stephen Chung 2021-02-23 16:06:36 +08:00
parent 8248c9999d
commit ba2b0630f7
4 changed files with 110 additions and 173 deletions

View File

@ -19,6 +19,7 @@ Breaking changes
New features 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. * `#[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. * Comparisons between `FLOAT`/[`Decimal`](https://crates.io/crates/rust_decimal) and `INT` are now built in.

View File

@ -2,7 +2,6 @@
use crate::ast::{Expr, FnCallExpr, Ident, ReturnType, Stmt}; use crate::ast::{Expr, FnCallExpr, Ident, ReturnType, Stmt};
use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant}; use crate::dynamic::{map_std_type_name, AccessMode, Union, Variant};
use crate::fn_call::run_builtin_op_assignment;
use crate::fn_native::{ use crate::fn_native::{
CallableFunction, IteratorFn, OnDebugCallback, OnPrintCallback, OnProgressCallback, CallableFunction, IteratorFn, OnDebugCallback, OnPrintCallback, OnProgressCallback,
OnVarCallback, OnVarCallback,
@ -18,7 +17,7 @@ use crate::stdlib::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
fmt, format, fmt, format,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
iter::{empty, once, FromIterator}, iter::{empty, FromIterator},
num::{NonZeroU64, NonZeroU8, NonZeroUsize}, num::{NonZeroU64, NonZeroU8, NonZeroUsize},
ops::DerefMut, ops::DerefMut,
string::{String, ToString}, string::{String, ToString},
@ -2004,28 +2003,6 @@ impl Engine {
} }
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} else { } else {
// Op-assignment - in order of precedence:
// 1) Native registered overriding function
// 2) Built-in implementation
// 3) Map to `var = var op rhs`
// 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();
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 mut lock_guard;
let lhs_ptr_inner; let lhs_ptr_inner;
@ -2038,43 +2015,25 @@ impl Engine {
let args = &mut [lhs_ptr_inner, &mut rhs_val]; let args = &mut [lhs_ptr_inner, &mut rhs_val];
// Overriding exact implementation match self.exec_fn_call(
let source = mods, state, lib, op, None, args, true, false, false, *op_pos, None, None,
source.or_else(|| state.source.as_ref()).map(|s| s.as_str()); level,
if func.is_plugin_fn() { ) {
func.get_plugin_fn() Ok(_) => (),
.call((self, op.as_ref(), source, &*mods, lib).into(), args)?; Err(err) if matches!(err.as_ref(), EvalAltResult::ErrorFunctionNotFound(f, _) if f.starts_with(op.as_ref())) =>
} else { {
func.get_native_fn()( // Expand to `var = var op rhs`
(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`
_ => {
let op = &op[..op.len() - 1]; // extract operator without = 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 // Run function
let (value, _) = self.exec_fn_call( let (value, _) = self.exec_fn_call(
mods, state, lib, op, None, args, false, false, false, *op_pos, mods, state, lib, op, None, args, false, false, false, *op_pos,
None, None, level, None, None, level,
)?; )?;
let value = value.flatten(); *args[0] = 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;
}
} }
err => return err.map(|(v, _)| v),
} }
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} }

View File

@ -185,26 +185,50 @@ impl Engine {
.fn_resolution_cache_mut() .fn_resolution_cache_mut()
.entry(hash_fn) .entry(hash_fn)
.or_insert_with(|| { .or_insert_with(|| {
// Search for the native function let num_args = args.len();
// First search registered functions (can override packages) let max_bitmask = 1usize << num_args;
// Then search packages let mut hash = hash_fn;
// Finally search modules let mut bitmask = 1usize;
//lib.get_fn(hash_fn, pub_only) loop {
self.global_namespace //lib.get_fn(hash, pub_only).or_else(||
.get_fn(hash_fn, pub_only) match self
.global_namespace
.get_fn(hash, pub_only)
.cloned() .cloned()
.map(|f| (f, None)) .map(|f| (f, None))
.or_else(|| { .or_else(|| {
self.global_modules.iter().find_map(|m| { self.global_modules.iter().find_map(|m| {
m.get_fn(hash_fn, false) m.get_fn(hash, false)
.map(|f| (f.clone(), m.id_raw().cloned())) .map(|f| (f.clone(), m.id_raw().cloned()))
}) })
}) })
.or_else(|| { .or_else(|| {
mods.get_fn(hash_fn) mods.get_fn(hash)
.map(|(f, source)| (f.clone(), source.cloned())) .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 { if let Some((func, src)) = func {
@ -257,6 +281,15 @@ impl Engine {
// See if it is built in. // See if it is built in.
if args.len() == 2 { 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])? { match run_builtin_binary_op(fn_name, args[0], args[1])? {
Some(v) => return Ok((v, false)), Some(v) => return Ok((v, false)),
None => (), None => (),
@ -642,7 +675,7 @@ impl Engine {
.map(|f| (f.clone(), m.id_raw().cloned())) .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() .as_ref()
.map(|(f, s)| (Some(f.clone()), s.clone())) .map(|(f, s)| (Some(f.clone()), s.clone()))

View File

@ -4,84 +4,9 @@
use crate::engine::{OP_EQUALS, TYPICAL_ARRAY_SIZE}; use crate::engine::{OP_EQUALS, TYPICAL_ARRAY_SIZE};
use crate::plugin::*; use crate::plugin::*;
use crate::stdlib::{any::TypeId, boxed::Box, cmp::max, cmp::Ordering, mem, string::ToString}; use crate::stdlib::{any::TypeId, boxed::Box, cmp::max, cmp::Ordering, mem, string::ToString};
use crate::{ use crate::{def_package, Array, Dynamic, EvalAltResult, FnPtr, NativeCallContext, Position, INT};
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);
)* }
}
def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { 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); combine_with_exported_module!(lib, "array", array_functions);
// Register array iterator // Register array iterator
@ -94,6 +19,10 @@ mod array_functions {
pub fn len(array: &mut Array) -> INT { pub fn len(array: &mut Array) -> INT {
array.len() as 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 = "+=")] #[rhai_fn(name = "append", name = "+=")]
pub fn append(array: &mut Array, y: Array) { pub fn append(array: &mut Array, y: Array) {
array.extend(y); array.extend(y);
@ -103,6 +32,38 @@ mod array_functions {
array.extend(y); array.extend(y);
array 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 { pub fn pop(array: &mut Array) -> Dynamic {
array.pop().unwrap_or_else(|| ().into()) array.pop().unwrap_or_else(|| ().into())
} }
@ -709,20 +670,3 @@ mod array_functions {
equals(ctx, array, array2).map(|r| (!r.as_bool().unwrap()).into()) 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);