commit
aaa5254c29
17
CHANGELOG.md
17
CHANGELOG.md
@ -4,6 +4,19 @@ Rhai Release Notes
|
||||
Version 1.10.0
|
||||
==============
|
||||
|
||||
This version introduces _Fast Operators_ mode, which is turned on by default but can be disabled via
|
||||
a new options API: `Engine::set_fast_operators`.
|
||||
|
||||
_Fast Operators_ mode assumes that none of Rhai's built-in operators for standard data types are
|
||||
overloaded by user-registered functions. In the vast majority of cases this should be so (really,
|
||||
who overloads the `+` operator for integers anyway?).
|
||||
|
||||
This assumption allows the `Engine` to avoid checking for overloads for every single operator call.
|
||||
This usually results in substantial speed improvements, especially for expressions.
|
||||
|
||||
Minimum Rust Version
|
||||
--------------------
|
||||
|
||||
The minimum Rust version is now `1.61.0` in order to use some `const` generics.
|
||||
|
||||
Bug fixes
|
||||
@ -21,6 +34,10 @@ Deprecated API
|
||||
New features
|
||||
------------
|
||||
|
||||
### Fast operators
|
||||
|
||||
* A new option `Engine::fast_operators` is introduced (default to `true`) to enable/disable _Fast Operators_ mode.
|
||||
|
||||
### Fallible type iterators
|
||||
|
||||
* For very special needs, the ability to register fallible type iterators is added.
|
||||
|
@ -235,6 +235,7 @@ fn multiple_fn_rename_test() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
let m = rhai::exported_module!(crate::multiple_fn_rename::my_adds);
|
||||
engine.register_global_module(m.into());
|
||||
engine.set_fast_operators(false);
|
||||
|
||||
let output_array = engine.eval::<Array>(
|
||||
"
|
||||
|
6480
scripts/all_in_one.d.rhai
Normal file
6480
scripts/all_in_one.d.rhai
Normal file
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,8 @@
|
||||
|
||||
let now = timestamp();
|
||||
|
||||
const MAX_NUMBER_TO_CHECK = 1_000_000; // 9592 primes <= 100000
|
||||
const ANSWER = 78_498;
|
||||
const MAX_NUMBER_TO_CHECK = 1_000_000;
|
||||
|
||||
let prime_mask = [];
|
||||
prime_mask.pad(MAX_NUMBER_TO_CHECK + 1, true);
|
||||
@ -27,6 +28,6 @@ for p in 2..=MAX_NUMBER_TO_CHECK {
|
||||
print(`Total ${total_primes_found} primes <= ${MAX_NUMBER_TO_CHECK}`);
|
||||
print(`Run time = ${now.elapsed} seconds.`);
|
||||
|
||||
if total_primes_found != 78_498 {
|
||||
print("The answer is WRONG! Should be 78,498!");
|
||||
if total_primes_found != ANSWER {
|
||||
print(`The answer is WRONG! Should be ${ANSWER}!`);
|
||||
}
|
||||
|
@ -7,34 +7,43 @@ use std::prelude::v1::*;
|
||||
|
||||
bitflags! {
|
||||
/// Bit-flags containing all language options for the [`Engine`].
|
||||
pub struct LangOptions: u8 {
|
||||
pub struct LangOptions: u16 {
|
||||
/// Is `if`-expression allowed?
|
||||
const IF_EXPR = 0b_0000_0001;
|
||||
const IF_EXPR = 0b_0000_0000_0001;
|
||||
/// Is `switch` expression allowed?
|
||||
const SWITCH_EXPR = 0b_0000_0010;
|
||||
const SWITCH_EXPR = 0b_0000_0000_0010;
|
||||
/// Is statement-expression allowed?
|
||||
const STMT_EXPR = 0b_0000_0100;
|
||||
const STMT_EXPR = 0b_0000_0000_0100;
|
||||
/// Is anonymous function allowed?
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
const ANON_FN = 0b_0000_1000;
|
||||
const ANON_FN = 0b_0000_0000_1000;
|
||||
/// Is looping allowed?
|
||||
const LOOPING = 0b_0001_0000;
|
||||
const LOOPING = 0b_0000_0001_0000;
|
||||
/// Is variables shadowing allowed?
|
||||
const SHADOW = 0b_0010_0000;
|
||||
const SHADOW = 0b_0000_0010_0000;
|
||||
/// Strict variables mode?
|
||||
const STRICT_VAR = 0b_0100_0000;
|
||||
const STRICT_VAR = 0b_0000_0100_0000;
|
||||
/// Raise error if an object map property does not exist?
|
||||
/// Returns `()` if `false`.
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
const FAIL_ON_INVALID_MAP_PROPERTY = 0b_1000_0000;
|
||||
const FAIL_ON_INVALID_MAP_PROPERTY = 0b_0000_1000_0000;
|
||||
/// Fast operators mode?
|
||||
const FAST_OPS = 0b_0001_0000_0000;
|
||||
}
|
||||
}
|
||||
|
||||
impl LangOptions {
|
||||
/// Create a new [`LangOptions`] with default values.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::IF_EXPR | Self::SWITCH_EXPR | Self::STMT_EXPR | Self::LOOPING | Self::SHADOW | {
|
||||
Self::IF_EXPR
|
||||
| Self::SWITCH_EXPR
|
||||
| Self::STMT_EXPR
|
||||
| Self::LOOPING
|
||||
| Self::SHADOW
|
||||
| Self::FAST_OPS
|
||||
| {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
{
|
||||
Self::ANON_FN
|
||||
@ -158,4 +167,16 @@ impl Engine {
|
||||
self.options
|
||||
.set(LangOptions::FAIL_ON_INVALID_MAP_PROPERTY, enable);
|
||||
}
|
||||
/// Is fast operators mode enabled?
|
||||
/// Default is `false`.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub const fn fast_operators(&self) -> bool {
|
||||
self.options.contains(LangOptions::FAST_OPS)
|
||||
}
|
||||
/// Set whether fast operators mode is enabled.
|
||||
#[inline(always)]
|
||||
pub fn set_fast_operators(&mut self, enable: bool) {
|
||||
self.options.set(LangOptions::FAST_OPS, enable);
|
||||
}
|
||||
}
|
||||
|
@ -190,6 +190,8 @@ pub struct FnCallExpr {
|
||||
pub args: StaticVec<Expr>,
|
||||
/// Does this function call capture the parent scope?
|
||||
pub capture_parent_scope: bool,
|
||||
/// Is this function call a native operator?
|
||||
pub is_native_operator: bool,
|
||||
/// [Position] of the function name.
|
||||
pub pos: Position,
|
||||
}
|
||||
@ -204,6 +206,9 @@ impl fmt::Debug for FnCallExpr {
|
||||
if self.capture_parent_scope {
|
||||
ff.field("capture_parent_scope", &self.capture_parent_scope);
|
||||
}
|
||||
if self.is_native_operator {
|
||||
ff.field("is_native_operator", &self.is_native_operator);
|
||||
}
|
||||
ff.field("hash", &self.hashes)
|
||||
.field("name", &self.name)
|
||||
.field("args", &self.args);
|
||||
@ -662,6 +667,7 @@ impl Expr {
|
||||
hashes: calc_fn_hash(f.fn_name(), 1).into(),
|
||||
args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(),
|
||||
capture_parent_scope: false,
|
||||
is_native_operator: false,
|
||||
pos,
|
||||
}
|
||||
.into(),
|
||||
|
@ -3,11 +3,16 @@
|
||||
use super::{Caches, EvalContext, GlobalRuntimeState, Target};
|
||||
use crate::ast::{Expr, FnCallExpr, OpAssignment};
|
||||
use crate::engine::{KEYWORD_THIS, OP_CONCAT};
|
||||
use crate::eval::FnResolutionCacheEntry;
|
||||
use crate::func::{
|
||||
calc_fn_params_hash, combine_hashes, gen_fn_call_signature, get_builtin_binary_op_fn,
|
||||
CallableFunction, FnAny,
|
||||
};
|
||||
use crate::types::dynamic::AccessMode;
|
||||
use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, ERR};
|
||||
use std::num::NonZeroUsize;
|
||||
#[cfg(feature = "no_std")]
|
||||
use std::prelude::v1::*;
|
||||
use std::{collections::btree_map::Entry, num::NonZeroUsize};
|
||||
|
||||
impl Engine {
|
||||
/// Search for a module within an imports stack.
|
||||
@ -218,19 +223,78 @@ impl Engine {
|
||||
level: usize,
|
||||
) -> RhaiResult {
|
||||
let FnCallExpr {
|
||||
name,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
namespace,
|
||||
capture_parent_scope: capture,
|
||||
hashes,
|
||||
args,
|
||||
..
|
||||
name, hashes, args, ..
|
||||
} = expr;
|
||||
|
||||
// Short-circuit native binary operator call if under Fast Operators mode
|
||||
if expr.is_native_operator && self.fast_operators() && (args.len() == 1 || args.len() == 2)
|
||||
{
|
||||
let mut lhs = self
|
||||
.get_arg_value(scope, global, caches, lib, this_ptr, &args[0], level)?
|
||||
.0
|
||||
.flatten();
|
||||
|
||||
let mut rhs = if args.len() == 2 {
|
||||
self.get_arg_value(scope, global, caches, lib, this_ptr, &args[1], level)?
|
||||
.0
|
||||
.flatten()
|
||||
} else {
|
||||
Dynamic::UNIT
|
||||
};
|
||||
|
||||
let mut operands = [&mut lhs, &mut rhs];
|
||||
let operands = if args.len() == 2 {
|
||||
&mut operands[..]
|
||||
} else {
|
||||
&mut operands[0..1]
|
||||
};
|
||||
|
||||
let hash = calc_fn_params_hash(operands.iter().map(|a| a.type_id()));
|
||||
let hash = combine_hashes(hashes.native, hash);
|
||||
|
||||
let cache = caches.fn_resolution_cache_mut();
|
||||
|
||||
let func = if let Entry::Vacant(entry) = cache.entry(hash) {
|
||||
let func = if args.len() == 2 {
|
||||
get_builtin_binary_op_fn(&name, operands[0], operands[1])
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(f) = func {
|
||||
entry.insert(Some(FnResolutionCacheEntry {
|
||||
func: CallableFunction::from_method(Box::new(f) as Box<FnAny>),
|
||||
source: None,
|
||||
}));
|
||||
&cache.get(&hash).unwrap().as_ref().unwrap().func
|
||||
} else {
|
||||
let result = self.exec_fn_call(
|
||||
None, global, caches, lib, name, *hashes, operands, false, false, pos,
|
||||
level,
|
||||
);
|
||||
return result.map(|(v, ..)| v);
|
||||
}
|
||||
} else if let Some(entry) = cache.get(&hash).unwrap() {
|
||||
&entry.func
|
||||
} else {
|
||||
let sig = gen_fn_call_signature(self, name, operands);
|
||||
return Err(ERR::ErrorFunctionNotFound(sig, pos).into());
|
||||
};
|
||||
|
||||
let context = (self, name, None, &*global, lib, pos, level).into();
|
||||
let result = if func.is_plugin_fn() {
|
||||
func.get_plugin_fn().unwrap().call(context, operands)
|
||||
} else {
|
||||
func.get_native_fn().unwrap()(context, operands)
|
||||
};
|
||||
return self.check_return_value(result, pos);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
if !namespace.is_empty() {
|
||||
if !expr.namespace.is_empty() {
|
||||
// Qualified function call
|
||||
let hash = hashes.native;
|
||||
let namespace = &expr.namespace;
|
||||
|
||||
return self.make_qualified_function_call(
|
||||
scope, global, caches, lib, this_ptr, namespace, name, args, hash, pos, level,
|
||||
@ -244,7 +308,18 @@ impl Engine {
|
||||
);
|
||||
|
||||
self.make_function_call(
|
||||
scope, global, caches, lib, this_ptr, name, first_arg, args, *hashes, *capture, pos,
|
||||
scope,
|
||||
global,
|
||||
caches,
|
||||
lib,
|
||||
this_ptr,
|
||||
name,
|
||||
first_arg,
|
||||
args,
|
||||
*hashes,
|
||||
expr.capture_parent_scope,
|
||||
expr.is_native_operator,
|
||||
pos,
|
||||
level,
|
||||
)
|
||||
}
|
||||
|
@ -115,6 +115,143 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option<Fn
|
||||
} };
|
||||
}
|
||||
|
||||
// Check for common patterns
|
||||
if type1 == type2 {
|
||||
if type1 == TypeId::of::<INT>() {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
use crate::packages::arithmetic::arith_basic::INT::functions::*;
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
match op {
|
||||
"+" => return Some(impl_op!(INT => add(as_int, as_int))),
|
||||
"-" => return Some(impl_op!(INT => subtract(as_int, as_int))),
|
||||
"*" => return Some(impl_op!(INT => multiply(as_int, as_int))),
|
||||
"/" => return Some(impl_op!(INT => divide(as_int, as_int))),
|
||||
"%" => return Some(impl_op!(INT => modulo(as_int, as_int))),
|
||||
"**" => return Some(impl_op!(INT => power(as_int, as_int))),
|
||||
">>" => return Some(impl_op!(INT => shift_right(as_int, as_int))),
|
||||
"<<" => return Some(impl_op!(INT => shift_left(as_int, as_int))),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
#[cfg(feature = "unchecked")]
|
||||
match op {
|
||||
"+" => return Some(impl_op!(INT => as_int + as_int)),
|
||||
"-" => return Some(impl_op!(INT => as_int - as_int)),
|
||||
"*" => return Some(impl_op!(INT => as_int * as_int)),
|
||||
"/" => return Some(impl_op!(INT => as_int / as_int)),
|
||||
"%" => return Some(impl_op!(INT => as_int % as_int)),
|
||||
"**" => return Some(impl_op!(INT => as_int.pow(as_int as u32))),
|
||||
">>" => return Some(impl_op!(INT => as_int >> as_int)),
|
||||
"<<" => return Some(impl_op!(INT => as_int << as_int)),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
return match op {
|
||||
"==" => Some(impl_op!(INT => as_int == as_int)),
|
||||
"!=" => Some(impl_op!(INT => as_int != as_int)),
|
||||
">" => Some(impl_op!(INT => as_int > as_int)),
|
||||
">=" => Some(impl_op!(INT => as_int >= as_int)),
|
||||
"<" => Some(impl_op!(INT => as_int < as_int)),
|
||||
"<=" => Some(impl_op!(INT => as_int <= as_int)),
|
||||
"&" => Some(impl_op!(INT => as_int & as_int)),
|
||||
"|" => Some(impl_op!(INT => as_int | as_int)),
|
||||
"^" => Some(impl_op!(INT => as_int ^ as_int)),
|
||||
".." => Some(|_, args| {
|
||||
let x = args[0].as_int().expect(BUILTIN);
|
||||
let y = args[1].as_int().expect(BUILTIN);
|
||||
Ok((x..y).into())
|
||||
}),
|
||||
"..=" => Some(|_, args| {
|
||||
let x = args[0].as_int().expect(BUILTIN);
|
||||
let y = args[1].as_int().expect(BUILTIN);
|
||||
Ok((x..=y).into())
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
if type1 == TypeId::of::<bool>() {
|
||||
return match op {
|
||||
"==" => Some(impl_op!(bool => as_bool == as_bool)),
|
||||
"!=" => Some(impl_op!(bool => as_bool != as_bool)),
|
||||
">" => Some(impl_op!(bool => as_bool > as_bool)),
|
||||
">=" => Some(impl_op!(bool => as_bool >= as_bool)),
|
||||
"<" => Some(impl_op!(bool => as_bool < as_bool)),
|
||||
"<=" => Some(impl_op!(bool => as_bool <= as_bool)),
|
||||
"&" => Some(impl_op!(bool => as_bool & as_bool)),
|
||||
"|" => Some(impl_op!(bool => as_bool | as_bool)),
|
||||
"^" => Some(impl_op!(bool => as_bool ^ as_bool)),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
if type1 == TypeId::of::<ImmutableString>() {
|
||||
return match op {
|
||||
"+" => Some(impl_op!(ImmutableString + ImmutableString)),
|
||||
"-" => Some(impl_op!(ImmutableString - ImmutableString)),
|
||||
"==" => Some(impl_op!(ImmutableString == ImmutableString)),
|
||||
"!=" => Some(impl_op!(ImmutableString != ImmutableString)),
|
||||
">" => Some(impl_op!(ImmutableString > ImmutableString)),
|
||||
">=" => Some(impl_op!(ImmutableString >= ImmutableString)),
|
||||
"<" => Some(impl_op!(ImmutableString < ImmutableString)),
|
||||
"<=" => Some(impl_op!(ImmutableString <= ImmutableString)),
|
||||
OP_CONTAINS => Some(impl_op!(ImmutableString.contains(ImmutableString.as_str()))),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
if type1 == TypeId::of::<char>() {
|
||||
return match op {
|
||||
"+" => Some(|_, args| {
|
||||
let x = args[0].as_char().expect(BUILTIN);
|
||||
let y = args[1].as_char().expect(BUILTIN);
|
||||
Ok(format!("{x}{y}").into())
|
||||
}),
|
||||
"==" => Some(impl_op!(char => as_char == as_char)),
|
||||
"!=" => Some(impl_op!(char => as_char != as_char)),
|
||||
">" => Some(impl_op!(char => as_char > as_char)),
|
||||
">=" => Some(impl_op!(char => as_char >= as_char)),
|
||||
"<" => Some(impl_op!(char => as_char < as_char)),
|
||||
"<=" => Some(impl_op!(char => as_char <= as_char)),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
if type1 == TypeId::of::<crate::Blob>() {
|
||||
use crate::Blob;
|
||||
|
||||
return match op {
|
||||
"+" => Some(|_, args| {
|
||||
let blob1 = &*args[0].read_lock::<Blob>().expect(BUILTIN);
|
||||
let blob2 = &*args[1].read_lock::<Blob>().expect(BUILTIN);
|
||||
|
||||
Ok(Dynamic::from_blob(if blob2.is_empty() {
|
||||
blob1.clone()
|
||||
} else if blob1.is_empty() {
|
||||
blob2.clone()
|
||||
} else {
|
||||
let mut blob = blob1.clone();
|
||||
blob.extend(blob2);
|
||||
blob
|
||||
}))
|
||||
}),
|
||||
"==" => Some(impl_op!(Blob == Blob)),
|
||||
"!=" => Some(impl_op!(Blob != Blob)),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
if type1 == TypeId::of::<()>() {
|
||||
return match op {
|
||||
"==" => Some(|_, _| Ok(Dynamic::TRUE)),
|
||||
"!=" | ">" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
macro_rules! impl_float {
|
||||
($x:ty, $xx:ident, $y:ty, $yy:ident) => {
|
||||
@ -406,141 +543,6 @@ pub fn get_builtin_binary_op_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Option<Fn
|
||||
}
|
||||
|
||||
// Beyond here, type1 == type2
|
||||
|
||||
if type1 == TypeId::of::<INT>() {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
use crate::packages::arithmetic::arith_basic::INT::functions::*;
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
match op {
|
||||
"+" => return Some(impl_op!(INT => add(as_int, as_int))),
|
||||
"-" => return Some(impl_op!(INT => subtract(as_int, as_int))),
|
||||
"*" => return Some(impl_op!(INT => multiply(as_int, as_int))),
|
||||
"/" => return Some(impl_op!(INT => divide(as_int, as_int))),
|
||||
"%" => return Some(impl_op!(INT => modulo(as_int, as_int))),
|
||||
"**" => return Some(impl_op!(INT => power(as_int, as_int))),
|
||||
">>" => return Some(impl_op!(INT => shift_right(as_int, as_int))),
|
||||
"<<" => return Some(impl_op!(INT => shift_left(as_int, as_int))),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
#[cfg(feature = "unchecked")]
|
||||
match op {
|
||||
"+" => return Some(impl_op!(INT => as_int + as_int)),
|
||||
"-" => return Some(impl_op!(INT => as_int - as_int)),
|
||||
"*" => return Some(impl_op!(INT => as_int * as_int)),
|
||||
"/" => return Some(impl_op!(INT => as_int / as_int)),
|
||||
"%" => return Some(impl_op!(INT => as_int % as_int)),
|
||||
"**" => return Some(impl_op!(INT => as_int.pow(as_int as u32))),
|
||||
">>" => return Some(impl_op!(INT => as_int >> as_int)),
|
||||
"<<" => return Some(impl_op!(INT => as_int << as_int)),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
return match op {
|
||||
"==" => Some(impl_op!(INT => as_int == as_int)),
|
||||
"!=" => Some(impl_op!(INT => as_int != as_int)),
|
||||
">" => Some(impl_op!(INT => as_int > as_int)),
|
||||
">=" => Some(impl_op!(INT => as_int >= as_int)),
|
||||
"<" => Some(impl_op!(INT => as_int < as_int)),
|
||||
"<=" => Some(impl_op!(INT => as_int <= as_int)),
|
||||
"&" => Some(impl_op!(INT => as_int & as_int)),
|
||||
"|" => Some(impl_op!(INT => as_int | as_int)),
|
||||
"^" => Some(impl_op!(INT => as_int ^ as_int)),
|
||||
".." => Some(|_, args| {
|
||||
let x = args[0].as_int().expect(BUILTIN);
|
||||
let y = args[1].as_int().expect(BUILTIN);
|
||||
Ok((x..y).into())
|
||||
}),
|
||||
"..=" => Some(|_, args| {
|
||||
let x = args[0].as_int().expect(BUILTIN);
|
||||
let y = args[1].as_int().expect(BUILTIN);
|
||||
Ok((x..=y).into())
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
if type1 == TypeId::of::<bool>() {
|
||||
return match op {
|
||||
"==" => Some(impl_op!(bool => as_bool == as_bool)),
|
||||
"!=" => Some(impl_op!(bool => as_bool != as_bool)),
|
||||
">" => Some(impl_op!(bool => as_bool > as_bool)),
|
||||
">=" => Some(impl_op!(bool => as_bool >= as_bool)),
|
||||
"<" => Some(impl_op!(bool => as_bool < as_bool)),
|
||||
"<=" => Some(impl_op!(bool => as_bool <= as_bool)),
|
||||
"&" => Some(impl_op!(bool => as_bool & as_bool)),
|
||||
"|" => Some(impl_op!(bool => as_bool | as_bool)),
|
||||
"^" => Some(impl_op!(bool => as_bool ^ as_bool)),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
if type1 == TypeId::of::<ImmutableString>() {
|
||||
return match op {
|
||||
"+" => Some(impl_op!(ImmutableString + ImmutableString)),
|
||||
"-" => Some(impl_op!(ImmutableString - ImmutableString)),
|
||||
"==" => Some(impl_op!(ImmutableString == ImmutableString)),
|
||||
"!=" => Some(impl_op!(ImmutableString != ImmutableString)),
|
||||
">" => Some(impl_op!(ImmutableString > ImmutableString)),
|
||||
">=" => Some(impl_op!(ImmutableString >= ImmutableString)),
|
||||
"<" => Some(impl_op!(ImmutableString < ImmutableString)),
|
||||
"<=" => Some(impl_op!(ImmutableString <= ImmutableString)),
|
||||
OP_CONTAINS => Some(impl_op!(ImmutableString.contains(ImmutableString.as_str()))),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
if type1 == TypeId::of::<char>() {
|
||||
return match op {
|
||||
"+" => Some(|_, args| {
|
||||
let x = args[0].as_char().expect(BUILTIN);
|
||||
let y = args[1].as_char().expect(BUILTIN);
|
||||
Ok(format!("{x}{y}").into())
|
||||
}),
|
||||
"==" => Some(impl_op!(char => as_char == as_char)),
|
||||
"!=" => Some(impl_op!(char => as_char != as_char)),
|
||||
">" => Some(impl_op!(char => as_char > as_char)),
|
||||
">=" => Some(impl_op!(char => as_char >= as_char)),
|
||||
"<" => Some(impl_op!(char => as_char < as_char)),
|
||||
"<=" => Some(impl_op!(char => as_char <= as_char)),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
if type1 == TypeId::of::<crate::Blob>() {
|
||||
use crate::Blob;
|
||||
|
||||
return match op {
|
||||
"+" => Some(|_, args| {
|
||||
let blob1 = &*args[0].read_lock::<Blob>().expect(BUILTIN);
|
||||
let blob2 = &*args[1].read_lock::<Blob>().expect(BUILTIN);
|
||||
|
||||
Ok(Dynamic::from_blob(if blob2.is_empty() {
|
||||
blob1.clone()
|
||||
} else if blob1.is_empty() {
|
||||
blob2.clone()
|
||||
} else {
|
||||
let mut blob = blob1.clone();
|
||||
blob.extend(blob2);
|
||||
blob
|
||||
}))
|
||||
}),
|
||||
"==" => Some(impl_op!(Blob == Blob)),
|
||||
"!=" => Some(impl_op!(Blob != Blob)),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
if type1 == TypeId::of::<()>() {
|
||||
return match op {
|
||||
"==" => Some(|_, _| Ok(Dynamic::TRUE)),
|
||||
"!=" | ">" | ">=" | "<" | "<=" => Some(|_, _| Ok(Dynamic::FALSE)),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
@ -594,6 +596,98 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio
|
||||
} };
|
||||
}
|
||||
|
||||
// Check for common patterns
|
||||
if type1 == type2 {
|
||||
if type1 == TypeId::of::<INT>() {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
use crate::packages::arithmetic::arith_basic::INT::functions::*;
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
match op {
|
||||
"+=" => return Some(impl_op!(INT => add(as_int, as_int))),
|
||||
"-=" => return Some(impl_op!(INT => subtract(as_int, as_int))),
|
||||
"*=" => return Some(impl_op!(INT => multiply(as_int, as_int))),
|
||||
"/=" => return Some(impl_op!(INT => divide(as_int, as_int))),
|
||||
"%=" => return Some(impl_op!(INT => modulo(as_int, as_int))),
|
||||
"**=" => return Some(impl_op!(INT => power(as_int, as_int))),
|
||||
">>=" => return Some(impl_op!(INT => shift_right(as_int, as_int))),
|
||||
"<<=" => return Some(impl_op!(INT => shift_left(as_int, as_int))),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
#[cfg(feature = "unchecked")]
|
||||
match op {
|
||||
"+=" => return Some(impl_op!(INT += as_int)),
|
||||
"-=" => return Some(impl_op!(INT -= as_int)),
|
||||
"*=" => return Some(impl_op!(INT *= as_int)),
|
||||
"/=" => return Some(impl_op!(INT /= as_int)),
|
||||
"%=" => return Some(impl_op!(INT %= as_int)),
|
||||
"**=" => return Some(impl_op!(INT => as_int.pow(as_int as u32))),
|
||||
">>=" => return Some(impl_op!(INT >>= as_int)),
|
||||
"<<=" => return Some(impl_op!(INT <<= as_int)),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
return match op {
|
||||
"&=" => Some(impl_op!(INT &= as_int)),
|
||||
"|=" => Some(impl_op!(INT |= as_int)),
|
||||
"^=" => Some(impl_op!(INT ^= as_int)),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
if type1 == TypeId::of::<bool>() {
|
||||
return match op {
|
||||
"&=" => Some(impl_op!(bool = x && as_bool)),
|
||||
"|=" => Some(impl_op!(bool = x || as_bool)),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
if type1 == TypeId::of::<char>() {
|
||||
return match op {
|
||||
"+=" => Some(|_, args| {
|
||||
let y = args[1].as_char().expect(BUILTIN);
|
||||
let x = &mut *args[0].write_lock::<Dynamic>().expect(BUILTIN);
|
||||
Ok((*x = format!("{x}{y}").into()).into())
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
if type1 == TypeId::of::<ImmutableString>() {
|
||||
return match op {
|
||||
"+=" => Some(|_, args| {
|
||||
let (first, second) = args.split_first_mut().expect(BUILTIN);
|
||||
let x = &mut *first.write_lock::<ImmutableString>().expect(BUILTIN);
|
||||
let y = std::mem::take(second[0]).cast::<ImmutableString>();
|
||||
Ok((*x += y).into())
|
||||
}),
|
||||
"-=" => Some(|_, args| {
|
||||
let (first, second) = args.split_first_mut().expect(BUILTIN);
|
||||
let x = &mut *first.write_lock::<ImmutableString>().expect(BUILTIN);
|
||||
let y = std::mem::take(second[0]).cast::<ImmutableString>();
|
||||
Ok((*x -= y).into())
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
if type1 == TypeId::of::<crate::Blob>() {
|
||||
use crate::Blob;
|
||||
|
||||
return match op {
|
||||
"+=" => Some(|_, args| {
|
||||
let blob2 = std::mem::take(args[1]).cast::<Blob>();
|
||||
let blob1 = &mut *args[0].write_lock::<Blob>().expect(BUILTIN);
|
||||
Ok(crate::packages::blob_basic::blob_functions::append(blob1, blob2).into())
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
macro_rules! impl_float {
|
||||
($x:ident, $xx:ident, $y:ty, $yy:ident) => {
|
||||
@ -752,100 +846,5 @@ pub fn get_builtin_op_assignment_fn(op: &str, x: &Dynamic, y: &Dynamic) -> Optio
|
||||
}
|
||||
}
|
||||
|
||||
// No built-in op-assignments for different types.
|
||||
if type2 != type1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Beyond here, type1 == type2
|
||||
if type1 == TypeId::of::<INT>() {
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
use crate::packages::arithmetic::arith_basic::INT::functions::*;
|
||||
|
||||
#[cfg(not(feature = "unchecked"))]
|
||||
match op {
|
||||
"+=" => return Some(impl_op!(INT => add(as_int, as_int))),
|
||||
"-=" => return Some(impl_op!(INT => subtract(as_int, as_int))),
|
||||
"*=" => return Some(impl_op!(INT => multiply(as_int, as_int))),
|
||||
"/=" => return Some(impl_op!(INT => divide(as_int, as_int))),
|
||||
"%=" => return Some(impl_op!(INT => modulo(as_int, as_int))),
|
||||
"**=" => return Some(impl_op!(INT => power(as_int, as_int))),
|
||||
">>=" => return Some(impl_op!(INT => shift_right(as_int, as_int))),
|
||||
"<<=" => return Some(impl_op!(INT => shift_left(as_int, as_int))),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
#[cfg(feature = "unchecked")]
|
||||
match op {
|
||||
"+=" => return Some(impl_op!(INT += as_int)),
|
||||
"-=" => return Some(impl_op!(INT -= as_int)),
|
||||
"*=" => return Some(impl_op!(INT *= as_int)),
|
||||
"/=" => return Some(impl_op!(INT /= as_int)),
|
||||
"%=" => return Some(impl_op!(INT %= as_int)),
|
||||
"**=" => return Some(impl_op!(INT => as_int.pow(as_int as u32))),
|
||||
">>=" => return Some(impl_op!(INT >>= as_int)),
|
||||
"<<=" => return Some(impl_op!(INT <<= as_int)),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
return match op {
|
||||
"&=" => Some(impl_op!(INT &= as_int)),
|
||||
"|=" => Some(impl_op!(INT |= as_int)),
|
||||
"^=" => Some(impl_op!(INT ^= as_int)),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
if type1 == TypeId::of::<bool>() {
|
||||
return match op {
|
||||
"&=" => Some(impl_op!(bool = x && as_bool)),
|
||||
"|=" => Some(impl_op!(bool = x || as_bool)),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
if type1 == TypeId::of::<char>() {
|
||||
return match op {
|
||||
"+=" => Some(|_, args| {
|
||||
let y = args[1].as_char().expect(BUILTIN);
|
||||
let x = &mut *args[0].write_lock::<Dynamic>().expect(BUILTIN);
|
||||
Ok((*x = format!("{x}{y}").into()).into())
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
if type1 == TypeId::of::<ImmutableString>() {
|
||||
return match op {
|
||||
"+=" => Some(|_, args| {
|
||||
let (first, second) = args.split_first_mut().expect(BUILTIN);
|
||||
let x = &mut *first.write_lock::<ImmutableString>().expect(BUILTIN);
|
||||
let y = std::mem::take(second[0]).cast::<ImmutableString>();
|
||||
Ok((*x += y).into())
|
||||
}),
|
||||
"-=" => Some(|_, args| {
|
||||
let (first, second) = args.split_first_mut().expect(BUILTIN);
|
||||
let x = &mut *first.write_lock::<ImmutableString>().expect(BUILTIN);
|
||||
let y = std::mem::take(second[0]).cast::<ImmutableString>();
|
||||
Ok((*x -= y).into())
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
if type1 == TypeId::of::<crate::Blob>() {
|
||||
use crate::Blob;
|
||||
|
||||
return match op {
|
||||
"+=" => Some(|_, args| {
|
||||
let blob2 = std::mem::take(args[1]).cast::<Blob>();
|
||||
let blob1 = &mut *args[0].write_lock::<Blob>().expect(BUILTIN);
|
||||
Ok(crate::packages::blob_basic::blob_functions::append(blob1, blob2).into())
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
@ -127,17 +127,35 @@ pub fn ensure_no_data_race(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
/// Generate the signature for a function call.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
fn gen_call_signature(
|
||||
&self,
|
||||
#[cfg(not(feature = "no_module"))] namespace: &crate::ast::Namespace,
|
||||
pub fn gen_fn_call_signature(engine: &Engine, fn_name: &str, args: &[&mut Dynamic]) -> String {
|
||||
format!(
|
||||
"{fn_name} ({})",
|
||||
args.iter()
|
||||
.map(|a| if a.is::<ImmutableString>() {
|
||||
"&str | ImmutableString | String"
|
||||
} else {
|
||||
engine.map_type_name(a.type_name())
|
||||
})
|
||||
.collect::<FnArgsVec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
|
||||
/// Generate the signature for a namespace-qualified function call.
|
||||
///
|
||||
/// Not available under `no_module`.
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn gen_qualified_fn_call_signature(
|
||||
engine: &Engine,
|
||||
namespace: &crate::ast::Namespace,
|
||||
fn_name: &str,
|
||||
args: &[&mut Dynamic],
|
||||
) -> String {
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
let (ns, sep) = (
|
||||
namespace.to_string(),
|
||||
if namespace.is_empty() {
|
||||
@ -146,22 +164,11 @@ impl Engine {
|
||||
crate::tokenizer::Token::DoubleColon.literal_syntax()
|
||||
},
|
||||
);
|
||||
#[cfg(feature = "no_module")]
|
||||
let (ns, sep) = ("", "");
|
||||
|
||||
format!(
|
||||
"{ns}{sep}{fn_name} ({})",
|
||||
args.iter()
|
||||
.map(|a| if a.is::<ImmutableString>() {
|
||||
"&str | ImmutableString | String"
|
||||
} else {
|
||||
self.map_type_name(a.type_name())
|
||||
})
|
||||
.collect::<FnArgsVec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
format!("{ns}{sep}{}", gen_fn_call_signature(engine, fn_name, args))
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
/// Resolve a normal (non-qualified) function call.
|
||||
///
|
||||
/// Search order:
|
||||
@ -174,26 +181,26 @@ impl Engine {
|
||||
fn resolve_fn<'s>(
|
||||
&self,
|
||||
_global: &GlobalRuntimeState,
|
||||
state: &'s mut Caches,
|
||||
caches: &'s mut Caches,
|
||||
lib: &[&Module],
|
||||
fn_name: &str,
|
||||
hash_script: u64,
|
||||
hash_base: u64,
|
||||
args: Option<&mut FnCallArgs>,
|
||||
allow_dynamic: bool,
|
||||
is_op_assignment: bool,
|
||||
) -> Option<&'s FnResolutionCacheEntry> {
|
||||
if hash_script == 0 {
|
||||
if hash_base == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut hash = args.as_ref().map_or(hash_script, |args| {
|
||||
let mut hash = args.as_ref().map_or(hash_base, |args| {
|
||||
combine_hashes(
|
||||
hash_script,
|
||||
hash_base,
|
||||
calc_fn_params_hash(args.iter().map(|a| a.type_id())),
|
||||
)
|
||||
});
|
||||
|
||||
let result = state
|
||||
let result = caches
|
||||
.fn_resolution_cache_mut()
|
||||
.entry(hash)
|
||||
.or_insert_with(|| {
|
||||
@ -248,21 +255,21 @@ impl Engine {
|
||||
|
||||
// Check `Dynamic` parameters for functions with parameters
|
||||
if allow_dynamic && max_bitmask == 0 && num_args > 0 {
|
||||
let is_dynamic = lib.iter().any(|&m| m.contains_dynamic_fn(hash_script))
|
||||
let is_dynamic = lib.iter().any(|&m| m.contains_dynamic_fn(hash_base))
|
||||
|| self
|
||||
.global_modules
|
||||
.iter()
|
||||
.any(|m| m.contains_dynamic_fn(hash_script));
|
||||
.any(|m| m.contains_dynamic_fn(hash_base));
|
||||
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
let is_dynamic = is_dynamic
|
||||
|| _global
|
||||
.iter_imports_raw()
|
||||
.any(|(_, m)| m.contains_dynamic_fn(hash_script))
|
||||
.any(|(_, m)| m.contains_dynamic_fn(hash_base))
|
||||
|| self
|
||||
.global_sub_modules
|
||||
.values()
|
||||
.any(|m| m.contains_dynamic_fn(hash_script));
|
||||
.any(|m| m.contains_dynamic_fn(hash_base));
|
||||
|
||||
// Set maximum bitmask when there are dynamic versions of the function
|
||||
if is_dynamic {
|
||||
@ -317,7 +324,7 @@ impl Engine {
|
||||
}
|
||||
}),
|
||||
);
|
||||
hash = combine_hashes(hash_script, hash_params);
|
||||
hash = combine_hashes(hash_base, hash_params);
|
||||
|
||||
bitmask += 1;
|
||||
}
|
||||
@ -405,11 +412,9 @@ impl Engine {
|
||||
let context = (self, name, source, &*global, lib, pos, level).into();
|
||||
|
||||
let result = if func.is_plugin_fn() {
|
||||
func.get_plugin_fn()
|
||||
.expect("plugin function")
|
||||
.call(context, args)
|
||||
func.get_plugin_fn().unwrap().call(context, args)
|
||||
} else {
|
||||
func.get_native_fn().expect("native function")(context, args)
|
||||
func.get_native_fn().unwrap()(context, args)
|
||||
};
|
||||
|
||||
// Restore the original reference
|
||||
@ -541,16 +546,9 @@ impl Engine {
|
||||
}
|
||||
|
||||
// Raise error
|
||||
_ => Err(ERR::ErrorFunctionNotFound(
|
||||
self.gen_call_signature(
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
&crate::ast::Namespace::NONE,
|
||||
name,
|
||||
args,
|
||||
),
|
||||
pos,
|
||||
)
|
||||
.into()),
|
||||
_ => {
|
||||
Err(ERR::ErrorFunctionNotFound(gen_fn_call_signature(self, name, args), pos).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -989,6 +987,7 @@ impl Engine {
|
||||
args_expr: &[Expr],
|
||||
hashes: FnCallHashes,
|
||||
capture_scope: bool,
|
||||
is_operator: bool,
|
||||
pos: Position,
|
||||
level: usize,
|
||||
) -> RhaiResult {
|
||||
@ -1001,6 +1000,8 @@ impl Engine {
|
||||
let redirected; // Handle call() - Redirect function call
|
||||
|
||||
match name {
|
||||
_ if is_operator => (),
|
||||
|
||||
// Handle call()
|
||||
KEYWORD_FN_PTR_CALL if total_args >= 1 => {
|
||||
let arg = first_arg.unwrap();
|
||||
@ -1429,7 +1430,7 @@ impl Engine {
|
||||
Some(f) => unreachable!("unknown function type: {:?}", f),
|
||||
|
||||
None => Err(ERR::ErrorFunctionNotFound(
|
||||
self.gen_call_signature(namespace, fn_name, &args),
|
||||
gen_qualified_fn_call_signature(self, namespace, fn_name, &args),
|
||||
pos,
|
||||
)
|
||||
.into()),
|
||||
|
@ -13,7 +13,9 @@ pub mod script;
|
||||
|
||||
pub use args::FuncArgs;
|
||||
pub use builtin::{get_builtin_binary_op_fn, get_builtin_op_assignment_fn};
|
||||
pub use call::FnCallArgs;
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
pub use call::gen_qualified_fn_call_signature;
|
||||
pub use call::{gen_fn_call_signature, FnCallArgs};
|
||||
pub use callable_function::CallableFunction;
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub use func::Func;
|
||||
|
@ -1181,7 +1181,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
|
||||
return;
|
||||
}
|
||||
// Overloaded operators can override built-in.
|
||||
_ if x.args.len() == 2 && !has_native_fn_override(state.engine, x.hashes.native, &arg_types) => {
|
||||
_ if x.args.len() == 2 && (state.engine.fast_operators() || !has_native_fn_override(state.engine, x.hashes.native, &arg_types)) => {
|
||||
if let Some(result) = get_builtin_binary_op_fn(&x.name, &arg_values[0], &arg_values[1])
|
||||
.and_then(|f| {
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
|
@ -123,7 +123,7 @@ pub mod array_functions {
|
||||
/// let x = [1, 2, 3];
|
||||
/// let y = [true, 'x'];
|
||||
///
|
||||
/// x.push(y);
|
||||
/// x.append(y);
|
||||
///
|
||||
/// print(x); // prints "[1, 2, 3, true, 'x']"
|
||||
/// ```
|
||||
|
@ -614,8 +614,9 @@ impl Engine {
|
||||
args.shrink_to_fit();
|
||||
|
||||
return Ok(FnCallExpr {
|
||||
name: id,
|
||||
name: state.get_interned_string(id),
|
||||
capture_parent_scope,
|
||||
is_native_operator: false,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
namespace,
|
||||
hashes,
|
||||
@ -687,6 +688,7 @@ impl Engine {
|
||||
return Ok(FnCallExpr {
|
||||
name: state.get_interned_string(id),
|
||||
capture_parent_scope,
|
||||
is_native_operator: false,
|
||||
#[cfg(not(feature = "no_module"))]
|
||||
namespace,
|
||||
hashes,
|
||||
@ -1920,6 +1922,7 @@ impl Engine {
|
||||
hashes: FnCallHashes::from_native(calc_fn_hash("-", 1)),
|
||||
args,
|
||||
pos,
|
||||
is_native_operator: true,
|
||||
..Default::default()
|
||||
}
|
||||
.into_fn_call_expr(pos))
|
||||
@ -1947,6 +1950,7 @@ impl Engine {
|
||||
hashes: FnCallHashes::from_native(calc_fn_hash("+", 1)),
|
||||
args,
|
||||
pos,
|
||||
is_native_operator: true,
|
||||
..Default::default()
|
||||
}
|
||||
.into_fn_call_expr(pos))
|
||||
@ -1965,6 +1969,7 @@ impl Engine {
|
||||
hashes: FnCallHashes::from_native(calc_fn_hash("!", 1)),
|
||||
args,
|
||||
pos,
|
||||
is_native_operator: true,
|
||||
..Default::default()
|
||||
}
|
||||
.into_fn_call_expr(pos))
|
||||
@ -2339,6 +2344,7 @@ impl Engine {
|
||||
name: state.get_interned_string(op.as_ref()),
|
||||
hashes: FnCallHashes::from_native(hash),
|
||||
pos,
|
||||
is_native_operator: !is_valid_function_name(&op),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
@ -169,6 +169,22 @@ fn test_arrays() -> Result<(), Box<EvalAltResult>> {
|
||||
"
|
||||
)?);
|
||||
|
||||
let value = vec![
|
||||
String::from("hello"),
|
||||
String::from("world"),
|
||||
String::from("foo"),
|
||||
String::from("bar"),
|
||||
];
|
||||
|
||||
let array: Dynamic = value.into();
|
||||
|
||||
assert_eq!(array.type_name(), "array");
|
||||
|
||||
let array = array.cast::<Array>();
|
||||
|
||||
assert_eq!(array[0].type_name(), "string");
|
||||
assert_eq!(array.len(), 4);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -75,6 +75,17 @@ fn test_native_overload() -> Result<(), Box<EvalAltResult>> {
|
||||
format!("{s1} Foo!").into()
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<String>(r#"let x = "hello"; let y = "world"; x + y"#)?,
|
||||
"helloworld"
|
||||
);
|
||||
assert_eq!(
|
||||
engine.eval::<String>(r#"let x = "hello"; let y = (); x + y"#)?,
|
||||
"hello"
|
||||
);
|
||||
|
||||
engine.set_fast_operators(false);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<String>(r#"let x = "hello"; let y = "world"; x + y"#)?,
|
||||
"hello***world"
|
||||
|
@ -53,6 +53,13 @@ fn test_optimizer_run() -> Result<(), Box<EvalAltResult>> {
|
||||
|
||||
engine.set_optimization_level(OptimizationLevel::Simple);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>("if 1 == 1 || 2 > 3 { 42 } else { 123 }")?,
|
||||
42
|
||||
);
|
||||
|
||||
engine.set_fast_operators(false);
|
||||
|
||||
assert_eq!(
|
||||
engine.eval::<INT>("if 1 == 1 || 2 > 3 { 42 } else { 123 }")?,
|
||||
123
|
||||
|
@ -75,12 +75,36 @@ fn make_greeting(n: impl std::fmt::Display) -> String {
|
||||
|
||||
gen_unary_functions!(greet = make_greeting(INT, bool, char) -> String);
|
||||
|
||||
macro_rules! expand_enum {
|
||||
($module:ident : $typ:ty => $($variant:ident),+) => {
|
||||
#[export_module]
|
||||
pub mod $module {
|
||||
$(
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const $variant: $typ = <$typ>::$variant;
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum MyEnum {
|
||||
Foo,
|
||||
Bar,
|
||||
Baz,
|
||||
Hello,
|
||||
World,
|
||||
}
|
||||
|
||||
expand_enum! { my_enum_module: MyEnum => Foo, Bar, Baz, Hello, World }
|
||||
|
||||
#[test]
|
||||
fn test_plugins_package() -> Result<(), Box<EvalAltResult>> {
|
||||
let mut engine = Engine::new();
|
||||
|
||||
let mut m = Module::new();
|
||||
combine_with_exported_module!(&mut m, "test", test::special_array_package);
|
||||
combine_with_exported_module!(&mut m, "enum", my_enum_module);
|
||||
engine.register_global_module(m.into());
|
||||
|
||||
reg_functions!(engine += greet::single(INT, bool, char));
|
||||
@ -104,11 +128,14 @@ fn test_plugins_package() -> Result<(), Box<EvalAltResult>> {
|
||||
assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; test(a, 2)")?, 6);
|
||||
assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; hi(a, 2)")?, 6);
|
||||
assert_eq!(engine.eval::<INT>("let a = [1, 2, 3]; test(a, 2)")?, 6);
|
||||
assert_eq!(engine.eval::<INT>("2 + 2")?, 5);
|
||||
assert_eq!(
|
||||
engine.eval::<String>("let a = [1, 2, 3]; greet(test(a, 2))")?,
|
||||
"6 kitties"
|
||||
);
|
||||
assert_eq!(engine.eval::<INT>("2 + 2")?, 4);
|
||||
|
||||
engine.set_fast_operators(false);
|
||||
assert_eq!(engine.eval::<INT>("2 + 2")?, 5);
|
||||
|
||||
engine.register_static_module("test", exported_module!(test::special_array_package).into());
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user