Merge pull request #369 from schungx/master

Fix switch statement panic.
This commit is contained in:
Stephen Chung 2021-03-05 13:51:08 +08:00 committed by GitHub
commit ec0d2ddcb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 473 additions and 286 deletions

View File

@ -10,6 +10,8 @@ Bug fixes
* Errors in native Rust functions now contain the correct function call positions. * Errors in native Rust functions now contain the correct function call positions.
* Fixed error types in `EvalAltResult::ErrorMismatchDataType` which were swapped. * Fixed error types in `EvalAltResult::ErrorMismatchDataType` which were swapped.
* Some expressions involving shared variables now work properly, for example `x in shared_value`, `return shared_value`, `obj.field = shared_value` etc. Previously, the resultant value is still shared which is counter-intuitive. * Some expressions involving shared variables now work properly, for example `x in shared_value`, `return shared_value`, `obj.field = shared_value` etc. Previously, the resultant value is still shared which is counter-intuitive.
* Potential overflow panics in `range(from, to, step)` is fixed.
* `switch` statements no longer panic on custom types.
Breaking changes Breaking changes
---------------- ----------------
@ -23,6 +25,7 @@ Breaking changes
* Function keywords (e.g. `type_of`, `eval`, `Fn`) can no longer be overloaded. It is more trouble than worth. To disable these keywords, use `Engine::disable_symbol`. * Function keywords (e.g. `type_of`, `eval`, `Fn`) can no longer be overloaded. It is more trouble than worth. To disable these keywords, use `Engine::disable_symbol`.
* `is_def_var` and `is_def_fn` are now reserved keywords. * `is_def_var` and `is_def_fn` are now reserved keywords.
* `Engine::id` field is removed. * `Engine::id` field is removed.
* `num-traits` is now a required dependency.
Enhancements Enhancements
------------ ------------

View File

@ -26,6 +26,7 @@ categories = [ "no-std", "embedded", "wasm", "parser-implementations" ]
[dependencies] [dependencies]
smallvec = { version = "1.6", default-features = false, features = ["union"] } smallvec = { version = "1.6", default-features = false, features = ["union"] }
ahash = { version = "0.6", default-features = false } ahash = { version = "0.6", default-features = false }
num-traits = { version = "0.2", default_features = false }
rhai_codegen = { version = "0.3.3", path = "codegen" } rhai_codegen = { version = "0.3.3", path = "codegen" }
[features] [features]
@ -65,11 +66,6 @@ version = "0.2"
default_features = false default_features = false
optional = true optional = true
[dependencies.num-traits]
version = "0.2"
default-features = false
optional = true
[dependencies.core-error] [dependencies.core-error]
version = "0.0" version = "0.0"
default_features = false default_features = false

View File

