Merge pull request #363 from schungx/master

Allow evaluating AST for Engine::call_fn_dynamic
This commit is contained in:
Stephen Chung 2021-02-26 11:37:31 +08:00 committed by GitHub
commit 1fb63c32a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 820 additions and 566 deletions

View File

@ -14,14 +14,15 @@ 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.
* Comparison operators between the sames types or different _numeric_ types now throw errors when they're not defined instead of returning the default. Only comparing between _different_ types will return the default.
* Default stack-overflow and top-level expression nesting limits for release builds are lowered to 64 from 128.
* `Engine::call_fn_dynamic` takes an additional parameter to optionally evaluate the given `AST` before calling the function.
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.
Enhancements
------------
@ -30,8 +31,12 @@ 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 `-`/`-=` operators and the `remove` method to delete a sub-string/character.
* Strings now have the `split_rev` method and variations of `split` with maximum number of segments.
* Arrays now have the `split` method.
* Comparisons between `FLOAT`/[`Decimal`](https://crates.io/crates/rust_decimal) and `INT` are now built in.
* Comparisons between string and `char` are now built in.
* `Engine::call_fn_dynamic` can now optionally evaluate the given `AST` before calling the function.
Version 0.19.12

View File

@ -126,7 +126,7 @@ fn bench_eval_loop_number(bench: &mut Bencher) {
#[bench]
fn bench_eval_loop_strings_build(bench: &mut Bencher) {
let script = r#"
let s = 0;
let s = "hello";
for x in range(0, 10000) {
s += "x";
}

View File

@ -1,5 +1,10 @@
Procedural Macros for Plugins
=============================
![Rhai logo](https://rhai.rs/book/images/logo/rhai-banner-transparent-colour.svg)
This crate holds procedural macros for code generation, supporting the plugins system
for [Rhai](https://github.com/rhaiscript/rhai).
This crate is automatically referenced by the [Rhai crate](https://crates.io/crates/rhai).
Normally it should not be used directly.

View File

@ -1112,8 +1112,6 @@ pub struct FnCallExpr {
pub hash_script: Option<NonZeroU64>,
/// Does this function call capture the parent scope?
pub capture: bool,
/// Default value when the function is not found, mostly used to provide a default for comparison functions.
pub def_value: Option<Dynamic>,
/// Namespace of the function, if any. Boxed because it occurs rarely.
pub namespace: Option<NamespaceRef>,
/// Function name.

View File

@ -475,7 +475,45 @@ impl fmt::Display for Dynamic {
#[cfg(not(feature = "no_std"))]
Union::TimeStamp(_, _) => f.write_str("<timestamp>"),
Union::Variant(value, _) => f.write_str((*value).type_name()),
Union::Variant(value, _) => {
let _type_id = (***value).type_id();
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
if _type_id == TypeId::of::<u8>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<u8>().unwrap());
} else if _type_id == TypeId::of::<u16>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<u16>().unwrap());
} else if _type_id == TypeId::of::<u32>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<u32>().unwrap());
} else if _type_id == TypeId::of::<u64>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<u64>().unwrap());
} else if _type_id == TypeId::of::<i8>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<i8>().unwrap());
} else if _type_id == TypeId::of::<i16>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<i16>().unwrap());
} else if _type_id == TypeId::of::<i32>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<i32>().unwrap());
} else if _type_id == TypeId::of::<i64>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<i64>().unwrap());
}
#[cfg(not(feature = "no_float"))]
if _type_id == TypeId::of::<f32>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<f32>().unwrap());
} else if _type_id == TypeId::of::<f64>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<f64>().unwrap());
}
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
if _type_id == TypeId::of::<u128>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<u128>().unwrap());
} else if _type_id == TypeId::of::<i128>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<i128>().unwrap());
}
f.write_str((***value).type_name())
}
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))]
@ -516,7 +554,53 @@ impl fmt::Debug for Dynamic {
#[cfg(not(feature = "no_std"))]
Union::TimeStamp(_, _) => write!(f, "<timestamp>"),
Union::Variant(value, _) => write!(f, "{}", (*value).type_name()),
Union::Variant(value, _) => {
let _type_id = (***value).type_id();
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
if _type_id == TypeId::of::<u8>() {
return write!(f, "{:?}", (**value).as_any().downcast_ref::<u8>().unwrap());
} else if _type_id == TypeId::of::<u16>() {
return write!(f, "{:?}", (**value).as_any().downcast_ref::<u16>().unwrap());
} else if _type_id == TypeId::of::<u32>() {
return write!(f, "{:?}", (**value).as_any().downcast_ref::<u32>().unwrap());
} else if _type_id == TypeId::of::<u64>() {
return write!(f, "{:?}", (**value).as_any().downcast_ref::<u64>().unwrap());
} else if _type_id == TypeId::of::<i8>() {
return write!(f, "{:?}", (**value).as_any().downcast_ref::<i8>().unwrap());
} else if _type_id == TypeId::of::<i16>() {
return write!(f, "{:?}", (**value).as_any().downcast_ref::<i16>().unwrap());
} else if _type_id == TypeId::of::<i32>() {
return write!(f, "{:?}", (**value).as_any().downcast_ref::<i32>().unwrap());
} else if _type_id == TypeId::of::<i64>() {
return write!(f, "{:?}", (**value).as_any().downcast_ref::<i64>().unwrap());
}
#[cfg(not(feature = "no_float"))]
if _type_id == TypeId::of::<f32>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<f32>().unwrap());
} else if _type_id == TypeId::of::<f64>() {
return write!(f, "{}", (**value).as_any().downcast_ref::<f64>().unwrap());
}
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
if _type_id == TypeId::of::<u128>() {
return write!(
f,
"{:?}",
(**value).as_any().downcast_ref::<u128>().unwrap()
);
} else if _type_id == TypeId::of::<i128>() {
return write!(
f,
"{:?}",
(**value).as_any().downcast_ref::<i128>().unwrap()
);
}
write!(f, "{}", (*value).type_name())
}
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))]

View File

