Merge pull request #363 from schungx/master
Allow evaluating AST for Engine::call_fn_dynamic
This commit is contained in:
commit
1fb63c32a4
@ -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
|
@ -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";
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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"))]
|
||||
|
222
src/engine.rs
222
src/engine.rs
@ -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)
|
||||
|
@ -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();
|
||||
///
|
||||
|
268
src/fn_call.rs
268
src/fn_call.rs
@ -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),
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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))
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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!(
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
12
src/token.rs
12
src/token.rs
@ -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 == '_'
|
||||
}
|
||||
|
||||
|
127
src/utils.rs
127
src/utils.rs
@ -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 {
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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"
|
||||
);
|
||||
|
||||
|
@ -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(())
|
||||
}
|
||||
|
39
tests/ops.rs
39
tests/ops.rs
@ -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!(
|
||||
|
@ -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()));
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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')"#
|
||||
|
Loading…
Reference in New Issue
Block a user