@ -35,7 +35,7 @@ Standard features
* Built-in support for most common [data types](https://rhai.rs/book/language/values-and-types.html) including booleans, integers, floating-point numbers (including [`Decimal`](https://crates.io/crates/rust_decimal)), strings, Unicode characters, arrays and maps. * Built-in support for most common [data types](https://rhai.rs/book/language/values-and-types.html) including booleans, integers, floating-point numbers (including [`Decimal`](https://crates.io/crates/rust_decimal)), strings, Unicode characters, arrays and maps.
* Easily [call a script-defined function](https://rhai.rs/book/engine/call-fn.html) from Rust. * Easily [call a script-defined function](https://rhai.rs/book/engine/call-fn.html) from Rust.
* Relatively little `unsafe` code (yes there are some for performance reasons). * Relatively little `unsafe` code (yes there are some for performance reasons).
* Few dependencies (currently only [`smallvec`](https://crates.io/crates/smallvec) and [`ahash`](https://crates.io/crates/ahash)). * Few dependencies (currently only [`smallvec`](https://crates.io/crates/smallvec), [`num-traits`](https://crates.io/crates/num-traits) and [`ahash`](https://crates.io/crates/ahash)).
* Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature). * Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature).
* Compile once to [AST](https://rhai.rs/book/engine/compile.html) form for repeated evaluations. * Compile once to [AST](https://rhai.rs/book/engine/compile.html) form for repeated evaluations.
* Scripts are [optimized](https://rhai.rs/book/engine/optimize.html) (useful for template-based machine-generated scripts). * Scripts are [optimized](https://rhai.rs/book/engine/optimize.html) (useful for template-based machine-generated scripts).

View File

@ -1109,7 +1109,7 @@ pub struct FloatWrapper(FLOAT);
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
impl Hash for FloatWrapper { impl Hash for FloatWrapper {
fn hash<H: crate::stdlib::hash::Hasher>(&self, state: &mut H) { fn hash<H: crate::stdlib::hash::Hasher>(&self, state: &mut H) {
self.0.to_le_bytes().hash(state); self.0.to_ne_bytes().hash(state);
} }
} }

View File

@ -414,13 +414,13 @@ impl Hash for Dynamic {
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
Union::Shared(cell, _) => (*cell.read().unwrap()).hash(state), Union::Shared(cell, _) => (*cell.read().unwrap()).hash(state),
_ => unimplemented!(), _ => unimplemented!("{} cannot be hashed", self.type_name()),
} }
} }
} }
/// Map the name of a standard type into a friendly form. /// Map the name of a standard type into a friendly form.
#[inline] #[inline(always)]
pub(crate) fn map_std_type_name(name: &str) -> &str { pub(crate) fn map_std_type_name(name: &str) -> &str {
if name == type_name::<String>() { if name == type_name::<String>() {
"string" "string"
@ -761,6 +761,32 @@ impl Dynamic {
_ => self.access_mode().is_read_only(), _ => self.access_mode().is_read_only(),
} }
} }
/// Can this [`Dynamic`] be hashed?
pub(crate) fn is_hashable(&self) -> bool {
match &self.0 {
Union::Unit(_, _)
| Union::Bool(_, _)
| Union::Str(_, _)
| Union::Char(_, _)
| Union::Int(_, _) => true,
#[cfg(not(feature = "no_float"))]
Union::Float(_, _) => true,
#[cfg(not(feature = "no_index"))]
Union::Array(_, _) => true,
#[cfg(not(feature = "no_object"))]
Union::Map(_, _) => true,
#[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))]
Union::Shared(cell, _) => cell.borrow().is_hashable(),
#[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")]
Union::Shared(cell, _) => cell.read().unwrap().is_hashable(),
_ => false,
}
}
/// Create a [`Dynamic`] from any type. A [`Dynamic`] value is simply returned as is. /// Create a [`Dynamic`] from any type. A [`Dynamic`] value is simply returned as is.
/// ///
/// # Safety /// # Safety
@ -1496,7 +1522,7 @@ impl Dynamic {
} }
/// Convert the [`Dynamic`] into an [`ImmutableString`] and return it. /// Convert the [`Dynamic`] into an [`ImmutableString`] and return it.
/// Returns the name of the actual type if the cast fails. /// Returns the name of the actual type if the cast fails.
#[inline] #[inline(always)]
pub fn take_immutable_string(self) -> Result<ImmutableString, &'static str> { pub fn take_immutable_string(self) -> Result<ImmutableString, &'static str> {
match self.0 { match self.0 {
Union::Str(s, _) => Ok(s), Union::Str(s, _) => Ok(s),

View File

@ -862,7 +862,7 @@ impl Engine {
/// Create a new [`Engine`] with minimal built-in functions. /// Create a new [`Engine`] with minimal built-in functions.
/// ///
/// Use [`register_global_module`][Engine::register_global_module] to add packages of functions. /// Use [`register_global_module`][Engine::register_global_module] to add packages of functions.
#[inline] #[inline(always)]
pub fn new_raw() -> Self { pub fn new_raw() -> Self {
Self { Self {
global_namespace: Default::default(), global_namespace: Default::default(),
@ -2111,18 +2111,29 @@ impl Engine {
Stmt::Switch(match_expr, x, _) => { Stmt::Switch(match_expr, x, _) => {
let (table, def_stmt) = x.as_ref(); let (table, def_stmt) = x.as_ref();
let value = self.eval_expr(scope, mods, state, lib, this_ptr, match_expr, level)?;
if value.is_hashable() {
let hasher = &mut get_hasher(); let hasher = &mut get_hasher();
self.eval_expr(scope, mods, state, lib, this_ptr, match_expr, level)? value.hash(hasher);
.hash(hasher);
let hash = hasher.finish(); let hash = hasher.finish();
if let Some(stmt) = table.get(&hash) { table
self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level) .get(&hash)
} else if let Some(def_stmt) = def_stmt { .map(|stmt| self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level))
self.eval_stmt(scope, mods, state, lib, this_ptr, def_stmt, level)
} else { } else {
Ok(Dynamic::UNIT) // Non-hashable values never match any specific clause
None
} }
.unwrap_or_else(|| {
// Default match clause
def_stmt.as_ref().map_or_else(
|| Ok(Dynamic::UNIT),
|def_stmt| {
self.eval_stmt(scope, mods, state, lib, this_ptr, def_stmt, level)
},
)
})
} }
// While loop // While loop

View File

@ -1034,7 +1034,6 @@ impl Engine {
/// Read the contents of a file into a string. /// Read the contents of a file into a string.
#[cfg(not(feature = "no_std"))] #[cfg(not(feature = "no_std"))]
#[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))] #[cfg(not(any(target_arch = "wasm32", target_arch = "wasm64")))]
#[inline]
fn read_file(path: crate::stdlib::path::PathBuf) -> Result<String, Box<EvalAltResult>> { fn read_file(path: crate::stdlib::path::PathBuf) -> Result<String, Box<EvalAltResult>> {
use crate::stdlib::io::Read; use crate::stdlib::io::Read;
@ -1276,7 +1275,7 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[inline] #[inline(always)]
pub fn compile_expression_with_scope( pub fn compile_expression_with_scope(
&self, &self,
scope: &Scope, scope: &Scope,
@ -1385,7 +1384,7 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[inline] #[inline(always)]
pub fn eval_with_scope<T: Variant + Clone>( pub fn eval_with_scope<T: Variant + Clone>(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
@ -1437,7 +1436,7 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[inline] #[inline(always)]
pub fn eval_expression_with_scope<T: Variant + Clone>( pub fn eval_expression_with_scope<T: Variant + Clone>(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
@ -1502,7 +1501,7 @@ impl Engine {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[inline] #[inline(always)]
pub fn eval_ast_with_scope<T: Variant + Clone>( pub fn eval_ast_with_scope<T: Variant + Clone>(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
@ -1578,7 +1577,7 @@ impl Engine {
} }
/// Evaluate a string with own scope, but throw away the result and only return error (if any). /// Evaluate a string with own scope, but throw away the result and only return error (if any).
/// Useful for when you don't need the result, but still need to keep track of possible errors. /// Useful for when you don't need the result, but still need to keep track of possible errors.
#[inline] #[inline(always)]
pub fn consume_with_scope( pub fn consume_with_scope(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
@ -1659,7 +1658,7 @@ impl Engine {
/// # } /// # }
/// ``` /// ```
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline] #[inline(always)]
pub fn call_fn<T: Variant + Clone>( pub fn call_fn<T: Variant + Clone>(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
@ -1760,7 +1759,7 @@ impl Engine {
/// Do not use the arguments after this call. If they are needed afterwards, /// Do not use the arguments after this call. If they are needed afterwards,
/// clone them _before_ calling this function. /// clone them _before_ calling this function.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
#[inline] #[inline(always)]
pub(crate) fn call_fn_dynamic_raw( pub(crate) fn call_fn_dynamic_raw(
&self, &self,
scope: &mut Scope, scope: &mut Scope,
@ -1814,7 +1813,7 @@ impl Engine {
/// (i.e. with [`Scope::push_constant`]). /// (i.e. with [`Scope::push_constant`]).
/// Then, the [`AST`] is cloned and the copy re-optimized before running. /// Then, the [`AST`] is cloned and the copy re-optimized before running.
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
#[inline] #[inline(always)]
pub fn optimize_ast( pub fn optimize_ast(
&self, &self,
scope: &Scope, scope: &Scope,

View File

@ -15,6 +15,7 @@ use rust_decimal::Decimal;
use num_traits::float::Float; use num_traits::float::Float;
/// Is the type a numeric type? /// Is the type a numeric type?
#[inline(always)]
fn is_numeric(type_id: TypeId) -> bool { fn is_numeric(type_id: TypeId) -> bool {
let result = type_id == TypeId::of::<u8>() let result = type_id == TypeId::of::<u8>()
|| type_id == TypeId::of::<u16>() || type_id == TypeId::of::<u16>()
@ -75,55 +76,48 @@ pub fn get_builtin_binary_op_fn(
Ok((x $op y).into()) Ok((x $op y).into())
}) })
}; };
($xx:ident $op:tt $yy:ident -> $base:ty) => { ($base:ty => $xx:ident $op:tt $yy:ident) => {
return Some(|_, args| { return Some(|_, args| {
let x = args[0].$xx().unwrap() as $base; let x = args[0].$xx().unwrap() as $base;
let y = args[1].$yy().unwrap() as $base; let y = args[1].$yy().unwrap() as $base;
Ok((x $op y).into()) Ok((x $op y).into())
}) })
}; };
($xx:ident . $func:ident ( $yy:ident as $yyy:ty) -> $base:ty) => { ($base:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty)) => {
return Some(|_, args| { return Some(|_, args| {
let x = args[0].$xx().unwrap() as $base; let x = args[0].$xx().unwrap() as $base;
let y = args[1].$yy().unwrap() as $base; let y = args[1].$yy().unwrap() as $base;
Ok(x.$func(y as $yyy).into()) Ok(x.$func(y as $yyy).into())
}) })
}; };
($func:ident ( $xx:ident, $yy:ident ) -> $base:ty) => { ($base:ty => $func:ident ( $xx:ident, $yy:ident )) => {
return Some(|_, args| { return Some(|_, args| {
let x = args[0].$xx().unwrap() as $base; let x = args[0].$xx().unwrap() as $base;
let y = args[1].$yy().unwrap() as $base; let y = args[1].$yy().unwrap() as $base;
$func(x, y) $func(x, y)
}) })
}; };
($xx:ident $op:tt $yy:ident -> from $base:ty) => { (from $base:ty => $xx:ident $op:tt $yy:ident) => {
return Some(|_, args| { return Some(|_, args| {
let x = <$base>::from(args[0].$xx().unwrap()); let x = <$base>::from(args[0].$xx().unwrap());
let y = <$base>::from(args[1].$yy().unwrap()); let y = <$base>::from(args[1].$yy().unwrap());
Ok((x $op y).into()) Ok((x $op y).into())
}) })
}; };
($xx:ident . $func:ident ( $yy:ident ) -> from $base:ty) => { (from $base:ty => $xx:ident . $func:ident ( $yy:ident )) => {
return Some(|_, args| { return Some(|_, args| {
let x = <$base>::from(args[0].$xx().unwrap()); let x = <$base>::from(args[0].$xx().unwrap());
let y = <$base>::from(args[1].$yy().unwrap()); let y = <$base>::from(args[1].$yy().unwrap());
Ok(x.$func(y).into()) Ok(x.$func(y).into())
}) })
}; };
($func:ident ( $xx:ident, $yy:ident ) -> from $base:ty) => { (from $base:ty => $func:ident ( $xx:ident, $yy:ident )) => {
return Some(|_, args| { return Some(|_, args| {
let x = <$base>::from(args[0].$xx().unwrap()); let x = <$base>::from(args[0].$xx().unwrap());
let y = <$base>::from(args[1].$yy().unwrap()); let y = <$base>::from(args[1].$yy().unwrap());
$func(x, y) $func(x, y)
}) })
}; };
(& $x:ident $op:tt & $y:ident) => {
return Some(|_, args| {
let x = &*args[0].read_lock::<$x>().unwrap();
let y = &*args[1].read_lock::<$y>().unwrap();
Ok((x $op y).into())
})
}
} }
macro_rules! impl_float { macro_rules! impl_float {
@ -131,18 +125,18 @@ pub fn get_builtin_binary_op_fn(
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
if types_pair == (TypeId::of::<$x>(), TypeId::of::<$y>()) { if types_pair == (TypeId::of::<$x>(), TypeId::of::<$y>()) {
match op { match op {
"+" => impl_op!($xx + $yy -> FLOAT), "+" => impl_op!(FLOAT => $xx + $yy),
"-" => impl_op!($xx - $yy -> FLOAT), "-" => impl_op!(FLOAT => $xx - $yy),
"*" => impl_op!($xx * $yy -> FLOAT), "*" => impl_op!(FLOAT => $xx * $yy),
"/" => impl_op!($xx / $yy -> FLOAT), "/" => impl_op!(FLOAT => $xx / $yy),
"%" => impl_op!($xx % $yy -> FLOAT), "%" => impl_op!(FLOAT => $xx % $yy),
"**" => impl_op!($xx.powf($yy as FLOAT) -> FLOAT), "**" => impl_op!(FLOAT => $xx.powf($yy as FLOAT)),
"==" => impl_op!($xx == $yy -> FLOAT), "==" => impl_op!(FLOAT => $xx == $yy),
"!=" => impl_op!($xx != $yy -> FLOAT), "!=" => impl_op!(FLOAT => $xx != $yy),
">" => impl_op!($xx > $yy -> FLOAT), ">" => impl_op!(FLOAT => $xx > $yy),
">=" => impl_op!($xx >= $yy -> FLOAT), ">=" => impl_op!(FLOAT => $xx >= $yy),
"<" => impl_op!($xx < $yy -> FLOAT), "<" => impl_op!(FLOAT => $xx < $yy),
"<=" => impl_op!($xx <= $yy -> FLOAT), "<=" => impl_op!(FLOAT => $xx <= $yy),
_ => return None, _ => return None,
} }
} }
@ -161,31 +155,31 @@ pub fn get_builtin_binary_op_fn(
use crate::packages::arithmetic::decimal_functions::*; use crate::packages::arithmetic::decimal_functions::*;
match op { match op {
"+" => impl_op!(add($xx, $yy) -> from Decimal), "+" => impl_op!(from Decimal => add($xx, $yy)),
"-" => impl_op!(subtract($xx, $yy) -> from Decimal), "-" => impl_op!(from Decimal => subtract($xx, $yy)),
"*" => impl_op!(multiply($xx, $yy) -> from Decimal), "*" => impl_op!(from Decimal => multiply($xx, $yy)),
"/" => impl_op!(divide($xx, $yy) -> from Decimal), "/" => impl_op!(from Decimal => divide($xx, $yy)),
"%" => impl_op!(modulo($xx, $yy) -> from Decimal), "%" => impl_op!(from Decimal => modulo($xx, $yy)),
_ => () _ => ()
} }
} else { } else {
match op { match op {
"+" => impl_op!($xx + $yy -> from Decimal), "+" => impl_op!(from Decimal => $xx + $yy),
"-" => impl_op!($xx - $yy -> from Decimal), "-" => impl_op!(from Decimal => $xx - $yy),
"*" => impl_op!($xx * $yy -> from Decimal), "*" => impl_op!(from Decimal => $xx * $yy),
"/" => impl_op!($xx / $yy -> from Decimal), "/" => impl_op!(from Decimal => $xx / $yy),
"%" => impl_op!($xx % $yy -> from Decimal), "%" => impl_op!(from Decimal => $xx % $yy),
_ => () _ => ()
} }
} }
match op { match op {
"==" => impl_op!($xx == $yy -> from Decimal), "==" => impl_op!(from Decimal => $xx == $yy),
"!=" => impl_op!($xx != $yy -> from Decimal), "!=" => impl_op!(from Decimal => $xx != $yy),
">" => impl_op!($xx > $yy -> from Decimal), ">" => impl_op!(from Decimal => $xx > $yy),
">=" => impl_op!($xx >= $yy -> from Decimal), ">=" => impl_op!(from Decimal => $xx >= $yy),
"<" => impl_op!($xx < $yy -> from Decimal), "<" => impl_op!(from Decimal => $xx < $yy),
"<=" => impl_op!($xx <= $yy -> from Decimal), "<=" => impl_op!(from Decimal => $xx <= $yy),
_ => return None _ => return None
} }
} }
@ -278,81 +272,100 @@ pub fn get_builtin_binary_op_fn(
use crate::packages::arithmetic::arith_basic::INT::functions::*; use crate::packages::arithmetic::arith_basic::INT::functions::*;
match op { match op {
"+" => impl_op!(add(as_int, as_int) -> INT), "+" => impl_op!(INT => add(as_int, as_int)),
"-" => impl_op!(subtract(as_int, as_int) -> INT), "-" => impl_op!(INT => subtract(as_int, as_int)),
"*" => impl_op!(multiply(as_int, as_int) -> INT), "*" => impl_op!(INT => multiply(as_int, as_int)),
"/" => impl_op!(divide(as_int, as_int) -> INT), "/" => impl_op!(INT => divide(as_int, as_int)),
"%" => impl_op!(modulo(as_int, as_int) -> INT), "%" => impl_op!(INT => modulo(as_int, as_int)),
"**" => impl_op!(power(as_int, as_int) -> INT), "**" => impl_op!(INT => power(as_int, as_int)),
">>" => impl_op!(shift_right(as_int, as_int) -> INT), ">>" => impl_op!(INT => shift_right(as_int, as_int)),
"<<" => impl_op!(shift_left(as_int, as_int) -> INT), "<<" => impl_op!(INT => shift_left(as_int, as_int)),
_ => (), _ => (),
} }
} else { } else {
match op { match op {
"+" => impl_op!(as_int + as_int -> INT), "+" => impl_op!(INT => as_int + as_int),
"-" => impl_op!(as_int - as_int -> INT), "-" => impl_op!(INT => as_int - as_int),
"*" => impl_op!(as_int * as_int -> INT), "*" => impl_op!(INT => as_int * as_int),
"/" => impl_op!(as_int / as_int -> INT), "/" => impl_op!(INT => as_int / as_int),
"%" => impl_op!(as_int % as_int -> INT), "%" => impl_op!(INT => as_int % as_int),
"**" => impl_op!(as_int.pow(as_int as u32) -> INT), "**" => impl_op!(INT => as_int.pow(as_int as u32)),
">>" => impl_op!(as_int >> as_int -> INT), ">>" => impl_op!(INT => as_int >> as_int),
"<<" => impl_op!(as_int << as_int -> INT), "<<" => impl_op!(INT => as_int << as_int),
_ => (), _ => (),
} }
} }
match op { match op {
"==" => impl_op!(as_int == as_int -> INT), "==" => impl_op!(INT => as_int == as_int),
"!=" => impl_op!(as_int != as_int -> INT), "!=" => impl_op!(INT => as_int != as_int),
">" => impl_op!(as_int > as_int -> INT), ">" => impl_op!(INT => as_int > as_int),
">=" => impl_op!(as_int >= as_int -> INT), ">=" => impl_op!(INT => as_int >= as_int),
"<" => impl_op!(as_int < as_int -> INT), "<" => impl_op!(INT => as_int < as_int),
"<=" => impl_op!(as_int <= as_int -> INT), "<=" => impl_op!(INT => as_int <= as_int),
"&" => impl_op!(as_int & as_int -> INT), "&" => impl_op!(INT => as_int & as_int),
"|" => impl_op!(as_int | as_int -> INT), "|" => impl_op!(INT => as_int | as_int),
"^" => impl_op!(as_int ^ as_int -> INT), "^" => impl_op!(INT => as_int ^ as_int),
_ => return None, _ => return None,
} }
} }
if type1 == TypeId::of::<bool>() { if type1 == TypeId::of::<bool>() {
match op { match op {
"==" => impl_op!(as_bool == as_bool -> bool), "==" => impl_op!(bool => as_bool == as_bool),
"!=" => impl_op!(as_bool != as_bool -> bool), "!=" => impl_op!(bool => as_bool != as_bool),
">" => impl_op!(as_bool > as_bool -> bool), ">" => impl_op!(bool => as_bool > as_bool),
">=" => impl_op!(as_bool >= as_bool -> bool), ">=" => impl_op!(bool => as_bool >= as_bool),
"<" => impl_op!(as_bool < as_bool -> bool), "<" => impl_op!(bool => as_bool < as_bool),
"<=" => impl_op!(as_bool <= as_bool -> bool), "<=" => impl_op!(bool => as_bool <= as_bool),
"&" => impl_op!(as_bool & as_bool -> bool), "&" => impl_op!(bool => as_bool & as_bool),
"|" => impl_op!(as_bool | as_bool -> bool), "|" => impl_op!(bool => as_bool | as_bool),
"^" => impl_op!(as_bool ^ as_bool -> bool), "^" => impl_op!(bool => as_bool ^ as_bool),
_ => return None, _ => return None,
} }
} }
if type1 == TypeId::of::<ImmutableString>() { if type1 == TypeId::of::<ImmutableString>() {
match op { match op {
"+" => impl_op!(&ImmutableString + &ImmutableString), "+" => {
"-" => impl_op!(&ImmutableString - &ImmutableString), return Some(|_, args| {
"==" => impl_op!(&ImmutableString == &ImmutableString), let x = &*args[0].read_lock::<ImmutableString>().unwrap();
"!=" => impl_op!(&ImmutableString != &ImmutableString), let y = &*args[1].read_lock::<ImmutableString>().unwrap();
">" => impl_op!(&ImmutableString > &ImmutableString), Ok((x + y).into())
">=" => impl_op!(&ImmutableString >= &ImmutableString), })
"<" => impl_op!(&ImmutableString < &ImmutableString), }
"<=" => impl_op!(&ImmutableString <= &ImmutableString), "-" => {
return Some(|_, args| {
let x = &*args[0].read_lock::<ImmutableString>().unwrap();
let y = &*args[1].read_lock::<ImmutableString>().unwrap();
Ok((x - y).into())
})
}
"==" => impl_op!(&str => as_str == as_str),
"!=" => impl_op!(&str => as_str != as_str),
">" => impl_op!(&str => as_str > as_str),
">=" => impl_op!(&str => as_str >= as_str),
"<" => impl_op!(&str => as_str < as_str),
"<=" => impl_op!(&str => as_str <= as_str),
_ => return None, _ => return None,
} }
} }
if type1 == TypeId::of::<char>() { if type1 == TypeId::of::<char>() {
match op { match op {
"==" => impl_op!(as_char == as_char -> char), "+" => {
"!=" => impl_op!(as_char != as_char -> char), return Some(|_, args| {
">" => impl_op!(as_char > as_char -> char), let x = args[0].as_char().unwrap();
">=" => impl_op!(as_char >= as_char -> char), let y = args[1].as_char().unwrap();
"<" => impl_op!(as_char < as_char -> char), Ok(format!("{}{}", x, y).into())
"<=" => impl_op!(as_char <= as_char -> char), })
}
"==" => impl_op!(char => as_char == as_char),
"!=" => impl_op!(char => as_char != as_char),
">" => impl_op!(char => as_char > as_char),
">=" => impl_op!(char => as_char >= as_char),
"<" => impl_op!(char => as_char < as_char),
"<=" => impl_op!(char => as_char <= as_char),
_ => return None, _ => return None,
} }
} }
@ -380,7 +393,7 @@ pub fn get_builtin_op_assignment_fn(
let types_pair = (type1, type2); let types_pair = (type1, type2);
macro_rules! impl_op { macro_rules! impl_op {
($x:ident = x $op:tt $yy:ident) => { ($x:ty = x $op:tt $yy:ident) => {
return Some(|_, args| { return Some(|_, args| {
let x = args[0].$yy().unwrap(); let x = args[0].$yy().unwrap();
let y = args[1].$yy().unwrap() as $x; let y = args[1].$yy().unwrap() as $x;
@ -399,14 +412,14 @@ pub fn get_builtin_op_assignment_fn(
Ok((*args[0].write_lock::<$x>().unwrap() $op y).into()) Ok((*args[0].write_lock::<$x>().unwrap() $op y).into())
}) })
}; };
($xx:ident . $func:ident ( $yy:ident as $yyy:ty ) -> $x:ty) => { ($x:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty )) => {
return Some(|_, args| { return Some(|_, args| {
let x = args[0].$xx().unwrap(); let x = args[0].$xx().unwrap();
let y = args[1].$yy().unwrap() as $x; let y = args[1].$yy().unwrap() as $x;
Ok((*args[0].write_lock::<$x>().unwrap() = x.$func(y as $yyy)).into()) Ok((*args[0].write_lock::<$x>().unwrap() = x.$func(y as $yyy)).into())
}) })
}; };
($func:ident ( $xx:ident, $yy:ident ) -> $x:ty) => { ($x:ty => $func:ident ( $xx:ident, $yy:ident )) => {
return Some(|_, args| { return Some(|_, args| {
let x = args[0].$xx().unwrap(); let x = args[0].$xx().unwrap();
let y = args[1].$yy().unwrap() as $x; let y = args[1].$yy().unwrap() as $x;
@ -419,14 +432,14 @@ pub fn get_builtin_op_assignment_fn(
Ok((*args[0].write_lock::<$x>().unwrap() $op y).into()) Ok((*args[0].write_lock::<$x>().unwrap() $op y).into())
}) })
}; };
($xx:ident . $func:ident ( $yy:ident ) -> from $x:ty) => { (from $x:ty => $xx:ident . $func:ident ( $yy:ident )) => {
return Some(|_, args| { return Some(|_, args| {
let x = args[0].$xx().unwrap(); let x = args[0].$xx().unwrap();
let y = <$x>::from(args[1].$yy().unwrap()); let y = <$x>::from(args[1].$yy().unwrap());
Ok((*args[0].write_lock::<$x>().unwrap() = x.$func(y)).into()) Ok((*args[0].write_lock::<$x>().unwrap() = x.$func(y)).into())
}) })
}; };
($func:ident ( $xx:ident, $yy:ident ) -> from $x:ty) => { (from $x:ty => $func:ident ( $xx:ident, $yy:ident )) => {
return Some(|_, args| { return Some(|_, args| {
let x = args[0].$xx().unwrap(); let x = args[0].$xx().unwrap();
let y = <$x>::from(args[1].$yy().unwrap()); let y = <$x>::from(args[1].$yy().unwrap());
@ -445,7 +458,7 @@ pub fn get_builtin_op_assignment_fn(
"*=" => impl_op!($x *= $yy), "*=" => impl_op!($x *= $yy),
"/=" => impl_op!($x /= $yy), "/=" => impl_op!($x /= $yy),
"%=" => impl_op!($x %= $yy), "%=" => impl_op!($x %= $yy),
"**=" => impl_op!($xx.powf($yy as $x) -> $x), "**=" => impl_op!($x => $xx.powf($yy as $x)),
_ => return None, _ => return None,
} }
} }
@ -463,11 +476,11 @@ pub fn get_builtin_op_assignment_fn(
use crate::packages::arithmetic::decimal_functions::*; use crate::packages::arithmetic::decimal_functions::*;
match op { match op {
"+=" => impl_op!(add($xx, $yy) -> from $x), "+=" => impl_op!(from $x => add($xx, $yy)),
"-=" => impl_op!(subtract($xx, $yy) -> from $x), "-=" => impl_op!(from $x => subtract($xx, $yy)),
"*=" => impl_op!(multiply($xx, $yy) -> from $x), "*=" => impl_op!(from $x => multiply($xx, $yy)),
"/=" => impl_op!(divide($xx, $yy) -> from $x), "/=" => impl_op!(from $x => divide($xx, $yy)),
"%=" => impl_op!(modulo($xx, $yy) -> from $x), "%=" => impl_op!(from $x => modulo($xx, $yy)),
_ => return None, _ => return None,
} }
} else { } else {
@ -522,14 +535,14 @@ pub fn get_builtin_op_assignment_fn(
use crate::packages::arithmetic::arith_basic::INT::functions::*; use crate::packages::arithmetic::arith_basic::INT::functions::*;
match op { match op {
"+=" => impl_op!(add(as_int, as_int) -> INT), "+=" => impl_op!(INT => add(as_int, as_int)),
"-=" => impl_op!(subtract(as_int, as_int) -> INT), "-=" => impl_op!(INT => subtract(as_int, as_int)),
"*=" => impl_op!(multiply(as_int, as_int) -> INT), "*=" => impl_op!(INT => multiply(as_int, as_int)),
"/=" => impl_op!(divide(as_int, as_int) -> INT), "/=" => impl_op!(INT => divide(as_int, as_int)),
"%=" => impl_op!(modulo(as_int, as_int) -> INT), "%=" => impl_op!(INT => modulo(as_int, as_int)),
"**=" => impl_op!(power(as_int, as_int) -> INT), "**=" => impl_op!(INT => power(as_int, as_int)),
">>=" => impl_op!(shift_right(as_int, as_int) -> INT), ">>=" => impl_op!(INT => shift_right(as_int, as_int)),
"<<=" => impl_op!(shift_left(as_int, as_int) -> INT), "<<=" => impl_op!(INT => shift_left(as_int, as_int)),
_ => (), _ => (),
} }
} else { } else {
@ -539,7 +552,7 @@ pub fn get_builtin_op_assignment_fn(
"*=" => impl_op!(INT *= as_int), "*=" => impl_op!(INT *= as_int),
"/=" => impl_op!(INT /= as_int), "/=" => impl_op!(INT /= as_int),
"%=" => impl_op!(INT %= as_int), "%=" => impl_op!(INT %= as_int),
"**=" => impl_op!(as_int.pow(as_int as u32) -> INT), "**=" => impl_op!(INT => as_int.pow(as_int as u32)),
">>=" => impl_op!(INT >>= as_int), ">>=" => impl_op!(INT >>= as_int),
"<<=" => impl_op!(INT <<= as_int), "<<=" => impl_op!(INT <<= as_int),
_ => (), _ => (),

View File

@ -176,7 +176,7 @@ impl Engine {
/// 3) Global modules - packages /// 3) Global modules - packages
/// 4) Imported modules - functions marked with global namespace /// 4) Imported modules - functions marked with global namespace
/// 5) Global sub-modules - functions marked with global namespace /// 5) Global sub-modules - functions marked with global namespace
#[inline] #[inline(always)]
fn resolve_function<'s>( fn resolve_function<'s>(
&self, &self,
mods: &Imports, mods: &Imports,
@ -831,7 +831,7 @@ impl Engine {
/// Evaluate a list of statements with no `this` pointer. /// Evaluate a list of statements with no `this` pointer.
/// This is commonly used to evaluate a list of statements in an [`AST`] or a script function body. /// This is commonly used to evaluate a list of statements in an [`AST`] or a script function body.
#[inline] #[inline(always)]
pub(crate) fn eval_global_statements( pub(crate) fn eval_global_statements(
&self, &self,
scope: &mut Scope, scope: &mut Scope,

View File

@ -18,11 +18,6 @@ use crate::{
Position, RhaiResult, Position, RhaiResult,
}; };
#[cfg(not(feature = "sync"))]
use crate::stdlib::rc::Rc;
#[cfg(feature = "sync")]
use crate::stdlib::sync::Arc;
/// Trait that maps to `Send + Sync` only under the `sync` feature. /// Trait that maps to `Send + Sync` only under the `sync` feature.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub trait SendSync: Send + Sync {} pub trait SendSync: Send + Sync {}
@ -39,19 +34,19 @@ impl<T> SendSync for T {}
/// Immutable reference-counted container. /// Immutable reference-counted container.
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type Shared<T> = Rc<T>; pub use crate::stdlib::rc::Rc as Shared;
/// Immutable reference-counted container. /// Immutable reference-counted container.
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type Shared<T> = Arc<T>; pub use crate::stdlib::sync::Arc as Shared;
/// Synchronized shared object. /// Synchronized shared object.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
pub type Locked<T> = crate::stdlib::cell::RefCell<T>; pub use crate::stdlib::cell::RefCell as Locked;
/// Synchronized shared object. /// Synchronized shared object.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type Locked<T> = crate::stdlib::sync::RwLock<T>; pub use crate::stdlib::sync::RwLock as Locked;
/// Context of a native Rust function call. /// Context of a native Rust function call.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
@ -215,10 +210,7 @@ impl<'a> NativeCallContext<'a> {
/// If the resource is shared (i.e. has other outstanding references), a cloned copy is used. /// If the resource is shared (i.e. has other outstanding references), a cloned copy is used.
#[inline(always)] #[inline(always)]
pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T { pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T {
#[cfg(not(feature = "sync"))] Shared::make_mut(value)
return Rc::make_mut(value);
#[cfg(feature = "sync")]
return Arc::make_mut(value);
} }
/// Consume a [`Shared`] resource if is unique (i.e. not shared), or clone it otherwise. /// Consume a [`Shared`] resource if is unique (i.e. not shared), or clone it otherwise.
@ -230,10 +222,7 @@ pub fn shared_take_or_clone<T: Clone>(value: Shared<T>) -> T {
/// Consume a [`Shared`] resource if is unique (i.e. not shared). /// Consume a [`Shared`] resource if is unique (i.e. not shared).
#[inline(always)] #[inline(always)]
pub fn shared_try_take<T>(value: Shared<T>) -> Result<T, Shared<T>> { pub fn shared_try_take<T>(value: Shared<T>) -> Result<T, Shared<T>> {
#[cfg(not(feature = "sync"))] Shared::try_unwrap(value)
return Rc::try_unwrap(value);
#[cfg(feature = "sync")]
return Arc::try_unwrap(value);
} }
/// Consume a [`Shared`] resource, assuming that it is unique (i.e. not shared). /// Consume a [`Shared`] resource, assuming that it is unique (i.e. not shared).

View File

@ -183,7 +183,7 @@ macro_rules! def_register {
RET: Variant + Clone RET: Variant + Clone
> RegisterFn<FN, ($($mark,)*), RET> for Engine > RegisterFn<FN, ($($mark,)*), RET> for Engine
{ {
#[inline] #[inline(always)]
fn register_fn(&mut self, name: &str, f: FN) -> &mut Self { fn register_fn(&mut self, name: &str, f: FN) -> &mut Self {
self.global_namespace.set_fn(name, FnNamespace::Global, FnAccess::Public, None, self.global_namespace.set_fn(name, FnNamespace::Global, FnAccess::Public, None,
&[$(map_type_id::<$par>()),*], &[$(map_type_id::<$par>()),*],
@ -198,7 +198,7 @@ macro_rules! def_register {
FN: Fn($($param),*) -> RhaiResult + SendSync + 'static, FN: Fn($($param),*) -> RhaiResult + SendSync + 'static,
> RegisterResultFn<FN, ($($mark,)*)> for Engine > RegisterResultFn<FN, ($($mark,)*)> for Engine
{ {
#[inline] #[inline(always)]
fn register_result_fn(&mut self, name: &str, f: FN) -> &mut Self { fn register_result_fn(&mut self, name: &str, f: FN) -> &mut Self {
self.global_namespace.set_fn(name, FnNamespace::Global, FnAccess::Public, None, self.global_namespace.set_fn(name, FnNamespace::Global, FnAccess::Public, None,
&[$(map_type_id::<$par>()),*], &[$(map_type_id::<$par>()),*],

View File

@ -125,7 +125,7 @@ pub type FLOAT = f32;
pub use ast::{FnAccess, ScriptFnMetadata, AST}; pub use ast::{FnAccess, ScriptFnMetadata, AST};
pub use dynamic::Dynamic; pub use dynamic::Dynamic;
pub use engine::{Engine, EvalContext}; pub use engine::{Engine, EvalContext};
pub use fn_native::{FnPtr, NativeCallContext, Shared}; pub use fn_native::{FnPtr, NativeCallContext};
pub use fn_register::{RegisterFn, RegisterResultFn}; pub use fn_register::{RegisterFn, RegisterResultFn};
pub use module::{FnNamespace, Module}; pub use module::{FnNamespace, Module};
pub use parse_error::{LexError, ParseError, ParseErrorType}; pub use parse_error::{LexError, ParseError, ParseErrorType};
@ -135,6 +135,9 @@ pub use syntax::Expression;
pub use token::Position; pub use token::Position;
pub use utils::ImmutableString; pub use utils::ImmutableString;
/// Alias to [`Rc`][std::rc::Rc] or [`Arc`][std::sync::Arc] depending on the `sync` feature flag.
pub use fn_native::Shared;
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
use fn_native::Locked; use fn_native::Locked;

View File

@ -598,7 +598,7 @@ impl Module {
/// let hash = module.set_fn_0("calc", || Ok(42_i64)); /// let hash = module.set_fn_0("calc", || Ok(42_i64));
/// assert!(module.contains_fn(hash, true)); /// assert!(module.contains_fn(hash, true));
/// ``` /// ```
#[inline] #[inline(always)]
pub fn contains_fn(&self, hash_fn: NonZeroU64, public_only: bool) -> bool { pub fn contains_fn(&self, hash_fn: NonZeroU64, public_only: bool) -> bool {
if public_only { if public_only {
self.functions self.functions

View File

@ -1,10 +1,15 @@
use crate::dynamic::Variant; use crate::dynamic::Variant;
use crate::stdlib::{ use crate::stdlib::{boxed::Box, ops::Range};
boxed::Box, use crate::{def_package, EvalAltResult, INT};
ops::{Add, Range},
string::ToString, #[cfg(not(feature = "unchecked"))]
}; use crate::stdlib::string::ToString;
use crate::{def_package, EvalAltResult, Position, INT};
#[cfg(not(feature = "unchecked"))]
use num_traits::{CheckedAdd as Add, CheckedSub as Sub};
#[cfg(feature = "unchecked")]
use crate::stdlib::ops::{Add, Sub};
fn get_range<T: Variant + Clone>(from: T, to: T) -> Result<Range<T>, Box<EvalAltResult>> { fn get_range<T: Variant + Clone>(from: T, to: T) -> Result<Range<T>, Box<EvalAltResult>> {
Ok(from..to) Ok(from..to)
@ -14,30 +19,35 @@ fn get_range<T: Variant + Clone>(from: T, to: T) -> Result<Range<T>, Box<EvalAlt
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
struct StepRange<T>(T, T, T) struct StepRange<T>(T, T, T)
where where
for<'a> &'a T: Add<&'a T, Output = T>, T: Variant + Copy + PartialOrd + Add<Output = T> + Sub<Output = T>;
T: Variant + Clone + PartialOrd;
impl<T> StepRange<T> impl<T> StepRange<T>
where where
for<'a> &'a T: Add<&'a T, Output = T>, T: Variant + Copy + PartialOrd + Add<Output = T> + Sub<Output = T>,
T: Variant + Clone + PartialOrd,
{ {
pub fn new(from: T, to: T, step: T) -> Result<Self, Box<EvalAltResult>> { pub fn new(from: T, to: T, step: T) -> Result<Self, Box<EvalAltResult>> {
if &from + &step == from { #[cfg(not(feature = "unchecked"))]
Err(Box::new(EvalAltResult::ErrorArithmetic( if let Some(r) = from.checked_add(&step) {
"invalid step value".to_string(), if r == from {
Position::NONE, return Err(Box::new(EvalAltResult::ErrorInFunctionCall(
))) "range".to_string(),
} else { "".to_string(),
Ok(Self(from, to, step)) Box::new(EvalAltResult::ErrorArithmetic(
"step value cannot be zero".to_string(),
crate::Position::NONE,
)),
crate::Position::NONE,
)));
} }
} }
Ok(Self(from, to, step))
}
} }
impl<T> Iterator for StepRange<T> impl<T> Iterator for StepRange<T>
where where
for<'a> &'a T: Add<&'a T, Output = T>, T: Variant + Copy + PartialOrd + Add<Output = T> + Sub<Output = T>,
T: Variant + Clone + PartialOrd,
{ {
type Item = T; type Item = T;
@ -45,29 +55,90 @@ where
if self.0 == self.1 { if self.0 == self.1 {
None None
} else if self.0 < self.1 { } else if self.0 < self.1 {
let v = self.0.clone(); #[cfg(not(feature = "unchecked"))]
let n = self.0.add(&self.2); let diff1 = if let Some(diff) = self.1.checked_sub(&self.0) {
self.0 = if n >= self.1 { self.1.clone() } else { n }; diff
Some(v)
} else { } else {
let v = self.0.clone(); return None;
let n = self.0.add(&self.2); };
self.0 = if n <= self.1 { self.1.clone() } else { n }; #[cfg(feature = "unchecked")]
let diff1 = self.1 - self.0;
let v = self.0;
#[cfg(not(feature = "unchecked"))]
let n = if let Some(num) = self.0.checked_add(&self.2) {
num
} else {
return None;
};
#[cfg(feature = "unchecked")]
let n = self.0 + self.2;
#[cfg(not(feature = "unchecked"))]
let diff2 = if let Some(diff) = self.1.checked_sub(&n) {
diff
} else {
return None;
};
#[cfg(feature = "unchecked")]
let diff2 = self.1 - n;
if diff2 >= diff1 {
None
} else {
self.0 = if n >= self.1 { self.1 } else { n };
Some(v) Some(v)
} }
} else {
#[cfg(not(feature = "unchecked"))]
let diff1 = if let Some(diff) = self.0.checked_sub(&self.1) {
diff
} else {
return None;
};
#[cfg(feature = "unchecked")]
let diff1 = self.0 - self.1;
let v = self.0;
#[cfg(not(feature = "unchecked"))]
let n = if let Some(num) = self.0.checked_add(&self.2) {
num
} else {
return None;
};
#[cfg(feature = "unchecked")]
let n = self.0 + self.2;
#[cfg(not(feature = "unchecked"))]
let diff2 = if let Some(diff) = n.checked_sub(&self.1) {
diff
} else {
return None;
};
#[cfg(feature = "unchecked")]
let diff2 = n - self.1;
if diff2 >= diff1 {
None
} else {
self.0 = if n <= self.1 { self.1 } else { n };
Some(v)
}
}
} }
} }
fn get_step_range<T>(from: T, to: T, step: T) -> Result<StepRange<T>, Box<EvalAltResult>> fn get_step_range<T>(from: T, to: T, step: T) -> Result<StepRange<T>, Box<EvalAltResult>>
where where
for<'a> &'a T: Add<&'a T, Output = T>, T: Variant + Copy + PartialOrd + Add<Output = T> + Sub<Output = T>,
T: Variant + Clone + PartialOrd,
{ {
StepRange::<T>::new(from, to, step) StepRange::<T>::new(from, to, step)
} }
macro_rules! reg_range { macro_rules! reg_range {
($lib:expr, $x:expr, $( $y:ty ),*) => ( ($lib:ident | $x:expr => $( $y:ty ),*) => {
$( $(
$lib.set_iterator::<Range<$y>>(); $lib.set_iterator::<Range<$y>>();
let hash = $lib.set_fn_2($x, get_range::<$y>); let hash = $lib.set_fn_2($x, get_range::<$y>);
@ -77,11 +148,8 @@ macro_rules! reg_range {
concat!("Iterator<Item=", stringify!($y), ">") concat!("Iterator<Item=", stringify!($y), ">")
]); ]);
)* )*
) };
} ($lib:ident | step $x:expr => $( $y:ty ),*) => {
macro_rules! reg_stepped_range {
($lib:expr, $x:expr, $( $y:ty ),*) => (
$( $(
$lib.set_iterator::<StepRange<$y>>(); $lib.set_iterator::<StepRange<$y>>();
let hash = $lib.set_fn_3($x, get_step_range::<$y>); let hash = $lib.set_fn_3($x, get_step_range::<$y>);
@ -92,31 +160,98 @@ macro_rules! reg_stepped_range {
concat!("Iterator<Item=", stringify!($y), ">") concat!("Iterator<Item=", stringify!($y), ">")
]); ]);
)* )*
) };
} }
def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, { def_package!(crate:BasicIteratorPackage:"Basic range iterators.", lib, {
reg_range!(lib, "range", INT); reg_range!(lib | "range" => INT);
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_range!(lib, "range", i8, u8, i16, u16, i32, u32, i64, u64); reg_range!(lib | "range" => i8, u8, i16, u16, i32, u32, i64, u64);
if cfg!(not(target_arch = "wasm32")) { if cfg!(not(target_arch = "wasm32")) {
reg_range!(lib, "range", i128, u128); reg_range!(lib | "range" => i128, u128);
} }
} }
reg_stepped_range!(lib, "range", INT); reg_range!(lib | step "range" => INT);
#[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
reg_stepped_range!(lib, "range", i8, u8, i16, u16, i32, u32, i64, u64); reg_range!(lib | step "range" => i8, u8, i16, u16, i32, u32, i64, u64);
if cfg!(not(target_arch = "wasm32")) { if cfg!(not(target_arch = "wasm32")) {
reg_stepped_range!(lib, "range", i128, u128); reg_range!(lib | step "range" => i128, u128);
} }
} }
#[cfg(feature = "decimal")]
{
use rust_decimal::{
prelude::{One, Zero},
Decimal,
};
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
struct StepDecimalRange(Decimal, Decimal, Decimal);
impl StepDecimalRange {
pub fn new(from: Decimal, to: Decimal, step: Decimal) -> Result<Self, Box<EvalAltResult>> {
#[cfg(not(feature = "unchecked"))]
if step.is_zero() {
use crate::stdlib::string::ToString;
return Err(Box::new(EvalAltResult::ErrorInFunctionCall("range".to_string(), "".to_string(),
Box::new(EvalAltResult::ErrorArithmetic("step value cannot be zero".to_string(), crate::Position::NONE)),
crate::Position::NONE,
)));
}
Ok(Self(from, to, step))
}
}
impl Iterator for StepDecimalRange {
type Item = Decimal;
fn next(&mut self) -> Option<Decimal> {
if self.0 == self.1 {
None
} else if self.0 < self.1 {
#[cfg(not(feature = "unchecked"))]
if self.2.is_sign_negative() {
return None;
}
let v = self.0;
let n = self.0 + self.2;
self.0 = if n >= self.1 { self.1 } else { n };
Some(v)
} else {
#[cfg(not(feature = "unchecked"))]
if self.2.is_sign_positive() {
return None;
}
let v = self.0;
let n = self.0 + self.2;
self.0 = if n <= self.1 { self.1 } else { n };
Some(v)
}
}
}
lib.set_iterator::<StepDecimalRange>();
let hash = lib.set_fn_2("range", |from, to| StepDecimalRange::new(from, to, Decimal::one()));
lib.update_fn_metadata(hash, &["from: Decimal", "to: Decimal", "Iterator<Item=Decimal>"]);
let hash = lib.set_fn_3("range", |from, to, step| StepDecimalRange::new(from, to, step));
lib.update_fn_metadata(hash, &["from: Decimal", "to: Decimal", "step: Decimal", "Iterator<Item=Decimal>"]);
}
}); });

View File

@ -3,9 +3,6 @@
use crate::def_package; use crate::def_package;
use crate::plugin::*; use crate::plugin::*;
#[cfg(feature = "decimal")]
use rust_decimal::Decimal;
#[cfg(any( #[cfg(any(
not(feature = "no_float"), not(feature = "no_float"),
all(not(feature = "only_i32"), not(feature = "only_i64")) all(not(feature = "only_i32"), not(feature = "only_i64"))
@ -59,12 +56,6 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, {
combine_with_exported_module!(lib, "f64", f64_functions); combine_with_exported_module!(lib, "f64", f64_functions);
} }
#[cfg(feature = "decimal")]
{
reg_functions!(lib += decimal; Decimal);
combine_with_exported_module!(lib, "decimal", decimal_functions);
}
set_exported_fn!(lib, "!", not); set_exported_fn!(lib, "!", not);
}); });
@ -91,9 +82,6 @@ gen_cmp_functions!(float => f32);
#[cfg(feature = "f32_float")] #[cfg(feature = "f32_float")]
gen_cmp_functions!(float => f64); gen_cmp_functions!(float => f64);
#[cfg(feature = "decimal")]
gen_cmp_functions!(decimal => Decimal);
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[export_module] #[export_module]
mod f32_functions { mod f32_functions {
@ -203,59 +191,3 @@ mod f64_functions {
(x as f64) <= (y as f64) (x as f64) <= (y as f64)
} }
} }
#[cfg(feature = "decimal")]
#[export_module]
mod decimal_functions {
use crate::INT;
use rust_decimal::Decimal;
#[rhai_fn(name = "==")]
pub fn eq_if(x: INT, y: Decimal) -> bool {
Decimal::from(x) == y
}
#[rhai_fn(name = "==")]
pub fn eq_fi(x: Decimal, y: INT) -> bool {
x == Decimal::from(y)
}
#[rhai_fn(name = "!=")]
pub fn neq_if(x: INT, y: Decimal) -> bool {
Decimal::from(x) != y
}
#[rhai_fn(name = "!=")]
pub fn neq_fi(x: Decimal, y: INT) -> bool {
x != Decimal::from(y)
}
#[rhai_fn(name = ">")]
pub fn gt_if(x: INT, y: Decimal) -> bool {
Decimal::from(x) > y
}
#[rhai_fn(name = ">")]
pub fn gt_fi(x: Decimal, y: INT) -> bool {
x > Decimal::from(y)
}
#[rhai_fn(name = ">=")]
pub fn gte_if(x: INT, y: Decimal) -> bool {
Decimal::from(x) >= y
}
#[rhai_fn(name = ">=")]
pub fn gte_fi(x: Decimal, y: INT) -> bool {
x >= Decimal::from(y)
}
#[rhai_fn(name = "<")]
pub fn lt_if(x: INT, y: Decimal) -> bool {
Decimal::from(x) < y
}
#[rhai_fn(name = "<")]
pub fn lt_fi(x: Decimal, y: INT) -> bool {
x < Decimal::from(y)
}
#[rhai_fn(name = "<=")]
pub fn lte_if(x: INT, y: Decimal) -> bool {
Decimal::from(x) <= y
}
#[rhai_fn(name = "<=")]
pub fn lte_fi(x: Decimal, y: INT) -> bool {
x <= Decimal::from(y)
}
}

View File

@ -105,7 +105,7 @@ impl<'e> ParseState<'e> {
/// i.e. the top element of the [`ParseState`] is offset 1. /// i.e. the top element of the [`ParseState`] is offset 1.
/// ///
/// Return `None` when the variable name is not found in the `stack`. /// Return `None` when the variable name is not found in the `stack`.
#[inline] #[inline(always)]
fn access_var(&mut self, name: &str, _pos: Position) -> Option<NonZeroUsize> { fn access_var(&mut self, name: &str, _pos: Position) -> Option<NonZeroUsize> {
let mut barrier = false; let mut barrier = false;
@ -230,7 +230,7 @@ impl Expr {
/// Convert a [`Variable`][Expr::Variable] into a [`Property`][Expr::Property]. /// Convert a [`Variable`][Expr::Variable] into a [`Property`][Expr::Property].
/// All other variants are untouched. /// All other variants are untouched.
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
#[inline] #[inline(always)]
fn into_property(self, state: &mut ParseState) -> Self { fn into_property(self, state: &mut ParseState) -> Self {
match self { match self {
Self::Variable(x) if x.1.is_none() => { Self::Variable(x) if x.1.is_none() => {

View File

@ -153,13 +153,13 @@ impl fmt::Display for EvalAltResult {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Self::ErrorInFunctionCall(s, src, err, _) if crate::engine::is_anonymous_fn(s) => { Self::ErrorInFunctionCall(s, src, err, _) if crate::engine::is_anonymous_fn(s) => {
write!(f, "{}, in call to closure", err)?; write!(f, "{} in call to closure", err)?;
if !src.is_empty() { if !src.is_empty() {
write!(f, " @ '{}'", src)?; write!(f, " @ '{}'", src)?;
} }
} }
Self::ErrorInFunctionCall(s, src, err, _) => { Self::ErrorInFunctionCall(s, src, err, _) => {
write!(f, "{}, in call to function {}", err, s)?; write!(f, "{} in call to function {}", err, s)?;
if !src.is_empty() { if !src.is_empty() {
write!(f, " @ '{}'", src)?; write!(f, " @ '{}'", src)?;
} }

View File

@ -237,7 +237,7 @@ impl<'a> Scope<'a> {
self.push_dynamic_value(name, AccessMode::ReadOnly, value) self.push_dynamic_value(name, AccessMode::ReadOnly, value)
} }
/// Add (push) a new entry with a [`Dynamic`] value to the [`Scope`]. /// Add (push) a new entry with a [`Dynamic`] value to the [`Scope`].
#[inline] #[inline(always)]
pub(crate) fn push_dynamic_value( pub(crate) fn push_dynamic_value(
&mut self, &mut self,
name: impl Into<Cow<'a, str>>, name: impl Into<Cow<'a, str>>,
@ -420,7 +420,7 @@ impl<'a> Scope<'a> {
} }
/// Clone the [`Scope`], keeping only the last instances of each variable name. /// Clone the [`Scope`], keeping only the last instances of each variable name.
/// Shadowed variables are omitted in the copy. /// Shadowed variables are omitted in the copy.
#[inline] #[inline(always)]
pub(crate) fn clone_visible(&self) -> Self { pub(crate) fn clone_visible(&self) -> Self {
let mut entries: Self = Default::default(); let mut entries: Self = Default::default();

View File

@ -1029,7 +1029,7 @@ fn scan_block_comment(
/// # Volatile API /// # Volatile API
/// ///
/// This function is volatile and may change. /// This function is volatile and may change.
#[inline] #[inline(always)]
pub fn get_next_token( pub fn get_next_token(
stream: &mut impl InputStream, stream: &mut impl InputStream,
state: &mut TokenizeState, state: &mut TokenizeState,
@ -1856,7 +1856,7 @@ impl Engine {
self.lex_raw(input, Some(map)) self.lex_raw(input, Some(map))
} }
/// Tokenize an input text stream with an optional mapping function. /// Tokenize an input text stream with an optional mapping function.
#[inline] #[inline(always)]
pub(crate) fn lex_raw<'a>( pub(crate) fn lex_raw<'a>(
&'a self, &'a self,
input: impl IntoIterator<Item = &'a &'a str>, input: impl IntoIterator<Item = &'a &'a str>,

View File

@ -49,7 +49,7 @@ pub fn unsafe_cast_box<X: Variant, T: Variant>(item: Box<X>) -> Result<Box<T>, B
/// ///
/// Force-casting a local variable's lifetime to the current [`Scope`][crate::Scope]'s larger lifetime saves /// Force-casting a local variable's lifetime to the current [`Scope`][crate::Scope]'s larger lifetime saves
/// on allocations and string cloning, thus avoids us having to maintain a chain of [`Scope`][crate::Scope]'s. /// on allocations and string cloning, thus avoids us having to maintain a chain of [`Scope`][crate::Scope]'s.
#[inline] #[inline(always)]
pub fn unsafe_cast_var_name_to_lifetime<'s>(name: &str) -> &'s str { pub fn unsafe_cast_var_name_to_lifetime<'s>(name: &str) -> &'s str {
// WARNING - force-cast the variable name into the scope's lifetime to avoid cloning it // WARNING - force-cast the variable name into the scope's lifetime to avoid cloning it
// this is safe because all local variables are cleared at the end of the block // this is safe because all local variables are cleared at the end of the block

View File

@ -34,11 +34,13 @@ impl Hasher for StraightHasher {
} }
#[inline(always)] #[inline(always)]
fn write(&mut self, bytes: &[u8]) { fn write(&mut self, bytes: &[u8]) {
assert_eq!(bytes.len(), 8, "StraightHasher can only hash u64 values");
let mut key = [0_u8; 8]; let mut key = [0_u8; 8];
key.copy_from_slice(&bytes[..8]); // Panics if fewer than 8 bytes key.copy_from_slice(bytes);
// HACK - If it so happens to hash directly to zero (OMG!) then change it to 42... // HACK - If it so happens to hash directly to zero (OMG!) then change it to 42...
self.0 = NonZeroU64::new(u64::from_le_bytes(key)) self.0 = NonZeroU64::new(u64::from_ne_bytes(key))
.unwrap_or_else(|| NonZeroU64::new(42).unwrap()); .unwrap_or_else(|| NonZeroU64::new(42).unwrap());
} }
} }
@ -58,9 +60,8 @@ impl BuildHasher for StraightHasherBuilder {
/// Create an instance of the default hasher. /// Create an instance of the default hasher.
#[inline(always)] #[inline(always)]
pub fn get_hasher() -> impl Hasher { pub fn get_hasher() -> ahash::AHasher {
let s: ahash::AHasher = Default::default(); Default::default()
s
} }
/// _(INTERNALS)_ Calculate a [`NonZeroU64`] hash key from a namespace-qualified function name and /// _(INTERNALS)_ Calculate a [`NonZeroU64`] hash key from a namespace-qualified function name and

View File

@ -2,7 +2,7 @@ use rhai::{Engine, EvalAltResult, Module, INT};
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
#[test] #[test]
fn test_for_array() -> Result<(), Box<EvalAltResult>> { fn test_for() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();
let script = r" let script = r"
@ -27,6 +27,81 @@ fn test_for_array() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>(script)?, 35); assert_eq!(engine.eval::<INT>(script)?, 35);
assert_eq!(
engine.eval::<INT>(
r"
let sum = 0;
for x in range(1, 10, 2) { sum += x; }
sum
"
)?,
25
);
assert_eq!(
engine.eval::<INT>(
r"
let sum = 0;
for x in range(10, 1, 2) { sum += x; }
sum
"
)?,
0
);
assert_eq!(
engine.eval::<INT>(
r"
let sum = 0;
for x in range(1, 10, -2) { sum += x; }
sum
"
)?,
0
);
assert_eq!(
engine.eval::<INT>(
r"
let sum = 0;
for x in range(10, 1, -2) { sum += x; }
sum
"
)?,
30
);
Ok(())
}
#[cfg(not(feature = "unchecked"))]
#[test]
fn test_for_overflow() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
#[cfg(not(feature = "only_i32"))]
let script = r"
let sum = 0;
for x in range(9223372036854775807, 0, 9223372036854775807) {
sum += 1;
}
sum
";
#[cfg(feature = "only_i32")]
let script = r"
let sum = 0;
for x in range(2147483647 , 0, 2147483647 ) {
sum += 1;
}
sum
";
assert_eq!(engine.eval::<INT>(script)?, 0);
Ok(()) Ok(())
} }

View File

@ -18,6 +18,10 @@ fn test_switch() -> Result<(), Box<EvalAltResult>> {
engine.eval_with_scope::<()>(&mut scope, "switch x { 1 => 123, 2 => 'a' }")?, engine.eval_with_scope::<()>(&mut scope, "switch x { 1 => 123, 2 => 'a' }")?,
() ()
); );
assert_eq!(
engine.eval::<INT>("let x = timestamp(); switch x { 1 => 123, _ => 42 }")?,
42
);
assert_eq!( assert_eq!(
engine.eval_with_scope::<INT>( engine.eval_with_scope::<INT>(
&mut scope, &mut scope,