@ -193,6 +193,8 @@ pub const MAX_EXPR_DEPTH: usize = 64;
#[cfg(not(debug_assertions))]
pub const MAX_FUNCTION_EXPR_DEPTH: usize = 32;
pub const MAX_DYNAMIC_PARAMETERS: usize = 16;
pub const KEYWORD_PRINT: &str = "print";
pub const KEYWORD_DEBUG: &str = "debug";
pub const KEYWORD_TYPE_OF: &str = "type_of";
@ -1088,7 +1090,7 @@ impl Engine {
idx_values: &mut StaticVec<ChainArgument>,
chain_type: ChainType,
level: usize,
new_val: Option<(Dynamic, Position)>,
new_val: Option<((Dynamic, Position), (&str, Position))>,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
if chain_type == ChainType::NonChaining {
unreachable!("should not be ChainType::NonChaining");
@ -1128,7 +1130,7 @@ impl Engine {
)
.map_err(|err| err.fill_position(*x_pos))
}
// xxx[rhs] = new_val
// xxx[rhs] op= new_val
_ if new_val.is_some() => {
let idx_val = idx_val.as_index_value();
let mut idx_val2 = idx_val.clone();
@ -1138,9 +1140,11 @@ impl Engine {
mods, state, lib, target_val, idx_val, pos, true, is_ref, false, level,
) {
// Indexed value is a reference - update directly
Ok(ref mut obj_ptr) => {
let (new_val, new_val_pos) = new_val.unwrap();
obj_ptr.set_value(new_val, new_val_pos)?;
Ok(obj_ptr) => {
let ((new_val, new_pos), (op, op_pos)) = new_val.unwrap();
self.eval_op_assignment(
mods, state, lib, op, op_pos, obj_ptr, new_val, new_pos,
)?;
None
}
Err(err) => match *err {
@ -1155,11 +1159,13 @@ impl Engine {
#[cfg(not(feature = "no_index"))]
if let Some(mut new_val) = _call_setter {
let val_type_name = target_val.type_name();
let args = &mut [target_val, &mut idx_val2, &mut new_val.0];
let ((_, val_pos), _) = new_val;
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,
new_val.1, None, None, level,
val_pos, None, level,
)
.map_err(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(fn_sig, _)
@ -1195,14 +1201,11 @@ impl Engine {
let FnCallExpr {
name,
hash_script: hash,
def_value,
..
} = x.as_ref();
let def_value = def_value.as_ref();
let args = idx_val.as_fn_call_args();
self.make_method_call(
mods, state, lib, name, *hash, target, args, def_value, false, *pos,
level,
mods, state, lib, name, *hash, target, args, false, *pos, level,
)
}
// xxx.fn_name(...) = ???
@ -1213,18 +1216,18 @@ impl Engine {
Expr::FnCall(_, _) => {
unreachable!("function call in dot chain should not be namespace-qualified")
}
// {xxx:map}.id = ???
// {xxx:map}.id op= ???
Expr::Property(x) if target_val.is::<Map>() && new_val.is_some() => {
let Ident { name, pos } = &x.2;
let index = name.clone().into();
let mut val = self.get_indexed_mut(
let val = self.get_indexed_mut(
mods, state, lib, target_val, index, *pos, true, is_ref, false, level,
)?;
let (new_val, new_val_pos) = new_val.unwrap();
val.set_value(new_val, new_val_pos)?;
Ok((Default::default(), true))
let ((new_val, new_pos), (op, op_pos)) = new_val.unwrap();
self.eval_op_assignment(
mods, state, lib, op, op_pos, val, new_val, new_pos,
)?;
Ok((Dynamic::UNIT, true))
}
// {xxx:map}.id
Expr::Property(x) if target_val.is::<Map>() => {
@ -1240,10 +1243,10 @@ impl Engine {
Expr::Property(x) if new_val.is_some() => {
let (_, setter, Ident { pos, .. }) = x.as_ref();
let mut new_val = new_val;
let mut args = [target_val, &mut new_val.as_mut().unwrap().0];
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, None, level,
None, level,
)
.map(|(v, _)| (v, true))
}
@ -1253,7 +1256,7 @@ impl Engine {
let mut args = [target_val];
self.exec_fn_call(
mods, state, lib, getter, None, &mut args, is_ref, true, false, *pos,
None, None, level,
None, level,
)
.map(|(v, _)| (v, false))
}
@ -1273,14 +1276,11 @@ impl Engine {
let FnCallExpr {
name,
hash_script: hash,
def_value,
..
} = x.as_ref();
let def_value = def_value.as_ref();
let args = idx_val.as_fn_call_args();
let (val, _) = self.make_method_call(
mods, state, lib, name, *hash, target, args, def_value, false,
*pos, level,
mods, state, lib, name, *hash, target, args, false, *pos, level,
)?;
val.into()
}
@ -1308,7 +1308,7 @@ impl Engine {
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, None, level,
*pos, None, level,
)?;
let val = &mut val;
@ -1334,7 +1334,7 @@ impl Engine {
arg_values[1] = val;
self.exec_fn_call(
mods, state, lib, setter, None, arg_values, is_ref, true,
false, *pos, None, None, level,
false, *pos, None, level,
)
.or_else(
|err| match *err {
@ -1355,14 +1355,11 @@ impl Engine {
let FnCallExpr {
name,
hash_script: hash,
def_value,
..
} = f.as_ref();
let def_value = def_value.as_ref();
let args = idx_val.as_fn_call_args();
let (mut val, _) = self.make_method_call(
mods, state, lib, name, *hash, target, args, def_value, false,
*pos, level,
mods, state, lib, name, *hash, target, args, false, *pos, level,
)?;
let val = &mut val;
let target = &mut val.into();
@ -1401,7 +1398,7 @@ impl Engine {
this_ptr: &mut Option<&mut Dynamic>,
expr: &Expr,
level: usize,
new_val: Option<(Dynamic, Position)>,
new_val: Option<((Dynamic, Position), (&str, Position))>,
) -> Result<Dynamic, Box<EvalAltResult>> {
let (crate::ast::BinaryExpr { lhs, rhs }, chain_type, op_pos) = match expr {
Expr::Index(x, pos) => (x.as_ref(), ChainType::Index, *pos),
@ -1633,7 +1630,7 @@ impl Engine {
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, None, _level,
None, _level,
)
.map(|(v, _)| v.into())
.map_err(|err| match *err {
@ -1676,23 +1673,16 @@ impl Engine {
#[cfg(not(feature = "no_index"))]
Dynamic(Union::Array(mut rhs_value, _)) => {
// Call the `==` operator to compare each value
let def_value = Some(false.into());
let def_value = def_value.as_ref();
for value in rhs_value.iter_mut() {
let args = &mut [&mut lhs_value.clone(), value];
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
let hash_fn =
calc_native_fn_hash(empty(), OP_EQUALS, args.iter().map(|a| a.type_id()))
.unwrap();
let pos = rhs.position();
if self
.call_native_fn(
mods, state, lib, OP_EQUALS, hash_fn, args, false, false, pos,
def_value,
)?
.0
.as_bool()
@ -1798,13 +1788,11 @@ impl Engine {
capture: cap_scope,
hash_script: hash,
args,
def_value,
..
} = x.as_ref();
let def_value = def_value.as_ref();
self.make_function_call(
scope, mods, state, lib, this_ptr, name, args, def_value, *hash, false, *pos,
*cap_scope, level,
scope, mods, state, lib, this_ptr, name, args, *hash, false, *pos, *cap_scope,
level,
)
}
@ -1815,15 +1803,12 @@ impl Engine {
namespace,
hash_script,
args,
def_value,
..
} = x.as_ref();
let namespace = namespace.as_ref();
let hash = hash_script.unwrap();
let def_value = def_value.as_ref();
self.make_qualified_function_call(
scope, mods, state, lib, this_ptr, namespace, name, args, def_value, hash,
*pos, level,
scope, mods, state, lib, this_ptr, namespace, name, args, hash, *pos, level,
)
}
@ -1944,6 +1929,62 @@ impl Engine {
result
}
pub(crate) fn eval_op_assignment(
&self,
mods: &mut Imports,
state: &mut State,
lib: &[&Module],
op: &str,
op_pos: Position,
mut target: Target,
mut new_value: Dynamic,
new_value_pos: Position,
) -> Result<(), Box<EvalAltResult>> {
if target.as_ref().is_read_only() {
unreachable!("LHS should not be read-only");
}
if op.is_empty() {
// Normal assignment
target.set_value(new_value, new_value_pos)?;
Ok(())
} else {
let mut lock_guard;
let lhs_ptr_inner;
if cfg!(not(feature = "no_closure")) && target.is_shared() {
lock_guard = target.as_mut().write_lock::<Dynamic>().unwrap();
lhs_ptr_inner = lock_guard.deref_mut();
} else {
lhs_ptr_inner = target.as_mut();
}
let args = &mut [lhs_ptr_inner, &mut new_value];
let hash_fn =
calc_native_fn_hash(empty(), op, args.iter().map(|a| a.type_id())).unwrap();
match self.call_native_fn(mods, state, lib, op, hash_fn, args, true, true, op_pos) {
Ok(_) => (),
Err(err) if matches!(err.as_ref(), EvalAltResult::ErrorFunctionNotFound(f, _) if f.starts_with(op)) =>
{
// Expand to `var = var op rhs`
let op = &op[..op.len() - 1]; // extract operator without =
let hash_fn =
calc_native_fn_hash(empty(), op, args.iter().map(|a| a.type_id())).unwrap();
// Run function
let (value, _) = self
.call_native_fn(mods, state, lib, op, hash_fn, args, true, false, op_pos)?;
*args[0] = value.flatten();
}
err => return err.map(|_| ()),
}
Ok(())
}
}
/// Evaluate a statement.
///
/// # Safety
@ -1972,10 +2013,10 @@ impl Engine {
// var op= rhs
Stmt::Assignment(x, op_pos) if x.0.get_variable_access(false).is_some() => {
let (lhs_expr, op, rhs_expr) = x.as_ref();
let mut rhs_val = self
let rhs_val = self
.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?
.flatten();
let (mut lhs_ptr, pos) =
let (lhs_ptr, pos) =
self.search_namespace(scope, mods, state, lib, this_ptr, lhs_expr)?;
if !lhs_ptr.is_ref() {
@ -1994,47 +2035,17 @@ impl Engine {
lhs_expr.get_variable_access(false).unwrap().to_string(),
pos,
)))
} else if op.is_empty() {
// Normal assignment
if cfg!(not(feature = "no_closure")) && lhs_ptr.is_shared() {
*lhs_ptr.as_mut().write_lock::<Dynamic>().unwrap() = rhs_val;
} else {
*lhs_ptr.as_mut() = rhs_val;
}
Ok(Dynamic::UNIT)
} else {
let mut lock_guard;
let lhs_ptr_inner;
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];
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 =
// Run function
let (value, _) = self.exec_fn_call(
mods, state, lib, op, None, args, false, false, false, *op_pos,
None, None, level,
)?;
*args[0] = value.flatten();
}
err => return err.map(|(v, _)| v),
}
self.eval_op_assignment(
mods,
state,
lib,
op,
*op_pos,
lhs_ptr,
rhs_val,
rhs_expr.position(),
)?;
Ok(Dynamic::UNIT)
}
}
@ -2042,28 +2053,8 @@ impl Engine {
// lhs op= rhs
Stmt::Assignment(x, op_pos) => {
let (lhs_expr, op, rhs_expr) = x.as_ref();
let mut rhs_val =
self.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?;
let _new_val = if op.is_empty() {
// Normal assignment
Some((rhs_val, rhs_expr.position()))
} else {
// Op-assignment - always map to `lhs = lhs op rhs`
let op = &op[..op.len() - 1]; // extract operator without =
let args = &mut [
&mut self.eval_expr(scope, mods, state, lib, this_ptr, lhs_expr, level)?,
&mut rhs_val,
];
Some(
self.exec_fn_call(
mods, state, lib, op, None, args, false, false, false, *op_pos, None,
None, level,
)
.map(|(v, _)| (v, rhs_expr.position()))?,
)
};
let rhs_val = self.eval_expr(scope, mods, state, lib, this_ptr, rhs_expr, level)?;
let _new_val = Some(((rhs_val, rhs_expr.position()), (op.as_ref(), *op_pos)));
// Must be either `var[index] op= val` or `var.prop op= val`
match lhs_expr {
@ -2582,9 +2573,12 @@ impl Engine {
Ok(())
}
/// Map a type_name into a pretty-print name
/// Pretty-print a type name.
///
/// If a type is registered via [`register_type_with_name`][Engine::register_type_with_name],
/// the type name provided for the registration will be used.
#[inline(always)]
pub(crate) fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
pub fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str {
self.type_names
.get(name)
.map(String::as_str)

View File

@ -1613,6 +1613,13 @@ impl Engine {
/// Call a script function defined in an [`AST`] with multiple arguments.
/// Arguments are passed as a tuple.
///
/// ## Warning
///
/// The [`AST`] is _not_ evaluated before calling the function. The function is called as-is.
///
/// If the [`AST`] needs to be evaluated before calling the function (usually to load external modules),
/// use [`call_fn_dynamic`][Engine::call_fn_dynamic].
///
/// # Example
///
/// ```
@ -1633,11 +1640,11 @@ impl Engine {
/// scope.push("foo", 42_i64);
///
/// // Call the script-defined function
/// let result: i64 = engine.call_fn(&mut scope, &ast, "add", ( String::from("abc"), 123_i64 ) )?;
/// let result: i64 = engine.call_fn(&mut scope, &ast, "add", ( "abc", 123_i64 ) )?;
/// assert_eq!(result, 168);
///
/// let result: i64 = engine.call_fn(&mut scope, &ast, "add1", ( String::from("abc"), ) )?;
/// // ^^^^^^^^^^^^^^^^^^^^^^^^ tuple of one
/// let result: i64 = engine.call_fn(&mut scope, &ast, "add1", ( "abc", ) )?;
/// // ^^^^^^^^^^ tuple of one
/// assert_eq!(result, 46);
///
/// let result: i64 = engine.call_fn(&mut scope, &ast, "bar", () )?;
@ -1659,8 +1666,7 @@ impl Engine {
args.parse(&mut arg_values);
let mut args: crate::StaticVec<_> = arg_values.as_mut().iter_mut().collect();
let result =
self.call_fn_dynamic_raw(scope, &[ast.lib()], name, &mut None, args.as_mut())?;
let result = self.call_fn_dynamic_raw(scope, ast, false, name, &mut None, args.as_mut())?;
let typ = self.map_type_name(result.type_name());
@ -1676,6 +1682,9 @@ impl Engine {
/// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments
/// and optionally a value for binding to the `this` pointer.
///
/// There is also an option to evaluate the [`AST`] (e.g. to configuration the environment)
/// before calling the function.
///
/// # WARNING
///
/// All the arguments are _consumed_, meaning that they're replaced by `()`.
@ -1704,19 +1713,19 @@ impl Engine {
/// scope.push("foo", 42_i64);
///
/// // Call the script-defined function
/// let result = engine.call_fn_dynamic(&mut scope, &ast, "add", None, [ String::from("abc").into(), 123_i64.into() ])?;
/// // ^^^^ no 'this' pointer
/// let result = engine.call_fn_dynamic(&mut scope, &ast, false, "add", None, [ "abc".into(), 123_i64.into() ])?;
/// // ^^^^ no 'this' pointer
/// assert_eq!(result.cast::<i64>(), 168);
///
/// let result = engine.call_fn_dynamic(&mut scope, &ast, "add1", None, [ String::from("abc").into() ])?;
/// let result = engine.call_fn_dynamic(&mut scope, &ast, false, "add1", None, [ "abc".into() ])?;
/// assert_eq!(result.cast::<i64>(), 46);
///
/// let result = engine.call_fn_dynamic(&mut scope, &ast, "bar", None, [])?;
/// let result = engine.call_fn_dynamic(&mut scope, &ast, false, "bar", None, [])?;
/// assert_eq!(result.cast::<i64>(), 21);
///
/// let mut value: Dynamic = 1_i64.into();
/// let result = engine.call_fn_dynamic(&mut scope, &ast, "action", Some(&mut value), [ 41_i64.into() ])?;
/// // ^^^^^^^^^^^^^^^^ binding the 'this' pointer
/// let result = engine.call_fn_dynamic(&mut scope, &ast, false, "action", Some(&mut value), [ 41_i64.into() ])?;
/// // ^^^^^^^^^^^^^^^^ binding the 'this' pointer
/// assert_eq!(value.as_int().unwrap(), 42);
/// # }
/// # Ok(())
@ -1727,14 +1736,15 @@ impl Engine {
pub fn call_fn_dynamic(
&self,
scope: &mut Scope,
lib: impl AsRef<crate::Module>,
ast: &AST,
eval_ast: bool,
name: &str,
mut this_ptr: Option<&mut Dynamic>,
mut arg_values: impl AsMut<[Dynamic]>,
) -> Result<Dynamic, Box<EvalAltResult>> {
let mut args: crate::StaticVec<_> = arg_values.as_mut().iter_mut().collect();
self.call_fn_dynamic_raw(scope, &[lib.as_ref()], name, &mut this_ptr, args.as_mut())
self.call_fn_dynamic_raw(scope, ast, eval_ast, name, &mut this_ptr, args.as_mut())
}
/// Call a script function defined in an [`AST`] with multiple [`Dynamic`] arguments.
///
@ -1749,18 +1759,24 @@ impl Engine {
pub(crate) fn call_fn_dynamic_raw(
&self,
scope: &mut Scope,
lib: &[&crate::Module],
ast: &AST,
eval_ast: bool,
name: &str,
this_ptr: &mut Option<&mut Dynamic>,
args: &mut FnCallArgs,
) -> Result<Dynamic, Box<EvalAltResult>> {
let fn_def = lib
.iter()
.find_map(|&m| m.get_script_fn(name, args.len(), true))
.ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::NONE))?;
let state = &mut Default::default();
let mods = &mut (&self.global_sub_modules).into();
let lib = &[ast.lib()];
let mut state = Default::default();
let mut mods = (&self.global_sub_modules).into();
if eval_ast {
self.eval_global_statements(scope, mods, state, ast.statements(), lib, 0)?;
}
let fn_def = ast
.lib()
.get_script_fn(name, args.len(), true)
.ok_or_else(|| EvalAltResult::ErrorFunctionNotFound(name.into(), Position::NONE))?;
// Check for data race.
if cfg!(not(feature = "no_closure")) {
@ -1769,8 +1785,8 @@ impl Engine {
self.call_script_fn(
scope,
&mut mods,
&mut state,
mods,
state,
lib,
this_ptr,
fn_def,
@ -1933,7 +1949,7 @@ impl Engine {
/// # use std::sync::Arc;
/// use rhai::Engine;
///
/// let result = Arc::new(RwLock::new(String::from("")));
/// let result = Arc::new(RwLock::new(String::new()));
///
/// let mut engine = Engine::new();
///
@ -1962,7 +1978,7 @@ impl Engine {
/// # use std::sync::Arc;
/// use rhai::Engine;
///
/// let result = Arc::new(RwLock::new(String::from("")));
/// let result = Arc::new(RwLock::new(String::new()));
///
/// let mut engine = Engine::new();
///

View File

@ -4,6 +4,7 @@ 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,
MAX_DYNAMIC_PARAMETERS,
};
use crate::fn_native::FnCallArgs;
use crate::module::NamespaceRef;
@ -16,7 +17,6 @@ use crate::stdlib::{
iter::{empty, once},
mem,
num::NonZeroU64,
ops::Deref,
string::ToString,
vec::Vec,
};
@ -172,9 +172,8 @@ impl Engine {
hash_fn: NonZeroU64,
args: &mut FnCallArgs,
is_ref: bool,
pub_only: bool,
is_op_assignment: bool,
pos: Position,
def_val: Option<&Dynamic>,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
self.inc_operations(state, pos)?;
@ -186,15 +185,15 @@ impl Engine {
.entry(hash_fn)
.or_insert_with(|| {
let num_args = args.len();
let max_bitmask = 1usize << num_args;
let max_bitmask = 1usize << args.len().min(MAX_DYNAMIC_PARAMETERS);
let mut hash = hash_fn;
let mut bitmask = 1usize;
let mut bitmask = 1usize; // Bitmask of which parameter to replace with `Dynamic`
loop {
//lib.get_fn(hash, pub_only).or_else(||
//lib.get_fn(hash, false).or_else(||
match self
.global_namespace
.get_fn(hash, pub_only)
.get_fn(hash, false)
.cloned()
.map(|f| (f, None))
.or_else(|| {
@ -210,21 +209,26 @@ impl Engine {
// Specific version found
Some(f) => return Some(f),
// No more permutations with `Dynamic` wildcards
// Stop when all permutations are exhausted
_ 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();
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::<Dynamic>()
} else {
a.type_id()
}
}),
)
.unwrap();
bitmask += 1;
}
}
@ -280,25 +284,24 @@ impl Engine {
}
// See if it is built in.
if args.len() == 2 {
if is_ref {
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 => (),
}
}
match run_builtin_binary_op(fn_name, args[0], args[1])? {
Some(v) => return Ok((v, false)),
None => (),
}
}
// Return default value (if any)
if let Some(val) = def_val {
return Ok((val.clone(), false));
}
// Getter function not found?
@ -599,7 +602,6 @@ impl Engine {
pub_only: bool,
pos: Position,
_capture_scope: Option<Scope>,
def_val: Option<&Dynamic>,
_level: usize,
) -> Result<(Dynamic, bool), Box<EvalAltResult>> {
// Check for data race.
@ -607,9 +609,8 @@ impl Engine {
ensure_no_data_race(fn_name, args, is_ref)?;
}
// Qualifiers (none) + function name + number of arguments + argument `TypeId`'s.
let arg_types = args.iter().map(|a| a.type_id());
let hash_fn = calc_native_fn_hash(empty(), fn_name, arg_types).unwrap();
let hash_fn =
calc_native_fn_hash(empty(), fn_name, args.iter().map(|a| a.type_id())).unwrap();
match fn_name {
// type_of
@ -756,15 +757,13 @@ impl Engine {
} else {
// Native function call
self.call_native_fn(
mods, state, lib, fn_name, hash_fn, args, is_ref, pub_only, pos, def_val,
mods, state, lib, fn_name, hash_fn, args, is_ref, false, pos,
)
}
}
// Native function call
_ => self.call_native_fn(
mods, state, lib, fn_name, hash_fn, args, is_ref, pub_only, pos, def_val,
),
_ => self.call_native_fn(mods, state, lib, fn_name, hash_fn, args, is_ref, false, pos),
}
}
@ -845,7 +844,6 @@ impl Engine {
hash_script: Option<NonZeroU64>,
target: &mut crate::engine::Target,
mut call_args: StaticVec<Dynamic>,
def_val: Option<&Dynamic>,
pub_only: bool,
pos: Position,
level: usize,
@ -874,8 +872,7 @@ impl Engine {
// 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, def_val,
level,
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
@ -898,8 +895,7 @@ impl Engine {
// 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, def_val,
level,
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::<FnPtr>() {
// Curry call
@ -964,8 +960,7 @@ impl Engine {
let args = arg_values.as_mut();
self.exec_fn_call(
mods, state, lib, fn_name, hash, args, is_ref, true, pub_only, pos, None, def_val,
level,
mods, state, lib, fn_name, hash, args, is_ref, true, pub_only, pos, None, level,
)
}?;
@ -987,7 +982,6 @@ impl Engine {
this_ptr: &mut Option<&mut Dynamic>,
fn_name: &str,
args_expr: impl AsRef<[Expr]>,
def_val: Option<&Dynamic>,
mut hash_script: Option<NonZeroU64>,
pub_only: bool,
pos: Position,
@ -1212,7 +1206,6 @@ impl Engine {
pub_only,
pos,
capture,
def_val,
level,
)
.map(|(v, _)| v)
@ -1229,7 +1222,6 @@ impl Engine {
namespace: Option<&NamespaceRef>,
fn_name: &str,
args_expr: impl AsRef<[Expr]>,
def_val: Option<&Dynamic>,
hash_script: NonZeroU64,
pos: Position,
level: usize,
@ -1351,7 +1343,6 @@ impl Engine {
args.as_mut(),
),
Some(f) => unreachable!("unknown function type: {:?}", f),
None if def_val.is_some() => Ok(def_val.unwrap().clone()),
None => EvalAltResult::ErrorFunctionNotFound(
format!(
"{}{} ({})",
@ -1379,17 +1370,29 @@ pub fn run_builtin_binary_op(
x: &Dynamic,
y: &Dynamic,
) -> Result<Option<Dynamic>, Box<EvalAltResult>> {
let first_type = x.type_id();
let second_type = y.type_id();
let type1 = x.type_id();
let type2 = y.type_id();
let type_id = (first_type, second_type);
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 type_id == (TypeId::of::<FLOAT>(), TypeId::of::<FLOAT>()) {
if let Some((x, y)) = if types_pair == (TypeId::of::<FLOAT>(), TypeId::of::<FLOAT>()) {
// FLOAT op FLOAT
Some((x.clone().cast::<FLOAT>(), y.clone().cast::<FLOAT>()))
} else if type_id == (TypeId::of::<FLOAT>(), TypeId::of::<INT>()) {
} else if types_pair == (TypeId::of::<FLOAT>(), TypeId::of::<INT>()) {
// FLOAT op INT
Some((x.clone().cast::<FLOAT>(), y.clone().cast::<INT>() as FLOAT))
} else if type_id == (TypeId::of::<INT>(), TypeId::of::<FLOAT>()) {
} else if types_pair == (TypeId::of::<INT>(), TypeId::of::<FLOAT>()) {
// INT op FLOAT
Some((x.clone().cast::<INT>() as FLOAT, y.clone().cast::<FLOAT>()))
} else {
None
@ -1412,17 +1415,20 @@ pub fn run_builtin_binary_op(
}
#[cfg(feature = "decimal")]
if let Some((x, y)) = if type_id == (TypeId::of::<Decimal>(), TypeId::of::<Decimal>()) {
if let Some((x, y)) = if types_pair == (TypeId::of::<Decimal>(), TypeId::of::<Decimal>()) {
// Decimal op Decimal
Some((
*x.read_lock::<Decimal>().unwrap(),
*y.read_lock::<Decimal>().unwrap(),
))
} else if type_id == (TypeId::of::<Decimal>(), TypeId::of::<INT>()) {
} else if types_pair == (TypeId::of::<Decimal>(), TypeId::of::<INT>()) {
// Decimal op INT
Some((
*x.read_lock::<Decimal>().unwrap(),
y.clone().cast::<INT>().into(),
))
} else if type_id == (TypeId::of::<INT>(), TypeId::of::<Decimal>()) {
} else if types_pair == (TypeId::of::<INT>(), TypeId::of::<Decimal>()) {
// INT op Decimal
Some((
x.clone().cast::<INT>().into(),
*y.read_lock::<Decimal>().unwrap(),
@ -1463,31 +1469,70 @@ pub fn run_builtin_binary_op(
}
}
if second_type != first_type {
if type_id == (TypeId::of::<char>(), TypeId::of::<ImmutableString>()) {
let x = x.clone().cast::<char>();
let y = &*y.read_lock::<ImmutableString>().unwrap();
// char op string
if types_pair == (TypeId::of::<char>(), TypeId::of::<ImmutableString>()) {
let x = x.clone().cast::<char>();
let y = &*y.read_lock::<ImmutableString>().unwrap();
match op {
"+" => return Ok(Some(format!("{}{}", x, y).into())),
_ => return Ok(None),
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::<ImmutableString>(), TypeId::of::<char>()) {
let x = &*x.read_lock::<ImmutableString>().unwrap();
let y = y.clone().cast::<char>();
if type_id == (TypeId::of::<ImmutableString>(), TypeId::of::<char>()) {
let x = &*x.read_lock::<ImmutableString>().unwrap();
let y = y.clone().cast::<char>();
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((x + y).into())),
_ => return Ok(None),
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),
}
return Ok(None);
}
if first_type == TypeId::of::<INT>() {
// 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::<INT>() {
let x = x.clone().cast::<INT>();
let y = y.clone().cast::<INT>();
@ -1533,7 +1578,7 @@ pub fn run_builtin_binary_op(
}
}
if first_type == TypeId::of::<bool>() {
if type1 == TypeId::of::<bool>() {
let x = x.clone().cast::<bool>();
let y = y.clone().cast::<bool>();
@ -1547,12 +1592,13 @@ pub fn run_builtin_binary_op(
}
}
if first_type == TypeId::of::<ImmutableString>() {
if type1 == TypeId::of::<ImmutableString>() {
let x = &*x.read_lock::<ImmutableString>().unwrap();
let y = &*y.read_lock::<ImmutableString>().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())),
@ -1563,7 +1609,7 @@ pub fn run_builtin_binary_op(
}
}
if first_type == TypeId::of::<char>() {
if type1 == TypeId::of::<char>() {
let x = x.clone().cast::<char>();
let y = y.clone().cast::<char>();
@ -1579,7 +1625,7 @@ pub fn run_builtin_binary_op(
}
}
if first_type == TypeId::of::<()>() {
if type1 == TypeId::of::<()>() {
match op {
"==" => return Ok(Some(true.into())),
"!=" | ">" | ">=" | "<" | "<=" => return Ok(Some(false.into())),
@ -1596,16 +1642,18 @@ pub fn run_builtin_op_assignment(
x: &mut Dynamic,
y: &Dynamic,
) -> Result<Option<()>, Box<EvalAltResult>> {
let first_type = x.type_id();
let second_type = y.type_id();
let type1 = x.type_id();
let type2 = y.type_id();
let type_id = (first_type, second_type);
let types_pair = (type1, type2);
#[cfg(not(feature = "no_float"))]
if let Some((mut x, y)) = if type_id == (TypeId::of::<FLOAT>(), TypeId::of::<FLOAT>()) {
if let Some((mut x, y)) = if types_pair == (TypeId::of::<FLOAT>(), TypeId::of::<FLOAT>()) {
// FLOAT op= FLOAT
let y = y.clone().cast::<FLOAT>();
Some((x.write_lock::<FLOAT>().unwrap(), y))
} else if type_id == (TypeId::of::<FLOAT>(), TypeId::of::<INT>()) {
} else if types_pair == (TypeId::of::<FLOAT>(), TypeId::of::<INT>()) {
// FLOAT op= INT
let y = y.clone().cast::<INT>() as FLOAT;
Some((x.write_lock::<FLOAT>().unwrap(), y))
} else {
@ -1623,10 +1671,12 @@ pub fn run_builtin_op_assignment(
}
#[cfg(feature = "decimal")]
if let Some((mut x, y)) = if type_id == (TypeId::of::<Decimal>(), TypeId::of::<Decimal>()) {
if let Some((mut x, y)) = if types_pair == (TypeId::of::<Decimal>(), TypeId::of::<Decimal>()) {
// Decimal op= Decimal
let y = *y.read_lock::<Decimal>().unwrap();
Some((x.write_lock::<Decimal>().unwrap(), y))
} else if type_id == (TypeId::of::<Decimal>(), TypeId::of::<INT>()) {
} else if types_pair == (TypeId::of::<Decimal>(), TypeId::of::<INT>()) {
// Decimal op= INT
let y = y.clone().cast::<INT>().into();
Some((x.write_lock::<Decimal>().unwrap(), y))
} else {
@ -1655,21 +1705,40 @@ pub fn run_builtin_op_assignment(
}
}
if second_type != first_type {
if type_id == (TypeId::of::<ImmutableString>(), TypeId::of::<char>()) {
let y = y.read_lock::<char>().unwrap().deref().clone();
let mut x = x.write_lock::<ImmutableString>().unwrap();
// string op= char
if types_pair == (TypeId::of::<ImmutableString>(), TypeId::of::<char>()) {
let y = y.clone().cast::<char>();
let mut x = x.write_lock::<ImmutableString>().unwrap();
match op {
"+=" => return Ok(Some(*x += y)),
_ => return Ok(None),
}
match op {
"+=" => return Ok(Some(*x += y)),
"-=" => return Ok(Some(*x -= y)),
_ => return Ok(None),
}
}
// char op= string
if types_pair == (TypeId::of::<char>(), TypeId::of::<ImmutableString>()) {
let y = y.read_lock::<ImmutableString>().unwrap();
let mut ch = x.read_lock::<char>().unwrap().to_string();
let mut x = x.write_lock::<Dynamic>().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);
}
if first_type == TypeId::of::<INT>() {
// Beyond here, type1 == type2
if type1 == TypeId::of::<INT>() {
let y = y.clone().cast::<INT>();
let mut x = x.write_lock::<INT>().unwrap();
@ -1709,7 +1778,7 @@ pub fn run_builtin_op_assignment(
}
}
if first_type == TypeId::of::<bool>() {
if type1 == TypeId::of::<bool>() {
let y = y.clone().cast::<bool>();
let mut x = x.write_lock::<bool>().unwrap();
@ -1720,8 +1789,8 @@ pub fn run_builtin_op_assignment(
}
}
if first_type == TypeId::of::<char>() {
let y = y.read_lock::<char>().unwrap().deref().clone();
if type1 == TypeId::of::<char>() {
let y = y.clone().cast::<char>();
let mut x = x.write_lock::<Dynamic>().unwrap();
match op {
@ -1730,12 +1799,13 @@ pub fn run_builtin_op_assignment(
}
}
if first_type == TypeId::of::<ImmutableString>() {
let y = y.read_lock::<ImmutableString>().unwrap().deref().clone();
if type1 == TypeId::of::<ImmutableString>() {
let y = &*y.read_lock::<ImmutableString>().unwrap();
let mut x = x.write_lock::<ImmutableString>().unwrap();
match op {
"+=" => return Ok(Some(*x += y)),
"-=" => return Ok(Some(*x -= y)),
_ => return Ok(None),
}
}

View File

@ -186,7 +186,6 @@ impl<'e, 'n, 's, 'a, 'm> NativeCallContext<'e, 'n, 's, 'a, 'm> {
is_method: bool,
public_only: bool,
args: &mut [&mut Dynamic],
def_value: Option<&Dynamic>,
) -> Result<Dynamic, Box<EvalAltResult>> {
self.engine()
.exec_fn_call(
@ -201,7 +200,6 @@ impl<'e, 'n, 's, 'a, 'm> NativeCallContext<'e, 'n, 's, 'a, 'm> {
public_only,
Position::NONE,
None,
def_value,
0,
)
.map(|(r, _)| r)
@ -339,7 +337,7 @@ impl FnPtr {
args.insert(0, obj);
}
ctx.call_fn_dynamic_raw(self.fn_name(), is_method, true, args.as_mut(), None)
ctx.call_fn_dynamic_raw(self.fn_name(), is_method, true, args.as_mut())
}
}

View File

@ -1855,7 +1855,6 @@ impl Module {
// Index all variables
module.variables.iter().for_each(|(var_name, value)| {
// Qualifiers + variable name
let hash_var =
crate::calc_script_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0).unwrap();
variables.insert(hash_var, value.clone());
@ -1888,7 +1887,6 @@ impl Module {
functions.insert(hash, func.clone());
}
// Qualifiers + function name + number of arguments.
let hash_qualified_script =
crate::calc_script_fn_hash(qualifiers.iter().cloned(), name, *params)
.unwrap();

View File

@ -149,9 +149,8 @@ fn call_fn_with_constant_arguments(
hash_fn.unwrap(),
arg_values.iter_mut().collect::<StaticVec<_>>().as_mut(),
false,
true,
false,
Position::NONE,
None,
)
.ok()
.map(|(v, _)| v)
@ -717,8 +716,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
// Handle `type_of()`
Some(arg_for_type_of.to_string().into())
} else {
// Otherwise use the default value, if any
x.def_value.clone()
None
}
})
.and_then(|result| map_dynamic_to_expr(result, *pos))

View File

@ -647,11 +647,9 @@ mod array_functions {
return Ok(true.into());
}
let def_value = Some(false.into());
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], def_value.as_ref())
.call_fn_dynamic_raw(OP_EQUALS, true, false, &mut [a1, a2])
.map(|v| v.as_bool().unwrap_or(false))?;
if !equals {

View File

@ -1,6 +1,7 @@
#![allow(non_snake_case)]
use crate::def_package;
use crate::fn_call::run_builtin_binary_op;
use crate::plugin::*;
#[cfg(feature = "decimal")]
@ -17,30 +18,12 @@ macro_rules! gen_cmp_functions {
#[export_module]
pub mod functions {
#[rhai_fn(name = "<")]
pub fn lt(x: $arg_type, y: $arg_type) -> bool {
x < y
}
#[rhai_fn(name = "<=")]
pub fn lte(x: $arg_type, y: $arg_type) -> bool {
x <= y
}
#[rhai_fn(name = ">")]
pub fn gt(x: $arg_type, y: $arg_type) -> bool {
x > y
}
#[rhai_fn(name = ">=")]
pub fn gte(x: $arg_type, y: $arg_type) -> bool {
x >= y
}
#[rhai_fn(name = "==")]
pub fn eq(x: $arg_type, y: $arg_type) -> bool {
x == y
}
#[rhai_fn(name = "!=")]
pub fn ne(x: $arg_type, y: $arg_type) -> bool {
x != y
}
#[rhai_fn(name = "<")] pub fn lt(x: $arg_type, y: $arg_type) -> bool { x < y }
#[rhai_fn(name = "<=")] pub fn lte(x: $arg_type, y: $arg_type) -> bool { x <= y }
#[rhai_fn(name = ">")] pub fn gt(x: $arg_type, y: $arg_type) -> bool { x > y }
#[rhai_fn(name = ">=")] pub fn gte(x: $arg_type, y: $arg_type) -> bool { x >= y }
#[rhai_fn(name = "==")] pub fn eq(x: $arg_type, y: $arg_type) -> bool { x == y }
#[rhai_fn(name = "!=")] pub fn ne(x: $arg_type, y: $arg_type) -> bool { x != y }
}
})* }
};
@ -57,6 +40,8 @@ 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"))]
{
@ -112,6 +97,65 @@ 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::<u8>()
|| type_id == TypeId::of::<u16>()
|| type_id == TypeId::of::<u32>()
|| type_id == TypeId::of::<u64>()
|| type_id == TypeId::of::<i8>()
|| type_id == TypeId::of::<i16>()
|| type_id == TypeId::of::<i32>()
|| type_id == TypeId::of::<i64>();
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
let result = result || type_id == TypeId::of::<u128>() || type_id == TypeId::of::<i128>();
#[cfg(not(feature = "no_float"))]
let result = result || type_id == TypeId::of::<f32>() || type_id == TypeId::of::<f64>();
#[cfg(feature = "decimal")]
let result = result || type_id == TypeId::of::<rust_decimal::Decimal>();
result
}
#[rhai_fn(
name = "==",
name = "!=",
name = ">",
name = ">=",
name = "<",
name = "<=",
return_raw
)]
pub fn cmp(
ctx: NativeCallContext,
x: Dynamic,
y: Dynamic,
) -> Result<Dynamic, Box<EvalAltResult>> {
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 {

View File

@ -58,12 +58,10 @@ mod map_functions {
return Ok(true.into());
}
let def_value = Some(false.into());
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], def_value.as_ref())
.call_fn_dynamic_raw(OP_EQUALS, true, false, &mut [v1, v2])
.map(|v| v.as_bool().unwrap_or(false))?;
if !equals {

View File

@ -1,13 +1,8 @@
#![allow(non_snake_case)]
use crate::engine::{KEYWORD_DEBUG, KEYWORD_PRINT};
use crate::plugin::*;
use crate::stdlib::{
fmt::{Debug, Display},
format,
string::ToString,
};
use crate::{def_package, FnPtr, ImmutableString, INT};
use crate::stdlib::{format, string::ToString};
use crate::{def_package, FnPtr, ImmutableString};
#[cfg(not(feature = "no_index"))]
use crate::Array;
@ -15,145 +10,18 @@ use crate::Array;
#[cfg(not(feature = "no_object"))]
use crate::Map;
#[cfg(feature = "decimal")]
use rust_decimal::Decimal;
const FUNC_TO_STRING: &'static str = "to_string";
const FUNC_TO_DEBUG: &'static str = "to_debug";
type Unit = ();
macro_rules! gen_functions {
($root:ident => $fn_name:ident ( $($arg_type:ident),+ )) => {
pub mod $root { $(pub mod $arg_type {
use super::super::*;
#[export_fn(pure)]
pub fn to_string_func(x: &mut $arg_type) -> ImmutableString {
super::super::$fn_name(x)
}
})* }
}
}
macro_rules! reg_print_functions {
($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $(
set_exported_fn!($mod_name, FUNC_TO_STRING, $root::$arg_type::to_string_func);
set_exported_fn!($mod_name, KEYWORD_PRINT, $root::$arg_type::to_string_func);
)* }
}
macro_rules! reg_debug_functions {
($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $(
set_exported_fn!($mod_name, FUNC_TO_DEBUG, $root::$arg_type::to_string_func);
set_exported_fn!($mod_name, KEYWORD_DEBUG, $root::$arg_type::to_string_func);
)* }
}
def_package!(crate:BasicStringPackage:"Basic string utilities, including printing.", lib, {
combine_with_exported_module!(lib, "print_debug", print_debug_functions);
reg_print_functions!(lib += print_basic; INT, bool, char, FnPtr);
reg_debug_functions!(lib += debug_basic; INT, bool, Unit, char, ImmutableString);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
{
reg_print_functions!(lib += print_numbers; i8, u8, i16, u16, i32, u32, i64, u64);
reg_debug_functions!(lib += debug_numbers; i8, u8, i16, u16, i32, u32, i64, u64);
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
{
reg_print_functions!(lib += print_num_128; i128, u128);
reg_debug_functions!(lib += debug_num_128; i128, u128);
}
}
#[cfg(not(feature = "no_float"))]
{
reg_print_functions!(lib += print_float_64; f64);
reg_debug_functions!(lib += print_float_64; f64);
reg_print_functions!(lib += print_float_32; f32);
reg_debug_functions!(lib += print_float_32; f32);
}
#[cfg(feature = "decimal")]
{
reg_print_functions!(lib += print_decimal; Decimal);
reg_debug_functions!(lib += debug_decimal; Decimal);
}
});
fn to_string<T: Display>(x: &mut T) -> ImmutableString {
x.to_string().into()
}
fn to_debug<T: Debug>(x: &mut T) -> ImmutableString {
format!("{:?}", x).into()
}
#[cfg(not(feature = "no_float"))]
fn print_f64(x: &mut f64) -> ImmutableString {
#[cfg(feature = "no_std")]
use num_traits::Float;
let abs = x.abs();
if abs > 10000000000000.0 || abs < 0.0000000000001 {
format!("{:e}", x).into()
} else {
x.to_string().into()
}
}
#[cfg(not(feature = "no_float"))]
fn print_f32(x: &mut f32) -> ImmutableString {
#[cfg(feature = "no_std")]
use num_traits::Float;
let abs = x.abs();
if abs > 10000000000000.0 || abs < 0.0000000000001 {
format!("{:e}", x).into()
} else {
x.to_string().into()
}
}
gen_functions!(print_basic => to_string(INT, bool, char, FnPtr));
gen_functions!(debug_basic => to_debug(INT, bool, Unit, char, ImmutableString));
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
gen_functions!(print_numbers => to_string(i8, u8, i16, u16, i32, u32, i64, u64));
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
gen_functions!(debug_numbers => to_debug(i8, u8, i16, u16, i32, u32, i64, u64));
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
gen_functions!(print_num_128 => to_string(i128, u128));
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
gen_functions!(debug_num_128 => to_debug(i128, u128));
#[cfg(not(feature = "no_float"))]
gen_functions!(print_float_64 => print_f64(f64));
#[cfg(not(feature = "no_float"))]
gen_functions!(print_float_32 => print_f32(f32));
#[cfg(feature = "decimal")]
gen_functions!(print_decimal => to_string(Decimal));
#[cfg(feature = "decimal")]
gen_functions!(debug_decimal => to_debug(Decimal));
// Register print and debug
#[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], None) {
match ctx.call_fn_dynamic_raw(fn_name, true, false, &mut [value]) {
Ok(result) if result.is::<ImmutableString>() => 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(),
@ -162,21 +30,63 @@ fn print_with_func(fn_name: &str, ctx: &NativeCallContext, value: &mut Dynamic)
#[export_module]
mod print_debug_functions {
use crate::ImmutableString;
#[rhai_fn(name = "print", name = "to_string", pure)]
pub fn print_generic(item: &mut Dynamic) -> ImmutableString {
item.to_string().into()
}
#[rhai_fn(name = "debug", name = "to_debug", pure)]
pub fn debug_generic(item: &mut Dynamic) -> ImmutableString {
format!("{:?}", item).into()
}
#[rhai_fn(name = "print", name = "debug")]
pub fn print_empty_string() -> ImmutableString {
"".to_string().into()
}
#[rhai_fn(name = "print", name = "to_string")]
pub fn print_unit(_x: ()) -> ImmutableString {
"".to_string().into()
}
#[rhai_fn(name = "print", name = "to_string")]
pub fn print_string(s: ImmutableString) -> ImmutableString {
s
}
#[rhai_fn(name = "debug", pure)]
#[rhai_fn(name = "debug", name = "to_debug", pure)]
pub fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString {
to_string(f)
f.to_string().into()
}
#[cfg(not(feature = "no_float"))]
pub mod float_functions {
#[rhai_fn(name = "print", name = "to_string")]
pub fn print_f64(number: f64) -> ImmutableString {
#[cfg(feature = "no_std")]
use num_traits::Float;
let abs = number.abs();
if abs > 10000000000000.0 || abs < 0.0000000000001 {
format!("{:e}", number).into()
} else {
number.to_string().into()
}
}
#[rhai_fn(name = "print", name = "to_string")]
pub fn print_f32(number: f32) -> ImmutableString {
#[cfg(feature = "no_std")]
use num_traits::Float;
let abs = number.abs();
if abs > 10000000000000.0 || abs < 0.0000000000001 {
format!("{:e}", number).into()
} else {
number.to_string().into()
}
}
#[rhai_fn(name = "debug", name = "to_debug")]
pub fn debug_f64(number: f64) -> ImmutableString {
number.to_string().into()
}
#[rhai_fn(name = "debug", name = "to_debug")]
pub fn debug_f32(number: f32) -> ImmutableString {
number.to_string().into()
}
}
#[cfg(not(feature = "no_index"))]
@ -186,15 +96,14 @@ mod print_debug_functions {
#[rhai_fn(
name = "print",
name = "to_string",
name = "to_debug",
name = "debug",
name = "to_debug",
pure
)]
pub fn format_array(ctx: NativeCallContext, array: &mut Array) -> ImmutableString {
let mut result = crate::stdlib::string::String::with_capacity(16);
result.push_str("[");
let len = array.len();
let mut result = crate::stdlib::string::String::with_capacity(len * 5 + 2);
result.push_str("[");
array.iter_mut().enumerate().for_each(|(i, x)| {
result.push_str(&print_with_func(FUNC_TO_DEBUG, &ctx, x));
@ -214,15 +123,14 @@ mod print_debug_functions {
#[rhai_fn(
name = "print",
name = "to_string",
name = "to_debug",
name = "debug",
name = "to_debug",
pure
)]
pub fn format_map(ctx: NativeCallContext, map: &mut Map) -> ImmutableString {
let mut result = crate::stdlib::string::String::with_capacity(16);
result.push_str("#{");
let len = map.len();
let mut result = crate::stdlib::string::String::with_capacity(len * 5 + 3);
result.push_str("#{");
map.iter_mut().enumerate().for_each(|(i, (k, v))| {
result.push_str(&format!(

View File

@ -4,50 +4,9 @@ use crate::plugin::*;
use crate::stdlib::{
any::TypeId, boxed::Box, format, mem, string::String, string::ToString, vec::Vec,
};
use crate::{def_package, Dynamic, FnPtr, ImmutableString, StaticVec, INT};
macro_rules! gen_concat_functions {
($root:ident => $($arg_type:ident),+ ) => {
pub mod $root { $( pub mod $arg_type {
use super::super::*;
#[export_module]
pub mod functions {
#[rhai_fn(name = "+")]
pub fn append_func(string: &str, arg: $arg_type) -> String {
format!("{}{}", string, arg)
}
#[rhai_fn(name = "+", pure)]
pub fn prepend_func(arg: &mut $arg_type, string: &str) -> String {
format!("{}{}", arg, string)
}
}
} )* }
}
}
macro_rules! reg_functions {
($mod_name:ident += $root:ident ; $($arg_type:ident),+) => { $(
combine_with_exported_module!($mod_name, "strings_concat", $root::$arg_type::functions);
)* }
}
use crate::{def_package, Dynamic, ImmutableString, StaticVec, INT};
def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, {
reg_functions!(lib += basic; INT, bool, FnPtr);
#[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);
combine_with_exported_module!(lib, "string", string_functions);
// Register string iterator
@ -57,24 +16,19 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
);
});
gen_concat_functions!(basic => INT, bool, char, FnPtr);
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
gen_concat_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_concat_functions!(num_128 => i128, u128);
#[cfg(not(feature = "no_float"))]
gen_concat_functions!(float => f32, f64);
#[export_module]
mod string_functions {
use crate::ImmutableString;
#[rhai_fn(name = "+", name = "append")]
pub fn add_append(string: &str, item: Dynamic) -> ImmutableString {
format!("{}{}", string, item).into()
}
#[rhai_fn(name = "+", pure)]
pub fn add_prepend(item: &mut Dynamic, string: &str) -> ImmutableString {
format!("{}{}", item, string).into()
}
#[rhai_fn(name = "+")]
pub fn add_append_unit(string: ImmutableString, _x: ()) -> ImmutableString {
string
@ -88,7 +42,13 @@ mod string_functions {
pub fn len(string: &str) -> INT {
string.chars().count() as INT
}
pub fn remove(string: &mut ImmutableString, sub_string: ImmutableString) {
*string -= sub_string;
}
#[rhai_fn(name = "remove")]
pub fn remove_char(string: &mut ImmutableString, character: char) {
*string -= character;
}
pub fn clear(string: &mut ImmutableString) {
string.make_mut().clear();
}
@ -111,15 +71,15 @@ mod string_functions {
}
#[rhai_fn(name = "contains")]
pub fn contains_char(string: &str, ch: char) -> bool {
string.contains(ch)
pub fn contains_char(string: &str, character: char) -> bool {
string.contains(character)
}
pub fn contains(string: &str, find_string: &str) -> bool {
string.contains(find_string)
}
#[rhai_fn(name = "index_of")]
pub fn index_of_char_starting_from(string: &str, ch: char, start: INT) -> INT {
pub fn index_of_char_starting_from(string: &str, character: char, start: INT) -> INT {
let start = if start < 0 {
0
} else if start as usize >= string.chars().count() {
@ -133,14 +93,14 @@ mod string_functions {
};
string[start..]
.find(ch)
.find(character)
.map(|index| string[0..start + index].chars().count() as INT)
.unwrap_or(-1 as INT)
}
#[rhai_fn(name = "index_of")]
pub fn index_of_char(string: &str, ch: char) -> INT {
pub fn index_of_char(string: &str, character: char) -> INT {
string
.find(ch)
.find(character)
.map(|index| string[0..index].chars().count() as INT)
.unwrap_or(-1 as INT)
}
@ -243,26 +203,33 @@ mod string_functions {
pub fn replace_string_with_char(
string: &mut ImmutableString,
find_string: &str,
substitute_char: char,
substitute_character: char,
) {
*string = string
.replace(find_string, &substitute_char.to_string())
.replace(find_string, &substitute_character.to_string())
.into();
}
#[rhai_fn(name = "replace")]
pub fn replace_char_with_string(
string: &mut ImmutableString,
find_char: char,
find_character: char,
substitute_string: &str,
) {
*string = string
.replace(&find_char.to_string(), substitute_string)
.replace(&find_character.to_string(), substitute_string)
.into();
}
#[rhai_fn(name = "replace")]
pub fn replace_char(string: &mut ImmutableString, find_char: char, substitute_char: char) {
pub fn replace_char(
string: &mut ImmutableString,
find_character: char,
substitute_character: char,
) {
*string = string
.replace(&find_char.to_string(), &substitute_char.to_string())
.replace(
&find_character.to_string(),
&substitute_character.to_string(),
)
.into();
}
@ -271,7 +238,7 @@ mod string_functions {
_ctx: NativeCallContext,
string: &mut ImmutableString,
len: INT,
ch: char,
character: char,
) -> Result<Dynamic, Box<crate::EvalAltResult>> {
// Check if string will be over max size limit
#[cfg(not(feature = "unchecked"))]
@ -290,7 +257,7 @@ mod string_functions {
let p = string.make_mut();
for _ in 0..(len as usize - orig_len) {
p.push(ch);
p.push(character);
}
#[cfg(not(feature = "unchecked"))]
@ -363,14 +330,6 @@ mod string_functions {
use crate::stdlib::vec;
use crate::{Array, ImmutableString};
#[rhai_fn(name = "+")]
pub fn append(string: &str, array: Array) -> String {
format!("{}{:?}", string, array)
}
#[rhai_fn(name = "+", pure)]
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()
@ -439,18 +398,4 @@ mod string_functions {
.collect()
}
}
#[cfg(not(feature = "no_object"))]
pub mod maps {
use crate::Map;
#[rhai_fn(name = "+")]
pub fn append(string: &str, map: Map) -> String {
format!("{}#{:?}", string, map)
}
#[rhai_fn(name = "+", pure)]
pub fn prepend(map: &mut Map, string: &str) -> String {
format!("#{:?}{}", map, string)
}
}
}

View File

@ -347,7 +347,6 @@ fn parse_fn_call(
let qualifiers = modules.iter().map(|m| m.name.as_str());
calc_script_fn_hash(qualifiers, &id, 0)
} else {
// Qualifiers (none) + function name + no parameters.
calc_script_fn_hash(empty(), &id, 0)
};
@ -399,7 +398,6 @@ fn parse_fn_call(
let qualifiers = modules.iter().map(|m| m.name.as_str());
calc_script_fn_hash(qualifiers, &id, args.len())
} else {
// Qualifiers (none) + function name + number of arguments.
calc_script_fn_hash(empty(), &id, args.len())
};
@ -1016,7 +1014,6 @@ fn parse_primary(
});
lib.insert(
// Qualifiers (none) + function name + number of arguments.
calc_script_fn_hash(empty(), &func.name, func.params.len()).unwrap(),
func,
);
@ -1246,7 +1243,6 @@ fn parse_primary(
}
.map(|x| match x.as_mut() {
(_, Some((ref mut hash, ref mut namespace)), Ident { name, .. }) => {
// Qualifiers + variable name
*hash =
calc_script_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0).unwrap();
@ -1351,7 +1347,6 @@ fn parse_unary(
Box::new(FnCallExpr {
name: op.into(),
args,
def_value: Some(false.into()), // NOT operator, when operating on invalid operand, defaults to false
..Default::default()
}),
pos,
@ -1796,7 +1791,6 @@ fn parse_binary_op(
#[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
let cmp_def = Some(false.into());
let op = op_token.syntax();
let op_base = FnCallExpr {
@ -1823,28 +1817,16 @@ fn parse_binary_op(
| Token::XOr => Expr::FnCall(Box::new(FnCallExpr { args, ..op_base }), pos),
// '!=' defaults to true when passed invalid operands
Token::NotEqualsTo => Expr::FnCall(
Box::new(FnCallExpr {
args,
def_value: Some(true.into()),
..op_base
}),
pos,
),
Token::NotEqualsTo => Expr::FnCall(Box::new(FnCallExpr { args, ..op_base }), pos),
// Comparison operators default to false when passed invalid operands
Token::EqualsTo
| Token::LessThan
| Token::LessThanEqualsTo
| Token::GreaterThan
| Token::GreaterThanEqualsTo => Expr::FnCall(
Box::new(FnCallExpr {
args,
def_value: cmp_def,
..op_base
}),
pos,
),
| Token::GreaterThanEqualsTo => {
Expr::FnCall(Box::new(FnCallExpr { args, ..op_base }), pos)
}
Token::Or => {
let rhs = args.pop().unwrap();
@ -2621,8 +2603,6 @@ fn parse_stmt(
};
let func = parse_fn(input, &mut new_state, lib, access, settings, _comments)?;
// Qualifiers (none) + function name + number of arguments.
let hash = calc_script_fn_hash(empty(), &func.name, func.params.len()).unwrap();
if lib.contains_key(&hash) {

View File

@ -1657,27 +1657,31 @@ pub fn is_valid_identifier(name: impl Iterator<Item = char>) -> bool {
first_alphabetic
}
/// Is a character valid to start an identifier?
#[cfg(feature = "unicode-xid-ident")]
#[inline(always)]
fn is_id_first_alphabetic(x: char) -> bool {
pub fn is_id_first_alphabetic(x: char) -> bool {
unicode_xid::UnicodeXID::is_xid_start(x)
}
/// Is a character valid for an identifier?
#[cfg(feature = "unicode-xid-ident")]
#[inline(always)]
fn is_id_continue(x: char) -> bool {
pub fn is_id_continue(x: char) -> bool {
unicode_xid::UnicodeXID::is_xid_continue(x)
}
/// Is a character valid to start an identifier?
#[cfg(not(feature = "unicode-xid-ident"))]
#[inline(always)]
fn is_id_first_alphabetic(x: char) -> bool {
pub fn is_id_first_alphabetic(x: char) -> bool {
x.is_ascii_alphabetic()
}
/// Is a character valid for an identifier?
#[cfg(not(feature = "unicode-xid-ident"))]
#[inline(always)]
fn is_id_continue(x: char) -> bool {
pub fn is_id_continue(x: char) -> bool {
x.is_ascii_alphanumeric() || x == '_'
}

View File

@ -12,7 +12,7 @@ use crate::stdlib::{
hash::{BuildHasher, Hash, Hasher},
iter::{empty, FromIterator},
num::NonZeroU64,
ops::{Add, AddAssign, Deref, DerefMut},
ops::{Add, AddAssign, Deref, DerefMut, Sub, SubAssign},
str::FromStr,
string::{String, ToString},
vec::Vec,
@ -468,6 +468,13 @@ impl Add<String> for &ImmutableString {
}
}
impl AddAssign<String> for ImmutableString {
#[inline(always)]
fn add_assign(&mut self, rhs: String) {
self.make_mut().push_str(&rhs);
}
}
impl Add<char> for ImmutableString {
type Output = Self;
@ -496,6 +503,124 @@ impl AddAssign<char> for ImmutableString {
}
}
impl Sub for ImmutableString {
type Output = Self;
#[inline]
fn sub(self, rhs: Self) -> Self::Output {
if rhs.is_empty() {
self
} else if self.is_empty() {
rhs
} else {
self.replace(rhs.as_str(), "").into()
}
}
}
impl Sub for &ImmutableString {
type Output = ImmutableString;
#[inline]
fn sub(self, rhs: Self) -> Self::Output {
if rhs.is_empty() {
self.clone()
} else if self.is_empty() {
rhs.clone()
} else {
self.replace(rhs.as_str(), "").into()
}
}
}
impl SubAssign<&ImmutableString> for ImmutableString {
#[inline]
fn sub_assign(&mut self, rhs: &ImmutableString) {
if !rhs.is_empty() {
if self.is_empty() {
self.0 = rhs.0.clone();
} else {
self.0 = self.replace(rhs.as_str(), "").into();
}
}
}
}
impl SubAssign<ImmutableString> for ImmutableString {
#[inline]
fn sub_assign(&mut self, rhs: ImmutableString) {
if !rhs.is_empty() {
if self.is_empty() {
self.0 = rhs.0;
} else {
self.0 = self.replace(rhs.as_str(), "").into();
}
}
}
}
impl Sub<String> for ImmutableString {
type Output = Self;
#[inline]
fn sub(self, rhs: String) -> Self::Output {
if rhs.is_empty() {
self
} else if self.is_empty() {
rhs.into()
} else {
self.replace(&rhs, "").into()
}
}
}
impl Sub<String> for &ImmutableString {
type Output = ImmutableString;
#[inline]
fn sub(self, rhs: String) -> Self::Output {
if rhs.is_empty() {
self.clone()
} else if self.is_empty() {
rhs.into()
} else {
self.replace(&rhs, "").into()
}
}
}
impl SubAssign<String> for ImmutableString {
#[inline(always)]
fn sub_assign(&mut self, rhs: String) {
self.0 = self.replace(&rhs, "").into();
}
}
impl Sub<char> for ImmutableString {
type Output = Self;
#[inline(always)]
fn sub(self, rhs: char) -> Self::Output {
self.replace(rhs, "").into()
}
}
impl Sub<char> for &ImmutableString {
type Output = ImmutableString;
#[inline(always)]
fn sub(self, rhs: char) -> Self::Output {
self.replace(rhs, "").into()
}
}
impl SubAssign<char> for ImmutableString {
#[inline(always)]
fn sub_assign(&mut self, rhs: char) {
self.0 = self.replace(rhs, "").into();
}
}
impl<S: AsRef<str>> PartialEq<S> for ImmutableString {
#[inline(always)]
fn eq(&self, other: &S) -> bool {

View File

@ -6,10 +6,10 @@ fn test_decrement() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>("let x = 10; x -= 7; x")?, 3);
assert!(matches!(
*engine.eval::<String>(r#"let s = "test"; s -= "ing"; s"#).expect_err("expects error"),
EvalAltResult::ErrorFunctionNotFound(err, _) if err == "- (&str | ImmutableString | String, &str | ImmutableString | String)"
));
assert_eq!(
engine.eval::<String>(r#"let s = "test"; s -= 's'; s"#)?,
"tet"
);
Ok(())
}

View File

@ -7,7 +7,7 @@ fn test_increment() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>("let x = 1; x += 2; x")?, 3);
assert_eq!(
engine.eval::<String>("let s = \"test\"; s += \"ing\"; s")?,
engine.eval::<String>(r#"let s = "test"; s += "ing"; s"#)?,
"testing"
);

View File

@ -12,7 +12,7 @@ fn test_mismatched_op() {
#[test]
#[cfg(not(feature = "no_object"))]
fn test_mismatched_op_custom_type() {
fn test_mismatched_op_custom_type() -> Result<(), Box<EvalAltResult>> {
#[derive(Debug, Clone)]
struct TestStruct {
x: INT,
@ -30,9 +30,18 @@ fn test_mismatched_op_custom_type() {
.register_type_with_name::<TestStruct>("TestStruct")
.register_fn("new_ts", TestStruct::new);
assert!(matches!(*engine.eval::<bool>(r"
let x = new_ts();
let y = new_ts();
x == y
").expect_err("should error"),
EvalAltResult::ErrorFunctionNotFound(f, _) if f == "== (TestStruct, TestStruct)"));
assert!(!engine.eval::<bool>("new_ts() == 42")?);
assert!(matches!(
*engine.eval::<INT>("60 + new_ts()").expect_err("should error"),
EvalAltResult::ErrorFunctionNotFound(err, _) if err == format!("+ ({}, TestStruct)", std::any::type_name::<INT>())
EvalAltResult::ErrorFunctionNotFound(f, _) if f == format!("+ ({}, TestStruct)", std::any::type_name::<INT>())
));
assert!(matches!(
@ -40,4 +49,6 @@ fn test_mismatched_op_custom_type() {
EvalAltResult::ErrorMismatchOutputType(need, actual, _)
if need == "TestStruct" && actual == std::any::type_name::<INT>()
));
Ok(())
}

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult, INT};
use rhai::{Engine, EvalAltResult, Scope, INT};
#[test]
fn test_ops() -> Result<(), Box<EvalAltResult>> {
@ -11,7 +11,42 @@ fn test_ops() -> Result<(), Box<EvalAltResult>> {
}
#[test]
fn test_op_precedence() -> Result<(), Box<EvalAltResult>> {
fn test_ops_numbers() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
let mut scope = Scope::new();
scope.push("x", 42_u16);
assert!(matches!(
*engine.eval_with_scope::<bool>(&mut scope, "x == 42").expect_err("should error"),
EvalAltResult::ErrorFunctionNotFound(f, _) if f.starts_with("== (u16,")
));
#[cfg(not(feature = "no_float"))]
assert!(matches!(
*engine.eval_with_scope::<bool>(&mut scope, "x == 42.0").expect_err("should error"),
EvalAltResult::ErrorFunctionNotFound(f, _) if f.starts_with("== (u16,")
));
assert!(!engine.eval_with_scope::<bool>(&mut scope, r#"x == "hello""#)?);
Ok(())
}
#[test]
fn test_ops_strings() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert!(engine.eval::<bool>(r#""hello" > 'c'"#)?);
assert!(engine.eval::<bool>(r#""" < 'c'"#)?);
assert!(engine.eval::<bool>(r#"'x' > "hello""#)?);
assert!(engine.eval::<bool>(r#""hello" > "foo""#)?);
Ok(())
}
#[test]
fn test_ops_precedence() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert_eq!(

View File

@ -1,6 +1,33 @@
use rhai::{Engine, EvalAltResult, RegisterFn, INT};
use rhai::{Engine, EvalAltResult, RegisterFn, Scope, INT};
use std::sync::{Arc, RwLock};
#[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))]
#[test]
fn test_to_string() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
let mut scope = Scope::new();
scope.push("x", 42_u8);
scope.push("y", 42_i32);
scope.push("z", 42_i16);
assert_eq!(
engine.eval_with_scope::<String>(&mut scope, "to_string(x)")?,
"42"
);
assert_eq!(
engine.eval_with_scope::<String>(&mut scope, "to_string(x)")?,
"42"
);
assert_eq!(
engine.eval_with_scope::<String>(&mut scope, "to_string(x)")?,
"42"
);
Ok(())
}
#[test]
fn test_print_debug() -> Result<(), Box<EvalAltResult>> {
let logbook = Arc::new(RwLock::new(Vec::<String>::new()));

View File

@ -66,7 +66,7 @@ fn test_side_effects_command() -> Result<(), Box<EvalAltResult>> {
#[test]
fn test_side_effects_print() -> Result<(), Box<EvalAltResult>> {
let result = Arc::new(RwLock::new(String::from("")));
let result = Arc::new(RwLock::new(String::new()));
let mut engine = Engine::new();

View File

@ -53,8 +53,8 @@ fn test_string() -> Result<(), Box<EvalAltResult>> {
fn test_string_dynamic() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
let mut scope = Scope::new();
scope.push("x", Dynamic::from("foo"));
scope.push("y", String::from("foo"));
scope.push("x", "foo");
scope.push("y", "foo");
scope.push("z", "foo");
assert!(engine.eval_with_scope::<bool>(&mut scope, r#"x == "foo""#)?);
@ -132,6 +132,19 @@ fn test_string_substring() -> Result<(), Box<EvalAltResult>> {
"❤❤ hello! ❤❤❤"
);
assert_eq!(
engine.eval::<String>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x -= 'l'; x"#
)?,
"❤❤❤ heo! ❤❤❤"
);
assert_eq!(
engine.eval::<String>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x -= "\u2764\u2764"; x"#
)?,
"❤ hello! ❤"
);
assert_eq!(
engine.eval::<INT>(
r#"let x = "\u2764\u2764\u2764 hello! \u2764\u2764\u2764"; x.index_of('\u2764')"#