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)]`. * 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. * 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. * 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 New features
------------ ------------
* Functions are now allowed to have `Dynamic` arguments. * 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.
Enhancements Enhancements
------------ ------------
@ -30,8 +31,12 @@ Enhancements
* Error position in `eval` statements is now wrapped in an `EvalAltResult::ErrorInFunctionCall`. * Error position in `eval` statements is now wrapped in an `EvalAltResult::ErrorInFunctionCall`.
* `Position` now implements `Add` and `AddAssign`. * `Position` now implements `Add` and `AddAssign`.
* `Scope` now implements `IntoIterator`. * `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. * Strings now have the `split_rev` method and variations of `split` with maximum number of segments.
* Arrays now have the `split` method. * 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 Version 0.19.12

View File

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

View File

@ -1,5 +1,10 @@
Procedural Macros for Plugins 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 This crate holds procedural macros for code generation, supporting the plugins system
for [Rhai](https://github.com/rhaiscript/rhai). 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>, pub hash_script: Option<NonZeroU64>,
/// Does this function call capture the parent scope? /// Does this function call capture the parent scope?
pub capture: bool, 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. /// Namespace of the function, if any. Boxed because it occurs rarely.
pub namespace: Option<NamespaceRef>, pub namespace: Option<NamespaceRef>,
/// Function name. /// Function name.

View File

@ -475,7 +475,45 @@ impl fmt::Display for Dynamic {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::TimeStamp(_, _) => f.write_str("<timestamp>"), 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 = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
@ -516,7 +554,53 @@ impl fmt::Debug for Dynamic {
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
Union::TimeStamp(_, _) => write!(f, "<timestamp>"), 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 = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]

View File

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

View File

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

View File

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

View File

@ -186,7 +186,6 @@ impl<'e, 'n, 's, 'a, 'm> NativeCallContext<'e, 'n, 's, 'a, 'm> {
is_method: bool, is_method: bool,
public_only: bool, public_only: bool,
args: &mut [&mut Dynamic], args: &mut [&mut Dynamic],
def_value: Option<&Dynamic>,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
self.engine() self.engine()
.exec_fn_call( .exec_fn_call(
@ -201,7 +200,6 @@ impl<'e, 'n, 's, 'a, 'm> NativeCallContext<'e, 'n, 's, 'a, 'm> {
public_only, public_only,
Position::NONE, Position::NONE,
None, None,
def_value,
0, 0,
) )
.map(|(r, _)| r) .map(|(r, _)| r)
@ -339,7 +337,7 @@ impl FnPtr {
args.insert(0, obj); 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 // Index all variables
module.variables.iter().for_each(|(var_name, value)| { module.variables.iter().for_each(|(var_name, value)| {
// Qualifiers + variable name
let hash_var = let hash_var =
crate::calc_script_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0).unwrap(); crate::calc_script_fn_hash(qualifiers.iter().map(|&v| v), var_name, 0).unwrap();
variables.insert(hash_var, value.clone()); variables.insert(hash_var, value.clone());
@ -1888,7 +1887,6 @@ impl Module {
functions.insert(hash, func.clone()); functions.insert(hash, func.clone());
} }
// Qualifiers + function name + number of arguments.
let hash_qualified_script = let hash_qualified_script =
crate::calc_script_fn_hash(qualifiers.iter().cloned(), name, *params) crate::calc_script_fn_hash(qualifiers.iter().cloned(), name, *params)
.unwrap(); .unwrap();

View File

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

View File

@ -647,11 +647,9 @@ mod array_functions {
return Ok(true.into()); return Ok(true.into());
} }
let def_value = Some(false.into());
for (a1, a2) in array.iter_mut().zip(array2.iter_mut()) { for (a1, a2) in array.iter_mut().zip(array2.iter_mut()) {
let equals = ctx 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))?; .map(|v| v.as_bool().unwrap_or(false))?;
if !equals { if !equals {

View File

@ -1,6 +1,7 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::def_package; use crate::def_package;
use crate::fn_call::run_builtin_binary_op;
use crate::plugin::*; use crate::plugin::*;
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
@ -17,30 +18,12 @@ macro_rules! gen_cmp_functions {
#[export_module] #[export_module]
pub mod functions { pub mod functions {
#[rhai_fn(name = "<")] #[rhai_fn(name = "<")] pub fn lt(x: $arg_type, y: $arg_type) -> bool { x < y }
pub fn lt(x: $arg_type, y: $arg_type) -> bool { #[rhai_fn(name = "<=")] pub fn lte(x: $arg_type, y: $arg_type) -> bool { x <= y }
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 = "<=")] #[rhai_fn(name = "==")] pub fn eq(x: $arg_type, y: $arg_type) -> bool { x == y }
pub fn lte(x: $arg_type, y: $arg_type) -> bool { #[rhai_fn(name = "!=")] pub fn ne(x: $arg_type, y: $arg_type) -> bool { x != y }
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, { def_package!(crate:LogicPackage:"Logical operators.", lib, {
combine_with_exported_module!(lib, "logic", logic_functions);
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
@ -112,6 +97,65 @@ gen_cmp_functions!(float => f64);
#[cfg(feature = "decimal")] #[cfg(feature = "decimal")]
gen_cmp_functions!(decimal => 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"))] #[cfg(not(feature = "no_float"))]
#[export_module] #[export_module]
mod f32_functions { mod f32_functions {

View File

@ -58,12 +58,10 @@ mod map_functions {
return Ok(true.into()); return Ok(true.into());
} }
let def_value = Some(false.into());
for (m1, v1) in map.iter_mut() { for (m1, v1) in map.iter_mut() {
if let Some(v2) = map2.get_mut(m1) { if let Some(v2) = map2.get_mut(m1) {
let equals = ctx 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))?; .map(|v| v.as_bool().unwrap_or(false))?;
if !equals { if !equals {

View File

@ -1,13 +1,8 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::engine::{KEYWORD_DEBUG, KEYWORD_PRINT};
use crate::plugin::*; use crate::plugin::*;
use crate::stdlib::{ use crate::stdlib::{format, string::ToString};
fmt::{Debug, Display}, use crate::{def_package, FnPtr, ImmutableString};
format,
string::ToString,
};
use crate::{def_package, FnPtr, ImmutableString, INT};
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
use crate::Array; use crate::Array;
@ -15,145 +10,18 @@ use crate::Array;
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
use crate::Map; 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"; 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, { def_package!(crate:BasicStringPackage:"Basic string utilities, including printing.", lib, {
combine_with_exported_module!(lib, "print_debug", print_debug_functions); 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 // Register print and debug
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[inline(always)] #[inline(always)]
fn print_with_func(fn_name: &str, ctx: &NativeCallContext, value: &mut Dynamic) -> ImmutableString { 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) if result.is::<ImmutableString>() => result.take_immutable_string().unwrap(),
Ok(result) => ctx.engine().map_type_name(result.type_name()).into(), Ok(result) => ctx.engine().map_type_name(result.type_name()).into(),
Err(_) => ctx.engine().map_type_name(value.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] #[export_module]
mod print_debug_functions { 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")] #[rhai_fn(name = "print", name = "debug")]
pub fn print_empty_string() -> ImmutableString { pub fn print_empty_string() -> ImmutableString {
"".to_string().into() "".to_string().into()
} }
#[rhai_fn(name = "print", name = "to_string")] #[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 { pub fn print_string(s: ImmutableString) -> ImmutableString {
s s
} }
#[rhai_fn(name = "debug", pure)] #[rhai_fn(name = "debug", name = "to_debug", pure)]
pub fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString { 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"))] #[cfg(not(feature = "no_index"))]
@ -186,15 +96,14 @@ mod print_debug_functions {
#[rhai_fn( #[rhai_fn(
name = "print", name = "print",
name = "to_string", name = "to_string",
name = "to_debug",
name = "debug", name = "debug",
name = "to_debug",
pure pure
)] )]
pub fn format_array(ctx: NativeCallContext, array: &mut Array) -> ImmutableString { 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 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)| { array.iter_mut().enumerate().for_each(|(i, x)| {
result.push_str(&print_with_func(FUNC_TO_DEBUG, &ctx, x)); result.push_str(&print_with_func(FUNC_TO_DEBUG, &ctx, x));
@ -214,15 +123,14 @@ mod print_debug_functions {
#[rhai_fn( #[rhai_fn(
name = "print", name = "print",
name = "to_string", name = "to_string",
name = "to_debug",
name = "debug", name = "debug",
name = "to_debug",
pure pure
)] )]
pub fn format_map(ctx: NativeCallContext, map: &mut Map) -> ImmutableString { 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 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))| { map.iter_mut().enumerate().for_each(|(i, (k, v))| {
result.push_str(&format!( result.push_str(&format!(

View File

@ -4,50 +4,9 @@ use crate::plugin::*;
use crate::stdlib::{ use crate::stdlib::{
any::TypeId, boxed::Box, format, mem, string::String, string::ToString, vec::Vec, any::TypeId, boxed::Box, format, mem, string::String, string::ToString, vec::Vec,
}; };
use crate::{def_package, Dynamic, FnPtr, ImmutableString, StaticVec, INT}; use crate::{def_package, Dynamic, 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);
)* }
}
def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, { 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); combine_with_exported_module!(lib, "string", string_functions);
// Register string iterator // 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] #[export_module]
mod string_functions { mod string_functions {
use crate::ImmutableString; 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 = "+")] #[rhai_fn(name = "+")]
pub fn add_append_unit(string: ImmutableString, _x: ()) -> ImmutableString { pub fn add_append_unit(string: ImmutableString, _x: ()) -> ImmutableString {
string string
@ -88,7 +42,13 @@ mod string_functions {
pub fn len(string: &str) -> INT { pub fn len(string: &str) -> INT {
string.chars().count() as 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) { pub fn clear(string: &mut ImmutableString) {
string.make_mut().clear(); string.make_mut().clear();
} }
@ -111,15 +71,15 @@ mod string_functions {
} }
#[rhai_fn(name = "contains")] #[rhai_fn(name = "contains")]
pub fn contains_char(string: &str, ch: char) -> bool { pub fn contains_char(string: &str, character: char) -> bool {
string.contains(ch) string.contains(character)
} }
pub fn contains(string: &str, find_string: &str) -> bool { pub fn contains(string: &str, find_string: &str) -> bool {
string.contains(find_string) string.contains(find_string)
} }
#[rhai_fn(name = "index_of")] #[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 { let start = if start < 0 {
0 0
} else if start as usize >= string.chars().count() { } else if start as usize >= string.chars().count() {
@ -133,14 +93,14 @@ mod string_functions {
}; };
string[start..] string[start..]
.find(ch) .find(character)
.map(|index| string[0..start + index].chars().count() as INT) .map(|index| string[0..start + index].chars().count() as INT)
.unwrap_or(-1 as INT) .unwrap_or(-1 as INT)
} }
#[rhai_fn(name = "index_of")] #[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 string
.find(ch) .find(character)
.map(|index| string[0..index].chars().count() as INT) .map(|index| string[0..index].chars().count() as INT)
.unwrap_or(-1 as INT) .unwrap_or(-1 as INT)
} }
@ -243,26 +203,33 @@ mod string_functions {
pub fn replace_string_with_char( pub fn replace_string_with_char(
string: &mut ImmutableString, string: &mut ImmutableString,
find_string: &str, find_string: &str,
substitute_char: char, substitute_character: char,
) { ) {
*string = string *string = string
.replace(find_string, &substitute_char.to_string()) .replace(find_string, &substitute_character.to_string())
.into(); .into();
} }
#[rhai_fn(name = "replace")] #[rhai_fn(name = "replace")]
pub fn replace_char_with_string( pub fn replace_char_with_string(
string: &mut ImmutableString, string: &mut ImmutableString,
find_char: char, find_character: char,
substitute_string: &str, substitute_string: &str,
) { ) {
*string = string *string = string
.replace(&find_char.to_string(), substitute_string) .replace(&find_character.to_string(), substitute_string)
.into(); .into();
} }
#[rhai_fn(name = "replace")] #[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 *string = string
.replace(&find_char.to_string(), &substitute_char.to_string()) .replace(
&find_character.to_string(),
&substitute_character.to_string(),
)
.into(); .into();
} }
@ -271,7 +238,7 @@ mod string_functions {
_ctx: NativeCallContext, _ctx: NativeCallContext,
string: &mut ImmutableString, string: &mut ImmutableString,
len: INT, len: INT,
ch: char, character: char,
) -> Result<Dynamic, Box<crate::EvalAltResult>> { ) -> Result<Dynamic, Box<crate::EvalAltResult>> {
// Check if string will be over max size limit // Check if string will be over max size limit
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
@ -290,7 +257,7 @@ mod string_functions {
let p = string.make_mut(); let p = string.make_mut();
for _ in 0..(len as usize - orig_len) { for _ in 0..(len as usize - orig_len) {
p.push(ch); p.push(character);
} }
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
@ -363,14 +330,6 @@ mod string_functions {
use crate::stdlib::vec; use crate::stdlib::vec;
use crate::{Array, ImmutableString}; 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")] #[rhai_fn(name = "split")]
pub fn chars(string: &str) -> Array { pub fn chars(string: &str) -> Array {
string.chars().map(Into::<Dynamic>::into).collect() string.chars().map(Into::<Dynamic>::into).collect()
@ -439,18 +398,4 @@ mod string_functions {
.collect() .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()); let qualifiers = modules.iter().map(|m| m.name.as_str());
calc_script_fn_hash(qualifiers, &id, 0) calc_script_fn_hash(qualifiers, &id, 0)
} else { } else {
// Qualifiers (none) + function name + no parameters.
calc_script_fn_hash(empty(), &id, 0) 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()); let qualifiers = modules.iter().map(|m| m.name.as_str());
calc_script_fn_hash(qualifiers, &id, args.len()) calc_script_fn_hash(qualifiers, &id, args.len())
} else { } else {
// Qualifiers (none) + function name + number of arguments.
calc_script_fn_hash(empty(), &id, args.len()) calc_script_fn_hash(empty(), &id, args.len())
}; };
@ -1016,7 +1014,6 @@ fn parse_primary(
}); });
lib.insert( lib.insert(
// Qualifiers (none) + function name + number of arguments.
calc_script_fn_hash(empty(), &func.name, func.params.len()).unwrap(), calc_script_fn_hash(empty(), &func.name, func.params.len()).unwrap(),
func, func,
); );
@ -1246,7 +1243,6 @@ fn parse_primary(
} }
.map(|x| match x.as_mut() { .map(|x| match x.as_mut() {
(_, Some((ref mut hash, ref mut namespace)), Ident { name, .. }) => { (_, Some((ref mut hash, ref mut namespace)), Ident { name, .. }) => {
// Qualifiers + variable name
*hash = *hash =
calc_script_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0).unwrap(); 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 { Box::new(FnCallExpr {
name: op.into(), name: op.into(),
args, args,
def_value: Some(false.into()), // NOT operator, when operating on invalid operand, defaults to false
..Default::default() ..Default::default()
}), }),
pos, pos,
@ -1796,7 +1791,6 @@ fn parse_binary_op(
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
let cmp_def = Some(false.into());
let op = op_token.syntax(); let op = op_token.syntax();
let op_base = FnCallExpr { let op_base = FnCallExpr {
@ -1823,28 +1817,16 @@ fn parse_binary_op(
| Token::XOr => Expr::FnCall(Box::new(FnCallExpr { args, ..op_base }), pos), | Token::XOr => Expr::FnCall(Box::new(FnCallExpr { args, ..op_base }), pos),
// '!=' defaults to true when passed invalid operands // '!=' defaults to true when passed invalid operands
Token::NotEqualsTo => Expr::FnCall( Token::NotEqualsTo => Expr::FnCall(Box::new(FnCallExpr { args, ..op_base }), pos),
Box::new(FnCallExpr {
args,
def_value: Some(true.into()),
..op_base
}),
pos,
),
// Comparison operators default to false when passed invalid operands // Comparison operators default to false when passed invalid operands
Token::EqualsTo Token::EqualsTo
| Token::LessThan | Token::LessThan
| Token::LessThanEqualsTo | Token::LessThanEqualsTo
| Token::GreaterThan | Token::GreaterThan
| Token::GreaterThanEqualsTo => Expr::FnCall( | Token::GreaterThanEqualsTo => {
Box::new(FnCallExpr { Expr::FnCall(Box::new(FnCallExpr { args, ..op_base }), pos)
args, }
def_value: cmp_def,
..op_base
}),
pos,
),
Token::Or => { Token::Or => {
let rhs = args.pop().unwrap(); let rhs = args.pop().unwrap();
@ -2621,8 +2603,6 @@ fn parse_stmt(
}; };
let func = parse_fn(input, &mut new_state, lib, access, settings, _comments)?; 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(); let hash = calc_script_fn_hash(empty(), &func.name, func.params.len()).unwrap();
if lib.contains_key(&hash) { if lib.contains_key(&hash) {

View File

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

View File

@ -12,7 +12,7 @@ use crate::stdlib::{
hash::{BuildHasher, Hash, Hasher}, hash::{BuildHasher, Hash, Hasher},
iter::{empty, FromIterator}, iter::{empty, FromIterator},
num::NonZeroU64, num::NonZeroU64,
ops::{Add, AddAssign, Deref, DerefMut}, ops::{Add, AddAssign, Deref, DerefMut, Sub, SubAssign},
str::FromStr, str::FromStr,
string::{String, ToString}, string::{String, ToString},
vec::Vec, 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 { impl Add<char> for ImmutableString {
type Output = Self; 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 { impl<S: AsRef<str>> PartialEq<S> for ImmutableString {
#[inline(always)] #[inline(always)]
fn eq(&self, other: &S) -> bool { 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_eq!(engine.eval::<INT>("let x = 10; x -= 7; x")?, 3);
assert!(matches!( assert_eq!(
*engine.eval::<String>(r#"let s = "test"; s -= "ing"; s"#).expect_err("expects error"), engine.eval::<String>(r#"let s = "test"; s -= 's'; s"#)?,
EvalAltResult::ErrorFunctionNotFound(err, _) if err == "- (&str | ImmutableString | String, &str | ImmutableString | String)" "tet"
)); );
Ok(()) 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::<INT>("let x = 1; x += 2; x")?, 3);
assert_eq!( assert_eq!(
engine.eval::<String>("let s = \"test\"; s += \"ing\"; s")?, engine.eval::<String>(r#"let s = "test"; s += "ing"; s"#)?,
"testing" "testing"
); );

View File

@ -12,7 +12,7 @@ fn test_mismatched_op() {
#[test] #[test]
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
fn test_mismatched_op_custom_type() { fn test_mismatched_op_custom_type() -> Result<(), Box<EvalAltResult>> {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct TestStruct { struct TestStruct {
x: INT, x: INT,
@ -30,9 +30,18 @@ fn test_mismatched_op_custom_type() {
.register_type_with_name::<TestStruct>("TestStruct") .register_type_with_name::<TestStruct>("TestStruct")
.register_fn("new_ts", TestStruct::new); .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!( assert!(matches!(
*engine.eval::<INT>("60 + new_ts()").expect_err("should error"), *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!( assert!(matches!(
@ -40,4 +49,6 @@ fn test_mismatched_op_custom_type() {
EvalAltResult::ErrorMismatchOutputType(need, actual, _) EvalAltResult::ErrorMismatchOutputType(need, actual, _)
if need == "TestStruct" && actual == std::any::type_name::<INT>() 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] #[test]
fn test_ops() -> Result<(), Box<EvalAltResult>> { fn test_ops() -> Result<(), Box<EvalAltResult>> {
@ -11,7 +11,42 @@ fn test_ops() -> Result<(), Box<EvalAltResult>> {
} }
#[test] #[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(); let engine = Engine::new();
assert_eq!( 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}; 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] #[test]
fn test_print_debug() -> Result<(), Box<EvalAltResult>> { fn test_print_debug() -> Result<(), Box<EvalAltResult>> {
let logbook = Arc::new(RwLock::new(Vec::<String>::new())); let logbook = Arc::new(RwLock::new(Vec::<String>::new()));

View File

@ -66,7 +66,7 @@ fn test_side_effects_command() -> Result<(), Box<EvalAltResult>> {
#[test] #[test]
fn test_side_effects_print() -> Result<(), Box<EvalAltResult>> { 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(); let mut engine = Engine::new();

View File

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